summaryrefslogtreecommitdiff
path: root/src/test/data/parser-torture.scm
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/data/parser-torture.scm')
-rw-r--r--src/test/data/parser-torture.scm132358
1 files changed, 132358 insertions, 0 deletions
diff --git a/src/test/data/parser-torture.scm b/src/test/data/parser-torture.scm
new file mode 100644
index 0000000..d475379
--- /dev/null
+++ b/src/test/data/parser-torture.scm
@@ -0,0 +1,132358 @@
+;;; rnrs exceptions (6) --- R6RS exceptions
+
+;; Copyright (C) 2013 Taylan Ulrich Bayırlı/Kammer
+
+;; Author: Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
+;; Keywords: ffi struct bytevector bytestructure
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; A clean implementation of (rnrs exceptions (6)). The dynamic environment
+;; capturing operations are noteworthy.
+
+
+;;; Code:
+
+(library
+ (rnrs exceptions (6))
+ (export with-exception-handler raise raise-continuable guard)
+ (import (rnrs base (6))
+ (srfi 39))
+
+;;; Helpers
+
+;;; Ignores any extra `else' clauses.
+;;; Helps to generate cond clauses with a default `else' clause.
+ (define-syntax cond+
+ (syntax-rules (else)
+ ((cond+ clause ... (else else1) (else else2))
+ (cond+ clause ... (else else1)))
+ ((cond+ clause ...)
+ (cond clause ...))))
+
+;;; Captures the current dynamic environment. It is reified as a procedure that
+;;; accepts a thunk and executes it in the captured dynenv.
+ (define (capture-dynenv)
+ ((call/cc
+ (lambda (captured-env)
+ (lambda ()
+ (lambda (proc)
+ (call/cc
+ (lambda (go-back)
+ (captured-env
+ (lambda ()
+ (call-with-values proc go-back)))))))))))
+
+;;; Captures the current dynamic environment and returns a procedure that
+;;; accepts as many arguments as PROC and applies PROC to them in that dynenv.
+;;; In other words, returns a version of PROC that's tied to the current dynenv.
+ (define (dynenv-proc proc)
+ (let ((env (capture-dynenv)))
+ (lambda args
+ (env (lambda () (apply proc args))))))
+
+;;; Returns a procedure that's always executed in the current dynamic
+;;; environment and not the one from which it's called.
+ (define-syntax dynenv-lambda
+ (syntax-rules ()
+ ((_ args body body* ...)
+ (dynenv-proc (lambda args body body* ...)))))
+
+
+;;; Main code:
+
+ (define handlers (make-parameter '()))
+
+ (define &non-continuable '&non-continuable)
+
+ (define (with-exception-handler handler thunk)
+ (parameterize ((handlers (cons handler (handlers))))
+ (thunk)))
+
+ (define (%raise condition continuable?)
+ (if (null? (handlers))
+ (error "unhandled exception" condition)
+ (let ((handler (car (handlers))))
+ (parameterize ((handlers (cdr (handlers))))
+ (if continuable?
+ (handler condition)
+ (begin
+ (handler condition)
+ (%raise &non-continuable #f)))))))
+
+ (define (raise-continuable condition)
+ (%raise condition #t))
+
+ (define (raise condition)
+ (%raise condition #f))
+
+ (define-syntax guard
+ (syntax-rules ()
+ ((guard (var clause clause* ...)
+ body body* ...)
+ (call/cc
+ (lambda (return)
+ (let ((handler (dynenv-lambda (var re-raise)
+ (return
+ (cond+ clause
+ clause*
+ ...
+ (else (re-raise)))))))
+ (with-exception-handler
+ (lambda (condition)
+ (let ((re-raise (dynenv-lambda ()
+ (raise condition))))
+ (handler condition re-raise)))
+ (lambda ()
+ body body* ...))))))))
+ )
+(define-module (test)
+ #\use-module (bytestructures guile))
+
+(display cstring-pointer)
+(newline)
+;;; align.scm --- Alignment calculation helpers.
+
+;; Copyright © 2018 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+
+;;; Code:
+
+;;; Either remains at 'position' or rounds up to the next multiple of
+;;; 'alignment' depending on whether 'size' (if not greater than 'alignment')
+;;; would fit. Returns three values: the chosen position, the start of the
+;;; alignment boundary of the chosen position, and the bit offset of the chosen
+;;; position from the start of the alignment boundary. A bit is represented by
+;;; the value 1/8.
+(define (align position size alignment)
+ (let* ((integer (floor position))
+ (fraction (- position integer)))
+ (let-values (((prev-boundary-index offset) (floor/ integer alignment)))
+ (let* ((prev-boundary (* prev-boundary-index alignment))
+ (next-boundary (+ prev-boundary alignment)))
+ (if (< next-boundary (+ position (min size alignment)))
+ (values next-boundary next-boundary 0)
+ (values position prev-boundary (* 8 (+ offset fraction))))))))
+
+;;; Returns 'position' if it's already a multiple of 'alignment'; otherwise
+;;; returns the next multiple.
+(define (next-boundary position alignment)
+ (align position +inf.0 alignment))
+
+;;; align.scm ends here
+;;; bytestructures --- Structured access to bytevector contents.
+
+;; Copyright © 2015, 2016 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This is the base of the module, defining the data types and procedures that
+;; make up the bytestructures framework.
+
+
+;;; Code:
+
+;;; Descriptors
+
+(define-record-type <bytestructure-descriptor>
+ (%make-bytestructure-descriptor size alignment unwrapper getter setter meta)
+ bytestructure-descriptor?
+ (size bd-size)
+ (alignment bd-alignment)
+ (unwrapper bd-unwrapper)
+ (getter bd-getter)
+ (setter bd-setter)
+ (meta bd-meta))
+
+(define make-bytestructure-descriptor
+ (case-lambda
+ ((size alignment unwrapper getter setter)
+ (%make-bytestructure-descriptor
+ size alignment unwrapper getter setter #f))
+ ((size alignment unwrapper getter setter meta)
+ (%make-bytestructure-descriptor
+ size alignment unwrapper getter setter meta))))
+
+(define bytestructure-descriptor-size
+ (case-lambda
+ ((descriptor) (bytestructure-descriptor-size descriptor #f #f))
+ ((descriptor bytevector offset)
+ (let ((size (bd-size descriptor)))
+ (if (procedure? size)
+ (size #f bytevector offset)
+ size)))))
+
+(define (bytestructure-descriptor-size/syntax bytevector offset descriptor)
+ (let ((size (bd-size descriptor)))
+ (if (procedure? size)
+ (size #t bytevector offset)
+ size)))
+
+(define bytestructure-descriptor-alignment bd-alignment)
+(define bytestructure-descriptor-unwrapper bd-unwrapper)
+(define bytestructure-descriptor-getter bd-getter)
+(define bytestructure-descriptor-setter bd-setter)
+(define bytestructure-descriptor-metadata bd-meta)
+
+
+;;; Bytestructures
+
+(define-record-type <bytestructure>
+ (make-bytestructure bytevector offset descriptor)
+ bytestructure?
+ (bytevector bytestructure-bytevector)
+ (offset bytestructure-offset)
+ (descriptor bytestructure-descriptor))
+
+(define bytestructure
+ (case-lambda ((descriptor) (%bytestructure descriptor #f #f))
+ ((descriptor values) (%bytestructure descriptor #t values))))
+
+(define (%bytestructure descriptor init? values)
+ (let ((bytevector (make-bytevector
+ (bytestructure-descriptor-size descriptor))))
+ (when init?
+ (bytestructure-primitive-set! bytevector 0 descriptor values))
+ (make-bytestructure bytevector 0 descriptor)))
+
+(define (bytestructure-size bytestructure)
+ (bytestructure-descriptor-size (bytestructure-descriptor bytestructure)
+ (bytestructure-bytevector bytestructure)
+ (bytestructure-offset bytestructure)))
+
+(define-syntax-rule (bytestructure-unwrap <bytestructure> <index> ...)
+ (let ((bytestructure <bytestructure>))
+ (let ((bytevector (bytestructure-bytevector bytestructure))
+ (offset (bytestructure-offset bytestructure))
+ (descriptor (bytestructure-descriptor bytestructure)))
+ (bytestructure-unwrap* bytevector offset descriptor <index> ...))))
+
+(define-syntax bytestructure-unwrap*
+ (syntax-rules ()
+ ((_ <bytevector> <offset> <descriptor>)
+ (values <bytevector> <offset> <descriptor>))
+ ((_ <bytevector> <offset> <descriptor> <index> <indices> ...)
+ (let ((bytevector <bytevector>)
+ (offset <offset>)
+ (descriptor <descriptor>))
+ (let ((unwrapper (bd-unwrapper descriptor)))
+ (when (not unwrapper)
+ (error "Cannot index through this descriptor." descriptor))
+ (let-values (((bytevector* offset* descriptor*)
+ (unwrapper #f bytevector offset <index>)))
+ (bytestructure-unwrap*
+ bytevector* offset* descriptor* <indices> ...)))))))
+
+(define-syntax-rule (bytestructure-ref <bytestructure> <index> ...)
+ (let-values (((bytevector offset descriptor)
+ (bytestructure-unwrap <bytestructure> <index> ...)))
+ (bytestructure-primitive-ref bytevector offset descriptor)))
+
+(define-syntax-rule (bytestructure-ref*
+ <bytevector> <offset> <descriptor> <index> ...)
+ (let-values (((bytevector offset descriptor)
+ (bytestructure-unwrap*
+ <bytevector> <offset> <descriptor> <index> ...)))
+ (bytestructure-primitive-ref bytevector offset descriptor)))
+
+(define (bytestructure-primitive-ref bytevector offset descriptor)
+ (let ((getter (bd-getter descriptor)))
+ (if getter
+ (getter #f bytevector offset)
+ (make-bytestructure bytevector offset descriptor))))
+
+(define-syntax-rule (bytestructure-set! <bytestructure> <index> ... <value>)
+ (let-values (((bytevector offset descriptor)
+ (bytestructure-unwrap <bytestructure> <index> ...)))
+ (bytestructure-primitive-set! bytevector offset descriptor <value>)))
+
+(define-syntax-rule (bytestructure-set!*
+ <bytevector> <offset> <descriptor> <index> ... <value>)
+ (let-values (((bytevector offset descriptor)
+ (bytestructure-unwrap*
+ <bytevector> <offset> <descriptor> <index> ...)))
+ (bytestructure-primitive-set! bytevector offset descriptor <value>)))
+
+(define (bytestructure-primitive-set! bytevector offset descriptor value)
+ (let ((setter (bd-setter descriptor)))
+ (if setter
+ (setter #f bytevector offset value)
+ (if (bytevector? value)
+ (bytevector-copy! bytevector offset value 0
+ (bytestructure-descriptor-size
+ descriptor bytevector offset))
+ (error "Cannot write value with this bytestructure descriptor."
+ value descriptor)))))
+
+(define (bytestructure-ref/dynamic bytestructure . indices)
+ (let-values (((bytevector offset descriptor)
+ (bytestructure-unwrap bytestructure)))
+ (let loop ((bytevector bytevector)
+ (offset offset)
+ (descriptor descriptor)
+ (indices indices))
+ (if (null? indices)
+ (bytestructure-primitive-ref bytevector offset descriptor)
+ (let-values (((bytevector* offset* descriptor*)
+ (bytestructure-unwrap*
+ bytevector offset descriptor (car indices))))
+ (loop bytevector*
+ offset*
+ descriptor*
+ (cdr indices)))))))
+
+(define (bytestructure-set!/dynamic bytestructure . args)
+ (let-values (((bytevector offset descriptor)
+ (bytestructure-unwrap bytestructure)))
+ (let loop ((bytevector bytevector)
+ (offset offset)
+ (descriptor descriptor)
+ (args args))
+ (if (null? (cdr args))
+ (bytestructure-primitive-set! bytevector offset descriptor (car args))
+ (let-values (((bytevector* offset* descriptor*)
+ (bytestructure-unwrap*
+ bytevector offset descriptor (car args))))
+ (loop bytevector*
+ offset*
+ descriptor*
+ (cdr args)))))))
+
+(define-syntax-case-stubs
+ bytestructure-unwrap/syntax
+ bytestructure-ref/syntax
+ bytestructure-set!/syntax
+ define-bytestructure-accessors)
+
+(cond-expand
+ (guile (include-from-path "bytestructures/body/base.syntactic.scm"))
+ (syntax-case (include "base.syntactic.scm"))
+ (else))
+
+;;; base.scm ends here
+;;; bytestructures --- Structured access to bytevector contents.
+
+;; Copyright © 2015 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This is an extension to the base of the module which allows using the API
+;; purely in the macro-expand phase, which puts some limitations on its use but
+;; reduces run-time overhead to zero or nearly zero.
+
+
+;;; Code:
+
+(define-syntax-rule (syntax-case-lambda <pattern> <body>)
+ (lambda (stx)
+ (syntax-case stx ()
+ (<pattern> <body>))))
+
+(define syntax-car (syntax-case-lambda (car . cdr) #'car))
+(define syntax-cdr (syntax-case-lambda (car . cdr) #'cdr))
+(define syntax-null? (syntax-case-lambda stx (null? (syntax->datum #'stx))))
+
+(define (syntactic-unwrap bytevector offset descriptor indices)
+ (let loop ((bytevector bytevector)
+ (offset offset)
+ (descriptor descriptor)
+ (indices indices))
+ (if (not (syntax-null? indices))
+ (let ((unwrapper (bd-unwrapper descriptor)))
+ (when (not unwrapper)
+ (error "Cannot index through this descriptor." descriptor))
+ (let-values (((bytevector* offset* descriptor*)
+ (unwrapper #t bytevector offset (syntax-car indices))))
+ (loop bytevector* offset* descriptor* (syntax-cdr indices))))
+ (let ((getter (bd-getter descriptor))
+ (setter (bd-setter descriptor)))
+ (values bytevector offset descriptor getter setter)))))
+
+(define (bytestructure-unwrap/syntax bytevector offset descriptor indices)
+ (let-values (((bytevector* offset* _descriptor _getter _setter)
+ (syntactic-unwrap bytevector offset descriptor indices)))
+ #`(values #,bytevector* #,offset*)))
+
+(define (bytestructure-ref/syntax bytevector offset descriptor indices)
+ (let-values (((bytevector* offset* descriptor* getter _setter)
+ (syntactic-unwrap bytevector offset descriptor indices)))
+ (if getter
+ (getter #t bytevector* offset*)
+ (error "The indices given to bytestructure-ref/syntax do not lead to a
+bytestructure descriptor that can decode values. You must have used the wrong
+getter macro, forgot to provide some of the indices, or meant to use the
+unwrapper instead of the getter. The given indices follow." indices))))
+
+(define (bytestructure-set!/syntax bytevector offset descriptor indices value)
+ (let-values (((bytevector* offset* descriptor* _getter setter)
+ (syntactic-unwrap bytevector offset descriptor indices)))
+ (if setter
+ (setter #t bytevector* offset* value)
+ (error "The indices given to bytestructure-set!/syntax do not lead to a
+bytestructure descriptor that can encode values. You must have used the wrong
+setter macro, or forgot to provide some of the indices. The given indices
+follow." indices))))
+
+(define-syntax-rule (define-bytestructure-unwrapper <name> <descriptor>)
+ (define-syntax <name>
+ (let ((descriptor <descriptor>))
+ (syntax-case-lambda (_ <bytevector> <offset> . <indices>)
+ (bytestructure-unwrap/syntax
+ #'<bytevector> #'<offset> descriptor #'<indices>)))))
+
+(define-syntax-rule (define-bytestructure-getter* <name> <descriptor>)
+ (define-syntax <name>
+ (let ((descriptor <descriptor>))
+ (syntax-case-lambda (_ <bytevector> <offset> . <indices>)
+ (bytestructure-ref/syntax
+ #'<bytevector> #'<offset> descriptor #'<indices>)))))
+
+(define-syntax-rule (define-bytestructure-setter* <name> <descriptor>)
+ (define-syntax <name>
+ (let ((descriptor <descriptor>))
+ (syntax-case-lambda (_ <bytevector> <offset> <index> (... ...) <value>)
+ (bytestructure-set!/syntax
+ #'<bytevector> #'<offset> descriptor #'(<index> (... ...)) #'<value>)))))
+
+(define-syntax-rule (define-bytestructure-getter <name> <descriptor>)
+ (define-syntax <name>
+ (let ((descriptor <descriptor>))
+ (syntax-case-lambda (_ <bytevector> . <indices>)
+ (bytestructure-ref/syntax #'<bytevector> 0 descriptor #'<indices>)))))
+
+(define-syntax-rule (define-bytestructure-setter <name> <descriptor>)
+ (define-syntax <name>
+ (let ((descriptor <descriptor>))
+ (syntax-case-lambda (_ <bytevector> <index> (... ...) <value>)
+ (bytestructure-set!/syntax
+ #'<bytevector> 0 descriptor #'(<index> (... ...)) #'<value>)))))
+
+(define-syntax define-bytestructure-accessors
+ (syntax-rules ()
+ ((_ <descriptor> <unwrapper> <getter> <setter>)
+ (begin
+ (define-bytestructure-unwrapper <unwrapper> <descriptor>)
+ (define-bytestructure-getter <getter> <descriptor>)
+ (define-bytestructure-setter <setter> <descriptor>)))
+ ((_ <descriptor> <unwrapper> <getter> <setter> <getter*> <setter*>)
+ (begin
+ (define-bytestructure-unwrapper <unwrapper> <descriptor>)
+ (define-bytestructure-getter <getter> <descriptor>)
+ (define-bytestructure-setter <setter> <descriptor>)
+ (define-bytestructure-getter* <getter*> <descriptor>)
+ (define-bytestructure-setter* <setter*> <descriptor>)))))
+
+;; Local Variables:
+;; eval: (put (quote syntax-case-lambda) (quote scheme-indent-function) 1)
+;; End:
+
+;;; base.syntactic.scm ends here
+;;; bitfields.scm --- Struct bitfield constructor.
+
+;; Copyright © 2015, 2016 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module is complementary to the struct module. It isn't used on its own.
+
+;; This code partly uses rational numbers for byte counts and offsets, to
+;; represent granularity down to bits. I.e. 1/8 is a size or offset of one bit.
+
+
+;;; Code:
+
+;;; Only a macro for efficiency reasons.
+(define-syntax bit-field/signed
+ (syntax-rules ()
+ ((_ <num> <width> <start> <end> <signed?>)
+ (let ((unsigned-value (bit-field <num> <start> <end>)))
+ (if (not <signed?>)
+ unsigned-value
+ (let ((sign (bit-set? (- <width> 1) unsigned-value)))
+ (if sign
+ (- unsigned-value (expt 2 <width>))
+ unsigned-value)))))))
+
+(define (validate-integer-descriptor descriptor)
+ (when (not (assq descriptor integer-descriptors))
+ (error "Invalid descriptor for bitfield." descriptor)))
+
+(define (integer-descriptor-signed? descriptor)
+ (assq descriptor signed-integer-descriptors))
+
+(define integer-descriptor-signed->unsigned-mapping
+ (map cons
+ (map car signed-integer-descriptors)
+ (map car unsigned-integer-descriptors)))
+
+(define (integer-descriptor-signed->unsigned descriptor)
+ (cdr (assq descriptor integer-descriptor-signed->unsigned-mapping)))
+
+(define (unsigned-integer-descriptor integer-descriptor)
+ (if (integer-descriptor-signed? integer-descriptor)
+ (integer-descriptor-signed->unsigned integer-descriptor)
+ integer-descriptor))
+
+(define-record-type <bitfield-metadata>
+ (make-bitfield-metadata int-descriptor width)
+ bitfield-metadata?
+ (int-descriptor bitfield-metadata-int-descriptor)
+ (width bitfield-metadata-width))
+
+(define (bitfield-descriptor int-descriptor bit-offset width)
+ (validate-integer-descriptor int-descriptor)
+ (let ((signed? (integer-descriptor-signed? int-descriptor))
+ (uint-descriptor (unsigned-integer-descriptor int-descriptor)))
+ (let ((num-getter (bytestructure-descriptor-getter uint-descriptor))
+ (num-setter (bytestructure-descriptor-setter uint-descriptor)))
+ (define start bit-offset)
+ (define end (+ start width))
+ (define (getter syntax? bytevector offset)
+ (let ((num (num-getter syntax? bytevector offset)))
+ (if syntax?
+ (quasisyntax
+ (bit-field/signed (unsyntax num) (unsyntax width)
+ (unsyntax start) (unsyntax end)
+ (unsyntax signed?)))
+ (bit-field/signed num width start end signed?))))
+ (define (setter syntax? bytevector offset value)
+ (let* ((oldnum (num-getter syntax? bytevector offset))
+ (newnum (if syntax?
+ (quasisyntax
+ (copy-bit-field (unsyntax oldnum) (unsyntax value)
+ (unsyntax start) (unsyntax end)))
+ (copy-bit-field oldnum value start end))))
+ (num-setter syntax? bytevector offset newnum)))
+ (define meta (make-bitfield-metadata int-descriptor width))
+ (make-bytestructure-descriptor #f #f #f getter setter meta))))
+
+;;; bitfields.scm ends here
+;;; explicit-endianness.scm --- Auxiliary bytevector operations.
+
+;; Copyright © 2015 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; The numeric module requires top-level bindings to bytevector procedures with
+;; an explicit endianness, instead of the ones that take an endianness
+;; argument. This library provides them.
+
+
+;;; Code:
+
+(define-syntax define-explicit-endianness-getters
+ (syntax-rules ()
+ ((_ (original le-name be-name) ...)
+ (begin
+ (begin
+ (define (le-name bytevector index)
+ (original bytevector index (endianness little)))
+ (define (be-name bytevector index)
+ (original bytevector index (endianness big))))
+ ...))))
+
+(define-explicit-endianness-getters
+ (bytevector-ieee-single-ref bytevector-ieee-single-le-ref
+ bytevector-ieee-single-be-ref)
+ (bytevector-ieee-double-ref bytevector-ieee-double-le-ref
+ bytevector-ieee-double-be-ref)
+ (bytevector-s16-ref bytevector-s16le-ref
+ bytevector-s16be-ref)
+ (bytevector-u16-ref bytevector-u16le-ref
+ bytevector-u16be-ref)
+ (bytevector-s32-ref bytevector-s32le-ref
+ bytevector-s32be-ref)
+ (bytevector-u32-ref bytevector-u32le-ref
+ bytevector-u32be-ref)
+ (bytevector-s64-ref bytevector-s64le-ref
+ bytevector-s64be-ref)
+ (bytevector-u64-ref bytevector-u64le-ref
+ bytevector-u64be-ref))
+
+(define-syntax define-explicit-endianness-setters
+ (syntax-rules ()
+ ((_ (original le-name be-name) ...)
+ (begin
+ (begin
+ (define (le-name bytevector index value)
+ (original bytevector index value (endianness little)))
+ (define (be-name bytevector index value)
+ (original bytevector index value (endianness big))))
+ ...))))
+
+(define-explicit-endianness-setters
+ (bytevector-ieee-single-set! bytevector-ieee-single-le-set!
+ bytevector-ieee-single-be-set!)
+ (bytevector-ieee-double-set! bytevector-ieee-double-le-set!
+ bytevector-ieee-double-be-set!)
+ (bytevector-s16-set! bytevector-s16le-set!
+ bytevector-s16be-set!)
+ (bytevector-u16-set! bytevector-u16le-set!
+ bytevector-u16be-set!)
+ (bytevector-s32-set! bytevector-s32le-set!
+ bytevector-s32be-set!)
+ (bytevector-u32-set! bytevector-u32le-set!
+ bytevector-u32be-set!)
+ (bytevector-s64-set! bytevector-s64le-set!
+ bytevector-s64be-set!)
+ (bytevector-u64-set! bytevector-u64le-set!
+ bytevector-u64be-set!))
+
+;;; explicit-endianness.scm ends here
+;;; numeric.scm --- Numeric types as supported by (rnrs bytevectors).
+
+;; Copyright © 2015, 2016 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module defines descriptors for numeric types of specific size, and
+;; native or specific endianness, as made possible by the bytevector referencing
+;; and assigning procedures in the (rnrs bytevectors) module.
+
+;; We use the strange cond-expand/runtime macro to make sure that certain checks
+;; for CPU architecture and data model are done at library-load-time and not
+;; compile time, since one might cross-compile the library.
+
+
+;;; Code:
+
+(define base-environment
+ (cond-expand
+ (guile-2
+ (current-module))
+ (else
+ (environment '(scheme base)))))
+
+(define-syntax cond-expand/runtime
+ (syntax-rules ()
+ ((_ (<cond> <expr>) ...)
+ (let ((const (eval '(cond-expand (<cond> '<expr>) ...)
+ base-environment)))
+ (cond
+ ((equal? const '<expr>) <expr>)
+ ...)))))
+
+(define i8align 1)
+
+(define i16align 2)
+
+(define i32align 4)
+
+(define i64align
+ (cond-expand/runtime
+ (i386 4)
+ (else 8)))
+
+(define f32align 4)
+
+(define f64align
+ (cond-expand/runtime
+ (i386 4)
+ (else 8)))
+
+(define-syntax-rule (make-numeric-descriptor <size> <align> <getter> <setter>)
+ (let ()
+ (define size <size>)
+ (define alignment <align>)
+ (define (getter syntax? bytevector offset)
+ (if syntax?
+ (quasisyntax
+ (<getter> (unsyntax bytevector) (unsyntax offset)))
+ (<getter> bytevector offset)))
+ (define (setter syntax? bytevector offset value)
+ (if syntax?
+ (quasisyntax
+ (<setter> (unsyntax bytevector) (unsyntax offset) (unsyntax value)))
+ (<setter> bytevector offset value)))
+ (make-bytestructure-descriptor size alignment #f getter setter)))
+
+(define-syntax-rule (define-numeric-descriptors <list>
+ (<name> <size> <align> <getter> <setter>)
+ ...)
+ (begin
+ (define <name>
+ (make-numeric-descriptor <size> <align> <getter> <setter>))
+ ...
+ (define <list> (list (list <name> '<name> <getter> <setter>) ...))))
+
+(define-numeric-descriptors
+ signed-integer-native-descriptors
+ (int8 1 i8align bytevector-s8-ref bytevector-s8-set!)
+ (int16 2 i16align bytevector-s16-native-ref bytevector-s16-native-set!)
+ (int32 4 i32align bytevector-s32-native-ref bytevector-s32-native-set!)
+ (int64 8 i64align bytevector-s64-native-ref bytevector-s64-native-set!))
+
+(define-numeric-descriptors
+ unsigned-integer-native-descriptors
+ (uint8 1 i8align bytevector-u8-ref bytevector-u8-set!)
+ (uint16 2 i16align bytevector-u16-native-ref bytevector-u16-native-set!)
+ (uint32 4 i32align bytevector-u32-native-ref bytevector-u32-native-set!)
+ (uint64 8 i64align bytevector-u64-native-ref bytevector-u64-native-set!))
+
+(define-numeric-descriptors
+ float-native-descriptors
+ (float32 4 f32align
+ bytevector-ieee-single-native-ref
+ bytevector-ieee-single-native-set!)
+ (float64 8 f64align
+ bytevector-ieee-double-native-ref
+ bytevector-ieee-double-native-set!))
+
+(define-syntax-rule (define-with-endianness <list> <endianness>
+ (<name> <size> <align> <native-name> <getter> <setter>)
+ ...)
+ (begin
+ (define <name>
+ (if (equal? <endianness> (native-endianness))
+ <native-name>
+ (make-numeric-descriptor <size> <align> <getter> <setter>)))
+ ...
+ (define <list> (list (list <name> '<name> <getter> <setter>) ...))))
+
+(define-with-endianness
+ signed-integer-le-descriptors (endianness little)
+ (int16le 2 i16align int16 bytevector-s16le-ref bytevector-s16le-set!)
+ (int32le 4 i32align int32 bytevector-s32le-ref bytevector-s32le-set!)
+ (int64le 8 i64align int64 bytevector-s64le-ref bytevector-s64le-set!))
+
+(define-with-endianness
+ signed-integer-be-descriptors (endianness big)
+ (int16be 2 i16align int16 bytevector-s16be-ref bytevector-s16be-set!)
+ (int32be 4 i32align int32 bytevector-s32be-ref bytevector-s32be-set!)
+ (int64be 8 i64align int64 bytevector-s64be-ref bytevector-s64be-set!))
+
+(define-with-endianness
+ unsigned-integer-le-descriptors (endianness little)
+ (uint16le 2 i16align uint16 bytevector-u16le-ref bytevector-u16le-set!)
+ (uint32le 4 i32align uint32 bytevector-u32le-ref bytevector-u32le-set!)
+ (uint64le 8 i64align uint64 bytevector-u64le-ref bytevector-u64le-set!))
+
+(define-with-endianness
+ unsigned-integer-be-descriptors (endianness big)
+ (uint16be 2 i16align uint16 bytevector-u16be-ref bytevector-u16be-set!)
+ (uint32be 4 i32align uint32 bytevector-u32be-ref bytevector-u32be-set!)
+ (uint64be 8 i64align uint64 bytevector-u64be-ref bytevector-u64be-set!))
+
+(define-with-endianness
+ float-le-descriptors (endianness little)
+ (float32le 4 f32align float32
+ bytevector-ieee-single-le-ref
+ bytevector-ieee-single-le-set!)
+ (float64le 8 f64align float64
+ bytevector-ieee-double-le-ref
+ bytevector-ieee-double-le-set!))
+
+(define-with-endianness
+ float-be-descriptors (endianness big)
+ (float32be 4 f32align float32
+ bytevector-ieee-single-be-ref
+ bytevector-ieee-single-be-set!)
+ (float64be 8 f64align float64
+ bytevector-ieee-double-be-ref
+ bytevector-ieee-double-be-set!))
+
+(define-syntax-rule (make-complex-descriptor
+ <float-size> <float-align> <float-getter> <float-setter>)
+ (let ()
+ (define size (* 2 <float-size>))
+ (define alignment <float-align>)
+ (define (getter syntax? bytevector offset)
+ (if syntax?
+ (quasisyntax
+ (let ((real (<float-getter> (unsyntax bytevector)
+ (unsyntax offset)))
+ (imag (<float-getter> (unsyntax bytevector)
+ (+ (unsyntax offset) <float-size>))))
+ (make-rectangular real imag)))
+ (let ((real (<float-getter> bytevector offset))
+ (imag (<float-getter> bytevector (+ offset <float-size>))))
+ (make-rectangular real imag))))
+ (define (setter syntax? bytevector offset value)
+ (if syntax?
+ (quasisyntax
+ (let ((val (unsyntax value)))
+ (let ((real (real-part val))
+ (imag (imag-part val)))
+ (<float-setter> (unsyntax bytevector)
+ (unsyntax offset)
+ real)
+ (<float-setter> (unsyntax bytevector)
+ (+ (unsyntax offset) <float-size>)
+ imag))))
+ (let ((real (real-part value))
+ (imag (imag-part value)))
+ (<float-setter> bytevector offset real)
+ (<float-setter> bytevector (+ offset <float-size>) imag))))
+ (make-bytestructure-descriptor size alignment #f getter setter)))
+
+(define-syntax-rule (define-complex-descriptors <list>
+ (<name> <float-size> <float-align>
+ <float-getter> <float-setter>)
+ ...)
+ (begin
+ (define <name>
+ (make-complex-descriptor <float-size> <float-align>
+ <float-getter> <float-setter>))
+ ...
+ (define <list> (list (list <name> '<name> <float-getter> <float-setter>)
+ ...))))
+
+(define-complex-descriptors
+ complex-native-descriptors
+ (complex64 4 f32align
+ bytevector-ieee-single-native-ref
+ bytevector-ieee-single-native-set!)
+ (complex128 8 f64align
+ bytevector-ieee-double-native-ref
+ bytevector-ieee-double-native-set!))
+
+(define-syntax-rule (define-complex-with-endianness <list> <endianness>
+ (<name> <float-size> <float-align> <native-name>
+ <float-getter> <float-setter>)
+ ...)
+ (begin
+ (define <name>
+ (if (equal? <endianness> (native-endianness))
+ <native-name>
+ (make-complex-descriptor <float-size> <float-align>
+ <float-getter> <float-setter>)))
+ ...
+ (define <list> (list (list <name> '<name> <float-getter> <float-setter>)
+ ...))))
+
+(define-complex-with-endianness
+ complex-le-descriptors (endianness little)
+ (complex64le 4 f32align complex64
+ bytevector-ieee-single-le-ref
+ bytevector-ieee-single-le-set!)
+ (complex128le 8 f64align complex128
+ bytevector-ieee-double-le-ref
+ bytevector-ieee-double-le-set!))
+
+(define-complex-with-endianness
+ complex-be-descriptors (endianness big)
+ (complex64be 4 f32align complex64
+ bytevector-ieee-single-be-ref
+ bytevector-ieee-single-be-set!)
+ (complex128be 8 f64align complex128
+ bytevector-ieee-double-be-ref
+ bytevector-ieee-double-be-set!))
+
+(define signed-integer-descriptors
+ (append signed-integer-native-descriptors
+ signed-integer-le-descriptors
+ signed-integer-be-descriptors))
+
+(define unsigned-integer-descriptors
+ (append unsigned-integer-native-descriptors
+ unsigned-integer-le-descriptors
+ unsigned-integer-be-descriptors))
+
+(define integer-descriptors
+ (append signed-integer-descriptors unsigned-integer-descriptors))
+
+(define float-descriptors
+ (append float-native-descriptors
+ float-le-descriptors
+ float-be-descriptors))
+
+(define complex-descriptors
+ (append complex-native-descriptors
+ complex-le-descriptors
+ complex-be-descriptors))
+
+(define numeric-descriptors
+ (append integer-descriptors float-descriptors complex-descriptors))
+
+(define short int16)
+(define unsigned-short uint16)
+
+(define int (cond-expand/runtime
+ (lp32 int16)
+ (ilp64 int64)
+ (else int32)))
+
+(define unsigned-int (cond-expand/runtime
+ (lp32 uint16)
+ (ilp64 uint64)
+ (else uint32)))
+
+(define long (cond-expand/runtime
+ (ilp64 int64)
+ (lp64 int64)
+ (else int32)))
+
+(define unsigned-long (cond-expand/runtime
+ (ilp64 uint64)
+ (lp64 uint64)
+ (else uint32)))
+
+(define long-long int64)
+(define unsigned-long-long uint64)
+
+(define arch32bit? (cond-expand/runtime
+ (lp32 #t)
+ (ilp32 #t)
+ (else #f)))
+
+(define intptr_t (if arch32bit?
+ int32
+ int64))
+
+(define uintptr_t (if arch32bit?
+ uint32
+ uint64))
+
+(define size_t uintptr_t)
+
+(define ssize_t intptr_t)
+
+(define ptrdiff_t intptr_t)
+
+(define float float32)
+(define double float64)
+
+;;; numeric.scm ends here
+;;; string.scm --- Strings in encodings supported by (rnrs bytevectors).
+
+;; Copyright © 2017 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module defines descriptors for strings encoded in various encodings, as
+;; supported by (rnrs bytevectors).
+
+
+;;; Code:
+
+(define (ascii->string bytevector start end)
+ (let ((string (utf8->string bytevector start end)))
+ (when (not (= (string-length string) (bytevector-length bytevector)))
+ (error "Bytevector contains non-ASCII characters." bytevector))
+ string))
+
+(define (string->ascii string)
+ (let ((bytevector (string->utf8 string)))
+ (when (not (= (string-length string) (bytevector-length bytevector)))
+ (error "String contains non-ASCII characters." string))
+ bytevector))
+
+(define (bytevector->string bytevector offset size encoding)
+ (case encoding
+ ((ascii) (ascii->string bytevector offset (+ offset size)))
+ ((utf8) (utf8->string bytevector offset (+ offset size)))
+ (else
+ (let ((bytevector (bytevector-copy bytevector offset (+ offset size))))
+ (case encoding
+ ((utf16le) (utf16->string bytevector 'little #t))
+ ((utf16be) (utf16->string bytevector 'big #t))
+ ((utf32le) (utf32->string bytevector 'little #t))
+ ((utf32be) (utf32->string bytevector 'big #t))
+ (else (error "Unknown string encoding." encoding)))))))
+
+(define (string->bytevector string encoding)
+ (case encoding
+ ((ascii) (string->ascii string))
+ ((utf8) (string->utf8 string))
+ ((utf16le) (string->utf16 string 'little))
+ ((utf16be) (string->utf16 string 'big))
+ ((utf32le) (string->utf32 string 'little))
+ ((utf32be) (string->utf32 string 'big))))
+
+;;; Note: because macro output may not contain raw symbols, we cannot output
+;;; (quote foo) for raw symbol foo either, so there's no way to inject symbol
+;;; literals into macro output. Hence we inject references to the following
+;;; variables instead.
+
+(define ascii 'ascii)
+(define utf8 'utf8)
+(define utf16le 'utf16le)
+(define utf16be 'utf16be)
+(define utf32le 'utf32le)
+(define utf32be 'utf32be)
+
+;;; Make sure this returns a boolean and not any other type of value, as the
+;;; output will be part of macro output.
+(define (fixed-width-encoding? encoding)
+ (not (not (memq encoding '(ascii utf32le utf32be)))))
+
+(define (bytevector-zero! bv start end)
+ (do ((i start (+ i 1)))
+ ((= i end))
+ (bytevector-u8-set! bv i #x00)))
+
+(define (bs:string size encoding)
+ (define alignment 1)
+ (define (getter syntax? bytevector offset)
+ (if syntax?
+ (quasisyntax
+ (bytevector->string (unsyntax bytevector)
+ (unsyntax offset)
+ (unsyntax size)
+ (unsyntax
+ (datum->syntax (syntax utf8) encoding))))
+ (bytevector->string bytevector offset size encoding)))
+ (define (setter syntax? bytevector offset string)
+ (if syntax?
+ (quasisyntax
+ (let* ((bv (string->bytevector
+ (unsyntax string)
+ (unsyntax
+ (datum->syntax (syntax utf8) encoding))))
+ (length (bytevector-length bv)))
+ (when (> length (unsyntax size))
+ (error "String too long." (unsyntax string)))
+ (when (and (unsyntax (fixed-width-encoding? encoding))
+ (< length (unsyntax size)))
+ (error "String too short." (unsyntax string)))
+ (bytevector-copy! (unsyntax bytevector)
+ (unsyntax offset)
+ bv)
+ (when (not (unsyntax (fixed-width-encoding? encoding)))
+ (bytevector-zero! (unsyntax bytevector)
+ (+ (unsyntax offset) (bytevector-length bv))
+ (+ (unsyntax offset) (unsyntax size))))))
+ (let* ((bv (string->bytevector string encoding))
+ (length (bytevector-length bv)))
+ (when (> length size)
+ (error "String too long." string))
+ (when (and (fixed-width-encoding? encoding) (< length size))
+ (error "String too short." string))
+ (bytevector-copy! bytevector offset bv)
+ (when (not (fixed-width-encoding? encoding))
+ (bytevector-zero! bytevector
+ (+ offset (bytevector-length bv))
+ (+ offset size))))))
+ (make-bytestructure-descriptor size alignment #f getter setter))
+
+;;; string.scm ends here
+;;; struct.scm --- Struct descriptor constructor.
+
+;; Copyright © 2015, 2016, 2021 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This constructor allows the creation of struct descriptors with named and
+;; ordered fields with a specific content descriptor.
+
+;; This code partly uses rational numbers for byte counts and offsets, to
+;; represent granularity down to bits. I.e. 1/8 is a size or offset of one bit.
+
+
+;;; Code:
+
+(define (pack-alignment pack alignment)
+ (case pack
+ ((#t) 1)
+ ((#f) alignment)
+ (else (min pack alignment))))
+
+(define-record-type <field>
+ (make-field name descriptor size alignment position)
+ field?
+ (name field-name)
+ (descriptor field-descriptor)
+ (size field-size)
+ (alignment field-alignment)
+ (position field-position))
+
+(define (construct-normal-field pack position name descriptor)
+ (let*-values
+ (((size)
+ (bytestructure-descriptor-size descriptor))
+ ((alignment)
+ (pack-alignment pack (bytestructure-descriptor-alignment descriptor)))
+ ((position _boundary _bit-offset)
+ (align position size alignment)))
+ (values (make-field name descriptor size alignment position)
+ (+ position size))))
+
+(define (construct-bit-field pack position name descriptor width)
+ (if (zero? width)
+ (let* ((alignment (bytestructure-descriptor-alignment descriptor))
+ (position (next-boundary position alignment)))
+ (values (make-field #f descriptor 0 1 position)
+ position))
+ (let*-values
+ (((int-size)
+ (bytestructure-descriptor-size descriptor))
+ ((size)
+ (* 1/8 width))
+ ((int-alignment)
+ (bytestructure-descriptor-alignment descriptor))
+ ((alignment)
+ (pack-alignment pack int-alignment))
+ ((position boundary offset)
+ (align position size alignment))
+ ((descriptor)
+ (bitfield-descriptor descriptor offset width)))
+ (values (make-field name descriptor int-size alignment boundary)
+ (+ position size)))))
+
+(define (construct-fields pack field-specs)
+ (let loop ((field-specs field-specs)
+ (position 0)
+ (fields '()))
+ (if (null? field-specs)
+ (reverse fields)
+ (let* ((field-spec (car field-specs))
+ (field-specs (cdr field-specs))
+ (name-or-type (car field-spec)))
+ (if (and (eq? name-or-type 'union)
+ (pair? (cadr field-spec)))
+ (let-values (((next-position fields)
+ (add-union-fields pack
+ position
+ (cadr field-spec)
+ fields)))
+ (loop field-specs
+ next-position
+ fields))
+ (let-values (((field next-position)
+ (construct-field pack position field-spec)))
+ (loop field-specs
+ next-position
+ (cons field fields))))))))
+
+(define (add-union-fields pack position field-specs fields)
+ (define (field-spec-alignment field-spec)
+ (let ((descriptor (cadr field-spec)))
+ (bytestructure-descriptor-alignment descriptor)))
+ (define (field-spec-size field-spec)
+ (let ((descriptor (cadr field-spec)))
+ (bytestructure-descriptor-size descriptor)))
+ (let* ((alignment (apply max (map field-spec-alignment field-specs)))
+ (alignment (pack-alignment pack alignment))
+ (size (apply max (map field-spec-size field-specs)))
+ (position (align position size alignment)))
+ (let loop ((field-specs field-specs)
+ (next-position position)
+ (fields fields))
+ (if (null? field-specs)
+ (values next-position fields)
+ (let ((field-spec (car field-specs))
+ (field-specs (cdr field-specs)))
+ (let-values (((field next-position)
+ (construct-field pack position field-spec)))
+ (loop field-specs
+ (max position next-position)
+ (cons field fields))))))))
+
+(define (construct-field pack position field-spec)
+ (let* ((name (car field-spec))
+ (descriptor (cadr field-spec))
+ (bitfield? (not (null? (cddr field-spec))))
+ (width (if bitfield?
+ (car (cddr field-spec))
+ #f)))
+ (if bitfield?
+ (construct-bit-field pack position name descriptor width)
+ (construct-normal-field pack position name descriptor))))
+
+(define-record-type <struct-metadata>
+ (make-struct-metadata field-alist)
+ struct-metadata?
+ (field-alist struct-metadata-field-alist))
+
+(define bs:struct
+ (case-lambda
+ ((field-specs)
+ (bs:struct #f field-specs))
+ ((pack field-specs)
+ (define %fields (construct-fields pack field-specs))
+ (define fields (filter field-name %fields))
+ (define field-alist (map (lambda (field)
+ (cons (field-name field) field))
+ fields))
+ (define alignment (apply max (map field-alignment fields)))
+ (define (field-end field)
+ (+ (field-position field) (field-size field)))
+ (define size (let ((end (apply max (map field-end %fields))))
+ (let-values (((size . _) (next-boundary end alignment)))
+ size)))
+ (define (unwrapper syntax? bytevector offset index)
+ (let* ((index (if syntax? (syntax->datum index) index))
+ (field-entry (assq index field-alist))
+ (field (if field-entry
+ (cdr field-entry)
+ (error "No such struct field." index))))
+ (let* ((descriptor (field-descriptor field))
+ (position (field-position field))
+ (offset (if syntax?
+ (quasisyntax
+ (+ (unsyntax offset) (unsyntax position)))
+ (+ offset position))))
+ (values bytevector offset descriptor))))
+ (define (setter syntax? bytevector offset value)
+ (define (count-error fields values)
+ (error "Mismatch between number of struct fields and given values."
+ fields values))
+ (when syntax?
+ (error "Writing into struct not supported with macro API."))
+ (cond
+ ((bytevector? value)
+ (bytevector-copy! bytevector offset value 0 size))
+ ((vector? value)
+ (let loop ((fields fields)
+ (values (vector->list value)))
+ (if (null? values)
+ (when (not (null? fields))
+ (count-error fields value))
+ (begin
+ (when (null? fields)
+ (count-error fields value))
+ (let* ((field (car fields))
+ (value (car values))
+ (descriptor (field-descriptor field))
+ (position (field-position field))
+ (offset (+ offset position)))
+ (bytestructure-set!* bytevector offset descriptor value)
+ (loop (cdr fields) (cdr values)))))))
+ ((pair? value)
+ ;; Assumed to be a pseudo-alist like ((k1 v1) (k2 v2) ...).
+ (for-each
+ (lambda (pair)
+ (let ((key (car pair))
+ (value (cadr pair)))
+ (let-values (((bytevector offset descriptor)
+ (unwrapper #f bytevector offset key)))
+ (bytestructure-set!* bytevector offset descriptor value))))
+ value))
+ (else
+ (error "Invalid value for writing into struct." value))))
+ (define meta
+ (let ((simple-field-alist (map (lambda (field)
+ (cons (field-name field)
+ (field-descriptor field)))
+ fields)))
+ (make-struct-metadata simple-field-alist)))
+ (make-bytestructure-descriptor size alignment unwrapper #f setter meta))))
+
+(define debug-alignment
+ (case-lambda
+ ((fields) (debug-alignment #f fields))
+ ((pack fields)
+ (let* ((fields (construct-fields pack fields))
+ (alignment (apply max (map field-alignment fields)))
+ (size (let* ((field (last fields))
+ (end (+ (field-position field) (field-size field))))
+ (let-values (((size . _) (next-boundary end alignment)))
+ size))))
+ (format #t "{\n")
+ (for-each (lambda (field)
+ (let ((name (field-name field))
+ (pos (* 8 (field-position field)))
+ (size (* 8 (field-size field)))
+ (align (* 8 (field-alignment field))))
+ (format #t " ~a - ~a: ~a (~a, ~a)\n"
+ pos (+ pos size) name size align)))
+ fields)
+ (format #t "} = ~a\n" (* 8 size))
+ (values)))))
+
+;;; struct.scm ends here
+;;; union.scm --- Union descriptor constructor.
+
+;; Copyright © 2015, 2016 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This constructor allows the creation of union descriptors with named fields
+;; with a specific content descriptor.
+
+
+;;; Code:
+
+(define make-field cons)
+(define field-name car)
+(define field-content cdr)
+(define find-field assq)
+
+(define (construct-fields fields)
+ (map (lambda (field)
+ (make-field (car field) (cadr field)))
+ fields))
+
+(define-record-type <union-metadata>
+ (make-union-metadata field-alist)
+ union-metadata?
+ (field-alist union-metadata-field-alist))
+
+(define (bs:union %fields)
+ (define fields (construct-fields %fields))
+ (define alignment (apply max (map (lambda (field)
+ (bytestructure-descriptor-alignment
+ (field-content field)))
+ fields)))
+ (define size (let ((max-element
+ (apply max (map (lambda (field)
+ (bytestructure-descriptor-size
+ (field-content field)))
+ fields))))
+ (let-values (((size . _) (next-boundary max-element alignment)))
+ size)))
+ (define (unwrapper syntax? bytevector offset index)
+ (let ((index (if syntax? (syntax->datum index) index)))
+ (values bytevector
+ offset
+ (field-content (find-field index fields)))))
+ (define (setter syntax? bytevector offset value)
+ (when syntax?
+ (error "Writing into union not supported with macro API."))
+ (cond
+ ((bytevector? value)
+ (bytevector-copy! bytevector offset value 0 size))
+ ((and (list? value) (= 2 (length value)))
+ (let-values (((bytevector* offset* descriptor)
+ (unwrapper #f bytevector offset (car value))))
+ (bytestructure-set!* bytevector* offset* descriptor (cadr value))))
+ (else
+ (error "Invalid value for writing into union." value))))
+ (define meta (make-union-metadata fields))
+ (make-bytestructure-descriptor size alignment unwrapper #f setter meta))
+
+;;; union.scm ends here
+;;; utils.scm --- Utility library for bytestructures.
+
+;; Copyright © 2015 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Just some utility procedures and macros.
+
+
+;;; Code:
+
+(define-syntax define-syntax-rule
+ (syntax-rules ()
+ ((_ (<name> . <args>) <expr>)
+ (define-syntax <name>
+ (syntax-rules ()
+ ((_ . <args>)
+ <expr>))))))
+
+(cond-expand
+ ((or guile syntax-case)
+ (define-syntax-rule (if-syntax-case <then> <else>)
+ <then>))
+ (else
+ (define-syntax-rule (if-syntax-case <then> <else>)
+ <else>)))
+
+(define-syntax-rule (define-syntax-case-stubs <name> ...)
+ (if-syntax-case
+ (begin)
+ (begin
+ (define (<name> . rest)
+ (error "Not supported. You need syntax-case."))
+ ...)))
+
+(define-syntax-case-stubs
+ syntax
+ quasisyntax
+ unsyntax
+ unsyntax-splicing
+ syntax->datum
+ datum->syntax)
+
+;;; utils.scm ends here
+;;; vector.scm --- Vector descriptor constructor.
+
+;; Copyright © 2015, 2016 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This constructor allows the creation of vector descriptors with a specific
+;; length and element descriptor.
+
+;; Be careful with identifier names here; don't confuse vector descriptor and
+;; Scheme vector APIs and variables.
+
+
+;;; Code:
+
+(define-record-type <vector-metadata>
+ (make-vector-metadata length element-descriptor)
+ vector-metadata?
+ (length vector-metadata-length)
+ (element-descriptor vector-metadata-element-descriptor))
+
+(define (bs:vector length descriptor)
+ (define element-size (bytestructure-descriptor-size descriptor))
+ (define size (* length element-size))
+ (define alignment (bytestructure-descriptor-alignment descriptor))
+ (define (unwrapper syntax? bytevector offset index)
+ (values bytevector
+ (if syntax?
+ (quasisyntax
+ (+ (unsyntax offset)
+ (* (unsyntax index) (unsyntax element-size))))
+ (+ offset (* index element-size)))
+ descriptor))
+ (define (setter syntax? bytevector offset value)
+ (when syntax?
+ (error "Writing into vector not supported with macro API."))
+ (cond
+ ((bytevector? value)
+ (bytevector-copy! bytevector offset value 0 size))
+ ((vector? value)
+ (do ((i 0 (+ i 1))
+ (offset offset (+ offset element-size)))
+ ((= i (vector-length value)))
+ (bytestructure-set!*
+ bytevector offset descriptor (vector-ref value i))))
+ (else
+ (error "Invalid value for writing into vector." value))))
+ (define meta (make-vector-metadata length descriptor))
+ (make-bytestructure-descriptor size alignment unwrapper #f setter meta))
+
+;;; vector.scm ends here
+(define-module (bytestructures guile base))
+(import
+ (srfi 9)
+ (srfi 11)
+ (ice-9 format)
+ (bytestructures guile bytevectors)
+ (bytestructures guile utils))
+(include-from-path "bytestructures/body/base.scm")
+(include-from-path "bytestructures/r7/base.exports.sld")
+
+(import (srfi srfi-9 gnu))
+
+(set-record-type-printer!
+ <bytestructure-descriptor>
+ (lambda (record port)
+ (format port "#<bytestructure-descriptor 0x~x>" (object-address record))))
+
+(set-record-type-printer!
+ <bytestructure>
+ (lambda (record port)
+ (format port "#<bytestructure 0x~x>" (object-address record))))
+(define-module (bytestructures guile bitfields))
+(import
+ (srfi 9)
+ (srfi 60)
+ (bytestructures guile utils)
+ (bytestructures guile base)
+ (bytestructures guile numeric-metadata))
+(include-from-path "bytestructures/body/bitfields.scm")
+(include-from-path "bytestructures/r7/bitfields.exports.sld")
+;;; Compatibility shim for Guile, because its implementation of utf16->string
+;;; and utf32->string doesn't conform to R6RS.
+(define-module (bytestructures guile bytevectors))
+
+(import
+ (rnrs base)
+ (rnrs control)
+ (bytestructures r6 bytevectors))
+
+(re-export
+ endianness native-endianness bytevector?
+ make-bytevector bytevector-length bytevector=? bytevector-fill!
+ bytevector-copy!
+ bytevector-copy
+
+ bytevector-u8-ref bytevector-s8-ref
+ bytevector-u8-set! bytevector-s8-set! bytevector->u8-list
+ u8-list->bytevector
+ bytevector-uint-ref bytevector-uint-set!
+ bytevector-sint-ref bytevector-sint-set!
+ bytevector->sint-list bytevector->uint-list
+ uint-list->bytevector sint-list->bytevector
+
+ bytevector-u16-ref bytevector-s16-ref
+ bytevector-u16-set! bytevector-s16-set!
+ bytevector-u16-native-ref bytevector-s16-native-ref
+ bytevector-u16-native-set! bytevector-s16-native-set!
+
+ bytevector-u32-ref bytevector-s32-ref
+ bytevector-u32-set! bytevector-s32-set!
+ bytevector-u32-native-ref bytevector-s32-native-ref
+ bytevector-u32-native-set! bytevector-s32-native-set!
+
+ bytevector-u64-ref bytevector-s64-ref
+ bytevector-u64-set! bytevector-s64-set!
+ bytevector-u64-native-ref bytevector-s64-native-ref
+ bytevector-u64-native-set! bytevector-s64-native-set!
+
+ bytevector-ieee-single-ref
+ bytevector-ieee-single-set!
+ bytevector-ieee-single-native-ref
+ bytevector-ieee-single-native-set!
+
+ bytevector-ieee-double-ref
+ bytevector-ieee-double-set!
+ bytevector-ieee-double-native-ref
+ bytevector-ieee-double-native-set!
+
+ string->utf8
+ utf8->string
+ string->utf16 string->utf32)
+
+(export
+ (r6rs-utf16->string . utf16->string)
+ (r6rs-utf32->string . utf32->string))
+
+(define (read-bom16 bv)
+ (let ((c0 (bytevector-u8-ref bv 0))
+ (c1 (bytevector-u8-ref bv 1)))
+ (cond
+ ((and (= c0 #xFE) (= c1 #xFF))
+ 'big)
+ ((and (= c0 #xFF) (= c1 #xFE))
+ 'little)
+ (else
+ #f))))
+
+(define r6rs-utf16->string
+ (case-lambda
+ ((bv default-endianness)
+ (let ((bom-endianness (read-bom16 bv)))
+ (if (not bom-endianness)
+ (utf16->string bv default-endianness)
+ (substring/shared (utf16->string bv bom-endianness) 1))))
+ ((bv endianness endianness-mandatory?)
+ (if endianness-mandatory?
+ (utf16->string bv endianness)
+ (r6rs-utf16->string bv endianness)))))
+
+(define (read-bom32 bv)
+ (let ((c0 (bytevector-u8-ref bv 0))
+ (c1 (bytevector-u8-ref bv 1))
+ (c2 (bytevector-u8-ref bv 2))
+ (c3 (bytevector-u8-ref bv 3)))
+ (cond
+ ((and (= c0 #x00) (= c1 #x00) (= c2 #xFE) (= c3 #xFF))
+ 'big)
+ ((and (= c0 #xFF) (= c1 #xFE) (= c2 #x00) (= c3 #x00))
+ 'little)
+ (else
+ #f))))
+
+(define r6rs-utf32->string
+ (case-lambda
+ ((bv default-endianness)
+ (let ((bom-endianness (read-bom32 bv)))
+ (if (not bom-endianness)
+ (utf32->string bv default-endianness)
+ (substring/shared (utf32->string bv bom-endianness) 1))))
+ ((bv endianness endianness-mandatory?)
+ (if endianness-mandatory?
+ (utf32->string bv endianness)
+ (r6rs-utf32->string bv endianness)))))
+;;; cstring-pointer.scm --- Pointers to null-terminated strings.
+
+;; Copyright © 2017 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; The cstring-pointer descriptor represents a pointer to a null-terminated
+;; string, and will return the string as a Scheme string upon a reference
+;; operation. Its setter however does not take Scheme strings, only addresses
+;; to existing strings in memory. The reason is: Guile's string->pointer
+;; creates a new C string in memory, returning an FFI pointer object holding its
+;; address; the string is freed when the pointer object is garbage collected.
+;; We have no means of holding a reference to the FFI pointer object; we can
+;; only write the address it holds into our bytevector, which won't protect the
+;; pointer object from GC.
+
+
+;;; Code:
+
+(define-module (bytestructures guile cstring-pointer))
+(import
+ (bytestructures guile base)
+ (bytestructures guile numeric)
+ (prefix (system foreign) ffi-))
+(export cstring-pointer)
+
+(define (bytevector-address-ref bv offset)
+ (bytestructure-ref* bv offset uintptr_t))
+
+(define (bytevector-address-set! bv offset address)
+ (bytestructure-set!* bv offset uintptr_t address))
+
+(define cstring-pointer
+ (let ()
+ (define size (bytestructure-descriptor-size intptr_t))
+ (define alignment (bytestructure-descriptor-alignment intptr_t))
+ (define unwrapper #f)
+ (define (getter syntax? bv offset)
+ (if syntax?
+ #`(let* ((address (bytevector-address-ref #,bv #,offset))
+ (pointer (ffi-make-pointer address)))
+ (ffi-pointer->string pointer))
+ (let* ((address (bytevector-address-ref bv offset))
+ (pointer (ffi-make-pointer address)))
+ (ffi-pointer->string pointer))))
+ (define (setter syntax? bv offset address)
+ (if syntax?
+ #`(bytevector-address-set! #,bv #,offset #,address)
+ (bytevector-address-set! bv offset address)))
+ (make-bytestructure-descriptor size alignment unwrapper getter setter)))
+(define-module (bytestructures guile explicit-endianness))
+(import
+ (bytestructures guile bytevectors)
+ (bytestructures guile utils))
+(include-from-path "bytestructures/body/explicit-endianness.scm")
+(include-from-path "bytestructures/r7/explicit-endianness.exports.sld")
+;;; ffi.scm --- Convert bytestructure descriptors to Guile/libffi types.
+
+;; Copyright © 2016 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module offers a way to convert bytestructure descriptors to Guile/libffi
+;; type objects. For instance, the bytestructure descriptor created with
+;; (bs:struct `((x ,uint8) (y ,uint16))) gets converted into a two-element list
+;; containing the libffi codes for uint8 and uint16.
+
+
+;;; Code:
+
+(define-module (bytestructures guile ffi))
+(import
+ (ice-9 match)
+ (prefix (system foreign) ffi-)
+ (bytestructures guile base)
+ (bytestructures guile numeric)
+ (bytestructures guile vector)
+ (bytestructures guile struct)
+ (bytestructures guile union)
+ (bytestructures guile pointer)
+ (bytestructures guile bitfields))
+(export
+ bytestructure-descriptor->ffi-descriptor
+ bs:pointer->proc
+ )
+
+(define numeric-type-mapping
+ `((,int8 . ,ffi-int8)
+ (,uint8 . ,ffi-uint8)
+ (,int16 . ,ffi-int16)
+ (,uint16 . ,ffi-uint16)
+ (,int32 . ,ffi-int32)
+ (,uint32 . ,ffi-uint32)
+ (,int64 . ,ffi-int64)
+ (,uint64 . ,ffi-uint64)
+ (,float32 . ,ffi-float)
+ (,float64 . ,ffi-double)))
+
+(define (bytestructure-descriptor->ffi-descriptor descriptor)
+ (define (convert descriptor)
+ (cond
+ ((assq descriptor numeric-type-mapping)
+ => (match-lambda ((key . val) val)))
+ (else
+ (let ((metadata (bytestructure-descriptor-metadata descriptor)))
+ (cond
+ ((vector-metadata? metadata)
+ (make-list
+ (vector-metadata-length metadata)
+ (convert (vector-metadata-element-descriptor metadata))))
+ ((struct-metadata? metadata)
+ (map convert (map cdr (struct-metadata-field-alist metadata))))
+ ((union-metadata? metadata)
+ ;; TODO: Add support once Guile/libffi supports this.
+ (error "Unions not supported." descriptor))
+ ((pointer-metadata? metadata)
+ '*)
+ ((bitfield-metadata? metadata)
+ ;; TODO: Add support once Guile/libffi supports this.
+ (error "Bitfields not supported." descriptor))
+ (else
+ (error "Unsupported bytestructure descriptor." descriptor)))))))
+ (cond
+ ((eq? descriptor 'void)
+ ffi-void)
+ ((vector-metadata? (bytestructure-descriptor-metadata descriptor))
+ '*)
+ (else
+ (convert descriptor))))
+
+(define (bs:pointer->proc ret-type func-ptr arg-types)
+ (define (type->raw-type type)
+ (if (bytestructure-descriptor? type)
+ (bytestructure-descriptor->ffi-descriptor type)
+ type))
+ (define (value->raw-value value)
+ (if (bytestructure? value)
+ (ffi-bytevector->pointer (bytestructure-bytevector value))
+ value))
+ (define (raw-value->value raw-value type)
+ (if (bytestructure-descriptor? type)
+ (make-bytestructure (ffi-pointer->bytevector
+ raw-value
+ (bytestructure-descriptor-size type))
+ 0
+ type)
+ raw-value))
+ (let* ((raw-ret-type (type->raw-type ret-type))
+ (raw-arg-types (map type->raw-type arg-types))
+ (raw-proc (ffi-pointer->procedure
+ raw-ret-type func-ptr raw-arg-types)))
+ (lambda args
+ (let* ((raw-args (map value->raw-value args))
+ (raw-ret-val (apply raw-proc raw-args)))
+ (raw-value->value raw-ret-val ret-type)))))
+(define-module (bytestructures guile numeric-all))
+(import
+ (bytestructures guile bytevectors)
+ (bytestructures guile utils)
+ (bytestructures guile base)
+ (bytestructures guile explicit-endianness)
+ (bytestructures guile numeric-data-model))
+(include-from-path "bytestructures/body/numeric.scm")
+(include-from-path "bytestructures/r7/numeric.exports.sld")
+(include-from-path "bytestructures/r7/numeric-metadata.exports.sld")
+(define-module (bytestructures guile numeric-data-model))
+
+(import (system foreign))
+(import (system base target))
+
+(define architecture
+ (let ((cpu (target-cpu)))
+ (cond
+ ((member cpu '("i386" "i486" "i586" "i686"))
+ 'i386)
+ ((string=? "x86_64" cpu)
+ 'x86-64)
+ ((string-prefix? "arm" cpu)
+ 'arm)
+ ((string-prefix? "aarch64" cpu)
+ 'aarch64))))
+
+(define data-model
+ (if (= 4 (sizeof '*))
+ (if (= 2 (sizeof int))
+ 'lp32
+ 'ilp32)
+ (cond
+ ((= 8 (sizeof int)) 'ilp64)
+ ((= 4 (sizeof long)) 'llp64)
+ (else 'lp64))))
+
+(cond-expand-provide
+ (current-module)
+ (list architecture data-model))
+(define-module (bytestructures guile numeric-metadata))
+(import (bytestructures guile numeric-all))
+(re-export
+ signed-integer-native-descriptors
+ signed-integer-le-descriptors
+ signed-integer-be-descriptors
+ signed-integer-descriptors
+ unsigned-integer-native-descriptors
+ unsigned-integer-le-descriptors
+ unsigned-integer-be-descriptors
+ unsigned-integer-descriptors
+ float-native-descriptors
+ float-le-descriptors
+ float-be-descriptors
+ integer-descriptors
+ float-descriptors
+ numeric-descriptors
+ )
+(define-module (bytestructures guile numeric))
+(import (bytestructures guile numeric-all))
+(re-export
+
+ int8 uint8 int16 uint16 int32 uint32 int64 uint64
+ int16le uint16le int32le uint32le int64le uint64le
+ int16be uint16be int32be uint32be int64be uint64be
+ float32 float64 float32le float64le float32be float64be
+
+ short unsigned-short
+ int unsigned-int
+ long unsigned-long
+ long-long unsigned-long-long
+ intptr_t uintptr_t
+ size_t ssize_t ptrdiff_t
+ float double
+
+ complex64 complex128
+ complex64le complex128le
+ complex64be complex128be
+ )
+;;; pointer.scm --- Pointer descriptor constructor.
+
+;; Copyright © 2015 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This constructor allows the creation of pointer descriptors with a specific
+;; pointed-to descriptor.
+
+
+;;; Code:
+
+(define-module (bytestructures guile pointer))
+(import
+ (srfi 9)
+ (bytestructures guile bytevectors)
+ (bytestructures guile utils)
+ (bytestructures guile base)
+ (prefix (system foreign) ffi-))
+(export
+ bs:pointer
+ pointer-metadata? pointer-metadata-content-descriptor
+ )
+
+(define pointer-size (ffi-sizeof '*))
+
+(define bytevector-address-ref
+ (case pointer-size
+ ((1) bytevector-u8-ref)
+ ((2) bytevector-u16-native-ref)
+ ((4) bytevector-u32-native-ref)
+ ((8) bytevector-u64-native-ref)))
+
+(define bytevector-address-set!
+ (case pointer-size
+ ((1) bytevector-u8-set!)
+ ((2) bytevector-u16-native-set!)
+ ((4) bytevector-u32-native-set!)
+ ((8) bytevector-u64-native-set!)))
+
+(define (pointer-ref bytevector offset index content-size)
+ (let* ((base-address (bytevector-address-ref bytevector offset))
+ (address (+ base-address (* index content-size))))
+ (if (zero? base-address)
+ (error "Tried to dereference null-pointer.")
+ (ffi-pointer->bytevector (ffi-make-pointer address) content-size))))
+
+(define (pointer-set! bytevector offset value)
+ (cond
+ ((exact-integer? value)
+ (bytevector-address-set! bytevector offset value))
+ ((bytevector? value)
+ (bytevector-address-set! bytevector offset
+ (ffi-pointer-address
+ (ffi-bytevector->pointer value))))
+ ((bytestructure? value)
+ (bytevector-address-set! bytevector offset
+ (ffi-pointer-address
+ (ffi-bytevector->pointer
+ (bytestructure-bytevector value)))))))
+
+(define-record-type <pointer-metadata>
+ (make-pointer-metadata content-descriptor)
+ pointer-metadata?
+ (content-descriptor pointer-metadata-content-descriptor))
+
+(define (bs:pointer %descriptor)
+ (define (get-descriptor)
+ (if (promise? %descriptor)
+ (force %descriptor)
+ %descriptor))
+ (define size pointer-size)
+ (define alignment size)
+ (define (unwrapper syntax? bytevector offset index)
+ (define (syntax-list id . elements)
+ (datum->syntax id (map syntax->datum elements)))
+ (let ((descriptor (get-descriptor)))
+ (when (eq? 'void descriptor)
+ (error "Tried to follow void pointer."))
+ (let* ((size (bytestructure-descriptor-size descriptor))
+ (index-datum (if syntax? (syntax->datum index) index))
+ (index (if (eq? '* index-datum) 0 index-datum))
+ (bytevector*
+ (if syntax?
+ #`(pointer-ref #,bytevector #,offset #,index #,size)
+ (pointer-ref bytevector offset index size))))
+ (values bytevector* 0 descriptor))))
+ (define (getter syntax? bytevector offset)
+ (if syntax?
+ #`(bytevector-address-ref #,bytevector #,offset)
+ (bytevector-address-ref bytevector offset)))
+ (define (setter syntax? bytevector offset value)
+ (if syntax?
+ #`(pointer-set! #,bytevector #,offset #,value)
+ (pointer-set! bytevector offset value)))
+ (define meta (make-pointer-metadata %descriptor))
+ (make-bytestructure-descriptor size alignment unwrapper getter setter meta))
+
+;;; pointer.scm ends here
+(define-module (bytestructures guile string))
+(import
+ (bytestructures guile bytevectors)
+ (bytestructures guile utils)
+ (bytestructures guile base))
+(include-from-path "bytestructures/body/string.scm")
+(include-from-path "bytestructures/r7/string.exports.sld")
+(define-module (bytestructures guile struct))
+(import
+ (srfi 1)
+ (srfi 9)
+ (srfi 11)
+ (bytestructures guile bytevectors)
+ (bytestructures guile utils)
+ (bytestructures guile base)
+ (bytestructures guile bitfields))
+(include-from-path "bytestructures/body/align.scm")
+(include-from-path "bytestructures/body/struct.scm")
+(include-from-path "bytestructures/r7/struct.exports.sld")
+(define-module (bytestructures guile union))
+(import
+ (srfi 9)
+ (srfi 11)
+ (bytestructures guile bytevectors)
+ (bytestructures guile utils)
+ (bytestructures guile base))
+(include-from-path "bytestructures/body/align.scm")
+(include-from-path "bytestructures/body/union.scm")
+(include-from-path "bytestructures/r7/union.exports.sld")
+(define-module (bytestructures guile utils))
+(include-from-path "bytestructures/body/utils.scm")
+(export
+ if-syntax-case
+ define-syntax-case-stubs
+ )
+(define-module (bytestructures guile vector))
+(import
+ (srfi 9)
+ (bytestructures guile bytevectors)
+ (bytestructures guile utils)
+ (bytestructures guile base))
+(include-from-path "bytestructures/body/vector.scm")
+(include-from-path "bytestructures/r7/vector.exports.sld")
+;;; Compatibility shim for R6RS systems, because R6RS and R7RS have different
+;;; semantics for some procedures of the same name. We use R7RS semantics
+;;; everywhere, so implement them in terms of R6RS.
+(library (bytestructures r6 bytevectors)
+ (export
+ endianness native-endianness bytevector?
+ make-bytevector bytevector-length bytevector=? bytevector-fill!
+ (rename (r7rs-bytevector-copy! bytevector-copy!))
+ (rename (r7rs-bytevector-copy bytevector-copy))
+
+ bytevector-u8-ref bytevector-s8-ref
+ bytevector-u8-set! bytevector-s8-set! bytevector->u8-list
+ u8-list->bytevector
+ bytevector-uint-ref bytevector-uint-set!
+ bytevector-sint-ref bytevector-sint-set!
+ bytevector->sint-list bytevector->uint-list
+ uint-list->bytevector sint-list->bytevector
+
+ bytevector-u16-ref bytevector-s16-ref
+ bytevector-u16-set! bytevector-s16-set!
+ bytevector-u16-native-ref bytevector-s16-native-ref
+ bytevector-u16-native-set! bytevector-s16-native-set!
+
+ bytevector-u32-ref bytevector-s32-ref
+ bytevector-u32-set! bytevector-s32-set!
+ bytevector-u32-native-ref bytevector-s32-native-ref
+ bytevector-u32-native-set! bytevector-s32-native-set!
+
+ bytevector-u64-ref bytevector-s64-ref
+ bytevector-u64-set! bytevector-s64-set!
+ bytevector-u64-native-ref bytevector-s64-native-ref
+ bytevector-u64-native-set! bytevector-s64-native-set!
+
+ bytevector-ieee-single-ref
+ bytevector-ieee-single-set!
+ bytevector-ieee-single-native-ref
+ bytevector-ieee-single-native-set!
+
+ bytevector-ieee-double-ref
+ bytevector-ieee-double-set!
+ bytevector-ieee-double-native-ref
+ bytevector-ieee-double-native-set!
+
+ (rename (r7rs-string->utf8 string->utf8))
+ (rename (r7rs-utf8->string utf8->string))
+ string->utf16 string->utf32
+ utf16->string utf32->string
+ )
+ (import
+ (rnrs base)
+ (rnrs control)
+ (rnrs bytevectors))
+ (define r7rs-bytevector-copy!
+ (case-lambda
+ ((to at from)
+ (bytevector-copy! from 0 to at (bytevector-length from)))
+ ((to at from start)
+ (bytevector-copy! from start to at (- (bytevector-length from) start)))
+ ((to at from start end)
+ (bytevector-copy! from start to at (- end start)))))
+ (define r7rs-bytevector-copy
+ (case-lambda
+ ((bytevector)
+ (bytevector-copy bytevector))
+ ((bytevector start)
+ (r7rs-bytevector-copy bytevector start (bytevector-length bytevector)))
+ ((bytevector start end)
+ (let* ((size (- end start))
+ (bytevector* (make-bytevector size)))
+ (bytevector-copy! bytevector start bytevector* 0 size)
+ bytevector*))))
+ (define r7rs-string->utf8
+ (case-lambda
+ ((string)
+ (string->utf8 string))
+ ((string start)
+ (string->utf8 (substring string start (string-length string))))
+ ((string start end)
+ (string->utf8 (substring string start end)))))
+ (define r7rs-utf8->string
+ (case-lambda
+ ((bytevector)
+ (utf8->string bytevector))
+ ((bytevector start)
+ (utf8->string (r7rs-bytevector-copy bytevector start)))
+ ((bytevector start end)
+ (utf8->string (r7rs-bytevector-copy bytevector start end))))))
+(export
+ make-bytestructure-descriptor
+ bytestructure-descriptor?
+ bytestructure-descriptor-size
+ bytestructure-descriptor-size/syntax
+ bytestructure-descriptor-alignment
+ bytestructure-descriptor-unwrapper
+ bytestructure-descriptor-getter
+ bytestructure-descriptor-setter
+ bytestructure-descriptor-metadata
+ make-bytestructure
+ bytestructure?
+ bytestructure-bytevector
+ bytestructure-offset
+ bytestructure-descriptor
+ bytestructure-size
+ bytestructure
+ bytestructure-unwrap
+ bytestructure-unwrap*
+ bytestructure-ref
+ bytestructure-ref*
+ bytestructure-set!
+ bytestructure-set!*
+ bytestructure-ref/dynamic
+ bytestructure-set!/dynamic
+ bytestructure-unwrap/syntax
+ bytestructure-ref/syntax
+ bytestructure-set!/syntax
+ define-bytestructure-accessors
+ )
+(define-library (bytestructures r7 base)
+ (import
+ (scheme base)
+ (scheme case-lambda)
+ (bytestructures r7 utils))
+ (cond-expand
+ ((library (rnrs syntax-case))
+ (import (rnrs syntax-case)))
+ (else))
+ (include-library-declarations "base.exports.sld")
+ (include "body/base.scm"))
+(export
+ bitfield-descriptor
+ bitfield-metadata?
+ bitfield-metadata-int-descriptor
+ bitfield-metadata-width
+ )
+(define-library (bytestructures r7 bitfields)
+ (import
+ (scheme base)
+ (srfi 60)
+ (bytestructures r7 utils)
+ (bytestructures r7 base)
+ (bytestructures r7 numeric-metadata))
+ (include-library-declarations "bitfields.exports.sld")
+ (include "body/bitfields.scm"))
+(define-library (bytestructures r7 bytevectors)
+ (cond-expand
+ ((library (rnrs bytevectors))
+ (import (except (rnrs bytevectors)
+ bytevector?
+ make-bytevector
+ bytevector-length
+ bytevector-u8-ref
+ bytevector-u8-set!
+ bytevector-copy
+ bytevector-copy!
+ string->utf8
+ utf8->string)))
+ (else
+ (import (except (r6rs bytevectors)
+ bytevector?
+ make-bytevector
+ bytevector-length
+ bytevector-u8-ref
+ bytevector-u8-set!
+ bytevector-copy
+ bytevector-copy!
+ string->utf8
+ utf8->string))))
+ (export
+ endianness
+ native-endianness
+
+ bytevector=?
+ bytevector-fill!
+
+ bytevector-s8-ref
+ bytevector-s8-set!
+ bytevector->u8-list u8-list->bytevector
+
+ bytevector-uint-ref bytevector-sint-ref
+ bytevector-uint-set! bytevector-sint-set!
+ bytevector->uint-list bytevector->sint-list
+ uint-list->bytevector sint-list->bytevector
+
+ bytevector-u16-ref bytevector-s16-ref
+ bytevector-u16-native-ref bytevector-s16-native-ref
+ bytevector-u16-set! bytevector-s16-set!
+ bytevector-u16-native-set! bytevector-s16-native-set!
+
+ bytevector-u32-ref bytevector-s32-ref
+ bytevector-u32-native-ref bytevector-s32-native-ref
+ bytevector-u32-set! bytevector-s32-set!
+ bytevector-u32-native-set! bytevector-s32-native-set!
+
+ bytevector-u64-ref bytevector-s64-ref
+ bytevector-u64-native-ref bytevector-s64-native-ref
+ bytevector-u64-set! bytevector-s64-set!
+ bytevector-u64-native-set! bytevector-s64-native-set!
+
+ bytevector-ieee-single-native-ref
+ bytevector-ieee-single-ref
+ bytevector-ieee-double-native-ref
+ bytevector-ieee-double-ref
+ bytevector-ieee-single-native-set!
+ bytevector-ieee-single-set!
+ bytevector-ieee-double-native-set!
+ bytevector-ieee-double-set!
+
+ string->utf16 string->utf32
+ utf16->string utf32->string
+ ))
+(export
+ bytevector-ieee-single-le-ref bytevector-ieee-single-be-ref
+ bytevector-ieee-single-le-set! bytevector-ieee-single-be-set!
+ bytevector-ieee-double-le-ref bytevector-ieee-double-be-ref
+ bytevector-ieee-double-le-set! bytevector-ieee-double-be-set!
+ bytevector-s16le-ref bytevector-s16be-ref
+ bytevector-s16le-set! bytevector-s16be-set!
+ bytevector-u16le-ref bytevector-u16be-ref
+ bytevector-u16le-set! bytevector-u16be-set!
+ bytevector-s32le-ref bytevector-s32be-ref
+ bytevector-s32le-set! bytevector-s32be-set!
+ bytevector-u32le-ref bytevector-u32be-ref
+ bytevector-u32le-set! bytevector-u32be-set!
+ bytevector-s64le-ref bytevector-s64be-ref
+ bytevector-s64le-set! bytevector-s64be-set!
+ bytevector-u64le-ref bytevector-u64be-ref
+ bytevector-u64le-set! bytevector-u64be-set!
+ )
+(define-library (bytestructures r7 explicit-endianness)
+ (import
+ (scheme base)
+ (bytestructures r7 utils)
+ (bytestructures r7 bytevectors))
+ (include-library-declarations "explicit-endianness.exports.sld")
+ (include "body/explicit-endianness.scm"))
+(define-library (bytestructures r7 numeric-all)
+ (import
+ (scheme base)
+ (scheme complex)
+ (scheme eval)
+ (bytestructures r7 utils)
+ (bytestructures r7 base)
+ (bytestructures r7 bytevectors)
+ (bytestructures r7 explicit-endianness))
+ (include-library-declarations "numeric.exports.sld")
+ (include-library-declarations "numeric-metadata.exports.sld")
+ (include "body/numeric.scm"))
+(export
+ signed-integer-native-descriptors
+ signed-integer-le-descriptors
+ signed-integer-be-descriptors
+ signed-integer-descriptors
+ unsigned-integer-native-descriptors
+ unsigned-integer-le-descriptors
+ unsigned-integer-be-descriptors
+ unsigned-integer-descriptors
+ float-native-descriptors
+ float-le-descriptors
+ float-be-descriptors
+ complex-native-descriptors
+ complex-le-descriptors
+ complex-be-descriptors
+ integer-descriptors
+ float-descriptors
+ complex-descriptors
+ numeric-descriptors
+ )
+(define-library (bytestructures r7 numeric-metadata)
+ (import (bytestructures r7 numeric-all))
+ (include-library-declarations "numeric-metadata.exports.sld"))
+(export
+ int8 int16 int32 int64
+ uint8 uint16 uint32 uint64
+ int16le int32le int64le
+ uint16le uint32le uint64le
+ int16be int32be int64be
+ uint16be uint32be uint64be
+ float32 float64
+ float32le float64le
+ float32be float64be
+
+ short unsigned-short
+ int unsigned-int
+ long unsigned-long
+ long-long unsigned-long-long
+ intptr_t uintptr_t
+ size_t ssize_t ptrdiff_t
+ float double
+
+ complex64 complex128
+ complex64le complex128le
+ complex64be complex128be
+ )
+(define-library (bytestructures r7 numeric)
+ (import (bytestructures r7 numeric-all))
+ (include-library-declarations "numeric.exports.sld"))
+(export bs:string)
+(cond-expand
+ (r6rs
+ (export bytevector->string string->bytevector
+ ascii utf8 utf16le utf16be utf32le utf32be
+ bytevector-zero!))
+ (else))
+(define-library (bytestructures r7 string)
+ (import
+ (scheme base)
+ (bytestructures r7 bytevectors)
+ (bytestructures r7 utils)
+ (bytestructures r7 base))
+ (cond-expand
+ ((library (rnrs syntax-case))
+ (import (rnrs syntax-case)))
+ (else))
+ (include-library-declarations "string.exports.sld")
+ (include "body/string.scm"))
+(export
+ bs:struct
+ struct-metadata?
+ struct-metadata-field-alist
+ )
+(define-library (bytestructures r7 struct)
+ (import
+ (scheme base)
+ (scheme case-lambda)
+ (srfi 1)
+ (srfi 28)
+ (bytestructures r7 utils)
+ (bytestructures r7 base)
+ (bytestructures r7 bitfields))
+ (include-library-declarations "struct.exports.sld")
+ (include "body/align.scm")
+ (include "body/struct.scm"))
+(export
+ bs:union
+ union-metadata?
+ union-metadata-field-alist
+ )
+(define-library (bytestructures r7 union)
+ (import
+ (scheme base)
+ (bytestructures r7 utils)
+ (bytestructures r7 base))
+ (include-library-declarations "union.exports.sld")
+ (include "body/align.scm")
+ (include "body/union.scm"))
+(define-library (bytestructures r7 utils)
+ (import (scheme base))
+ (cond-expand
+ ((library (rnrs syntax-case))
+ (import (rnrs syntax-case)))
+ (else))
+ (export
+ define-syntax-rule
+ if-syntax-case
+ define-syntax-case-stubs
+ quasisyntax
+ unsyntax
+ unsyntax-splicing
+ syntax->datum
+ datum->syntax
+ )
+ (include "body/utils.scm"))
+(export
+ bs:vector
+ vector-metadata?
+ vector-metadata-length
+ vector-metadata-element-descriptor
+ )
+(define-library (bytestructures r7 vector)
+ (import
+ (scheme base)
+ (bytestructures r7 utils)
+ (bytestructures r7 base))
+ (include-library-declarations "vector.exports.sld")
+ (include "body/vector.scm"))
+(define-module (bytestructures guile))
+
+;;; Note: cstring-pointer import/export hack: Guile 2.0.x has a problem when a
+;;; module has the same name as an identifier defined in it, and the identifier
+;;; is imported and re-exported. To work around it, we import `cstring-pointer'
+;;; with a rename to `_cstring-pointer', define `cstring-pointer' explicitly in
+;;; this module, and export that.
+
+(import
+ (bytestructures guile base)
+ (bytestructures guile vector)
+ (bytestructures guile struct)
+ (bytestructures guile union)
+ (bytestructures guile pointer)
+ (bytestructures guile numeric)
+ (bytestructures guile string)
+ (rename (bytestructures guile cstring-pointer)
+ (cstring-pointer _cstring-pointer)))
+(re-export
+ make-bytestructure-descriptor
+ bytestructure-descriptor?
+ bytestructure-descriptor-size
+ bytestructure-descriptor-size/syntax
+ bytestructure-descriptor-alignment
+ bytestructure-descriptor-unwrapper
+ bytestructure-descriptor-getter
+ bytestructure-descriptor-setter
+ bytestructure-descriptor-metadata
+ make-bytestructure
+ bytestructure?
+ bytestructure-bytevector
+ bytestructure-offset
+ bytestructure-descriptor
+ bytestructure-size
+ bytestructure
+ bytestructure-unwrap
+ bytestructure-unwrap*
+ bytestructure-ref
+ bytestructure-ref*
+ bytestructure-set!
+ bytestructure-set!*
+ bytestructure-ref/dynamic
+ bytestructure-set!/dynamic
+ bytestructure-unwrap/syntax
+ bytestructure-ref/syntax
+ bytestructure-set!/syntax
+ define-bytestructure-accessors
+
+ bs:vector
+ vector-metadata? vector-metadata-length vector-metadata-element-descriptor
+
+ bs:struct
+ struct-metadata? struct-metadata-field-alist
+
+ bs:union
+ union-metadata? union-metadata-field-alist
+
+ bs:pointer
+ pointer-metadata? pointer-metadata-content-descriptor
+
+ int8 int16 int32 int64
+ int16le int32le int64le
+ int16be int32be int64be
+ uint8 uint16 uint32 uint64
+ uint16le uint32le uint64le
+ uint16be uint32be uint64be
+ float32 float64
+ float32le float64le
+ float32be float64be
+
+ short unsigned-short
+ int unsigned-int
+ long unsigned-long
+ long-long unsigned-long-long
+ intptr_t uintptr_t
+ size_t ssize_t ptrdiff_t
+ float double
+
+ complex64 complex128
+ complex64le complex128le
+ complex64be complex128be
+
+ bs:string
+ )
+
+(define cstring-pointer _cstring-pointer)
+
+(export cstring-pointer)
+(define-library (bytestructures r7)
+ (import
+ (bytestructures r7 base)
+ (bytestructures r7 vector)
+ (bytestructures r7 struct)
+ (bytestructures r7 union)
+ (bytestructures r7 numeric)
+ (bytestructures r7 string))
+ (include-library-declarations "r7/base.exports.sld")
+ (include-library-declarations "r7/vector.exports.sld")
+ (include-library-declarations "r7/struct.exports.sld")
+ (include-library-declarations "r7/union.exports.sld")
+ (include-library-declarations "r7/numeric.exports.sld")
+ (include-library-declarations "r7/string.exports.sld"))
+;;; Warning: nasal demons.
+;;;
+;;; Will output differences between GCC's behavior and our behavior, but not in
+;;; a very nice format. Zero output is good. The C code and Scheme procedure
+;;; we generate are fairly straightforward so read the code to understand.
+
+(define-module (bytestructures bitfield-tests))
+
+(export run-bitfield-tests)
+
+(use-modules (srfi srfi-1)
+ (srfi srfi-9)
+ (ice-9 rdelim)
+ (bytestructures r6 bytevectors)
+ (bytestructures guile))
+
+(define-record-type <struct>
+ (make-struct name fields)
+ struct?
+ (name struct-name)
+ (fields struct-fields))
+
+(define-record-type <field>
+ (make-field name int-size bit-size signed? value)
+ struct?
+ (name field-name)
+ (int-size field-int-size)
+ (bit-size field-bit-size)
+ (signed? field-signed?)
+ (value field-value))
+
+(define *keep-files* (make-parameter #f))
+
+(define (run-bitfield-tests count random-seed-string keep-files)
+ (set! *random-state* (seed->random-state random-seed-string))
+ (parameterize ((*keep-files* keep-files))
+ (test-structs (generate-structs count))))
+
+(define (generate-structs n)
+ (remove-bad-structs (map random-struct (iota n))))
+
+(define (remove-bad-structs structs)
+ (filter (lambda (struct)
+ (find (lambda (field)
+ (not (zero? (field-bit-size field))))
+ (struct-fields struct)))
+ structs))
+
+(define (random-struct i)
+ (let ((field-count (+ 1 (random 50))))
+ (make-struct (format #f "s~a" i)
+ (map random-field (iota field-count)))))
+
+(define (random-field i)
+ (let* ((name (format #f "f~a" i))
+ (int-size (* 8 (expt 2 (random 4))))
+ (bit-size (random (+ 1 int-size)))
+ (signed? (= 0 (random 2)))
+ (value (random (expt 2 bit-size)))
+ (value (if (and signed? (> value (+ -1 (expt 2 (- bit-size 1)))))
+ (- value (expt 2 bit-size))
+ value)))
+ (make-field name int-size bit-size signed? value)))
+
+(define (test-structs structs)
+ (let* ((c-code (c-code-for-structs structs))
+ (c-output (get-c-output c-code))
+ (scm-code (scm-code-for-structs structs))
+ (scm-output (get-scm-output scm-code)))
+ (diff-outputs c-output scm-output)))
+
+(define (c-code-for-structs structs)
+ (string-concatenate
+ (append
+ (list "#include <stdio.h>\n"
+ "#include <stdint.h>\n"
+ "#include <strings.h>\n"
+ "int main(){\n")
+ (map c-code-for-struct structs)
+ (list "return 0;}"))))
+
+(define (c-code-for-struct struct)
+ (let ((name (struct-name struct))
+ (fields (struct-fields struct)))
+ (string-concatenate
+ (append
+ (list (format #f "struct ~a {\n" name))
+ (map c-decl-for-field fields)
+ (list "};\n"
+ (format #f "{ struct ~a foo;\n" name)
+ (format #f " bzero((void*)&foo, sizeof(foo));\n"))
+ (map c-assignment-for-field fields)
+ (list (format #f " printf(\"struct ~a:\\n\");\n" name)
+ " uint8_t *ptr = (void*)&foo;\n"
+ " for (int i = 0; i < sizeof(foo); ++i) {\n"
+ " printf(\"%d \", *(ptr+i));\n"
+ " }\n"
+ " printf(\"\\n\");\n"
+ "}\n")))))
+
+(define (c-decl-for-field field)
+ (let ((name (field-name field))
+ (int-size (field-int-size field))
+ (bit-size (field-bit-size field))
+ (signed? (field-signed? field)))
+ (format #f " ~aint~a_t ~a:~a;\n"
+ (if signed? "" "u")
+ int-size
+ (if (zero? bit-size) "" name)
+ bit-size)))
+
+(define (c-assignment-for-field field)
+ (let ((name (field-name field))
+ (bit-size (field-bit-size field))
+ (signed? (field-signed? field))
+ (value (field-value field)))
+ (if (zero? bit-size)
+ ""
+ (format #f " foo.~a = ~a~a;\n" name value (if signed? "" "u")))))
+
+(define (get-c-output code)
+ (let* ((port (mkstemp! (string-copy "/tmp/bitfield-XXXXXX")))
+ (file (port-filename port))
+ (exe-port (mkstemp! (string-copy "/tmp/bitfield-compiled-XXXXXX")))
+ (exe-file (port-filename exe-port))
+ (output-port (mkstemp! (string-copy "/tmp/bitfield-output-XXXXXX")))
+ (output-file (port-filename output-port)))
+ (close-port exe-port)
+ (close-port output-port)
+ (display code port)
+ (force-output port)
+ (unless (zero? (system* "gcc" "-x" "c" "-std=c11" file "-o" exe-file))
+ (error "gcc failed"))
+ (unless (zero? (system (format #f "~a > ~a" exe-file output-file)))
+ (error "exe failed"))
+ (let ((out (read-string (open output-file O_RDONLY))))
+ (unless (*keep-files*)
+ (for-each delete-file (list file exe-file output-file)))
+ out)))
+
+(define (scm-code-for-structs structs)
+ (lambda ()
+ (string-concatenate
+ (map scm-code-for-struct structs))))
+
+(define (scm-code-for-struct struct)
+ (let* ((name (struct-name struct))
+ (fields (struct-fields struct))
+ (descriptor (struct->descriptor struct))
+ (values (map field-value (filter-nonzero-fields fields)))
+ (bs (bytestructure descriptor (list->vector values))))
+ (string-concatenate
+ (append
+ (list (format #f "struct ~a:\n" name))
+ (let ((bv (bytestructure-bytevector bs)))
+ (map (lambda (i)
+ (format #f "~a " (bytevector-u8-ref bv i)))
+ (iota (bytevector-length bv))))
+ (list "\n")))))
+
+(define (struct->descriptor struct)
+ (let ((fields (struct-fields struct)))
+ (bs:struct (map field->struct-descriptor-field fields))))
+
+(define (field->struct-descriptor-field field)
+ (let ((name (field-name field))
+ (int-size (field-int-size field))
+ (bit-size (field-bit-size field))
+ (signed? (field-signed? field)))
+ (list name
+ (module-ref (resolve-module
+ '(bytestructures bitfield-tests))
+ (string->symbol
+ (format #f "~aint~a"
+ (if signed? "" "u")
+ int-size)))
+ bit-size)))
+
+(define (filter-nonzero-fields fields)
+ (filter (lambda (field)
+ (not (zero? (field-bit-size field))))
+ fields))
+
+(define (get-scm-output code)
+ (code))
+
+(define (diff-outputs o1 o2)
+ (let* ((p1 (mkstemp! (string-copy "/tmp/bitfield-out1-XXXXXX")))
+ (f1 (port-filename p1))
+ (p2 (mkstemp! (string-copy "/tmp/bitfield-out2-XXXXXX")))
+ (f2 (port-filename p2)))
+ (display o1 p1)
+ (display o2 p2)
+ (flush-all-ports)
+ (close-port p1)
+ (close-port p2)
+ (let ((retval (system* "diff" "-y" "--suppress-common" f1 f2)))
+ (unless (*keep-files*)
+ (for-each delete-file (list f1 f2)))
+ retval)))
+;;; Use this in the REPL. It produces wrong results when ran as a script.
+
+(use-modules (system vm coverage)
+ (system vm vm)
+ (srfi srfi-11))
+
+(let ((output-directory
+ (string-append
+ (getenv "HOME") "/srv/http/htdocs/lcov/scheme-bytestructures")))
+ (let-values (((data . values)
+ (with-code-coverage (the-vm)
+ (lambda ()
+ (load "run-tests.guile.scm")))))
+ (let* ((port (mkstemp! (string-copy "/tmp/bytestructures-coverage-XXXXXX")))
+ (file (port-filename port)))
+ (coverage-data->lcov data port)
+ (close port)
+ (when (not (zero? (system* "genhtml" file "-o" output-directory)))
+ (error "genhtml failed"))
+ (delete-file file))))
+;;; run-tests.body.scm --- Bytestructures test suite.
+
+;; Copyright © 2015, 2021 Taylan Kammer <taylan.kammer@gmail.com>
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; A relatively simple SRFI-64 test suite.
+
+
+;;; Code:
+
+(define-syntax-rule (test-= name expected expr)
+ (test-approximate name expected expr 0))
+
+(define-syntax-rule (maybe-skip-syntax . <body>)
+ (if-syntax-case
+ (begin . <body>)
+ (begin)))
+
+(test-begin "bytestructures")
+
+(test-group "numeric"
+ (define-syntax test-numeric-descriptors
+ (syntax-rules ()
+ ((_ <descriptor-id> ...)
+ (let ()
+ (define (destructure-numeric-descriptor-entry descriptor-entry proc)
+ (define descriptor (list-ref descriptor-entry 0))
+ (define name (list-ref descriptor-entry 1))
+ (define getter (list-ref descriptor-entry 2))
+ (define setter (list-ref descriptor-entry 3))
+ (define size (bytestructure-descriptor-size descriptor))
+ (define float? (assq descriptor float-descriptors))
+ (define signed? (or float? (assq descriptor signed-integer-descriptors)))
+ (proc descriptor name getter setter size float? signed?))
+ (define (get-min/max float? signed? size)
+ (cond
+ (float? (inexact (expt 2 (case size ((4) 24) ((8) 53)))))
+ (signed? (- (expt 256 (- size 1))))
+ (else (- (expt 256 size) 1))))
+ (destructure-numeric-descriptor-entry
+ (assq <descriptor-id> numeric-descriptors)
+ (lambda (descriptor name getter setter size float? signed?)
+ (test-group (symbol->string name)
+ (let ((test-value-1 (if float? 1.0 1))
+ (test-value-2 (if float? 2.0 1)))
+ (test-group "procedural"
+ (define min/max (get-min/max float? signed? size))
+ (define bs (bytestructure descriptor))
+ (test-eqv "size" size (bytevector-length
+ (bytestructure-bytevector bs)))
+ (test-= "ref" test-value-1
+ (begin
+ (setter (bytestructure-bytevector bs) 0 test-value-1)
+ (bytestructure-ref bs)))
+ (test-= "set" test-value-2
+ (begin
+ (bytestructure-set! bs test-value-2)
+ (getter (bytestructure-bytevector bs) 0)))
+ (test-= "min/max" min/max
+ (begin
+ (bytestructure-set! bs min/max)
+ (bytestructure-ref bs))))
+ (maybe-skip-syntax
+ (test-group "syntactic"
+ (define min/max (get-min/max float? signed? size))
+ ;; Must insert the top-level reference <descriptor-id> here.
+ (define-bytestructure-accessors <descriptor-id>
+ bs-unwrapper bs-getter bs-setter)
+ (define bv (make-bytevector size))
+ (test-= "ref" test-value-1
+ (begin
+ (setter bv 0 test-value-1)
+ (bs-getter bv)))
+ (test-= "set" test-value-2
+ (begin
+ (bs-setter bv test-value-2)
+ (getter bv 0)))
+ (test-= "min/max" min/max
+ (begin
+ (bs-setter bv min/max)
+ (bs-getter bv)))))))))
+ ...))))
+ (test-numeric-descriptors
+ float32 float32le float32be
+ float64 float64le float64be
+ int8 int16 int32 int64
+ int16le int32le int64le
+ int16be int32be int64be
+ uint8 uint16 uint32 uint64
+ uint16le uint32le uint64le
+ uint16be uint32be uint64be))
+
+(test-group "vector"
+ (test-assert "create" (bs:vector 3 uint16))
+ (test-group "procedural"
+ (define bs (bytestructure (bs:vector 3 uint16)))
+ (bytevector-u16-native-set! (bytestructure-bytevector bs) 2 321)
+ (test-eqv "ref" 321 (bytestructure-ref bs 1))
+ (test-eqv "set" 456 (begin (bytestructure-set! bs 1 456)
+ (bytestructure-ref bs 1)))
+ (test-eqv "init" 321
+ (let ((bs (bytestructure (bs:vector 3 uint16) '#(321 123 321))))
+ (bytestructure-ref bs 2))))
+ (maybe-skip-syntax
+ (test-group "syntactic"
+ (define-bytestructure-accessors (bs:vector 3 uint16)
+ unwrapper getter setter)
+ (define bv (make-bytevector 6))
+ (bytevector-u16-native-set! bv 2 321)
+ (test-eqv "ref" 321 (getter bv 1))
+ (test-eqv "set" 456 (begin (setter bv 1 456)
+ (getter bv 1))))))
+
+(test-group "struct"
+ (test-group "aligned"
+ (test-assert "create" (bs:struct `((x ,uint8) (y ,uint16))))
+ (test-group "procedural"
+ (define bs (bytestructure (bs:struct `((x ,uint8) (y ,uint16)))))
+ (bytevector-u16-native-set! (bytestructure-bytevector bs) 2 321)
+ (test-eqv "ref" 321 (bytestructure-ref bs 'y))
+ (test-eqv "set" 456 (begin (bytestructure-set! bs 'y 456)
+ (bytestructure-ref bs 'y)))
+ (test-eqv "init" 321
+ (let ((bs (bytestructure (bs:struct `((x ,uint8) (y ,uint16)))
+ '#(123 321))))
+ (bytestructure-ref bs 'y))))
+ (maybe-skip-syntax
+ (test-group "syntactic"
+ (define-bytestructure-accessors (bs:struct `((x ,uint8) (y ,uint16)))
+ unwrapper getter setter)
+ (define bv (make-bytevector 4))
+ (bytevector-u16-native-set! bv 2 321)
+ (test-eqv "ref" 321 (getter bv y))
+ (test-eqv "set" 456 (begin (setter bv y 456)
+ (getter bv y))))))
+ (test-group "packed"
+ (test-assert "create" (bs:struct #t `((x ,uint8) (y ,uint16))))
+ (test-group "procedural"
+ (define bs (bytestructure (bs:struct #t `((x ,uint8) (y ,uint16)))))
+ ;; u16-native-set! may error on non-aligned access.
+ (guard (err (else (test-skip 3)))
+ (bytevector-u16-native-set! (bytestructure-bytevector bs) 1 321))
+ (test-eqv "ref" 321 (bytestructure-ref bs 'y))
+ (test-eqv "set" 456 (begin (bytestructure-set! bs 'y 456)
+ (bytestructure-ref bs 'y)))
+ (test-eqv "init" 321
+ (let ((bs (bytestructure (bs:struct #t `((x ,uint8) (y ,uint16)))
+ '#(123 321))))
+ (bytestructure-ref bs 'y))))
+ (maybe-skip-syntax
+ (test-group "syntactic"
+ (define-bytestructure-accessors (bs:struct #t `((x ,uint8) (y ,uint16)))
+ unwrapper getter setter)
+ (define bv (make-bytevector 4))
+ ;; u16-native-set! may error on non-aligned access.
+ (guard (err (else (test-skip 2)))
+ (bytevector-u16-native-set! bv 1 321))
+ (test-eqv "ref" 321 (getter bv y))
+ (test-eqv "set" 456 (begin (setter bv y 456)
+ (getter bv y))))))
+
+ (test-group "anonymous-union"
+ (test-assert "create"
+ (bs:struct
+ `((x ,uint8)
+ (union
+ ((a ,uint16)
+ (b ,uint32))))))
+ ;; Don't use 64-bit ints; their alignment differs between platforms.
+ (test-group "aligned"
+ (define bs
+ (bytestructure
+ (bs:struct
+ `((union
+ ((x ,uint8)
+ (y ,uint16)))
+ (union
+ ((a ,uint16)
+ (b ,uint32)))))))
+ (test-eqv "size" 8 (bytevector-length (bytestructure-bytevector bs)))
+ (bytevector-u16-native-set! (bytestructure-bytevector bs) 4 321)
+ (test-eqv "ref1" 321 (bytestructure-ref bs 'a))
+ (bytevector-u32-native-set! (bytestructure-bytevector bs) 4 456)
+ (test-eqv "ref2" 456 (bytestructure-ref bs 'b))
+ (test-eqv "set1" 789 (begin (bytestructure-set! bs 'a 789)
+ (bytestructure-ref bs 'a)))
+ (test-eqv "set2" 987 (begin (bytestructure-set! bs 'b 987)
+ (bytestructure-ref bs 'b))))
+ (test-group "packed"
+ (define bs
+ (bytestructure
+ (bs:struct
+ #t
+ `((union
+ ((x ,uint8)
+ (y ,uint16)))
+ (union
+ ((a ,uint16)
+ (b ,uint32)))))))
+ (test-eqv "size" 6 (bytevector-length (bytestructure-bytevector bs)))
+ (bytevector-u16-native-set! (bytestructure-bytevector bs) 2 321)
+ (test-eqv "ref1" 321 (bytestructure-ref bs 'a))
+ (bytevector-u32-native-set! (bytestructure-bytevector bs) 2 456)
+ (test-eqv "ref2" 456 (bytestructure-ref bs 'b))
+ (test-eqv "set1" 789 (begin (bytestructure-set! bs 'a 789)
+ (bytestructure-ref bs 'a)))
+ (test-eqv "set2" 987 (begin (bytestructure-set! bs 'b 987)
+ (bytestructure-ref bs 'b))))))
+
+(test-group "union"
+ (test-assert "create" (bs:union `((x ,uint8) (y ,uint16))))
+ (test-group "procedural"
+ (define bs (bytestructure (bs:union `((x ,uint8) (y ,uint16)))))
+ (bytevector-u16-native-set! (bytestructure-bytevector bs) 0 321)
+ (test-eqv "ref" 321 (bytestructure-ref bs 'y))
+ (test-eqv "set" 456 (begin (bytestructure-set! bs 'y 456)
+ (bytestructure-ref bs 'y))))
+ (maybe-skip-syntax
+ (test-group "syntactic"
+ (define-bytestructure-accessors (bs:union `((x ,uint8) (y ,uint16)))
+ unwrapper getter setter)
+ (define bv (make-bytevector 2))
+ (bytevector-u16-native-set! bv 0 321)
+ (test-eqv "ref" 321 (getter bv y))
+ (test-eqv "set" 456 (begin (setter bv y 456)
+ (getter bv y))))))
+
+(test-group "string"
+ (test-group "ascii"
+ (test-assert "create" (bs:string 4 'ascii))
+ (test-group "procedural"
+ (define bsd (bs:string 4 'ascii))
+ (define bs (make-bytestructure (string->utf8 "1234") 0 bsd))
+ (test-equal "ref" "1234" (bytestructure-ref bs))
+ (test-equal "set" "4321" (begin
+ (bytestructure-set! bs "4321")
+ (bytestructure-ref bs)))
+ (test-error "too-long" #t (bytestructure-set! bs "12345"))
+ (test-error "too-short" #t (bytestructure-set! bs "123"))
+ (set! bs (make-bytestructure (string->utf8 "äåãø") 0 bsd))
+ (test-error "decoding-error" #t (bytestructure-ref bs))
+ (test-error "encoding-error" #t (bytestructure-set! bs "øãåä")))
+ (test-group "syntactic"
+ (define-bytestructure-accessors (bs:string 4 'ascii)
+ unwrapper getter setter)
+ (define bv (string->utf8 "1234"))
+ (test-equal "ref" "1234" (getter bv))
+ (test-equal "set" "4321" (begin
+ (setter bv "4321")
+ (getter bv)))
+ (test-error "too-long" #t (setter bv "12345"))
+ (test-error "too-short" #t (setter bv "123"))
+ (set! bv (string->utf8 "äåãø"))
+ (test-error "ref-error" #t (getter bv))
+ (test-error "set-error" #t (setter bv "øãåä"))))
+ (test-group "utf8"
+ (test-assert "create" (bs:string 4 'utf8))
+ (test-group "procedural"
+ (define bsd (bs:string 4 'utf8))
+ (define bs (make-bytestructure (string->utf8 "1234") 0 bsd))
+ (test-equal "ref" "1234" (bytestructure-ref bs))
+ (test-equal "set" "4321" (begin
+ (bytestructure-set! bs "4321")
+ (bytestructure-ref bs)))
+ (test-error "too-long" #t (bytestructure-set! bs "äåãø"))
+ (test-equal (string-append "123" (string #\nul))
+ (begin
+ (bytestructure-set! bs "123")
+ (bytestructure-ref bs))))
+ (test-group "syntactic"
+ (define-bytestructure-accessors (bs:string 4 'utf8)
+ unwrapper getter setter)
+ (define bv (string->utf8 "1234"))
+ (test-equal "ref" "1234" (getter bv))
+ (test-equal "set" "4321" (begin
+ (setter bv "4321")
+ (getter bv)))
+ (test-error "too-long" #t (setter bv "äåãø"))
+ (test-equal (string-append "123" (string #\nul))
+ (begin
+ (setter bv "123")
+ (getter bv)))))
+ (let ()
+ (define-syntax-rule
+ (test-string-encodings
+ (<name> <encoding> <endianness> <size> <fixed-width?> <string->utf>)
+ ...)
+ (begin
+ (test-group <name>
+ (test-assert "create" (bs:string <size> '<encoding>))
+ (test-group "procedural"
+ (define bs (make-bytestructure (<string->utf> "1234" '<endianness>)
+ 0
+ (bs:string <size> '<encoding>)))
+ (test-equal "ref" "1234" (bytestructure-ref bs))
+ (test-equal "set" "4321" (begin
+ (bytestructure-set! bs "4321")
+ (bytestructure-ref bs)))
+ (test-error "too-long" #t (bytestructure-set! bs "12345"))
+ (if <fixed-width?>
+ (test-error "too-short" #t (bytestructure-set! bs "123"))
+ (test-equal (string-append "123" (string #\nul))
+ (begin
+ (bytestructure-set! bs "123")
+ (bytestructure-ref bs)))))
+ (test-group "syntactic"
+ (define-bytestructure-accessors (bs:string <size> '<encoding>)
+ unwrapper getter setter)
+ (define bv (<string->utf> "1234" '<endianness>))
+ (test-equal "ref" "1234" (getter bv))
+ (test-equal "set" "4321" (begin
+ (setter bv "4321")
+ (getter bv)))
+ (test-error "too-long" #t (setter bv "12345"))
+ (if <fixed-width?>
+ (test-error "too-short" #t (setter bv "123"))
+ (test-equal (string-append "123" (string #\nul))
+ (begin
+ (setter bv "123")
+ (getter bv))))))
+ ...))
+ (test-string-encodings
+ ("utf16le" utf16le little 8 #f string->utf16)
+ ("utf16be" utf16be big 8 #f string->utf16)
+ ("utf32le" utf32le little 16 #t string->utf32)
+ ("utf32be" utf32be big 16 #t string->utf32))))
+
+(cond-expand
+ (guile
+ (let ()
+
+ (define (protect-from-gc-upto-here obj)
+ (with-output-to-file *null-device*
+ (lambda ()
+ (display (eq? #f obj)))))
+
+ (define pointer-size (ffi-sizeof '*))
+ (define bytevector-address-set!
+ (case pointer-size
+ ((1) bytevector-u8-set!)
+ ((2) bytevector-u16-native-set!)
+ ((4) bytevector-u32-native-set!)
+ ((8) bytevector-u64-native-set!)))
+
+ (test-group "pointer"
+ (test-assert "create" (bs:pointer uint16))
+ (test-group "procedural"
+ (define bs (bytestructure (bs:pointer uint16)))
+ (define bv1 (make-bytevector 2))
+ (define bv2 (make-bytevector 4))
+ (define address1 (ffi-pointer-address (ffi-bytevector->pointer bv1)))
+ (define address2 (ffi-pointer-address (ffi-bytevector->pointer bv2)))
+ (bytevector-address-set! (bytestructure-bytevector bs) 0 address1)
+ (bytevector-u16-native-set! bv1 0 321)
+ (test-eqv "ref1" 321 (bytestructure-ref bs '*))
+ (test-eqv "set1" 456 (begin (bytestructure-set! bs '* 456)
+ (bytestructure-ref bs '*)))
+ (test-eqv "ref2" address1 (bytestructure-ref bs))
+ (test-eqv "set2" address2 (begin (bytestructure-set! bs address2)
+ (bytestructure-ref bs)))
+ (bytevector-address-set! (bytestructure-bytevector bs) 0 address2)
+ (bytevector-u16-native-set! bv2 2 456)
+ (test-eqv "ref3" 456 (bytestructure-ref bs 1))
+ (test-eqv "set3" 789 (begin (bytestructure-set! bs 1 789)
+ (bytestructure-ref bs 1)))
+ (protect-from-gc-upto-here bv1)
+ (protect-from-gc-upto-here bv2))
+ (test-group "syntactic"
+ (define-bytestructure-accessors (bs:pointer uint16)
+ unwrapper getter setter)
+ (define bv (make-bytevector pointer-size))
+ (define bv1 (make-bytevector 2))
+ (define bv2 (make-bytevector 4))
+ (define address1 (ffi-pointer-address (ffi-bytevector->pointer bv1)))
+ (define address2 (ffi-pointer-address (ffi-bytevector->pointer bv2)))
+ (bytevector-address-set! bv 0 address1)
+ (bytevector-u16-native-set! bv1 0 321)
+ (test-eqv "ref" 321 (getter bv *))
+ (test-eqv "set" 456 (begin (setter bv * 456)
+ (getter bv *)))
+ (test-eqv "ref2" address1 (getter bv))
+ (test-eqv "set2" address1 (begin (setter bv address1)
+ (getter bv)))
+ (bytevector-address-set! bv 0 address2)
+ (bytevector-u16-native-set! bv2 2 456)
+ (test-eqv "ref3" 456 (getter bv 1))
+ (test-eqv "set3" 789 (begin (setter bv 1 789)
+ (getter bv 1)))
+ (protect-from-gc-upto-here bv1)
+ (protect-from-gc-upto-here bv2)))
+
+ (test-group "cstring-pointer"
+ (let* ((cstr1-ptr (ffi-string->pointer "abc"))
+ (cstr2-ptr (ffi-string->pointer "cba"))
+ (cstr1-addr (ffi-pointer-address cstr1-ptr))
+ (cstr2-addr (ffi-pointer-address cstr2-ptr)))
+ (test-group "procedural"
+ (define bs (bytestructure cstring-pointer))
+ (bytevector-address-set! (bytestructure-bytevector bs) 0 cstr1-addr)
+ (test-equal "ref" "abc" (bytestructure-ref bs))
+ (test-equal "set" "cba" (begin (bytestructure-set! bs cstr2-addr)
+ (bytestructure-ref bs))))
+ (test-group "syntactic"
+ (define-bytestructure-accessors cstring-pointer
+ unwrapper getter setter)
+ (define bv (make-bytevector pointer-size))
+ (bytevector-address-set! bv 0 cstr1-addr)
+ (test-equal "ref" "abc" (getter bv))
+ (test-equal "set" "cba" (begin (setter bv cstr2-addr)
+ (getter bv))))))))
+
+ (else
+ ))
+
+;; Do this before test-end since it removes the auto-inserted test runner.
+(define success
+ (let ((runner (test-runner-current)))
+ (and (zero? (test-runner-xpass-count runner))
+ (zero? (test-runner-fail-count runner)))))
+
+(test-end "bytestructures")
+
+(exit (if success 0 1))
+
+;; Local Variables:
+;; eval: (put (quote test-group) (quote scheme-indent-function) 1)
+;; eval: (put (quote test-=) (quote scheme-indent-function) 2)
+;; End:
+
+(use-modules
+ (srfi srfi-11)
+ (srfi srfi-64)
+ ((rnrs exceptions) #\select (guard))
+ ((system foreign) #\prefix ffi-)
+ (bytestructures r6 bytevectors)
+ (bytestructures guile utils)
+ (bytestructures guile)
+ (bytestructures guile numeric-metadata))
+
+(define inexact exact->inexact)
+
+(include-from-path "run-tests.body.scm")
+(import
+ (scheme base)
+ (srfi 64)
+ (bytestructures r7 utils)
+ (bytestructures r7)
+ (bytestructures r7 numeric-metadata)
+ (bytestructures r7 bytevectors)
+ (bytestructures r7 explicit-endianness))
+
+(include "run-tests.body.scm")
+;;; commonmark.scm --- An implementation of CommonMark markdown
+
+;; Copyright (C) 2014 Taylan Ulrich Bayirli/Kammer
+
+;; Author: Taylan Ulrich Bayirli/Kammer <taylanbayirli@gmail.com>
+;; Keywords:
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(define (parse port)
+ (let* ((lines (preprocess port))
+ (blocks (parse-blocks lines)))
+ blocks))
+
+(define (preprocess port)
+ (do ((line (read-line port) (read-line port))
+ (lines '() (cons (preprocess-line line) lines)))
+ ((eof-object? line) (reverse lines))))
+
+(define (preprocess-line line)
+ (do ((chars (string->list line) (cdr chars))
+ (processed-chars '() (let ((char (car chars)))
+ (if (char=? char #\tab)
+ (append (make-list 4 #\space)
+ processed-chars)
+ (cons char processed-chars)))))
+ ((null? chars) (apply string (reverse processed-chars)))))
+
+(define (parse-blocks lines)
+ (do ((lines lines (cdr lines))
+ (blocks '() (let ((blocks* (add-line blocks (car lines))))
+ (if blocks*
+ blocks*
+ (begin (close-block! (car blocks))
+ blocks))))))
+ ((null? lines) (reverse blocks)))
+
+;;; BLOCKS is in reverse here.
+(define (add-line blocks line)
+ (if (null? blocks)
+ (cons (new-block line) blocks)
+ (let ((last-block (car blocks)))
+ (cond
+ ((and (open-text-block? last-block)
+ (plain-text-line? line))
+ (add-line-to-text-block last-block line))
+ ((and (open-container-block? last-block)
+ ()))))))
+
+;;; commonmark.scm ends here
+(export
+
+ )
+(define-library (commonmark r7rs)
+ (import (scheme base))
+ (include-library-declarations "r7rs-exports.scm")
+ (include "commonmark.scm"))
+;;; generic-ref-set --- Generic accessor and modifier operators.
+
+;; Copyright © 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
+
+;; Permission is hereby granted, free of charge, to any person obtaining
+;; a copy of this software and associated documentation files (the
+;; "Software"), to deal in the Software without restriction, including
+;; without limitation the rights to use, copy, modify, merge, publish,
+;; distribute, sublicense, and/or sell copies of the Software, and to
+;; permit persons to whom the Software is furnished to do so, subject to
+;; the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+;;; Helpers
+
+(define-syntax push!
+ (syntax-rules ()
+ ((_ <list-var> <x>)
+ (set! <list-var> (cons <x> <list-var>)))))
+
+(define (alist->hashtable alist)
+ (let ((table (make-eqv-hashtable 100)))
+ (for-each (lambda (entry)
+ (hashtable-set! table (car entry) (cdr entry)))
+ alist)
+ table))
+
+;;; Main
+
+(define ref
+ (case-lambda
+ ((object field)
+ (let ((getter (lookup-getter object))
+ (sparse? (sparse-type? object)))
+ (if sparse?
+ (let* ((not-found (cons #f #f))
+ (result (getter object field not-found)))
+ (if (eqv? result not-found)
+ (error "Object has no entry for field." object field)
+ result))
+ (getter object field))))
+ ((object field default)
+ (let ((getter (lookup-getter object)))
+ (getter object field default)))))
+
+(define-syntax set!
+ (syntax-rules ()
+ ((set! <place> <expression>)
+ (%set! <place> <expression>))
+ ((set! <object> <field> <value>)
+ (let* ((object <object>)
+ (setter (lookup-setter object)))
+ (setter object <field> <value>)))))
+
+(set! (setter ref) (lambda (object field value) (set! object field value)))
+
+(define (lookup-getter object)
+ (or (hashtable-ref getter-table (type-of object) #f)
+ (error "No generic getter for object's type." object)))
+
+(define (lookup-setter object)
+ (or (hashtable-ref setter-table (type-of object) #f)
+ (error "No generic setter for object's type." object)))
+
+(define (sparse-type? object)
+ (memv (type-of object) sparse-types))
+
+(define (type-of object)
+ (find (lambda (pred) (pred object)) type-list))
+
+(define getter-table
+ (alist->hashtable
+ (list (cons bytevector? bytevector-u8-ref)
+ (cons hashtable? hashtable-ref)
+ (cons pair? list-ref)
+ (cons string? string-ref)
+ (cons vector? vector-ref))))
+
+(define setter-table
+ (alist->hashtable
+ (list (cons bytevector? bytevector-u8-set!)
+ (cons hashtable? hashtable-set!)
+ (cons pair? list-set!)
+ (cons string? string-set!)
+ (cons vector? vector-set!))))
+
+(define sparse-types
+ (list hashtable?))
+
+(define type-list
+ (list boolean? bytevector? char? eof-object? hashtable? null? number? pair?
+ port? procedure? string? symbol? vector?))
+
+(define-syntax define-record-type
+ (syntax-rules ()
+ ((_ <name> <constructor> <pred> <field> ...)
+ (begin
+ (%define-record-type <name> <constructor> <pred> <field> ...)
+ (push! type-list <pred>)
+ (register-record-getter <pred> <field> ...)
+ (register-record-setter <pred> <field> ...)))))
+
+(define-syntax register-record-getter
+ (syntax-rules ()
+ ((_ <pred> (<field> <getter> . <rest>) ...)
+ (let ((getters (alist->hashtable (list (cons '<field> <getter>) ...))))
+ (define (getter record field)
+ (let ((getter (or (ref getters field #f)
+ (error "No such field of record." record field))))
+ (getter record field)))
+ (set! getter-table <pred> getter)))))
+
+(define-syntax register-record-setter
+ (syntax-rules ()
+ ((_ . <rest>)
+ (%register-record-setter () . <rest>))))
+
+(define-syntax %register-record-setter
+ (syntax-rules ()
+ ((_ <setters> <pred> (<field> <getter>) . <rest>)
+ (%register-record-setter <setters> <pred> . <rest>))
+ ((_ <setters> <pred> (<field> <getter> <setter>) . <rest>)
+ (%register-record-setter ((<field> <setter>) . <setters>) <pred> . <rest>))
+ ((_ ((<field> <setter>) ...) <pred>)
+ (let ((setters (alist->hashtable (list (cons '<field> <setter>) ...))))
+ (define (setter record field value)
+ (let ((setter (or (ref setters field #f)
+ (error "No such assignable field of record."
+ record field))))
+ (setter record value)))
+ (set! setter-table <pred> setter)))))
+
+;;; generic-ref-set.body.scm ends here
+(define-library (generic-ref-set)
+ (export
+ ref set! define-record-type (rename ref $bracket-apply$))
+ (import
+ (rename (except (scheme base) set!)
+ (define-record-type %define-record-type))
+ (scheme case-lambda)
+ (r6rs hashtables)
+ (srfi 1)
+ (rename (srfi 17) (set! %set!)))
+ (include "generic-ref-set.body.scm"))
+(define-module (ie-reader cre))
+
+(use-modules
+ (bytestructures guile))
+
+(define cre-header
+ (bs:struct
+ `((signature ,(bs:string 4 'ascii))
+ (version ,(bs:string 4 'ascii))
+ (long-name ))))
+;; One advantage of dlists is that they allow you to write more
+;; efficient programs, while keeping the lucidity of the less
+;; efficient version. Take the naïve version of 'reverse'
+
+(define (reverse l)
+ (if (null? l)
+ '()
+ (append (reverse (cdr l))
+ (list (car l)))))
+
+;; The definition is obviously correct, however it isn't very
+;; efficient. For a given step, the cost of the non-trivial case is
+;; dependant on the size of the list we have gotten from the recursive
+;; call. That is, it takes time proportional to the square of its
+;; input list.
+;; Of course, no self respecting functional programmer would write
+;; reverse in this manner, as the trick of using an accumulating
+;; parameter is so well established. Instead we would write
+
+(define (reverse l)
+ (define (reverse-helper from to)
+ (if (null? from)
+ to
+ (reverse-helper (cdr from)
+ (cons (car from) to))))
+ (reverse-helper l '()))
+
+;; By introducing this additional parameter, we have reclaimed a more
+;; reasonable complexity of constant time at each recursive call,
+;; giving us linear complexity overall.
+;; This is a big improvement, and with a little practice, it becomes
+;; easy to convince yourself of the correctness of code written in
+;; this manner.
+
+;; However, why should you have to practice? Why can't there be a
+;; definition as obviously correct as the former, with the efficiency
+;; of the latter?
+;; Turns out, it is possible to do this, by using a different
+;; representation for lists.
+
+(define (reverse* l)
+ (if (null? l)
+ (dlist)
+ (dlist-append (reverse* (cdr l))
+ (dlist (car l)))))
+
+(define (reverse l)
+ (dlist->list (reverse* l)))
+
+;; Difference lists, or representing lists as functions, gives us a
+;; constant time version of append, thus reducing the complexity of
+;; reverse* to O(n), and the definition differs from the original,
+;; only in the names we use for the append and list procedures. The
+;; final result of this function, however, is a dlist rather than a
+;; list, so we must convert back. This also has linear complexity, so
+;; the overall complexity is still linear.
+
+;; How does this work? Well, let's replace dlist and dlist-append with
+;; their definitions
+(define (reverse* l)
+ (if (null? l)
+ (lambda (x) (append '() x))
+ (compose (reverse* (cdr l))
+ (lambda (x) (append (list (car l)) x)))))
+
+(define (reverse l)
+ ((reverse* l) '()))
+
+;; Now, we replace compose with its definition
+(define (reverse* l)
+ (if (null? l)
+ (lambda (x) (append '() x))
+ (lambda (x)
+ ((reverse* (cdr l))
+ ((lambda (x) (append (list (car l)) x)) x)))))
+
+(define (reverse l)
+ ((reverse* l) '()))
+
+;; With a few simplifications: substituting x for its definition,
+;; x for (append '() x), and (cons x y) for (append (list x) y)
+(define (reverse* l)
+ (if (null? l)
+ (lambda (x) x)
+ (lambda (x)
+ ((reverse* (cdr l))
+ (cons (car l) x)))))
+
+(define (reverse l)
+ ((reverse* l) '()))
+
+;; Now, if we uncurry reverse*
+(define (reverse* l x)
+ (if (null? l)
+ x
+ (reverse* (cdr l) (cons (car l) x))))
+
+(define (reverse l)
+ (reverse* l '()))
+
+;; Then, it turns out the dlist version is the traditional O(n)
+;; implementation in disguise.
+
+;; As an exercise, you can try doing the same thing for the flatten
+;; function
+(define (flatten xs)
+ (cond ((null? xs) '())
+ ((pair? xs)
+ (append (flatten (car xs))
+ (flatten (cdr xs))))
+ (else (list xs))))
+;;; Functional Breadth First Search
+(import (rnrs)
+ (pfds queues))
+
+;; This is the traditional solution using Queues, for a more
+;; interesting solution, see "The Under-Appreciated Unfold" by Jeremy
+;; Gibbons and Geraint Jones.
+
+;; We'll need a tree type, we'll use #f for an empty child.
+(define-record-type tree
+ (fields value left right))
+
+;; A small section of the Stern-Brocot Tree
+;; https://en.wikipedia.org/wiki/Stern%E2%80%93Brocot_tree
+(define stern-brocot
+ (make-tree 1
+ (make-tree 1/2
+ (make-tree 1/3
+ (make-tree 1/4 #f #f)
+ (make-tree 2/5 #f #f))
+ (make-tree 2/3
+ (make-tree 3/5 #f #f)
+ (make-tree 3/4 #f #f)))
+ (make-tree 2
+ (make-tree 3/2
+ (make-tree 4/3 #f #f)
+ (make-tree 5/3 #f #f))
+ (make-tree 3
+ (make-tree 5/2 #f #f)
+ (make-tree 4 #f #f)))))
+
+;; We'll search it breadth-first for the first fraction expressed in
+;; fifths.
+(define (fifth? f)
+ (= 5 (denominator f)))
+
+;; The queue search
+(define (bfs p? tree)
+ (define (step queue)
+ (if (queue-empty? queue)
+ #f
+ (let-values ([(head queue*) (dequeue queue)])
+ (cond ((not head) ; empty-tree, skip
+ (step queue*))
+ ((p? (tree-value head)) (tree-value head))
+ (else
+ (step (enqueue (enqueue queue* (tree-left head))
+ (tree-right head))))))))
+
+ (step (enqueue (make-queue) tree)))
+
+(equal? 2/5 (bfs fifth? stern-brocot))
+(define-library (pfds assert)
+ (export assert assertion-violation)
+ (import (scheme base))
+ (begin
+ ()))
+;;; bbtrees.sls --- Bounded Balance trees
+
+;; Copyright (C) 2012 Ian Price <ianprice90@googlemail.com>
+;; Copyright (C) 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
+
+;; Author: Ian Price <ianprice90@googlemail.com>
+
+;; This program is free software, you can redistribute it and/or
+;; modify it under the terms of the new-style BSD license.
+
+;; You should have received a copy of the BSD license along with this
+;; program. If not, see <http://www.debian.org/misc/bsd.license>.
+
+;; Documentation:
+;;
+;; Note: For all procedures which take a key as an argument, the key
+;; must be comparable with the ordering procedure of the bbtree.
+;;
+;; make-bbtree : (any -> any -> boolean) -> bbtree
+;; returns an empty bbtree. bbtrees derived from this one will use the
+;; procedure argument for ordering keys.
+;;
+;; bbtree? : any -> bool
+;; returns #t if the argument is a bbtree, #f otherwise
+;;
+;; bbtree-size : bbtree -> non-negative integer
+;; returns the number of elements in a bbtree
+;;
+;; bbtree-ref : bbtree any [any] -> any
+;; returns the value associated with the key in the bbtree. If the
+;; value is not in the tree, then, if the optional third argument is
+;; passed, it is returned, otherwise an &assertion-violation condition
+;; is raised.
+;;
+;; bbtree-set : bbtree any any -> bbtree
+;; returns a new bbtree with the key associated with the value. If the
+;; key is already in the bbtree, its associated value is replaced with
+;; the new value in the returned bbtree.
+;;
+;; bbtree-update : bbtree any (any -> any) any -> bbtree
+;; returns a new bbtree with the value associated with the key updated
+;; according to the update procedure. If the key was not already in
+;; the bbtree, the update procedure is called on the default value,
+;; and the association is added to the bbtree.
+;;
+;; bbtree-delete : bbtree any -> bbtree
+;; returns a new bbtree with the key and its associated value
+;; removed. If the key is not in the bbtree, the returned bbtree is a
+;; copy of the original
+;;
+;; bbtree-contains? : bbtree any -> boolean
+;; returns #t if there is association for key in the bbtree, false
+;; otherwise
+;;
+;; bbtree-traverse : (any any (any -> any) (any -> any) any) any bbtree -> any
+;; A general tree traversal procedure. Returns the value of applying
+;; the traverser procedure to the current node's key, value, a
+;; procedure to traverse the left subtree, a procedure to traverse the
+;; right subtree, and a base value. The subtree traversal procedures
+;; both take a base argument, and call bbtree-traverse recursively on
+;; the appropriate subtree. It is mostly useful for implementing
+;; other, more specific tree traversal procedures. For example,
+;; (define (l-to-r-pre-order cons base bbtree)
+;; (bbtree-traverse (lambda (key value left right base)
+;; (right (left (cons key value base))))
+;; base
+;; bbtree))
+;; implements a left-to-right pre-order traversal variant of bbtree-fold
+;;
+;; bbtree-fold : (any any any -> any) any bbtree -> any
+;; returns the value obtained by the iterating the combine procedure
+;; over each node in the tree. The combine procedure takes three
+;; arguments, the key and value of the current node, and an
+;; accumulator value, and its return value is used as the accumulator
+;; value for the next node. The initial accumulator value is provided
+;; by the base argument. bbtree-fold performs an left-to-right
+;; in-order traversal or "minimum key to maximum key".
+;;
+;; bbtree-fold-right : (any any any -> any) any bbtree -> any
+;; like bbtree-fold, but it performs a right-to-left in-order
+;; traversal instead (i.e. maximum to minimum).
+;;
+;; bbtree-map : (any -> any) bbtree -> bbtree
+;; returns the tree obtained by updating the value of each node with
+;; the result of applying the procedure to its value.
+;;
+;; bbtree->alist : bbtree -> Listof(Pairs)
+;; returns the key value associations of the bbtree as a list of
+;; pairs. The list returned is in sorted order according to the
+;; ordering procedure of the bbtree. A consequence of this is that one
+;; could write a sort procedure for lists of pairs as
+;; (define (alist-sort alist <)
+;; (bbtree->alist (alist->bbtree alist <)))
+;;
+;; alist->bbtree : Listof(Pairs) -> (any any -> boolean) -> bbtree
+;; returns the bbtree containing each of the key value pairs in the
+;; alist, using the < argument as the ordering procedure.
+;;
+;; bbtree-keys : bbtree -> Listof(any)
+;; returns a list containing all the keys of the bbtree. The keys are
+;; sorted according to the bbtree's ordering procedure.
+;;
+;; bbtree-union : bbtree bbtree -> bbtree
+;; returns a bbtree containing the union of the associations in
+;; bbtree1 and bbtree2. Where the same key occurs in both, the value
+;; in bbtree1 is preferred.
+;;
+;; bbtree-difference : bbtree bbtree -> bbtree
+;; returns a bbtree containing the all the associations in bbtree1,
+;; which do not occur in bbtree2.
+;;
+;; bbtree-intersection : bbtree bbtree -> bbtree
+;; returns a bbtree containing all the associations which appear in
+;; both bbtree1 and bbtree2. The value in bbtree1 are preferred over
+;; those in bbtree2.
+;;
+;; bbtree-index bbtree any -> non-negative integer
+;; returns the index of the key in the bbtree. Index is an integer
+;; between 0 and size - 1, with the a key having a lower index than
+;; another if first-key < second-key, according to the bbtree ordering
+;; procedure.
+;;
+;; bbtree-ref/index bbtree non-negative-integer -> any any
+;; returns the key and value of the association in the bbtree at the
+;; given index.
+;;
+;; bbtree-ordering-procedure : bbtree -> (any any -> bool)
+;; returns the ordering procedure used internally to order the
+;; bbtree.
+(define-library (pfds bbtrees)
+(export make-bbtree
+ bbtree?
+ bbtree-size
+ bbtree-ref
+ bbtree-set
+ bbtree-update
+ bbtree-delete
+ bbtree-contains?
+ bbtree-ordering-procedure
+ bbtree-traverse
+ bbtree-fold
+ bbtree-fold-right
+ bbtree-map
+ bbtree->alist
+ alist->bbtree
+ bbtree-keys
+ bbtree-union
+ bbtree-difference
+ bbtree-intersection
+ bbtree-index
+ bbtree-ref/index
+ )
+
+(import (except (scheme base) min member))
+
+(begin
+
+(define weight 4)
+
+;;; bbtree is the wrapper that you interact with from outside the
+;;; module, so there is no need to deal with empty and node record types
+(define-record-type (bbtree %make-bbtree bbtree?)
+ (fields tree ordering-procedure))
+
+(define (update-tree bbtree new-tree)
+ (%make-bbtree new-tree (bbtree-ordering-procedure bbtree)))
+
+;;; inner representation of trees
+;;; all non exposed methods can assume a valid tree
+(define-record-type empty)
+
+(define-record-type node
+ (fields key value length left right))
+
+;;; smart constructor for nodes, automatically fills in size field
+(define (node* key value left right)
+ (make-node key value (+ 1 (size left) (size right)) left right))
+
+(define (size tree)
+ (if (empty? tree)
+ 0
+ (node-length tree)))
+
+;; looks key up in the tree, and applies proc to the value if it finds
+;; it, and calls failure otherwise
+(define (lookup tree key proc failure <)
+ (define (search tree)
+ (cond ((empty? tree) (failure))
+ ((< (node-key tree) key)
+ (search (node-right tree)))
+ ((< key (node-key tree))
+ (search (node-left tree)))
+ (else (proc tree))))
+ (search tree))
+
+;; returns the key and value of the minimum element in the tree
+(define (min tree)
+ (cond ((empty? tree)
+ (assertion-violation 'min "Can't take the minimum value of an empty tree"))
+ ((empty? (node-left tree))
+ (values (node-key tree)
+ (node-value tree)))
+ (else
+ (min (node-left tree)))))
+
+;;; rotations
+(define (rotate-left key value left right)
+ (let ((r-key (node-key right))
+ (r-value (node-value right))
+ (r-left (node-left right))
+ (r-right (node-right right)))
+ (node* r-key
+ r-value
+ (node* key value left r-left)
+ r-right)))
+
+(define (rotate-right key value left right)
+ (let ((l-key (node-key left))
+ (l-value (node-value left))
+ (l-left (node-left left))
+ (l-right (node-right left)))
+ (node* l-key
+ l-value
+ l-left
+ (node* key value l-right right))))
+
+(define (rotate-left/double key value left right)
+ (let ((r-key (node-key right))
+ (r-value (node-value right))
+ (r-left (node-left right))
+ (r-right (node-right right)))
+ (let ((rl-key (node-key r-left))
+ (rl-value (node-value r-left))
+ (rl-left (node-left r-left))
+ (rl-right (node-right r-left)))
+ (node* rl-key
+ rl-value
+ (node* key value left rl-left)
+ (node* r-key r-value rl-right r-right)))))
+
+(define (rotate-right/double key value left right)
+ (let ((l-key (node-key left))
+ (l-value (node-value left))
+ (l-left (node-left left))
+ (l-right (node-right left)))
+ (let ((lr-key (node-key l-right))
+ (lr-value (node-value l-right))
+ (lr-left (node-left l-right))
+ (lr-right (node-right l-right)))
+ (node* lr-key
+ lr-value
+ (node* l-key l-value l-left lr-left)
+ (node* key value lr-right right)))))
+
+;;; smart constructor for after adding/removing a node
+(define (T key value left right)
+ (let ((l-size (size left))
+ (r-size (size right)))
+ (cond ((< (+ l-size r-size) 2)
+ (node* key value left right))
+ ((> r-size (* weight l-size))
+ (let ((r-left (node-left right))
+ (r-right (node-right right)))
+ (if (< (size r-left) (size r-right))
+ (rotate-left key value left right)
+ (rotate-left/double key value left right))))
+ ((> l-size (* weight r-size))
+ (let ((l-left (node-left left))
+ (l-right (node-right left)))
+ (if (< (size l-right) (size l-left))
+ (rotate-right key value left right)
+ (rotate-right/double key value left right))))
+ (else
+ (node* key value left right)))))
+
+(define (update tree key proc default <)
+ (define (add-to tree)
+ (if (empty? tree)
+ (make-node key (proc default) 1 (make-empty) (make-empty))
+ (let ((k (node-key tree))
+ (v (node-value tree))
+ (l (node-left tree))
+ (r (node-right tree)))
+ (cond ((< key k)
+ (T k v (add-to l) r))
+ ((< k key)
+ (T k v l (add-to r)))
+ (else
+ (node* key (proc v) l r))))))
+ (add-to tree))
+
+(define (add tree key value <)
+ (define (replace _) value)
+ (update tree key replace #f <))
+
+(define (delete tree key <)
+ (define (delete-from tree)
+ (if (empty? tree)
+ tree
+ (let ((k (node-key tree))
+ (v (node-value tree))
+ (l (node-left tree))
+ (r (node-right tree)))
+ (cond ((< key k)
+ (T k v (delete-from l) r))
+ ((< k key)
+ (T k v l (delete-from r)))
+ (else
+ (delete* l r))))))
+ (delete-from tree))
+
+(define (delete* left right)
+ (cond ((empty? left) right)
+ ((empty? right) left)
+ (else
+ (let-values (((k v) (min right)))
+ (T k v left (delete-min right))))))
+
+(define (delete-min tree)
+ (cond ((empty? tree)
+ (assertion-violation 'delete-min
+ "Can't delete the minimum value of an empty tree"))
+ ((empty? (node-left tree))
+ (node-right tree))
+ (else
+ (T (node-key tree)
+ (node-value tree)
+ (delete-min (node-left tree))
+ (node-right tree)))))
+
+(define (concat3 key value left right lt)
+ (cond ((empty? left)
+ (add right key value lt))
+ ((empty? right)
+ (add left key value lt))
+ ((< (* weight (size left)) (size right))
+ (T (node-key right)
+ (node-value right)
+ (concat3 key value left (node-left right) lt)
+ (node-right right)))
+ ((< (* weight (size right)) (size left))
+ (T (node-key left)
+ (node-value left)
+ (node-left left)
+ (concat3 key value (node-right left) right lt)))
+ (else
+ (node* key value left right))))
+
+(define (split-lt tree key <)
+ (cond ((empty? tree) tree)
+ ((< key (node-key tree))
+ (split-lt (node-left tree) key <))
+ ((< (node-key tree) key)
+ (concat3 (node-key tree)
+ (node-value tree)
+ (node-left tree)
+ (split-lt (node-right tree) key <)
+ <))
+ (else (node-left tree))))
+
+(define (split-gt tree key <)
+ (cond ((empty? tree) tree)
+ ((< key (node-key tree))
+ (concat3 (node-key tree)
+ (node-value tree)
+ (split-gt (node-left tree) key <)
+ (node-right tree)
+ <))
+ ((< (node-key tree) key)
+ (split-gt (node-right tree) key <))
+ (else (node-right tree))))
+
+(define (difference tree1 tree2 <)
+ (cond ((empty? tree1) tree1)
+ ((empty? tree2) tree1)
+ (else
+ (let ((l* (split-lt tree1 (node-key tree2) <))
+ (r* (split-gt tree1 (node-key tree2) <)))
+ (concat (difference l* (node-left tree2) <)
+ (difference r* (node-right tree2) <))))))
+
+(define (concat left right)
+ (cond ((empty? left) right)
+ ((empty? right) left)
+ ((< (* weight (size left)) (size right))
+ (T (node-key right)
+ (node-value right)
+ (concat left (node-left right))
+ (node-right right)))
+ ((< (* weight (size right)) (size left))
+ (T (node-key left)
+ (node-value left)
+ (node-left left)
+ (concat (node-right left) right)))
+ (else
+ (let-values (((k v) (min right)))
+ (T k v left (delete-min right))))))
+
+(define (member key tree <)
+ (define (yes x) #t)
+ (define (no) #f)
+ (lookup tree key yes no <))
+
+(define (intersection t1 t2 <)
+ (cond ((empty? t1) t1)
+ ((empty? t2) t2)
+ (else
+ (let ((l* (split-lt t2 (node-key t1) <))
+ (r* (split-gt t2 (node-key t1) <)))
+ (if (member (node-key t1) t2 <)
+ (concat3 (node-key t1)
+ (node-value t1)
+ (intersection (node-left t1) l* <)
+ (intersection (node-right t1) r* <)
+ <)
+ (concat (intersection (node-left t1) l* <)
+ (intersection (node-right t1) r* <)))))))
+
+;;; hedge union
+
+;; ensures that tree is either empty, or root lies in range low--high
+(define (trim low high tree <)
+ (cond ((empty? tree) tree)
+ ((< low (node-key tree))
+ (if (< (node-key tree) high)
+ tree
+ (trim low high (node-left tree) <)))
+ (else
+ (trim low high (node-right tree) <))))
+
+(define (uni-bd tree1 tree2 low high <)
+ (cond ((empty? tree2) tree1)
+ ((empty? tree1)
+ (concat3 (node-key tree2)
+ (node-value tree2)
+ (split-gt (node-left tree2) low <)
+ (split-lt (node-right tree2) high <)
+ <))
+ (else
+ (let ((key (node-key tree1)))
+ (concat3 key
+ (node-value tree1)
+ (uni-bd (node-left tree1) (trim low key tree2 <) low key <)
+ (uni-bd (node-right tree1) (trim key high tree2 <) key high <)
+ <)))))
+
+;; specialisation of trim for high=+infinity
+(define (trim-low low tree <)
+ (cond ((empty? tree) tree)
+ ((< low (node-key tree)) tree)
+ (else
+ (trim-low low (node-right tree) <))))
+
+;; trim for low=-infinity
+(define (trim-high high tree <)
+ (cond ((empty? tree) tree)
+ ((< (node-key tree) high) tree)
+ (else
+ (trim-high high (node-left tree) <))))
+
+;; uni-bd for low=-infinity
+(define (uni-high tree1 tree2 high <)
+ (cond ((empty? tree2) tree1)
+ ((empty? tree1)
+ (concat3 (node-key tree2)
+ (node-value tree2)
+ (node-left tree2)
+ (split-lt (node-right tree2) high <)
+ <))
+ (else
+ (let ((key (node-key tree1)))
+ (concat3 key
+ (node-value tree1)
+ (uni-high (node-left tree1) (trim-high key tree2 <) key <)
+ (uni-bd (node-right tree1) (trim key high tree2 <) key high <)
+ <)))))
+
+;; uni-bd for high=+infinity
+(define (uni-low tree1 tree2 low <)
+ (cond ((empty? tree2) tree1)
+ ((empty? tree1)
+ (concat3 (node-key tree2)
+ (node-value tree2)
+ (split-gt (node-left tree2) low <)
+ (node-right tree2)
+ <))
+ (else
+ (let ((key (node-key tree1)))
+ (concat3 key
+ (node-value tree1)
+ (uni-bd (node-left tree1) (trim low key tree2 <) low key <)
+ (uni-low (node-right tree1) (trim-low key tree2 <) key <)
+ <)))))
+
+(define (hedge-union tree1 tree2 <)
+ (cond ((empty? tree2) tree1)
+ ((empty? tree1) tree2)
+ (else
+ (let ((key (node-key tree1)))
+ (concat3 key
+ (node-value tree1)
+ (uni-high (node-left tree1) (trim-high key tree2 <) key <)
+ (uni-low (node-right tree1) (trim-low key tree2 <) key <)
+ <)))))
+
+;;; rank and indexing
+
+(define (rank tree key <)
+ (cond ((empty? tree);; error
+ (assertion-violation 'rank "Key is not in the tree" key))
+ ((< key (node-key tree))
+ (rank (node-left tree) key <))
+ ((< (node-key tree) key)
+ (+ (rank (node-right tree) key <)
+ (size (node-left tree))
+ 1))
+ (else
+ (size (node-left tree)))))
+
+(define (index tree idx)
+ (if (empty? tree)
+ (assertion-violation 'index "No value at index" idx)
+ (let ((l-size (size (node-left tree))))
+ (cond ((< idx l-size)
+ (index (node-left tree) idx))
+ ((< l-size idx)
+ (index (node-right tree)
+ (- idx l-size 1)))
+ (else
+ (values (node-key tree)
+ (node-value tree)))))))
+
+;;; External procedures
+
+(define (make-bbtree <)
+ (assert (procedure? <))
+ (%make-bbtree (make-empty) <))
+
+(define (bbtree-size bbtree)
+ (assert (bbtree? bbtree))
+ (size (bbtree-tree bbtree)))
+
+(define bbtree-ref
+ (let ((ref (lambda (bbtree key failure)
+ (assert (bbtree? bbtree))
+ (lookup (bbtree-tree bbtree)
+ key
+ node-value
+ failure
+ (bbtree-ordering-procedure bbtree)))))
+ (case-lambda
+ ((bbtree key)
+ (define (fail)
+ (assertion-violation 'bbtree-ref "Key is not in the tree" key))
+ (ref bbtree key fail))
+ ((bbtree key ret)
+ (ref bbtree key (lambda () ret))))))
+
+(define (bbtree-set bbtree key value)
+ (assert (bbtree? bbtree))
+ (update-tree bbtree
+ (add (bbtree-tree bbtree)
+ key
+ value
+ (bbtree-ordering-procedure bbtree))))
+
+(define (bbtree-update bbtree key proc default)
+ (assert (bbtree? bbtree))
+ (update-tree bbtree
+ (update (bbtree-tree bbtree)
+ key
+ proc
+ default
+ (bbtree-ordering-procedure bbtree))))
+
+(define (bbtree-delete bbtree key)
+ (assert (bbtree? bbtree))
+ (update-tree bbtree
+ (delete (bbtree-tree bbtree)
+ key
+ (bbtree-ordering-procedure bbtree))))
+
+(define (bbtree-contains? bbtree key)
+ (assert (bbtree? bbtree))
+ (lookup (bbtree-tree bbtree)
+ key
+ (lambda (_) #t)
+ (lambda () #f)
+ (bbtree-ordering-procedure bbtree)))
+
+;; iterators
+
+(define (traverse traverser base tree)
+ (define (left base)
+ (traverse traverser base (node-left tree)))
+ (define (right base)
+ (traverse traverser base (node-right tree)))
+ (if (empty? tree)
+ base
+ (traverser (node-key tree)
+ (node-value tree)
+ left
+ right
+ base)))
+
+(define (bbtree-traverse traverser base bbtree)
+ (assert (bbtree? bbtree))
+ (traverse traverser base (bbtree-tree bbtree)))
+
+(define (bbtree-fold combine base bbtree)
+ (assert (bbtree? bbtree))
+ (traverse (lambda (k v l r n)
+ (r (combine k v (l n))))
+ base
+ (bbtree-tree bbtree)))
+
+(define (bbtree-fold-right combine base bbtree)
+ (assert (bbtree? bbtree))
+ (traverse (lambda (k v l r n)
+ (l (combine k v (r n))))
+ base
+ (bbtree-tree bbtree)))
+
+;; I could do this more efficiently, but is it worth it?
+(define (bbtree-map mapper bbtree)
+ (bbtree-fold (lambda (key value tree)
+ (bbtree-set tree key (mapper value)))
+ (make-bbtree (bbtree-ordering-procedure bbtree))
+ bbtree))
+
+(define (alist-cons a b c)
+ (cons (cons a b) c))
+
+(define (bbtree->alist bbtree)
+ (bbtree-fold-right alist-cons '() bbtree))
+
+(define (alist->bbtree list <)
+ (fold-left (lambda (tree kv-pair)
+ (bbtree-set tree (car kv-pair) (cdr kv-pair)))
+ (make-bbtree <)
+ list))
+
+(define (bbtree-keys bbtree)
+ (bbtree-fold-right (lambda (key value base)
+ (cons key base))
+ '()
+ bbtree))
+
+(define (bbtree-union bbtree1 bbtree2)
+ (update-tree bbtree1
+ (hedge-union (bbtree-tree bbtree1)
+ (bbtree-tree bbtree2)
+ (bbtree-ordering-procedure bbtree1))))
+
+(define (bbtree-difference bbtree1 bbtree2)
+ (update-tree bbtree1
+ (difference (bbtree-tree bbtree1)
+ (bbtree-tree bbtree2)
+ (bbtree-ordering-procedure bbtree1))))
+
+(define (bbtree-intersection bbtree1 bbtree2)
+ (update-tree bbtree1
+ (intersection (bbtree-tree bbtree1)
+ (bbtree-tree bbtree2)
+ (bbtree-ordering-procedure bbtree1))))
+
+(define (bbtree-index bbtree key)
+ ;; maybe this should return #f instead of throwing an exception?
+ (assert (bbtree? bbtree))
+ (rank (bbtree-tree bbtree)
+ key
+ (bbtree-ordering-procedure bbtree)))
+
+(define (bbtree-ref/index bbtree idx)
+ (assert (bbtree? bbtree))
+ (let ((tree (bbtree-tree bbtree)))
+ (unless (and (integer? idx)
+ (<= 0 idx (- (size tree) 1)))
+ (assertion-violation 'bbtree-ref/index
+ "Not a valid index into the bbtree"
+ idx))
+ (index tree idx)))
+
+))
+
+;;; deques.sls --- Purely functional deques
+
+;; Copyright (C) 2011,2012 Ian Price <ianprice90@googlemail.com>
+
+;; Author: Ian Price <ianprice90@googlemail.com>
+
+;; This program is free software, you can redistribute it and/or
+;; modify it under the terms of the new-style BSD license.
+
+;; You should have received a copy of the BSD license along with this
+;; program. If not, see <http://www.debian.org/misc/bsd.license>.
+
+;; Documentation:
+;;
+;; make-deque : () -> deque
+;; returns a deque containing to items
+;;
+;; deque? : any -> boolean
+;; tests if an object is a deque
+;;
+;; deque-length : deque -> non-negative integer
+;; returns the number of items in the deque
+;;
+;; deque-empty? : deque -> boolean
+;; returns true if there are no items in the deque, false otherwise
+;;
+;; enqueue-front : deque any -> deque
+;; returns a new deque with the inserted item at the front
+;;
+;; enqueue-rear : deque any -> deque
+;; returns a new deque with the inserted item at the rear
+;;
+;; dequeue-front : deque -> any queue
+;; returns two values, the item at the front of the deque, and a new
+;; deque containing all the other items
+;; raises a &deque-empty condition if the deque is empty
+;;
+;; dequeue-rear : deque -> any queue
+;; returns two values, the item at the rear of the deque, and a new
+;; deque containing all the other items
+;; raises a &deque-empty condition if the deque is empty
+;;
+;; deque-empty-condition? : object -> boolean
+;; tests if an object is a &deque-empty condition
+;;
+;; deque->list : deque -> listof(any)
+;; returns a list containing all the elements of the deque. The order
+;; of the elements in the list is the same as the order they would be
+;; dequeued from the front of the deque.
+;;
+;; list->deque : listof(any) -> deque
+;; returns a deque containing all of the elements in the list. The
+;; order of the elements in the deque is the same as the order of the
+;; elements in the list.
+;;
+(library (pfds deques)
+(export make-deque
+ deque?
+ deque-length
+ deque-empty?
+ enqueue-front
+ enqueue-rear
+ dequeue-front
+ dequeue-rear
+ deque-empty-condition?
+ deque->list
+ list->deque
+ )
+(import (except (rnrs) cons*)
+ (pfds deques private condition)
+ (pfds private lazy-lists))
+
+(define c 2)
+
+(define (rot1 n l r)
+ (if (>= n c)
+ (cons* (head l)
+ (rot1 (- n c) (tail l) (drop c r)))
+ (rot2 l (drop n r) '())))
+
+(define (rot2 l r a)
+ (if (empty? l)
+ (append* (rev r) a)
+ (cons* (head l)
+ (rot2 (tail l)
+ (drop c r)
+ (append* (rev (take c r)) a)))))
+
+(define-record-type (deque %make-deque deque?)
+ (fields
+ (immutable length)
+ (immutable lenL)
+ (immutable lenR)
+ (immutable l)
+ (immutable r)
+ (immutable l^)
+ (immutable r^)))
+
+(define (make-deque)
+ (%make-deque 0 0 0 '() '() '() '()))
+
+(define (deque-empty? deque)
+ (zero? (deque-length deque)))
+
+(define (enqueue-front deque item)
+ (let ((len (deque-length deque))
+ (l (deque-l deque))
+ (r (deque-r deque))
+ (lenL (deque-lenL deque))
+ (lenR (deque-lenR deque))
+ (l^ (deque-l^ deque))
+ (r^ (deque-r^ deque)))
+ (makedq (+ 1 len) (+ 1 lenL) lenR (cons* item l) r (tail l^) (tail r^))))
+
+(define (enqueue-rear deque item)
+ (let ((len (deque-length deque))
+ (l (deque-l deque))
+ (r (deque-r deque))
+ (lenL (deque-lenL deque))
+ (lenR (deque-lenR deque))
+ (l^ (deque-l^ deque))
+ (r^ (deque-r^ deque)))
+ (makedq (+ 1 len) lenL (+ 1 lenR) l (cons* item r) (tail l^) (tail r^))))
+
+(define (dequeue-front deque)
+ (when (deque-empty? deque)
+ (raise (condition
+ (make-deque-empty-condition)
+ (make-who-condition 'dequeue-front)
+ (make-message-condition "There are no elements to remove")
+ (make-irritants-condition (list deque)))))
+ (let ((len (deque-length deque))
+ (lenL (deque-lenL deque))
+ (lenR (deque-lenR deque))
+ (l (deque-l deque))
+ (r (deque-r deque))
+ (l^ (deque-l^ deque))
+ (r^ (deque-r^ deque)))
+ (if (empty? l)
+ (values (head r) (make-deque))
+ (values (head l)
+ (makedq (- len 1)
+ (- lenL 1)
+ lenR
+ (tail l)
+ r
+ (tail (tail l^))
+ (tail (tail r^)))))))
+
+(define (dequeue-rear deque)
+ (when (deque-empty? deque)
+ (raise (condition
+ (make-deque-empty-condition)
+ (make-who-condition 'dequeue-rear)
+ (make-message-condition "There are no elements to remove")
+ (make-irritants-condition (list deque)))))
+ (let ((len (deque-length deque))
+ (lenL (deque-lenL deque))
+ (lenR (deque-lenR deque))
+ (l (deque-l deque))
+ (r (deque-r deque))
+ (l^ (deque-l^ deque))
+ (r^ (deque-r^ deque)))
+ (if (empty? r)
+ (values (head l) (make-deque))
+ (values (head r)
+ (makedq (- len 1)
+ lenL
+ (- lenR 1)
+ l
+ (tail r)
+ (tail (tail l^))
+ (tail (tail r^)))))))
+
+
+
+(define (makedq len lenL lenR l r l^ r^)
+ (cond ((> lenL (+ 1 (* c lenR)))
+ (let* ((n (floor (/ (+ lenL lenR) 2)))
+ (l* (take n l))
+ (r* (rot1 n r l)))
+ (%make-deque len n (- len n) l* r* l* r*)))
+ ((> lenR (+ 1 (* c lenL)))
+ (let* ((n (floor (/ (+ lenL lenR) 2)))
+ (l* (rot1 n l r))
+ (r* (take n r)))
+ (%make-deque len (- len n) n l* r* l* r*)))
+ (else
+ (%make-deque len lenL lenR l r l^ r^))))
+
+(define (list->deque l)
+ (fold-left enqueue-rear (make-deque) l))
+
+(define (deque->list deq)
+ (define (recur deq l)
+ (if (deque-empty? deq)
+ l
+ (let-values ([(last deq*) (dequeue-rear deq)])
+ (recur deq* (cons last l)))))
+ (recur deq '()))
+
+)
+
+;;; dlists.sls --- Difference Lists
+
+;; Copyright (C) 2012 Ian Price <ianprice90@googlemail.com>
+
+;; Author: Ian Price <ianprice90@googlemail.com>
+
+;; This program is free software, you can redistribute it and/or
+;; modify it under the terms of the new-style BSD license.
+
+;; You should have received a copy of the BSD license along with this
+;; program. If not, see <http://www.debian.org/misc/bsd.license>.
+
+;;; Commentary:
+;;
+;; Repeatedly appending to a list is a common, if inefficient pattern
+;; in functional programs. Usually the trick we use is to build up the
+;; list in reverse, and then to reverse it as the last action of a
+;; function.
+;;
+;; Dlists are a representation of lists as functions that provide for
+;; constant time append to either the front or end of a dlist that may
+;; be used instead.
+
+;;; Documentation:
+;;
+;; dlist : any ... -> dlist
+;; returns a dlist containing all its arguments.
+;;
+;; dlist? : any -> boolean
+;; returns #t if its argument is a dlist, #f otherwise.
+;;
+;; dlist-cons : any dlist -> dlist
+;; returns a new dlist created by prepending the element to the head
+;; of the dlist argument.
+;;
+;; dlist-snoc : dlist any -> dlist
+;; returns a new dlist created by appending the element to the tail of
+;; the dlist argument.
+;;
+;; dlist-append : dlist dlist -> dlist
+;; returns a new dlist consisting of all the elements of the first
+;; dlist, followed by all the items of the second dlist.
+;;
+;; dlist->list : dlist -> listof(any)
+;; returns a list consisting of all the elements of the dlist.
+;;
+;; list->dlist : listof(any) -> dlist
+;; returns a dlist consisting of all the elements of the list.
+(library (pfds dlists)
+(export (rename (%dlist dlist))
+ dlist?
+ dlist-cons
+ dlist-snoc
+ dlist-append
+ dlist->list
+ list->dlist
+ )
+(import (rnrs))
+
+(define-record-type dlist
+ (fields
+ (immutable proc undl)))
+
+(define (%dlist . args)
+ (list->dlist args))
+
+(define (compose f g)
+ (lambda (x)
+ (f (g x))))
+
+(define (singleton x)
+ (list->dlist (list x)))
+
+(define (dlist-append dl1 dl2)
+ (make-dlist (compose (undl dl1) (undl dl2))))
+
+(define (dlist-cons element dlist)
+ (dlist-append (singleton element) dlist))
+
+(define (dlist-snoc dlist element)
+ (dlist-append dlist (singleton element)))
+
+(define (dlist->list dlist)
+ ((undl dlist) '()))
+
+(define (list->dlist list)
+ (make-dlist
+ (lambda (rest)
+ (append list rest))))
+
+)
+
+;;; fingertrees.sls --- A Simple General-Purpose Data Structure
+
+;; Copyright (C) 2012 Ian Price <ianprice90@googlemail.com>
+
+;; Author: Ian Price <ianprice90@googlemail.com>
+
+;; This program is free software, you can redistribute it and/or
+;; modify it under the terms of the new-style BSD license.
+
+;; You should have received a copy of the BSD license along with this
+;; program. If not, see <http://www.debian.org/misc/bsd.license>.
+
+;;; Commentary:
+;;
+;; Fingertrees are a generalised form of deque, that you can parameterise
+;; to compute a value, called the "measure" of a fingertree. This measure
+;; will be updated incrementally as you add and remove elements from the
+;; fingertree. Among other things, this allows fingertrees to be used
+;; where you otherwise might have written a custom data structure.
+;;
+;; To compute the measure, fingertrees require pieces of information: a
+;; converter, a combiner, and an identity.
+;;
+;; The converter is a procedure of one argument, that maps values in the
+;; fingertree to other values which are used for computing the measure.
+;;
+;; The combiner is a procedure of two arguments, and combines these into
+;; one value representing them both. A combiner must be associative
+;; i.e. (combine A (combine B C)) must be equivalent to (combine (combine
+;; A B) C) for all values A, B and C.
+;;
+;; An identity is a value that represents the measure of an empty
+;; fingertree. It must obey the rule that (combine X identity), (combine
+;; identity X) and X are always the same.
+;;
+;; To make things more concrete, a simple use of a fingertree is as a
+;; deque that keeps a running total. In this case, the converter can
+;; simply be the function (lambda (x) x) if it is a deque of integers,
+;; the combiner would be +, and the identity 0.
+;;
+;; (define l '(3 1 4 1 5 9))
+;;
+;; (define ft (list->fingertree l 0 + (lambda (x) x)))
+;;
+;; (fingertree-measure ft)
+;; ; => 23
+;; (fingertree-measure (fingertree-snoc ft 2))
+;; ; => 25
+;; (let-values (((head tail) (fingertree-uncons ft)))
+;; (fingertree-measure tail))
+;; ; => 20
+;;
+;; Mathematically speaking, the _return type_ of the converter, the
+;; combiner and the identity element are expected to form a
+;; monoid.
+;;
+;; Below, I use the slightly incorrect terminology of referring to the
+;; combiner, the converter, and the identity, together as a
+;; monoid. Mathematicians, please forgive me. Programmers please forgive
+;; me even more. If you can provide a better name (from a programmers,
+;; not a mathematicians, point of view) that works in most circumstances,
+;; I will be happy to use it.
+;;
+;; (FWIW the Haskell Data.Fingertree package uses odd name of Measured
+;; (which are expected to be instances of Monoid))
+;;
+;; fingertree? : any -> bool
+;; returns #t if argument is a fingertree, #f otherwise.
+;;
+;; fingertree-empty? : fingertree -> bool
+;; returns #t if there are no items in the fingertree, #f otherwise.
+;;
+;; make-fingertree : id combine measure -> fingertree
+;; returns a new fingertree, parameterised by the given monoid.
+;;
+;; fingertree-cons : any fingertree -> fingertree
+;; returns the new fingertree created by adding the element to the front
+;; of the argument fingertree.
+;;
+;; fingertree-snoc : fingertree any -> fingertree
+;; returns the new fingertree created by adding the element to the end of
+;; the fingertree.
+;;
+;; fingertree-uncons : fingertree -> any + fingertree
+;; returns two values: the element at the front of the fingertree, and a
+;; new fingertree containing all but the front element. If the fingertree
+;; is empty, a &fingertree-empty condition is raised.
+;;
+;; fingertree-unsnoc : fingertree -> fingertree + any
+;; returns two values: a new fingertree containing all but the rear
+;; element of the argument fingertree, and the rear element itself. If
+;; the fingertree is empty, a &fingertree-empty-condition is raised.
+;;
+;; fingertree-append : fingertree fingertree -> fingertree
+;; returns a new fingertree which contains all of the elements of the
+;; first fingertree argument, followed by all the elements of the
+;; second. The argument fingertrees are assumed to be parameterised by
+;; the same monoid.
+;;
+;; list->fingertree : (list->fingertree l id append convert)
+;; returns a fingertree containing all of the elements of the argument
+;; list, in the same order.
+;;
+;; fingertree->list : fingertree -> Listof(Any)
+;; returns a list of all the elements in the fingertree, in the order
+;; they would be unconsed.
+;;
+;; fingertree-measure : fingertree -> any
+;; returns the measure of the fingertree, as defined by the fingertree's
+;; monoid.
+;;
+;; fingertree-split : (any -> bool) fingertree -> fingertree + fingertree
+;; returns two values: the first is the largest prefix of the fingertree for
+;; which applying the predicate to it's accumulated measure returns
+;; #f. The second values is a fingertree containing all those elements
+;; not in the first fingertree.
+;;
+;; fingertree-split3: (any -> bool) fingertree -> fingertree + value + fingertree
+;; similar to fingertree-split, however, instead of returning the
+;; remainder as the second argument, it returns the head of the remainder
+;; as the second argument, and tail of the remainder as the third
+;; argument.
+;; TODO: what error should I give if the remainder was empty?
+;;
+;; fingertree-fold : (any -> any -> any) any fingertree
+;; returns the value obtained by iterating the combiner procedure over
+;; the fingertree in left-to-right order. This procedure takes two
+;; arguments, the current value from the fingertree, and an accumulator,
+;; and it's return value is used as the accumulator for the next
+;; iteration. The initial value for the accumulator is given by the base
+;; argument.
+;;
+;; fingertree-fold-right : (any -> any -> any) any fingertree
+;; similar to fingertree-fold, but iterates in right-to-left order.
+;;
+;; fingertree-reverse : fingertree -> fingertree
+;; returns a new fingertree in which the elements are in the opposite
+;; order from the argument fingertree.
+;;
+;; fingertree-empty-condition? : condition -> bool
+;; returns #t if the argument is a &fingertree-empty condition, #f otherwise.
+;;
+(library (pfds fingertrees)
+(export fingertree?
+ fingertree-empty?
+ make-fingertree
+ fingertree-cons
+ fingertree-snoc
+ fingertree-uncons
+ fingertree-unsnoc
+ fingertree-append
+ list->fingertree
+ fingertree->list
+ fingertree-measure
+ fingertree-split
+ fingertree-split3
+ fingertree-fold
+ fingertree-fold-right
+ fingertree-reverse
+ fingertree-empty-condition?
+ )
+(import (rnrs))
+
+;;; List helpers
+
+(define (snoc l val)
+ (append l (list val)))
+
+(define (take l n)
+ (if (or (null? l) (zero? n))
+ '()
+ (cons (car l)
+ (take (cdr l) (- n 1)))))
+
+(define (last list)
+ (if (null? (cdr list))
+ (car list)
+ (last (cdr list))))
+
+(define (but-last list)
+ (if (null? (cdr list))
+ '()
+ (cons (car list)
+ (but-last (cdr list)))))
+
+(define (map-reverse f l)
+ (fold-left (lambda (o n) (cons (f n) o)) '() l))
+
+;;; Node type
+
+(define-record-type node2
+ (protocol
+ (lambda (new)
+ (lambda (monoid a b)
+ (define app (mappend monoid))
+ (new (app (measure-nodetree a monoid)
+ (measure-nodetree b monoid))
+ a
+ b))))
+ (fields measure a b))
+
+(define-record-type node3
+ (protocol
+ (lambda (new)
+ (lambda (monoid a b c)
+ (define app (mappend monoid))
+ (new (app (app (measure-nodetree a monoid)
+ (measure-nodetree b monoid))
+ (measure-nodetree c monoid))
+ a
+ b
+ c))))
+ (fields measure a b c))
+
+(define (node-case node k2 k3)
+ (if (node2? node)
+ (k2 (node2-a node) (node2-b node))
+ (k3 (node3-a node) (node3-b node) (node3-c node))))
+
+(define (node-fold-right f base node)
+ (node-case node
+ (lambda (a b)
+ (f a (f b base)))
+ (lambda (a b c)
+ (f a (f b (f c base))))))
+
+(define (node->list node)
+ (node-fold-right cons '() node))
+
+(define (nodetree-fold-right f base nodetree)
+ (define (foldr node base)
+ (cond ((node2? node)
+ (foldr (node2-a node)
+ (foldr (node2-b node) base)))
+ ((node3? node)
+ (foldr (node3-a node)
+ (foldr (node3-b node)
+ (foldr (node3-c node) base))))
+ (else (f node base))))
+ (foldr nodetree base))
+
+(define (nodetree-fold-left f base nodetree)
+ (define (foldl node base)
+ (cond ((node2? node)
+ (foldl (node2-b node)
+ (foldl (node2-a node) base)))
+ ((node3? node)
+ (foldl (node3-c node)
+ (foldl (node3-b node)
+ (foldl (node3-a node) base))))
+ (else (f node base))))
+ (foldl nodetree base))
+
+;;; Tree type
+
+(define-record-type empty)
+
+(define-record-type single
+ (fields value))
+
+(define-record-type rib
+ (protocol
+ (lambda (new)
+ (lambda (monoid left middle right)
+ (define app (mappend monoid))
+ (new (app (app (measure-digit left monoid)
+ (measure-ftree middle monoid))
+ (measure-digit right monoid))
+ left
+ middle
+ right)
+ )))
+ ;; left and right expected to be lists of length 0 < l < 5
+ (fields measure left middle right))
+
+(define (ftree-case ftree empty-k single-k rib-k)
+ (cond ((empty? ftree) (empty-k))
+ ((single? ftree)
+ (single-k (single-value ftree)))
+ (else
+ (rib-k (rib-left ftree)
+ (rib-middle ftree)
+ (rib-right ftree)))))
+
+(define (digits-fold-right f b d)
+ (fold-right (lambda (ntree base)
+ (nodetree-fold-right f base ntree))
+ b
+ d))
+
+(define (digits-fold-left f b d)
+ (fold-left (lambda (base ntree)
+ (nodetree-fold-left f base ntree))
+ b
+ d))
+
+(define (ftree-fold-right proc base ftree)
+ (ftree-case ftree
+ (lambda () base)
+ (lambda (x) (nodetree-fold-right proc base x))
+ (lambda (l x r)
+ (define base* (digits-fold-right proc base r))
+ (define base** (ftree-fold-right proc base* x))
+ (digits-fold-right proc base** l))))
+
+(define (ftree-fold-left proc base ftree)
+ (ftree-case ftree
+ (lambda () base)
+ (lambda (x) (nodetree-fold-left proc base x))
+ (lambda (l x r)
+ (define base* (digits-fold-left proc base l))
+ (define base** (ftree-fold-left proc base* x))
+ (digits-fold-left proc base** r))))
+
+(define (insert-front ftree val monoid)
+ (ftree-case ftree
+ (lambda ()
+ (make-single val))
+ (lambda (a)
+ (make-rib monoid (list val) (make-empty) (list a)))
+ (lambda (l m r)
+ (if (= (length l) 4)
+ (make-rib monoid
+ (list val (car l))
+ (insert-front m (apply make-node3 monoid (cdr l)) monoid)
+ r)
+ (make-rib monoid (cons val l) m r)))))
+
+(define (view-front ftree empty-k cons-k monoid)
+ (ftree-case ftree
+ empty-k
+ (lambda (a)
+ (cons-k a (make-empty)))
+ (lambda (l r m)
+ (cons-k (car l)
+ (rib-l (cdr l) r m monoid)))))
+
+(define (list->tree l monoid)
+ (fold-right (lambda (val tree)
+ (insert-front tree val monoid))
+ (make-empty)
+ l))
+
+(define (rib-l l m r monoid)
+ (if (null? l)
+ (view-front m
+ (lambda ()
+ (list->tree r monoid))
+ (lambda (x xs)
+ (make-rib monoid
+ (node->list x)
+ xs
+ r))
+ monoid)
+ (make-rib monoid l m r)))
+
+(define (remove-front ftree monoid)
+ (view-front ftree
+ (lambda ()
+ (error 'remove-front "can't remove from an empty tree"))
+ values
+ monoid))
+
+(define (insert-rear ftree val monoid)
+ (ftree-case ftree
+ (lambda ()
+ (make-single val))
+ (lambda (a)
+ (make-rib monoid (list a) (make-empty) (list val)))
+ (lambda (l m r)
+ ;; TODO: should r be maintained in reverse order, rather than
+ ;; normal?
+ ;; yes! it will make concatenation slightly slower, but will
+ ;; speed up inserts and removals
+ (if (= (length r) 4)
+ (make-rib monoid
+ l
+ (insert-rear m (apply make-node3 monoid (take r 3)) monoid)
+ (list (list-ref r 3) val))
+ (make-rib monoid l m (snoc r val))))))
+
+(define (remove-rear ftree monoid)
+ (view-rear ftree
+ (lambda ()
+ (error 'remove-rear "can't remove from an empty tree"))
+ values
+ monoid))
+
+(define (view-rear ftree empty-k snoc-k monoid)
+ (ftree-case ftree
+ empty-k
+ (lambda (a)
+ (snoc-k (make-empty) a))
+ (lambda (l r m)
+ (snoc-k (rib-r l r (but-last m) monoid)
+ (last m)))))
+
+(define (rib-r l m r monoid)
+ (if (null? r)
+ (view-rear m
+ (lambda ()
+ (list->tree l monoid))
+ (lambda (m* r*)
+ (make-rib monoid l m* (node->list r*)))
+ monoid)
+ (make-rib monoid l m r)))
+
+(define (insert-front/list tree l monoid)
+ (fold-right (lambda (val tree)
+ (insert-front tree val monoid))
+ tree
+ l))
+
+(define (insert-rear/list tree l monoid)
+ (fold-left (lambda (tree val)
+ (insert-rear tree val monoid))
+ tree
+ l))
+
+(define (app3 ftree1 ts ftree2 monoid)
+ (cond ((empty? ftree1)
+ (insert-front/list ftree2 ts monoid))
+ ((empty? ftree2)
+ (insert-rear/list ftree1 ts monoid))
+ ((single? ftree1)
+ (insert-front (insert-front/list ftree2 ts monoid)
+ (single-value ftree1)
+ monoid))
+ ((single? ftree2)
+ (insert-rear (insert-rear/list ftree1 ts monoid)
+ (single-value ftree2)
+ monoid))
+ (else
+ (let ((l1 (rib-left ftree1))
+ (m1 (rib-middle ftree1))
+ (r1 (rib-right ftree1))
+ (l2 (rib-left ftree2))
+ (m2 (rib-middle ftree2))
+ (r2 (rib-right ftree2)))
+ (make-rib monoid
+ l1
+ (app3 m1
+ (nodes (append r1 ts l2) monoid)
+ m2
+ monoid)
+ r2)))))
+
+(define (nodes lst monoid)
+ ;; *sigh*
+ (let ((a (car lst))
+ (b (cadr lst)))
+ (cond ((null? (cddr lst))
+ (list (make-node2 monoid a b)))
+ ((null? (cdddr lst))
+ (list (make-node3 monoid a b (caddr lst))))
+ ((null? (cddddr lst))
+ (list (make-node2 monoid a b)
+ (make-node2 monoid (caddr lst) (cadddr lst))))
+ (else
+ (cons (make-node3 monoid a b (caddr lst))
+ (nodes (cdddr lst) monoid))))))
+
+(define (reverse-tree tree monoid)
+ (ftree-case tree
+ (lambda () (make-empty))
+ (lambda (x) (make-single (reverse-nodetree x monoid)))
+ (lambda (l x r)
+ (make-rib monoid
+ (reverse-digit r monoid)
+ (reverse-tree x monoid)
+ (reverse-digit l monoid)))))
+
+(define (reverse-digit l monoid)
+ (map-reverse (lambda (a) (reverse-nodetree a monoid)) l))
+
+(define (reverse-nodetree l monoid)
+ (cond ((node2? l)
+ (make-node2 monoid
+ (reverse-nodetree (node2-b l) monoid)
+ (reverse-nodetree (node2-a l) monoid)))
+ ((node3? l)
+ (make-node3 monoid
+ (reverse-nodetree (node3-c l) monoid)
+ (reverse-nodetree (node3-b l) monoid)
+ (reverse-nodetree (node3-a l) monoid)))
+ (else l)))
+
+;; generalising fingertrees with monoids
+
+;; I think I'm going to need a "configuration" type and pass it around
+;; in order to generalize over arbitrary monoids
+;; call the type iMeasured or something
+
+(define-record-type monoid*
+ ;; a monoid, but augmented with a procedure to convert objects into the
+ ;; monoid type
+ (fields (immutable empty mempty)
+ (immutable append mappend)
+ (immutable convert mconvert)))
+
+(define (measure-digit obj monoid)
+ (fold-left (lambda (i a)
+ ((mappend monoid) i (measure-nodetree a monoid)))
+ (mempty monoid)
+ obj))
+
+(define (measure-ftree obj monoid)
+ (cond ((empty? obj)
+ (mempty monoid))
+ ((single? obj)
+ (measure-nodetree (single-value obj) monoid))
+ (else
+ (rib-measure obj))))
+
+(define (measure-nodetree obj monoid)
+ (cond ((node2? obj) (node2-measure obj))
+ ((node3? obj) (node3-measure obj))
+ (else ((mconvert monoid) obj))))
+
+(define (split proc tree monoid)
+ (if (empty? tree)
+ (values (make-empty) (make-empty))
+ (if (proc (measure-ftree tree monoid))
+ (let-values (((l x r) (split-tree proc (mempty monoid) tree monoid)))
+ (values l (insert-front r x monoid)))
+ (values tree (make-empty)))))
+
+(define (split-tree proc i tree monoid)
+ (ftree-case tree
+ (lambda ()
+ (error 'split-tree "shouldn't happen?"))
+ (lambda (a)
+ (values (make-empty) a (make-empty)))
+ (lambda (l m r)
+ (define app (mappend monoid))
+ (define vpr (app i (measure-digit l monoid)))
+ (define vm (app vpr (measure-ftree m monoid)))
+ (cond ((proc vpr)
+ (let-values (((l* x* r*) (split-digit proc i l monoid)))
+ (values (list->tree l* monoid)
+ x*
+ (rib-l r* m r monoid))))
+ ((proc vm)
+ (let*-values (((ml xs mr) (split-tree proc vpr m monoid))
+ ((l* x* r*)
+ (split-digit proc
+ (app vpr (measure-ftree ml monoid))
+ (node->list xs)
+ monoid)))
+ (values (rib-r l ml l* monoid)
+ x*
+ (rib-l r* mr r monoid))))
+ (else
+ (let-values (((l* x* r*) (split-digit proc vm r monoid)))
+ (values (rib-r l m l* monoid)
+ x*
+ (list->tree r* monoid))))))))
+
+(define (split-digit proc i xs monoid)
+ (if (null? (cdr xs))
+ (values '() (car xs) '())
+ (let ((i* ((mappend monoid) i (measure-nodetree (car xs) monoid))))
+ (if (proc i*)
+ (values '() (car xs) (cdr xs))
+ (let-values (((l x r)
+ (split-digit proc i* (cdr xs) monoid)))
+ (values (cons (car xs) l) x r))))))
+
+;; exported interface
+(define-condition-type &fingertree-empty
+ &assertion
+ make-fingertree-empty-condition
+ fingertree-empty-condition?)
+
+(define-record-type (fingertree %make-fingertree fingertree?)
+ (fields tree monoid))
+
+(define (%wrap fingertree tree)
+ (%make-fingertree tree
+ (fingertree-monoid fingertree)))
+
+(define (make-fingertree id append convert)
+ (%make-fingertree (make-empty)
+ (make-monoid* id append convert)))
+
+(define (fingertree-cons a fingertree)
+ ;; TODO: should it obey normal cons interface, or have fingertree
+ ;; first?
+ (%wrap fingertree
+ (insert-front (fingertree-tree fingertree)
+ a
+ (fingertree-monoid fingertree))))
+
+(define (fingertree-snoc fingertree a)
+ (%wrap fingertree
+ (insert-rear (fingertree-tree fingertree)
+ a
+ (fingertree-monoid fingertree))))
+
+(define (fingertree-uncons fingertree)
+ (call-with-values
+ (lambda ()
+ (define t (fingertree-tree fingertree))
+ (when (empty? t)
+ (raise
+ (condition
+ (make-fingertree-empty-condition)
+ (make-who-condition 'fingertree-uncons)
+ (make-message-condition "There are no elements to uncons")
+ (make-irritants-condition (list fingertree)))))
+ (remove-front t (fingertree-monoid fingertree)))
+ (lambda (val rest)
+ (values val
+ (%wrap fingertree rest)))))
+
+(define (fingertree-unsnoc fingertree)
+ (call-with-values
+ (lambda ()
+ (define t (fingertree-tree fingertree))
+ (when (empty? t)
+ (raise
+ (condition
+ (make-fingertree-empty-condition)
+ (make-who-condition 'fingertree-unsnoc)
+ (make-message-condition "There are no elements to unsnoc")
+ (make-irritants-condition (list fingertree)))))
+ (remove-rear t (fingertree-monoid fingertree)))
+ (lambda (rest val)
+ (values (%wrap fingertree rest) val))))
+
+(define (fingertree-empty? fingertree)
+ (empty? (fingertree-tree fingertree)))
+
+(define (fingertree-append fingertree1 fingertree2)
+ (%wrap fingertree1
+ (app3 (fingertree-tree fingertree1)
+ '()
+ (fingertree-tree fingertree2)
+ (fingertree-monoid fingertree1))))
+
+;; TODO: fix this
+(define (list->fingertree l id append convert)
+ (define monoid (make-monoid* id append convert))
+ (%make-fingertree (list->tree l monoid) monoid))
+
+(define (fingertree->list t)
+ (fingertree-fold-right cons '() t))
+
+(define (fingertree-measure fingertree)
+ (measure-ftree (fingertree-tree fingertree)
+ (fingertree-monoid fingertree)))
+
+
+(define (fingertree-split p fingertree)
+ (call-with-values
+ (lambda ()
+ (split p
+ (fingertree-tree fingertree)
+ (fingertree-monoid fingertree)))
+ (lambda (a b)
+ (values (%wrap fingertree a)
+ (%wrap fingertree b)))))
+
+(define (fingertree-split3 p fingertree)
+ (call-with-values
+ (lambda ()
+ (define monoid (fingertree-monoid fingertree))
+ (split-tree p
+ (mempty monoid)
+ (fingertree-tree fingertree)
+ monoid))
+ (lambda (a b c)
+ (values (%wrap fingertree a)
+ b
+ (%wrap fingertree c)))))
+
+(define (fingertree-fold f b fingertree)
+ (ftree-fold-left f b (fingertree-tree fingertree)))
+
+(define (fingertree-fold-right f b fingertree)
+ (ftree-fold-right f b (fingertree-tree fingertree)))
+
+(define (fingertree-reverse fingertree)
+ (%wrap fingertree
+ (reverse-tree (fingertree-tree fingertree)
+ (fingertree-monoid fingertree))))
+
+)
+
+;;; hamts.sls --- Hash Array Mapped Tries
+
+;; Copyright (C) 2014 Ian Price <ianprice90@googlemail.com>
+
+;; Author: Ian Price <ianprice90@googlemail.com>
+
+;; This program is free software, you can redistribute it and/or
+;; modify it under the terms of the new-style BSD license.
+
+;; You should have received a copy of the BSD license along with this
+;; program. If not, see <http://www.debian.org/misc/bsd.license>.
+
+;; Documentation:
+;;
+;; Note: For all procedures which take a key as an argument, the key
+;; must be hashable with the hamt hash function, and comparable with
+;; the hamt equivalence predicate.
+;;
+;; make-hamt : (any -> non-negative integer) (any -> any -> boolean) -> hamt
+;; returns a new empty hamt using the given hash and equivalence functions.
+;;
+;; hamt? : any -> boolean
+;; returns #t if argument is a hamt, #f otherwise.
+;;
+;; hamt-size : hamt -> non-negative integer
+;; returns the number of associations in the hamt.
+;;
+;; hamt-ref : hamt any [any] -> any
+;; returns the value associated with the key in the hamt. If there is
+;; no value associated with the key, it returns the default value if
+;; provided, or raises an &assertion-violation if it isn't.
+;;
+;; hamt-contains? : hamt any -> boolean
+;; returns #t if there is an association for the key in the hamt, #f
+;; otherwise.
+;;
+;; hamt-set : hamt any any -> hamt
+;; returns a new hamt with the key associated to the value. If the key
+;; is already associated with a value, it is replaced.
+;;
+;; hamt-update : hamt any (any -> any) any -> hamt
+;; returns a new hamt with the valued associated with the key updated
+;; by the update procedure. If the hamt does not already have a value
+;; associated with the key, then it applies the update procedure to
+;; the default value, and associates the key with that.
+;;
+;; hamt-delete : hamt any -> hamt
+;; returns a hamt with the key and its associated value removed. If
+;; the key is not in the hamt, a copy of the original hamt is
+;; returned.
+;;
+;; hamt-fold : (any any any -> any) any hamt -> hamt
+;; returns the value obtained by iterating the combine procedure over
+;; each key value pair in the hamt. The combine procedure takes three
+;; arguments, the key and value of an association, and an accumulator,
+;; and returns a new accumulator value. The initial value of the
+;; accumulator is provided by the base argument. The order in which
+;; the hamt is traversed is not guaranteed.
+;;
+;; hamt-map : (any -> any) hamt -> hamt
+;; returns the hamt obtained by applying the update procedure to each
+;; of the values in the hamt.
+;;
+;; hamt->alist : hamt -> Listof(Pairs)
+;; returns the key/value associations of the hamt as a list of pairs.
+;; The order of the list is not guaranteed.
+;;
+;; alist->hamt : Listof(Pairs) (any -> non-negative integer) (any -> any -> boolean) -> hamt
+;; returns the hamt containing the associations specified by the pairs
+;; in the alist. If the same key appears in the alist multiple times,
+;; its leftmost value is the one that is used.
+;;
+;; hamt-equivalence-predicate : hamt -> (any -> any -> boolean)
+;; returns the procedure used internally by the hamt to compare keys.
+;;
+;; hamt-hash-function : hamt -> (any -> non-negative integer)
+;; returns the hash procedure used internally by the hamt.
+;;
+(library (pfds hamts)
+(export make-hamt
+ hamt?
+ hamt-size
+ hamt-ref
+ hamt-set
+ hamt-update
+ hamt-delete
+ hamt-contains?
+ hamt-equivalence-predicate
+ hamt-hash-function
+ hamt-fold
+ hamt-map
+ hamt->alist
+ alist->hamt
+ )
+(import (rnrs)
+ (pfds private vectors)
+ (pfds private alists)
+ (pfds private bitwise))
+
+;;; Helpers
+
+(define cardinality 32) ; 64
+
+(define (mask key level)
+ (bitwise-arithmetic-shift-right (bitwise-and key (- (expt 2 5) 1)) level))
+
+(define (level-up level)
+ (+ level 5))
+
+(define (ctpop key index)
+ (bitwise-bit-count (bitwise-arithmetic-shift-right key (+ 1 index))))
+
+;;; Node types
+
+(define-record-type (subtrie %make-subtrie subtrie?)
+ (fields size bitmap vector))
+
+(define (make-subtrie bitmap vector)
+ (define vecsize
+ (vector-fold (lambda (val accum)
+ (+ (size val) accum))
+ 0
+ vector))
+ (%make-subtrie vecsize bitmap vector))
+
+(define-record-type leaf
+ (fields key value))
+
+(define-record-type (collision %make-collision collision?)
+ (fields size hash alist))
+
+(define (make-collision hash alist)
+ (%make-collision (length alist) hash alist))
+
+;;; Main
+
+(define (lookup vector key default hash eqv?)
+ (define (handle-subtrie node level)
+ (define bitmap (subtrie-bitmap node))
+ (define vector (subtrie-vector node))
+ (define index (mask h level))
+ (if (not (bitwise-bit-set? bitmap index))
+ default
+ (let ((node (vector-ref vector (ctpop bitmap index))))
+ (cond ((leaf? node)
+ (handle-leaf node))
+ ((collision? node)
+ (handle-collision node))
+ (else
+ (handle-subtrie node (level-up level)))))))
+
+ (define (handle-leaf node)
+ (if (eqv? key (leaf-key node))
+ (leaf-value node)
+ default))
+
+ (define (handle-collision node)
+ (alist-ref (collision-alist node) key default eqv?))
+
+ (define h (hash key))
+ (define node (vector-ref vector (mask h 0)))
+
+ (cond ((not node) default)
+ ((leaf? node) (handle-leaf node))
+ ((collision? node) (handle-collision node))
+ (else
+ (handle-subtrie node (level-up 0)))))
+
+(define (insert hvector key update base hash eqv?)
+ (define (handle-subtrie subtrie level)
+ (define bitmap (subtrie-bitmap subtrie))
+ (define vector (subtrie-vector subtrie))
+ (define index (mask h level))
+ (define (fixup node)
+ (make-subtrie bitmap (vector-set vector index node)))
+ (if (not (bitwise-bit-set? bitmap index))
+ (make-subtrie (bitwise-bit-set bitmap index)
+ (vector-insert vector
+ (ctpop bitmap index)
+ (make-leaf key (update base))))
+ (let ((node (vector-ref vector (ctpop bitmap index))))
+ (cond ((leaf? node)
+ (fixup (handle-leaf node level)))
+ ((collision? node)
+ (fixup (handle-collision node level)))
+ (else
+ (fixup (handle-subtrie node (level-up level))))))))
+
+ (define (handle-leaf node level)
+ (define lkey (leaf-key node))
+ (define khash (bitwise-arithmetic-shift-right h level))
+ (define lhash (bitwise-arithmetic-shift-right (hash lkey) level))
+ (cond ((eqv? key lkey)
+ (make-leaf key (update (leaf-value node))))
+ ((equal? khash lhash)
+ (make-collision lhash
+ (list (cons lkey (leaf-value node))
+ (cons key (update base)))))
+ (else
+ (handle-subtrie (wrap-subtrie node lhash) (level-up level)))))
+
+ (define (handle-collision node level)
+ (define khash (bitwise-arithmetic-shift-right h level))
+ (define chash (bitwise-arithmetic-shift-right (collision-hash node) level))
+ (if (equal? khash chash)
+ (make-collision (collision-hash node)
+ (alist-update (collision-alist node) key update base eqv?))
+ ;; TODO: there may be a better (more efficient) way to do this
+ ;; but simple is better for now (see also handle-leaf)
+ (handle-subtrie (wrap-subtrie node chash) (level-up level))))
+
+ (define (wrap-subtrie node chash)
+ (make-subtrie (bitwise-bit-set 0 (mask chash 0)) (vector node)))
+
+ (define h (hash key))
+ (define idx (mask h 0))
+ (define node (vector-ref hvector idx))
+ (define initial-level (level-up 0))
+
+ (cond ((not node)
+ (vector-set hvector idx (make-leaf key (update base))))
+ ((leaf? node)
+ (vector-set hvector idx (handle-leaf node initial-level)))
+ ((collision? node)
+ (vector-set hvector idx (handle-collision node initial-level)))
+ (else
+ (vector-set hvector idx (handle-subtrie node initial-level)))))
+
+(define (delete vector key hash eqv?)
+ (define (handle-subtrie subtrie level)
+ (define bitmap (subtrie-bitmap subtrie))
+ (define vector (subtrie-vector subtrie))
+ (define index (mask h level))
+ (define (fixup node)
+ (update bitmap vector index node))
+ (if (not (bitwise-bit-set? bitmap index))
+ subtrie
+ (let ((node (vector-ref vector (ctpop bitmap index))))
+ (cond ((leaf? node)
+ (fixup (handle-leaf node)))
+ ((collision? node)
+ (fixup (handle-collision node)))
+ (else
+ (fixup (handle-subtrie node (level-up level))))))))
+
+ (define (update bitmap vector index value)
+ (if value
+ (make-subtrie bitmap (vector-set vector index value))
+ (let ((vector* (vector-remove vector index)))
+ (if (equal? '#() vector)
+ #f
+ (make-subtrie (bitwise-bit-unset bitmap index)
+ vector*)))))
+
+ (define (handle-leaf node)
+ (if (eqv? key (leaf-key node))
+ #f
+ node))
+
+ (define (handle-collision node)
+ (let ((al (alist-delete (collision-alist node) key eqv?)))
+ (cond ((null? (cdr al))
+ (make-leaf (car (car al)) (cdr (car al))))
+ (else
+ (make-collision (collision-hash node) al)))))
+
+ (define h (hash key))
+ (define idx (mask h 0))
+ (define node (vector-ref vector idx))
+
+ (cond ((not node) vector)
+ ((leaf? node)
+ (vector-set vector idx (handle-leaf node)))
+ ((collision? node)
+ (vector-set vector idx (handle-collision node)))
+ (else
+ (vector-set vector idx (handle-subtrie node (level-up 0))))))
+
+(define (vec-map mapper vector)
+ (define (handle-subtrie trie)
+ (make-subtrie (subtrie-bitmap trie)
+ (vector-map dispatch (subtrie-vector vector))))
+
+ (define (handle-leaf leaf)
+ (make-leaf (leaf-key leaf)
+ (mapper (leaf-value leaf))))
+
+ (define (handle-collision collision)
+ (make-collision (collision-hash collision)
+ (map (lambda (pair)
+ (cons (car pair) (mapper (cdr pair))))
+ (collision-alist collision))))
+
+ (define (dispatch val)
+ (cond ((leaf? val)
+ (handle-leaf val))
+ ((collision? val)
+ (handle-collision val))
+ (else
+ (handle-subtrie val))))
+
+ (vector-map (lambda (val)
+ ;; top can have #f values
+ (and val (dispatch val)))
+ vector))
+
+(define (fold combine initial vector)
+ (define (handle-subtrie trie accum)
+ (vector-fold dispatch accum (subtrie-vector vector)))
+
+ (define (handle-leaf leaf accum)
+ (combine (leaf-key leaf) (leaf-value leaf) accum))
+
+ (define (handle-collision collision accum)
+ (fold-right (lambda (pair acc)
+ (combine (car pair) (cdr pair) acc))
+ accum
+ (collision-alist collision)))
+
+ (define (dispatch val accum)
+ (cond ((leaf? val)
+ (handle-leaf val accum))
+ ((collision? val)
+ (handle-collision val accum))
+ (else
+ (handle-subtrie val accum))))
+
+ (vector-fold (lambda (val accum)
+ ;; top level can have false values
+ (if (not val) accum (dispatch val accum)))
+ initial
+ vector))
+
+(define (size node)
+ (cond ((not node) 0)
+ ((leaf? node) 1)
+ ((collision? node) (collision-size node))
+ (else (subtrie-size node))))
+
+;;; Exported Interface
+
+(define-record-type (hamt %make-hamt hamt?)
+ (fields size root hash-function equivalence-predicate))
+
+(define (wrap-root root hamt)
+ (define vecsize
+ (vector-fold (lambda (val accum)
+ (+ (size val) accum))
+ 0
+ root))
+ (%make-hamt vecsize
+ root
+ (hamt-hash-function hamt)
+ (hamt-equivalence-predicate hamt)))
+
+(define (make-hamt hash eqv?)
+ (%make-hamt 0 (make-vector cardinality #f) hash eqv?))
+
+(define hamt-ref
+ (case-lambda
+ ((hamt key)
+ (define token (cons #f #f))
+ (define return-val (hamt-ref hamt key token))
+ (when (eqv? token return-val)
+ (assertion-violation 'hamt-ref "Key is not in the hamt" key))
+ return-val)
+ ((hamt key default)
+ ;; assert hamt?
+ (lookup (hamt-root hamt)
+ key
+ default
+ (hamt-hash-function hamt)
+ (hamt-equivalence-predicate hamt)))))
+
+(define (hamt-set hamt key value)
+ (define root
+ (insert (hamt-root hamt)
+ key
+ (lambda (old) value)
+ 'dummy
+ (hamt-hash-function hamt)
+ (hamt-equivalence-predicate hamt)))
+ (wrap-root root hamt))
+
+(define (hamt-update hamt key proc default)
+ (define root
+ (insert (hamt-root hamt)
+ key
+ proc
+ default
+ (hamt-hash-function hamt)
+ (hamt-equivalence-predicate hamt)))
+ (wrap-root root hamt))
+
+(define (hamt-delete hamt key)
+ (define root
+ (delete (hamt-root hamt)
+ key
+ (hamt-hash-function hamt)
+ (hamt-equivalence-predicate hamt)))
+ (wrap-root root hamt))
+
+(define (hamt-contains? hamt key)
+ (define token (cons #f #f))
+ (if (eqv? token (hamt-ref hamt key token))
+ #f
+ #t))
+
+(define (hamt-map mapper hamt)
+ (%make-hamt (hamt-size hamt)
+ (vec-map mapper (hamt-root hamt))
+ (hamt-hash-function hamt)
+ (hamt-equivalence-predicate hamt)))
+
+(define (hamt-fold combine initial hamt)
+ (fold combine initial (hamt-root hamt)))
+
+(define (hamt->alist hamt)
+ (hamt-fold (lambda (key value accumulator)
+ (cons (cons key value) accumulator))
+ '()
+ hamt))
+
+(define (alist->hamt alist hash eqv?)
+ (fold-right (lambda (kv-pair hamt)
+ (hamt-set hamt (car kv-pair) (cdr kv-pair)))
+ (make-hamt hash eqv?)
+ alist))
+
+)
+
+;; Copyright (C) 2012 Ian Price <ianprice90@googlemail.com>
+
+;; Author: Ian Price <ianprice90@googlemail.com>
+
+;; This program is free software, you can redistribute it and/or
+;; modify it under the terms of the new-style BSD license.
+
+;; You should have received a copy of the BSD license along with this
+;; program. If not, see <http://www.debian.org/misc/bsd.license>.
+
+;; Documentation:
+;;
+;; make-heap : (any any -> bool) -> heap
+;; returns a new empty heap which uses the ordering procedure.
+;;
+;; heap : (any any -> bool) any ... -> heap
+;; return a new heap, ordered by the procedure argument, that contains
+;; all the other arguments as elements.
+;;
+;; heap? : any -> bool
+;; returns #t if the argument is a heap, #f otherwise.
+;;
+;; heap-size : heap -> non-negative integer
+;; returns the number of elements in the heap.
+;;
+;; heap-empty? : heap -> bool
+;; returns #t if the heap contains no elements, #f otherwise.
+;;
+;; heap-min : heap -> any
+;; returns the minimum element in the heap, according the heap's
+;; ordering procedure. If there are no elements, a
+;; &heap-empty-condition is raised.
+;;
+;; heap-delete-min : heap -> heap
+;; returns a new heap containing all the elements of the heap
+;; argument, except for the minimum argument, as determined by the
+;; heap's ordering procedure. If there are no elements, a
+;; &heap-empty-condition is raised.
+;;
+;; heap-pop : any + heap
+;; returns two values: the the minimum value, and a heap obtained by
+;; removing the minimum value from the original heap. If the heap is
+;; empty, a &heap-empty-condition is raised.
+;;
+;; heap-insert : heap any -> heap
+;; returns the new heap obtained by adding the element to those in the
+;; argument heap.
+;;
+;; heap->list : heap -> Listof(any)
+;; returns the heap containing all the elements of the heap. The
+;; elements of the list are ordered according to the heap's ordering
+;; procedure.
+;;
+;; list->heap : Listof(any) (any any -> boolean) -> heap
+;; returns the heap containing all the elements of the list, and using
+;; the procedure argument to order the elements.
+;;
+;; heap-merge : heap heap -> heap
+;; returns the heap containing all the elements of the argument
+;; heaps. The argument heaps are assumed to be using the same ordering
+;; procedure.
+;;
+;; heap-sort : (any any -> bool) list -> list
+;; returns a new list that is a permutation of the argument list, such
+;; that all the elements are ordered by the given procedure.
+;;
+;; heap-ordering-procedure : heap -> (any any -> boolean)
+;; returns the ordering procedure used internally by the heap.
+;;
+;; heap-empty-condition? : any -> bool
+;; returns #t if argument is a &heap-empty condition, #f otherwise.
+;;
+(library (pfds heaps)
+(export make-heap
+ (rename (%heap heap))
+ heap?
+ heap-size
+ heap-empty?
+ heap-min
+ heap-delete-min
+ heap-insert
+ heap-pop
+ heap->list
+ list->heap
+ heap-merge
+ heap-sort
+ (rename (heap-ordering-predicate heap-ordering-procedure))
+ heap-empty-condition?
+ )
+(import (rnrs))
+
+(define-record-type (node %make-node node?)
+ (fields size height value left right))
+
+(define-record-type leaf)
+
+(define (height x)
+ (if (leaf? x)
+ 0
+ (node-height x)))
+
+(define (size x)
+ (if (leaf? x)
+ 0
+ (node-size x)))
+
+(define (make-node v l r)
+ (define sl (height l))
+ (define sr (height r))
+ (define m (+ 1 (min sl sr)))
+ (define sz (+ 1 (size l) (size r)))
+ (if (< sl sr)
+ (%make-node sz m v r l)
+ (%make-node sz m v l r)))
+
+(define (singleton v)
+ (%make-node 1 0 v (make-leaf) (make-leaf)))
+
+(define (insert tree value prio<?)
+ (merge-trees tree (singleton value) prio<?))
+
+(define (delete-min tree prio<?)
+ (merge-trees (node-left tree)
+ (node-right tree)
+ prio<?))
+
+(define (merge-trees tree1 tree2 prio<?)
+ (cond ((leaf? tree1) tree2)
+ ((leaf? tree2) tree1)
+ ((prio<? (node-value tree2)
+ (node-value tree1))
+ (make-node (node-value tree2)
+ (node-left tree2)
+ (merge-trees tree1
+ (node-right tree2)
+ prio<?)))
+ (else
+ (make-node (node-value tree1)
+ (node-left tree1)
+ (merge-trees (node-right tree1)
+ tree2
+ prio<?)))))
+
+
+;; outside interface
+(define-record-type (heap %make-heap heap?)
+ (fields tree ordering-predicate))
+
+(define (make-heap priority<?)
+ (%make-heap (make-leaf) priority<?))
+
+(define (%heap < . vals)
+ (list->heap vals <))
+
+(define (heap-size heap)
+ (size (heap-tree heap)))
+
+(define (heap-empty? heap)
+ (leaf? (heap-tree heap)))
+
+(define (heap-min heap)
+ (when (heap-empty? heap)
+ (raise (condition
+ (make-heap-empty-condition)
+ (make-who-condition 'heap-min)
+ (make-message-condition "There is no minimum element.")
+ (make-irritants-condition (list heap)))))
+ (node-value (heap-tree heap)))
+
+(define (heap-delete-min heap)
+ (when (heap-empty? heap)
+ (raise (condition
+ (make-heap-empty-condition)
+ (make-who-condition 'heap-delete-min)
+ (make-message-condition "There is no minimum element.")
+ (make-irritants-condition (list heap)))))
+ (let ((< (heap-ordering-predicate heap)))
+ (%make-heap (delete-min (heap-tree heap) <) <)))
+
+(define (heap-pop heap)
+ (when (heap-empty? heap)
+ (raise (condition
+ (make-heap-empty-condition)
+ (make-who-condition 'heap-pop)
+ (make-message-condition "There is no minimum element.")
+ (make-irritants-condition (list heap)))))
+ (let* ((tree (heap-tree heap))
+ (top (node-value tree))
+ (< (heap-ordering-predicate heap))
+ (rest (delete-min tree <)))
+ (values top
+ (%make-heap rest <))))
+
+(define (heap-insert heap value)
+ (assert (heap? heap))
+ (let ((< (heap-ordering-predicate heap)))
+ (%make-heap (insert (heap-tree heap) value <) <)))
+
+(define (heap->list heap)
+ (assert (heap? heap))
+ (let ((< (heap-ordering-predicate heap)))
+ (let loop ((tree (heap-tree heap)) (list '()))
+ (if (leaf? tree)
+ (reverse list)
+ (loop (delete-min tree <)
+ (cons (node-value tree) list))))))
+
+(define (list->heap list <)
+ (%make-heap
+ (fold-left (lambda (h item)
+ (insert h item <))
+ (make-leaf)
+ list)
+ <))
+
+(define (heap-merge heap1 heap2)
+ (define < (heap-ordering-predicate heap1))
+ (%make-heap
+ (merge-trees (heap-tree heap1)
+ (heap-tree heap2)
+ <)
+ <))
+
+(define (heap-sort < list)
+ (heap->list (list->heap list <)))
+
+(define-condition-type &heap-empty
+ &assertion
+ make-heap-empty-condition
+ heap-empty-condition?)
+)
+(package (pfds (0 3))
+ (depends (wak-trc-testing))
+ (synopsis "Purely Functional Data Structures")
+ (description
+ "A library of data structures for functional programmers."
+ "It contains implementations of:"
+ "- queues"
+ "- deques"
+ "- bbtrees"
+ "- sets"
+ "- dlists"
+ "- priority search queues"
+ "- heaps"
+ "- hamts"
+ "- finger trees"
+ "- sequences")
+ (homepage "http://github.com/ijp/pfds")
+ (documentation
+ "README.org"
+ "LICENSE")
+ (libraries
+ (sls -> "pfds")
+ ("queues" -> ("pdfs" "queues"))
+ ("deques" -> ("pdfs" "deques"))
+ ("private" -> ("pfds" "private"))))
+
+;;; psqs.sls --- Priority Search Queues
+
+;; Copyright (C) 2012 Ian Price <ianprice90@googlemail.com>
+
+;; Author: Ian Price <ianprice90@googlemail.com>
+
+;; This program is free software, you can redistribute it and/or
+;; modify it under the terms of the new-style BSD license.
+
+;; You should have received a copy of the BSD license along with this
+;; program. If not, see <http://www.debian.org/misc/bsd.license>.
+
+;;;; Documentation
+;;
+;; Priority search queues are a combination of two common abstract
+;; data types: finite maps, and priority queues. As such, it provides
+;; for access, insertion, removal and update on arbitrary keys, as
+;; well as for easy removal of the element with the lowest priority.
+;;
+;; Note: where a procedure takes a key or priority these are expected
+;; to be compatible with the relevant ordering procedures on the psq.
+;;
+;;;; Basic operations
+;;
+;; make-psq : < < -> psq
+;; takes a two ordering procedures, one for keys, and another for
+;; priorities, and returns an empty priority search queue
+;;
+;; psq? : obj -> boolean
+;; returns #t if the object is a priority search queue, #f otherwise.
+;;
+;; psq-empty? : psq -> boolean
+;; returns #t if the priority search queue contains no elements, #f
+;; otherwise.
+;;
+;; psq-size : psq -> non-negative integer
+;; returns the number of associations in the priority search queue
+;;
+;;;; Finite map operations
+;;
+;; psq-ref : psq key -> priority
+;; returns the priority of a key if it is in the priority search
+;; queue. If the key is not in the priority queue an
+;; assertion-violation is raised.
+;;
+;; psq-set : psq key priority -> psq
+;; returns the priority search queue obtained from inserting a key
+;; with a given priority. If the key is already in the priority search
+;; queue, it updates the priority to the new value.
+;;
+;; psq-update : psq key (priority -> priority) priority -> psq
+;; returns the priority search queue obtained by modifying the
+;; priority of key, by the given function. If the key is not in the
+;; priority search queue, it is inserted with the priority obtained by
+;; calling the function on the default value.
+;;
+;; psq-delete : psq key -> psq
+;; returns the priority search queue obtained by removing the
+;; key-priority association from the priority search queue. If the key
+;; is not in the queue, then the returned search queue will be the
+;; same as the original.
+;;
+;; psq-contains? : psq key -> boolean
+;; returns #t if there is an association for the given key in the
+;; priority search queue, #f otherwise.
+;;
+;;;; Priority queue operations
+;;
+;; psq-min : psq -> key
+;;
+;; returns the key of the minimum association in the priority search
+;; queue. If the queue is empty, an assertion violation is raised.
+;;
+;; psq-delete-min : psq -> psq
+;; returns the priority search queue obtained by removing the minimum
+;; association in the priority search queue. If the queue is empty, an
+;; assertion violation is raised.
+;;
+;; psq-pop : psq -> key + psq
+;; returns two values: the minimum key and the priority search queue
+;; obtained by removing the minimum association from the original
+;; queue. If the queue is empty, an assertion violation is raised.
+;;
+;;;; Ranged query functions
+;;
+;; psq-at-most : psq priority -> ListOf(key . priority)
+;; returns an alist containing all the associations in the priority
+;; search queue with priority less than or equal to a given value. The
+;; alist returned is ordered by key according to the predicate for the
+;; psq.
+;;
+;; psq-at-most-range : psq priority key key -> ListOf(key . priority)
+;; Similar to psq-at-most, but it also takes an upper and lower bound,
+;; for the keys it will return. These bounds are inclusive.
+;;
+(library (pfds psqs)
+(export make-psq
+ psq?
+ psq-empty?
+ psq-size
+ ;; map operations
+ psq-ref
+ psq-set
+ psq-update
+ psq-delete
+ psq-contains?
+ ;; priority queue operations
+ psq-min
+ psq-delete-min
+ psq-pop
+ ;; ranged query operations
+ psq-at-most
+ psq-at-most-range
+ )
+(import (except (rnrs) min))
+
+;;; record types
+
+(define-record-type void)
+
+(define-record-type winner
+ (fields key priority loser-tree maximum-key))
+
+(define-record-type start)
+
+(define-record-type (loser %make-loser loser?)
+ (fields size key priority left split-key right))
+
+(define (make-loser key priority left split-key right)
+ (%make-loser (+ (size left) (size right) 1)
+ key
+ priority
+ left
+ split-key
+ right))
+
+;;; functions
+(define (maximum-key psq)
+ (winner-maximum-key psq))
+
+(define max-key maximum-key)
+
+(define empty (make-void))
+
+(define (singleton key priority)
+ (make-winner key priority (make-start) key))
+
+(define (play-match psq1 psq2 key<? prio<?)
+ (cond ((void? psq1) psq2)
+ ((void? psq2) psq1)
+ ((not (prio<? (winner-priority psq2)
+ (winner-priority psq1)))
+ (let ((k1 (winner-key psq1))
+ (p1 (winner-priority psq1))
+ (t1 (winner-loser-tree psq1))
+ (m1 (winner-maximum-key psq1))
+ (k2 (winner-key psq2))
+ (p2 (winner-priority psq2))
+ (t2 (winner-loser-tree psq2))
+ (m2 (winner-maximum-key psq2)))
+ (make-winner k1
+ p1
+ (balance k2 p2 t1 m1 t2 key<? prio<?)
+ m2)))
+ (else
+ (let ((k1 (winner-key psq1))
+ (p1 (winner-priority psq1))
+ (t1 (winner-loser-tree psq1))
+ (m1 (winner-maximum-key psq1))
+ (k2 (winner-key psq2))
+ (p2 (winner-priority psq2))
+ (t2 (winner-loser-tree psq2))
+ (m2 (winner-maximum-key psq2)))
+ (make-winner k2
+ p2
+ (balance k1 p1 t1 m1 t2 key<? prio<?)
+ m2)))))
+
+(define (second-best ltree key key<? prio<?)
+ (if (start? ltree)
+ (make-void)
+ (let ((k (loser-key ltree))
+ (p (loser-priority ltree))
+ (l (loser-left ltree))
+ (m (loser-split-key ltree))
+ (r (loser-right ltree)))
+ (if (not (key<? m k))
+ (play-match (make-winner k p l m)
+ (second-best r key key<? prio<?)
+ key<?
+ prio<?)
+ (play-match (second-best l m key<? prio<?)
+ (make-winner k p r key)
+ key<?
+ prio<?)))))
+
+(define (delete-min psq key<? prio<?)
+ ;; maybe void psqs should return void?
+ (second-best (winner-loser-tree psq) (winner-maximum-key psq) key<? prio<?))
+
+(define (psq-case psq empty-k singleton-k match-k key<?)
+ (if (void? psq)
+ (empty-k)
+ (let ((k1 (winner-key psq))
+ (p1 (winner-priority psq))
+ (t (winner-loser-tree psq))
+ (m (winner-maximum-key psq)))
+ (if (start? t)
+ (singleton-k k1 p1)
+ (let ((k2 (loser-key t))
+ (p2 (loser-priority t))
+ (l (loser-left t))
+ (s (loser-split-key t))
+ (r (loser-right t)))
+ (if (not (key<? s k2))
+ (match-k (make-winner k2 p2 l s)
+ (make-winner k1 p1 r m))
+ (match-k (make-winner k1 p1 l s)
+ (make-winner k2 p2 r m))))))))
+
+(define (lookup psq key default key<?)
+ (psq-case psq
+ (lambda () default)
+ (lambda (k p)
+ (if (or (key<? k key) (key<? key k))
+ default
+ p))
+ (lambda (w1 w2)
+ (if (not (key<? (max-key w1) key))
+ (lookup w1 key default key<?)
+ (lookup w2 key default key<?)))
+ key<?))
+
+(define (update psq key f default key<? prio<?)
+ (psq-case psq
+ (lambda () (singleton key (f default)))
+ (lambda (k p)
+ (cond ((key<? key k)
+ (play-match (singleton key (f default))
+ (singleton k p)
+ key<?
+ prio<?))
+ ((key<? k key)
+ (play-match (singleton k p)
+ (singleton key (f default))
+ key<?
+ prio<?))
+ (else
+ (singleton key (f p)))))
+ (lambda (w1 w2)
+ (if (not (key<? (max-key w1) key))
+ (play-match (update w1 key f default key<? prio<?)
+ w2
+ key<?
+ prio<?)
+ (play-match w1
+ (update w2 key f default key<? prio<?)
+ key<?
+ prio<?)))
+ key<?))
+
+(define (insert psq key val key<? prio<?)
+ (psq-case psq
+ (lambda () (singleton key val))
+ (lambda (k p)
+ (cond ((key<? key k)
+ (play-match (singleton key val)
+ (singleton k p)
+ key<?
+ prio<?))
+ ((key<? k key)
+ (play-match (singleton k p)
+ (singleton key val)
+ key<?
+ prio<?))
+ (else
+ (singleton key val))))
+ (lambda (w1 w2)
+ (if (not (key<? (max-key w1) key))
+ (play-match (insert w1 key val key<? prio<?) w2 key<? prio<?)
+ (play-match w1 (insert w2 key val key<? prio<?) key<? prio<?)))
+ key<?))
+
+(define (delete psq key key<? prio<?)
+ (psq-case psq
+ (lambda () empty)
+ (lambda (k p)
+ (if (or (key<? k key)
+ (key<? key k))
+ (singleton k p)
+ empty))
+ (lambda (w1 w2)
+ (if (not (key<? (max-key w1) key))
+ (play-match (delete w1 key key<? prio<?) w2 key<? prio<?)
+ (play-match w1 (delete w2 key key<? prio<?) key<? prio<?)))
+ key<?))
+
+(define (min tree)
+ (when (void? tree)
+ (assertion-violation 'psq-min
+ "Can't take the minimum of an empty priority search queue"))
+ (winner-key tree))
+
+(define (pop tree key<? prio<?)
+ (when (void? tree)
+ (assertion-violation 'psq-pop
+ "Can't pop from an empty priority search queue"))
+ (values (winner-key tree)
+ (delete-min tree key<? prio<?)))
+
+;; at-most and at-most-range are perfect examples of when to use
+;; dlists, but we do not do that here
+(define (at-most psq p key<? prio<?)
+ (define (at-most psq accum)
+ (if (and (winner? psq)
+ (prio<? p (winner-priority psq)))
+ accum
+ (psq-case psq
+ (lambda () accum)
+ (lambda (k p) (cons (cons k p) accum))
+ (lambda (m1 m2)
+ (at-most m1 (at-most m2 accum)))
+ key<?)))
+ (at-most psq '()))
+
+(define (at-most-range psq p lower upper key<? prio<?)
+ (define (within-range? key)
+ ;; lower <= k <= upper
+ (not (or (key<? key lower) (key<? upper key))))
+ (define (at-most psq accum)
+ (if (and (winner? psq)
+ (prio<? p (winner-priority psq)))
+ accum
+ (psq-case psq
+ (lambda () accum)
+ (lambda (k p)
+ (if (within-range? k)
+ (cons (cons k p) accum)
+ accum))
+ (lambda (m1 m2)
+ (let ((accum* (if (key<? upper (max-key m1))
+ accum
+ (at-most m2 accum))))
+ (if (key<? (max-key m1) lower)
+ accum*
+ (at-most m1 accum*))))
+ key<?)))
+ (at-most psq '()))
+
+;;; Maintaining balance
+(define weight 4) ; balancing constant
+
+(define (size ltree)
+ (if (start? ltree)
+ 0
+ (loser-size ltree)))
+
+(define (balance key priority left split-key right key<? prio<?)
+ (let ((l-size (size left))
+ (r-size (size right)))
+ (cond ((< (+ l-size r-size) 2)
+ (make-loser key priority left split-key right))
+ ((> r-size (* weight l-size))
+ (balance-left key priority left split-key right key<? prio<?))
+ ((> l-size (* weight r-size))
+ (balance-right key priority left split-key right key<? prio<?))
+ (else
+ (make-loser key priority left split-key right)))))
+
+(define (balance-left key priority left split-key right key<? prio<?)
+ (if (< (size (loser-left right))
+ (size (loser-right right)))
+ (single-left key priority left split-key right key<? prio<?)
+ (double-left key priority left split-key right key<? prio<?)))
+
+(define (balance-right key priority left split-key right key<? prio<?)
+ (if (< (size (loser-right left))
+ (size (loser-left left)))
+ (single-right key priority left split-key right key<? prio<?)
+ (double-right key priority left split-key right key<? prio<?)))
+
+(define (single-left key priority left split-key right key<? prio<?)
+ (let ((right-key (loser-key right))
+ (right-priority (loser-priority right))
+ (right-left (loser-left right))
+ (right-split-key (loser-split-key right))
+ (right-right (loser-right right)))
+ ;; test
+ (if (and (not (key<? right-split-key right-key))
+ (not (prio<? right-priority priority)))
+ (make-loser key
+ priority
+ (make-loser right-key right-priority left split-key right-left)
+ right-split-key
+ right-right
+ )
+ (make-loser right-key
+ right-priority
+ (make-loser key priority left split-key right-left)
+ right-split-key
+ right-right))))
+
+(define (double-left key priority left split-key right key<? prio<?)
+ (let ((right-key (loser-key right))
+ (right-priority (loser-priority right))
+ (right-left (loser-left right))
+ (right-split-key (loser-split-key right))
+ (right-right (loser-right right)))
+ (single-left key
+ priority
+ left
+ split-key
+ (single-right right-key
+ right-priority
+ right-left
+ right-split-key
+ right-right
+ key<?
+ prio<?)
+ key<?
+ prio<?)))
+
+(define (single-right key priority left split-key right key<? prio<?)
+ (let ((left-key (loser-key left))
+ (left-priority (loser-priority left))
+ (left-left (loser-left left))
+ (left-split-key (loser-split-key left))
+ (left-right (loser-right left)))
+ (if (and (key<? left-split-key left-key)
+ (not (prio<? left-priority priority)))
+ (make-loser key
+ priority
+ left-left
+ left-split-key
+ (make-loser left-key left-priority left-right split-key right))
+ (make-loser left-key
+ left-priority
+ left-left
+ left-split-key
+ (make-loser key priority left-right split-key right)))))
+
+(define (double-right key priority left split-key right key<? prio<?)
+ (let ((left-key (loser-key left))
+ (left-priority (loser-priority left))
+ (left-left (loser-left left))
+ (left-split-key (loser-split-key left))
+ (left-right (loser-right left)))
+ (single-right key
+ priority
+ (single-left left-key
+ left-priority
+ left-left
+ left-split-key
+ left-right
+ key<?
+ prio<?)
+ split-key
+ right
+ key<?
+ prio<?)))
+
+;;; Exported Type
+
+(define-record-type (psq %make-psq psq?)
+ (fields key<? priority<? tree))
+
+(define (%update-psq psq new-tree)
+ (%make-psq (psq-key<? psq)
+ (psq-priority<? psq)
+ new-tree))
+
+;;; Exported Procedures
+
+(define (make-psq key<? priority<?)
+ (%make-psq key<? priority<? (make-void)))
+
+(define (psq-empty? psq)
+ (assert (psq? psq))
+ (void? (psq-tree psq)))
+
+(define (psq-ref psq key)
+ (define cookie (cons #f #f))
+ (assert (psq? psq))
+ (let ((val (lookup (psq-tree psq) key cookie (psq-key<? psq))))
+ (if (eq? val cookie)
+ (assertion-violation 'psq-ref "not in tree")
+ val)))
+
+(define (psq-set psq key priority)
+ (assert (psq? psq))
+ (%update-psq psq
+ (insert (psq-tree psq) key priority (psq-key<? psq) (psq-priority<? psq))))
+
+(define (psq-update psq key f default)
+ (assert (psq? psq))
+ (%update-psq psq (update (psq-tree psq) key f default (psq-key<? psq) (psq-priority<? psq))))
+
+(define (psq-delete psq key)
+ (assert (psq? psq))
+ (%update-psq psq (delete (psq-tree psq) key (psq-key<? psq) (psq-priority<? psq))))
+
+(define (psq-contains? psq key)
+ (define cookie (cons #f #f))
+ (assert (psq? psq))
+ (let ((val (lookup (psq-tree psq) key cookie (psq-key<? psq))))
+ (not (eq? val cookie))))
+
+(define (psq-min psq)
+ (assert (psq? psq))
+ (min (psq-tree psq)))
+
+(define (psq-delete-min psq)
+ (assert (and (psq? psq)
+ (not (psq-empty? psq))))
+ (%update-psq psq (delete-min (psq-tree psq) (psq-key<? psq) (psq-priority<? psq))))
+
+(define (psq-pop psq)
+ (assert (psq? psq))
+ (let-values (((min rest) (pop (psq-tree psq) (psq-key<? psq) (psq-priority<? psq))))
+ (values min (%update-psq psq rest))))
+
+(define (psq-at-most psq max-priority)
+ (assert (psq? psq))
+ (let ((tree (psq-tree psq))
+ (key<? (psq-key<? psq))
+ (prio<? (psq-priority<? psq)))
+ (at-most tree max-priority key<? prio<?)))
+
+(define (psq-at-most-range psq max-priority min-key max-key)
+ (assert (psq? psq))
+ (let ((tree (psq-tree psq))
+ (key<? (psq-key<? psq))
+ (prio<? (psq-priority<? psq)))
+ (at-most-range tree max-priority min-key max-key key<? prio<?)))
+
+(define (psq-size psq)
+ (assert (psq? psq))
+ (let ((tree (psq-tree psq)))
+ (if (winner? tree)
+ (+ 1 (size (winner-loser-tree tree)))
+ 0)))
+
+)
+
+;;; queues.sls --- Purely functional queues
+
+;; Copyright (C) 2011,2012 Ian Price <ianprice90@googlemail.com>
+
+;; Author: Ian Price <ianprice90@googlemail.com>
+
+;; This program is free software, you can redistribute it and/or
+;; modify it under the terms of the new-style BSD license.
+
+;; You should have received a copy of the BSD license along with this
+;; program. If not, see <http://www.debian.org/misc/bsd.license>.
+
+;;; Commentary:
+;;
+;; A scheme translation of "Simple and Efficient Purely Functional
+;; Queues and Deques" by Chris Okazaki
+;;
+;;
+;;; Documentation:
+;;
+;; make-queue : () -> queue
+;; returns a queue containing no items
+;;
+;; queue? : any -> boolean
+;; tests if an object is a queue
+;;
+;; queue-length : queue -> non-negative integer
+;; returns the number of items in the queue
+;;
+;; queue-empty? : queue -> boolean
+;; returns true if there are no items in the queue, false otherwise
+;;
+;; enqueue : queue any -> queue
+;; returns a new queue with the enqueued item at the end
+;;
+;; dequeue : queue -> value queue
+;; returns two values, the item at the front of the queue, and a new
+;; queue containing the all the other items
+;; raises a &queue-empty condition if the queue is empty
+;;
+;; queue-empty-condition? : object -> boolean
+;; tests if an object is a &queue-empty condition
+;;
+;; queue->list : queue -> listof(any)
+;; returns a queue containing all the items in the list. The order of
+;; the elements in the queue is the same as the order of the elements
+;; in the list.
+;;
+;; list->queue : listof(any) -> queue
+;; returns a list containing all the items in the queue. The order of
+;; the items in the list is the same as the order in the queue.
+;; For any list l, (equal? (queue->list (list->queue l)) l) is #t.
+;;
+(library (pfds queues)
+(export make-queue
+ queue?
+ queue-length
+ queue-empty?
+ enqueue
+ dequeue
+ queue-empty-condition?
+ list->queue
+ queue->list
+ )
+(import (except (rnrs) cons*)
+ (pfds private lazy-lists)
+ (pfds queues private condition)
+ (rnrs r5rs))
+
+(define (rotate l r a)
+ (if (empty? l)
+ (cons* (head r) a)
+ (cons* (head l)
+ (rotate (tail l)
+ (tail r)
+ (cons* (head r) a)))))
+
+
+;;; Implementation
+
+(define-record-type (queue %make-queue queue?)
+ (fields
+ (immutable length)
+ (immutable l)
+ (immutable r)
+ (immutable l^)))
+
+
+(define (make-queue)
+ (%make-queue 0 '() '() '()))
+
+(define (enqueue queue item)
+ (let ((len (queue-length queue))
+ (l (queue-l queue))
+ (r (queue-r queue))
+ (l^ (queue-l^ queue)))
+ (makeq (+ len 1) l (cons* item r) l^)))
+
+(define (dequeue queue)
+ (when (queue-empty? queue)
+ ;; (error 'dequeue "Can't dequeue empty queue")
+ (raise (condition
+ (make-queue-empty-condition)
+ (make-who-condition 'dequeue)
+ (make-message-condition "There are no elements to dequeue")
+ (make-irritants-condition (list queue)))))
+ (let ((len (queue-length queue))
+ (l (queue-l queue))
+ (r (queue-r queue))
+ (l^ (queue-l^ queue)))
+ (values (head l)
+ (makeq (- len 1) (tail l) r l^))))
+
+(define (makeq length l r l^)
+ (if (empty? l^)
+ (let ((l* (rotate l r '())))
+ (%make-queue length l* '() l*))
+ (%make-queue length l r (tail l^))))
+
+(define (queue-empty? queue)
+ (zero? (queue-length queue)))
+
+(define (list->queue list)
+ (fold-left enqueue (make-queue) list))
+
+(define (queue->list queue)
+ (let loop ((rev-list '()) (queue queue))
+ (if (queue-empty? queue)
+ (reverse rev-list)
+ (let-values (((val queue) (dequeue queue)))
+ (loop (cons val rev-list)
+ queue)))))
+
+)
+
+;;; sequences.sls --- Purely Functional Sequences
+
+;; Copyright (C) 2012 Ian Price <ianprice90@googlemail.com>
+
+;; Author: Ian Price <ianprice90@googlemail.com>
+
+;; This program is free software, you can redistribute it and/or
+;; modify it under the terms of the new-style BSD license.
+
+;; You should have received a copy of the BSD license along with this
+;; program. If not, see <http://www.debian.org/misc/bsd.license>.
+
+;;; Commentary:
+
+;; Sequences are a general-purpose, variable-length collection,
+;; similar to lists, however they support efficient addition and
+;; removal from both ends, and random-access. Like other Scheme
+;; collections, sequences are zero-indexed.
+;;
+;; make-sequence : () -> sequence
+;; returns a new empty sequence
+;;
+;; sequence any ... -> sequence
+;; returns a new sequence containing all of the argument elements, in the
+;; same order.
+;;
+;; sequence? : any -> bool
+;; returns #t if the argument is a sequence, #f otherwise.
+;;
+;; sequence-empty? : sequence -> bool
+;; returns #t if the argument sequence contains no elements, #f otherwise.
+;;
+;; sequence-size : sequence -> non-negative integer
+;; returns the number of elements in the sequence
+;;
+;; sequence-cons : any sequence -> sequence
+;; return the new sequence created by adding the element to the front of
+;; the sequence.
+;;
+;; sequence-uncons : sequence -> any sequence
+;; returns two values: the first element of the sequence, and a new
+;; sequence containing all but the first element. If the sequence is
+;; empty, a &sequence-empty condition is raised.
+;;
+;; sequence-snoc : sequence any -> sequence
+;; return the new sequence created by adding the element to the end of
+;; the sequence.
+;;
+;; sequence-unsnoc : sequence -> sequence any
+;; returns two values: a new sequence containing all but the last
+;; element of the sequence, and the last element itself. If the
+;; sequence is empty, a &sequence-empty condition is raised.
+;;
+;; sequence-append : sequence sequence -> sequence
+;; returns a new sequence containing all the elements of the first
+;; sequence, followed by all the elements of the second sequence.
+;;
+;; list->sequence : Listof(Any) -> sequence
+;; returns a new sequence containing all the elements of the argument
+;; list, in the same order.
+;;
+;; sequence->list : sequence -> Listof(Any)
+;; returns a new list containing all the elements of the sequence, in the
+;; same order.
+;;
+;; sequence-split-at sequence integer -> sequence + sequence
+;; returns two new sequences, the first containing the first N elements
+;; of the sequence, the second containing the remaining elements. If N is
+;; negative, it returns the empty sequence as the first argument, and the
+;; original sequence as the second argument. Similarly, if N is greater
+;; than the length of the list, it returns the original sequence as the
+;; first argument, and the empty sequence as the second argument.
+;;
+;; Consequently, (let-values (((a b) (sequence-split-at s i)))
+;; (sequence-append a b)) is equivalent to s for all sequences s, and
+;; integers i.
+;;
+;; sequence-take sequence integer -> sequence
+;; returns a new sequence containing the first N elements of the
+;; argument sequence. If N is negative, the empty sequence is
+;; returned. If N is larger than the length of the sequence, the whole
+;; sequence is returned.
+;;
+;; sequence-drop sequence integer -> sequence
+;; returns a new sequence containing all but the first N elements of the
+;; argument sequence. If N is negative, the whole sequence is
+;; returned. If N is larger than the length of the sequence, the empty
+;; sequence is returned.
+;;
+;; sequence-ref : sequence non-negative-integer -> any
+;; returns the element at the specified index in the sequence. If the
+;; index is outside the range 0 <= i < (sequence-size sequence), an
+;; assertion violation is raised.
+;;
+;; sequence-set : sequence non-negative-integer any -> sequence
+;; returns the new sequence obtained by replacing the element at the
+;; specified index in the sequence with the given value. If the index
+;; is outside the range 0 <= i < (sequence-size sequence), an
+;; assertion violation is raised.
+;;
+;; sequence-fold (any -> any -> any) any sequence
+;; returns the value obtained by iterating the combiner procedure over
+;; the sequence in left-to-right order. The combiner procedure takes two
+;; arguments, the value of the position in the sequence, and an
+;; accumulator, and its return value is used as the value of the
+;; accumulator for the next call. The initial accumulator value is given
+;; by the base argument.
+;;
+;; sequence-fold-right (any -> any -> any) any sequence
+;; Like sequence-fold, but the sequence is traversed in right-to-left
+;; order, rather than left-to-right.
+;;
+;; sequence-reverse : sequence -> sequence
+;; returns a new sequence containing all the arguments of the argument
+;; list, in reverse order.
+;;
+;; sequence-map : (any -> any) sequence -> sequence
+;; returns a new sequence obtained by applying the procedure to each
+;; element of the argument sequence in turn.
+;;
+;; sequence-filter : (any -> bool) sequence -> sequence
+;; returns a new sequence containing all the elements of the argument
+;; sequence for which the predicate is true.
+;;
+;; sequence-empty-condition? : any -> bool
+;; returns #t if an object is a &sequence-empty condition, #f otherwise.
+;;
+(library (pfds sequences)
+(export make-sequence
+ sequence?
+ sequence-empty?
+ sequence-size
+ sequence-cons
+ sequence-uncons
+ sequence-snoc
+ sequence-unsnoc
+ sequence-append
+ list->sequence
+ sequence->list
+ (rename (%sequence sequence))
+ sequence-split-at
+ sequence-take
+ sequence-drop
+ sequence-ref
+ sequence-set
+ sequence-fold
+ sequence-fold-right
+ sequence-reverse
+ sequence-map
+ sequence-filter
+ sequence-empty-condition?
+ )
+
+(import (rnrs)
+ (pfds fingertrees))
+
+;; Note: as sequences are not a subtype of fingertrees, but rather a
+;; particular instantiation of them, &sequence-empty is not a subtype
+;; of &fingertree-empty
+(define-condition-type &sequence-empty
+ &assertion
+ make-sequence-empty-condition
+ sequence-empty-condition?)
+
+(define-record-type (sequence %make-sequence sequence?)
+ (fields fingertree))
+
+(define (make-sequence)
+ (%make-sequence (make-fingertree 0 + (lambda (x) 1))))
+
+(define (sequence-empty? seq)
+ (fingertree-empty? (sequence-fingertree seq)))
+
+(define (sequence-size seq)
+ (fingertree-measure (sequence-fingertree seq)))
+
+(define (sequence-cons value seq)
+ (%make-sequence
+ (fingertree-cons value (sequence-fingertree seq))))
+
+(define (sequence-snoc seq value)
+ (%make-sequence
+ (fingertree-snoc (sequence-fingertree seq) value)))
+
+(define (sequence-uncons seq)
+ (call-with-values
+ (lambda ()
+ (define ft (sequence-fingertree seq))
+ (when (fingertree-empty? ft)
+ (raise
+ (condition
+ (make-sequence-empty-condition)
+ (make-who-condition 'sequence-uncons)
+ (make-message-condition "There are no elements to uncons")
+ (make-irritants-condition (list seq)))))
+ (fingertree-uncons ft))
+ (lambda (head tree)
+ (values head (%make-sequence tree)))))
+
+(define (sequence-unsnoc seq)
+ (call-with-values
+ (lambda ()
+ (define ft (sequence-fingertree seq))
+ (when (fingertree-empty? ft)
+ (raise
+ (condition
+ (make-sequence-empty-condition)
+ (make-who-condition 'sequence-unsnoc)
+ (make-message-condition "There are no elements to unsnoc")
+ (make-irritants-condition (list seq)))))
+ (fingertree-unsnoc ft))
+ (lambda (tree last)
+ (values (%make-sequence tree) last))))
+
+(define (sequence-append seq1 seq2)
+ (%make-sequence
+ (fingertree-append (sequence-fingertree seq1)
+ (sequence-fingertree seq2))))
+
+(define (list->sequence list)
+ (fold-left sequence-snoc
+ (make-sequence)
+ list))
+
+(define (sequence->list seq)
+ (fingertree->list (sequence-fingertree seq)))
+
+(define (%sequence . args)
+ (list->sequence args))
+
+(define (sequence-split-at seq i)
+ (let-values (((l r)
+ (fingertree-split (lambda (x) (< i x))
+ (sequence-fingertree seq))))
+ (values (%make-sequence l)
+ (%make-sequence r))))
+
+(define (sequence-take seq i)
+ (let-values (((head tail)
+ (sequence-split-at seq i)))
+ head))
+
+(define (sequence-drop seq i)
+ (let-values (((head tail)
+ (sequence-split-at seq i)))
+ tail))
+
+(define (sequence-ref seq i)
+ (define size (sequence-size seq))
+ (unless (and (<= 0 i) (< i size))
+ (assertion-violation 'sequence-ref "Index out of range" i))
+ (let-values (((_l x _r)
+ (fingertree-split3 (lambda (x) (< i x))
+ (sequence-fingertree seq))))
+ x))
+
+(define (sequence-set seq i val)
+ (define size (sequence-size seq))
+ (unless (and (<= 0 i) (< i size))
+ (assertion-violation 'sequence-set "Index out of range" i))
+ (let-values (((l x r)
+ (fingertree-split3 (lambda (x) (< i x))
+ (sequence-fingertree seq))))
+ (%make-sequence
+ (fingertree-append l (fingertree-cons val r)))))
+
+(define (sequence-fold proc base seq)
+ (fingertree-fold proc base (sequence-fingertree seq)))
+
+(define (sequence-fold-right proc base seq)
+ (fingertree-fold-right proc base (sequence-fingertree seq)))
+
+(define (sequence-reverse seq)
+ (%make-sequence (fingertree-reverse (sequence-fingertree seq))))
+
+(define (sequence-map proc seq)
+ (define (combine element seq)
+ (sequence-cons (proc element) seq))
+ (sequence-fold-right combine (make-sequence) seq))
+
+(define (sequence-filter pred? seq)
+ (define (combine element seq)
+ (if (pred? element)
+ (sequence-cons element seq)
+ seq))
+ (sequence-fold-right combine (make-sequence) seq))
+
+)
+
+;;; sets.sls --- Purely Functional Sets
+
+;; Copyright (C) 2012 Ian Price <ianprice90@googlemail.com>
+
+;; Author: Ian Price <ianprice90@googlemail.com>
+
+;; This program is free software, you can redistribute it and/or
+;; modify it under the terms of the new-style BSD license.
+
+;; You should have received a copy of the BSD license along with this
+;; program. If not, see <http://www.debian.org/misc/bsd.license>.
+
+;; Documentation:
+;;
+;; set? : any -> boolean
+;; returns #t if the object is a set, #f otherwise
+;;
+;; make-set : (any any -> boolean) -> set
+;; returns a new empty set ordered by the < procedure
+;;
+;; set-member? : set any -> boolean
+;; returns true if element is in the set
+;;
+;; set-insert : set any -> set
+;; returns a new set created by inserting element into the set argument
+;;
+;; set-remove : set element -> set
+;; returns a new set created by removing element from the set
+;;
+;; set-size : set -> non-negative integer
+;; returns the number of elements in the set
+;;
+;; set<? : set set -> boolean
+;; returns #t if set1 is a proper subset of set2, #f otherwise. That
+;; is, if all elements of set1 are in set2, and there is at least one
+;; element of set2 not in set1.
+;;
+;; set<=? : set set -> boolean
+;; returns #t if set1 is a subset of set2, #f otherwise, i.e. if all
+;; elements of set1 are in set2.
+;;
+;; set=? : set set -> boolean
+;; returns #t if every element of set1 is in set2, and vice versa, #f
+;; otherwise.
+;;
+;; set>=? : set set -> boolean
+;; returns #t if set2 is a subset of set1, #f otherwise.
+;;
+;; set>? : set set -> boolean
+;; returns #t if set2 is a proper subset of set1, #f otherwise.
+;;
+;; subset? : set set -> boolean
+;; same as set<=?
+;;
+;; proper-subset? : set set -> boolean
+;; same as set<?
+;;
+;; set-map : (any -> any) set -> set
+;; returns the new set created by applying proc to each element of the set
+;;
+;; set-fold : (any any -> any) any set -> any
+;; returns the value obtained by iterating the procedure over each
+;; element of the set and an accumulator value. The value of the
+;; accumulator is initially base, and the return value of proc is used
+;; as the accumulator for the next iteration.
+;;
+;; list->set : Listof(any) (any any -> any) -> set
+;; returns the set containing all the elements of the list, ordered by <.
+;;
+;; set->list : set -> Listof(any)
+;; returns all the elements of the set as a list
+;;
+;; set-union : set set -> set
+;; returns the union of set1 and set2, i.e. contains all elements of
+;; set1 and set2.
+;;
+;; set-intersection : set set -> set
+;; returns the intersection of set1 and set2, i.e. the set of all
+;; items that are in both set1 and set2.
+;;
+;; set-difference : set set -> set
+;; returns the difference of set1 and set2, i.e. the set of all items
+;; in set1 that are not in set2.
+;;
+;; set-ordering-procedure : set -> (any any -> boolean)
+;; returns the ordering procedure used internall by the set.
+(library (pfds sets)
+(export set?
+ make-set
+ set-member?
+ set-insert
+ set-remove
+ set-size
+ set<?
+ set<=?
+ set=?
+ set>=?
+ set>?
+ subset?
+ proper-subset?
+ set-map
+ set-fold
+ list->set
+ set->list
+ set-union
+ set-intersection
+ set-difference
+ set-ordering-procedure
+ )
+(import (rnrs)
+ (pfds bbtrees))
+
+(define dummy #f)
+
+;;; basic sets
+(define-record-type (set %make-set set?)
+ (fields tree))
+
+(define (set-ordering-procedure set)
+ (bbtree-ordering-procedure (set-tree set)))
+
+(define (make-set <)
+ (%make-set (make-bbtree <)))
+
+;; provide a (make-equal-set) function?
+
+(define (set-member? set element)
+ (bbtree-contains? (set-tree set) element))
+
+(define (set-insert set element)
+ (%make-set (bbtree-set (set-tree set) element dummy)))
+
+(define (set-remove set element)
+ (%make-set (bbtree-delete (set-tree set) element)))
+
+(define (set-size set)
+ (bbtree-size (set-tree set)))
+
+;;; set equality
+(define (set<=? set1 set2)
+ (let ((t (set-tree set2)))
+ (bbtree-traverse (lambda (k _ l r b)
+ (and (bbtree-contains? t k)
+ (l #t)
+ (r #t)))
+ #t
+ (set-tree set1))))
+
+(define (set<? set1 set2)
+ (and (< (set-size set1)
+ (set-size set2))
+ (set<=? set1 set2)))
+
+(define (set>=? set1 set2)
+ (set<=? set2 set1))
+
+(define (set>? set1 set2)
+ (set<? set2 set1))
+
+(define (set=? set1 set2)
+ (and (set<=? set1 set2)
+ (set>=? set1 set2)))
+
+(define subset? set<=?)
+
+(define proper-subset? set<?)
+
+;;; iterators
+(define (set-map proc set)
+ ;; currently restricted to returning a set with the same ordering, I
+ ;; could weaken this to, say, comparing with < on the object-hash,
+ ;; or I make it take a < argument for the result set.
+ (let ((tree (set-tree set)))
+ (%make-set
+ (bbtree-fold (lambda (key _ tree)
+ (bbtree-set tree (proc key) dummy))
+ (make-bbtree (bbtree-ordering-procedure tree))
+ tree))))
+
+(define (set-fold proc base set)
+ (bbtree-fold (lambda (key value base)
+ (proc key base))
+ base
+ (set-tree set)))
+
+;;; conversion
+(define (list->set list <)
+ (fold-left (lambda (tree element)
+ (set-insert tree element))
+ (make-set <)
+ list))
+
+(define (set->list set)
+ (set-fold cons '() set))
+
+;;; set operations
+(define (set-union set1 set2)
+ (%make-set (bbtree-union (set-tree set1) (set-tree set2))))
+
+(define (set-intersection set1 set2)
+ (%make-set (bbtree-intersection (set-tree set1) (set-tree set2))))
+
+(define (set-difference set1 set2)
+ (%make-set (bbtree-difference (set-tree set1) (set-tree set2))))
+
+)
+
+;; Copyright (C) 2011-2014 Ian Price <ianprice90@googlemail.com>
+
+;; Author: Ian Price <ianprice90@googlemail.com>
+
+;; This program is free software, you can redistribute it and/or
+;; modify it under the terms of the new-style BSD license.
+
+;; You should have received a copy of the BSD license along with this
+;; program. If not, see <http://www.debian.org/misc/bsd.license>.
+
+;;; Code:
+(import (rnrs)
+ (pfds tests queues)
+ (pfds tests deques)
+ (pfds tests bbtrees)
+ (pfds tests sets)
+ (pfds tests psqs)
+ (pfds tests heaps)
+ (pfds tests fingertrees)
+ (pfds tests sequences)
+ (pfds tests hamts)
+ (pfds tests utils)
+ (wak trc-testing))
+
+;; Some schemes use lazy loading of modules, and so I can't just use
+;; (run-test pfds) and rely on the side effects in the other modules
+;; to add them to the pfds parent suite.
+(define-syntax add-tests!
+ (syntax-rules ()
+ ((add-tests! suite ...)
+ (begin (add-test! pfds 'suite suite) ...))))
+
+(add-tests! queues deques bbtrees sets psqs
+ heaps fingertrees sequences hamts)
+
+(run-test pfds)
+(define-library (r7rs-extras all)
+ (import (r7rs-extras higher-order))
+ (include-library-declarations "higher-order.exports.sld")
+ (import (r7rs-extras io))
+ (include-library-declarations "io.exports.sld")
+ (import (r7rs-extras partition))
+ (include-library-declarations "partition.exports.sld")
+ (import (r7rs-extras arithmetic))
+ (include-library-declarations "arithmetic.exports.sld")
+ (import (r7rs-extras pushpop))
+ (include-library-declarations "pushpop.exports.sld")
+ )
+;;; arithmetic.body.scm --- Extra arithmetic operations
+
+;; Copyright © 2014 Taylan Ulrich Bayırlı/Kammer
+;; Copyright © 2015 Taylan Ulrich Bayırlı/Kammer
+
+;; Author: Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
+;; Keywords: extensions arithmetic number
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; If you're desperate for performance, you might benefit from implementing the
+;; euclidean variants in terms of the floor and ceiling variants for positive
+;; and negative values of `y' respectively. The floor variants are in the
+;; (scheme base) library and might be more efficient in your implementation.
+
+;; These might also otherwise have significantly more efficient implementations.
+;; Let me know.
+
+;;; Code:
+
+(define-syntax define-divisions
+ (syntax-rules ()
+ ((_ div div-doc quotient quotient-doc remainder remainder-doc x y
+ quotient-expr)
+ (begin
+ (define (div x y)
+ div-doc
+ (let* ((q quotient-expr)
+ (r (- x (* y quotient-expr))))
+ (values q r)))
+ (define (quotient x y)
+ quotient-doc
+ quotient-expr)
+ (define (remainder x y)
+ remainder-doc
+ (- x (* y quotient-expr)))))))
+
+(define-divisions
+ euclidean/
+ "Return Q and R in X = Q*Y + R where 0 <= R < |Y|."
+ euclidean-quotient
+ "Return Q in X = Q*Y + R where 0 <= R < |Y|."
+ euclidean-remainder
+ "Return R in X = Q*Y + R where 0 <= R < |Y|."
+ x y
+ (cond ((positive? y)
+ (floor (/ x y)))
+ ((negative? y)
+ (ceiling (/ x y)))
+ ((zero? y)
+ (error "division by zero"))
+ (else +nan.0)))
+
+(define-divisions
+ ceiling/
+ "Return Q and R in X = Q*Y + R where Q = ceiling(X/Y)."
+ ceiling-quotient
+ "Return Q in X = Q*Y + R where Q = ceiling(X/Y)."
+ ceiling-remainder
+ "Return R in X = Q*Y + R where Q = ceiling(X/Y)."
+ x y
+ (ceiling (/ x y)))
+
+(define-divisions
+ centered/
+ "Return Q and R in X = Q*Y + R where -|Y/2| <= R < |Y/2|."
+ centered-quotient
+ "Return Q in X = Q*Y + R where -|Y/2| <= R < |Y/2|."
+ centered-remainder
+ "Return R in X = Q*Y + R where -|Y/2| <= R < |Y/2|."
+ x y
+ (cond ((positive? y)
+ (floor (+ 1/2 (/ x y))))
+ ((negative? y)
+ (ceiling (+ -1/2 (/ x y))))
+ ((zero? y)
+ (error "division by zero"))
+ (else +nan.0)))
+
+(define-divisions
+ round/
+ "Return Q and R in X = Q*Y + R where Q = round(X/Y)."
+ round-quotient
+ "Return Q in X = Q*Y + R where Q = round(X/Y)."
+ round-remainder
+ "Return R in X = Q*Y + R where Q = round(X/Y)."
+ x y
+ (round (/ x y)))
+
+;;; arithmetic.body.scm ends here
+(export
+ euclidean/
+ euclidean-quotient
+ euclidean-remainder
+ ceiling/
+ ceiling-quotient
+ ceiling-remainder
+ centered/
+ centered-quotient
+ centered-remainder
+ round/
+ round-quotient
+ round-remainder
+ )
+(define-library (r7rs-extras arithmetic)
+ (import (scheme base))
+ (include-library-declarations "arithmetic.exports.sld")
+ (include "arithmetic.body.scm"))
+;;; higher-order.body.scm --- Auxiliary higher-oder procedures
+
+;; Copyright © 2014 Taylan Ulrich Bayırlı/Kammer
+;; Copyright © 2015 Taylan Ulrich Bayırlı/Kammer
+
+;; Author: Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
+;; Keywords: extensions higher-order
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Miscellaneous higher-oder procedures for creating constant functions,
+;; negating functions, etc.
+
+;;; Code:
+
+(define (const value)
+ "Make a nullary procedure always returning VALUE."
+ (lambda () value))
+
+(define (negate proc)
+ "Make a procedure negating the application of PROC to its arguments."
+ (lambda x (not (apply proc x))))
+
+(define (compose proc . rest)
+ "Functional composition; e.g. ((compose x y) a) = (x (y a))."
+ (if (null? rest)
+ proc
+ (let ((rest-proc (apply compose rest)))
+ (lambda x
+ (let-values ((x (apply rest-proc x)))
+ (apply proc x))))))
+
+(define (pipeline proc . rest)
+ "Reverse functional composition; e.g. ((pipeline x y) a) = (y (x a))."
+ (if (null? rest)
+ proc
+ (let ((rest-proc (apply pipeline rest)))
+ (lambda x
+ (let-values ((x (apply proc x)))
+ (apply rest-proc x))))))
+
+(define (identity . x)
+ "Returns values given to it as-is."
+ (apply values x))
+
+(define (and=> value proc)
+ "If VALUE is true, call PROC on it, else return false."
+ (if value (proc value) value))
+
+;;; higher-order.body.scm ends here
+(export
+ const
+ negate
+ compose
+ pipeline
+ identity
+ and=>
+ )
+(define-library (r7rs-extras higher-order)
+ (import (scheme base))
+ (include-library-declarations "higher-order.exports.sld")
+ (include "higher-order.body.scm"))
+;;; io.body.scm --- Input/Output extensions for R7RS
+
+;; Copyright © 2014 Taylan Ulrich Bayırlı/Kammer
+;; Copyright © 2015 Taylan Ulrich Bayırlı/Kammer
+
+;; Author: Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
+;; Keywords: extensions io i/o input output input/output
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; R7RS leaves out some conceivable combinations of:
+;;
+;; [call-]with-(input|output|error)[-(from|to)]-port
+;;
+;; Some of these are nontrivial and annoying to redefine every time one needs
+;; them. Others are actually so trivial that their body could be inlined at any
+;; place of usage, but it's nevertheless distracting having to remember which
+;; ones are or aren't in the base library, so we just define them all.
+
+;;; Code:
+
+(define (call-with-input-string string proc)
+ "Applies PROC to an input port fed with STRING."
+ (call-with-port (open-input-string string) proc))
+
+(define (call-with-output-string proc)
+ "Applies PROC to a port feeding a string which is then returned."
+ (let ((port (open-output-string)))
+ (call-with-port port proc)
+ (get-output-string port)))
+
+(define-syntax with-port
+ (syntax-rules ()
+ ((with-port port-param port thunk closer)
+ (parameterize ((port-param port))
+ (call-with-values thunk
+ (lambda vals
+ (closer port)
+ (apply values vals)))))))
+
+(define (with-input-port port thunk)
+ "Closes PORT after calling THUNK with it as the `current-input-port'."
+ (with-port current-input-port port thunk close-input-port))
+
+(define (with-output-port port thunk)
+ "Closes PORT after calling THUNK with it as the `current-output-port'."
+ (with-port current-output-port port thunk close-output-port))
+
+(define (with-error-port port thunk)
+ "Closes PORT after calling THUNK with it as the `current-error-port'."
+ (with-port current-error-port port thunk close-output-port))
+
+(define (with-input-from-port port thunk)
+ "Calls THUNK with PORT as the `current-input-port'. Doesn't close PORT."
+ (parameterize ((current-input-port port))
+ (thunk)))
+
+(define (with-output-to-port port thunk)
+ "Calls THUNK with PORT as the `current-output-port'. Doesn't close PORT."
+ (parameterize ((current-output-port port))
+ (thunk)))
+
+(define (with-error-to-port port thunk)
+ "Calls THUNK with PORT as the `current-error-port'. Doesn't close PORT."
+ (parameterize ((current-error-port port))
+ (thunk)))
+
+(define (with-error-to-file file thunk)
+ "Calls THUNK with `current-error-port' bound to FILE."
+ (with-error-port (open-output-file file) thunk))
+
+(define (with-input-from-string string thunk)
+ "Calls THUNK with `current-input-port' bound to a port fed with STRING."
+ (with-input-port (open-input-string string) thunk))
+
+(define (with-output-to-string thunk)
+ "Calls THUNK with `current-output-port' bound to a port feeding a string which
+is then returned."
+ (let ((port (open-output-string)))
+ (with-output-port port thunk)
+ (get-output-string port)))
+
+(define (with-error-to-string thunk)
+ "Calls THUNK with `current-error-port' bound to a port feeding a string which
+is then returned."
+ (let ((port (open-output-string)))
+ (with-error-port port thunk)
+ (get-output-string port)))
+
+;;; io.body.scm ends here
+(export
+ call-with-input-string
+ call-with-output-string
+ with-input-port
+ with-output-port
+ with-error-port
+ with-input-from-port
+ with-output-to-port
+ with-error-to-port
+ with-error-to-file
+ with-input-from-string
+ with-output-to-string
+ with-error-to-string
+ )
+(define-library (r7rs-extras io)
+ (import (scheme base)
+ (scheme file))
+ (include-library-declarations "io.exports.sld")
+ (include "io.body.scm"))
+;;; partition.body.scm --- Variable-arity partition procedures
+
+;; Copyright © 2014 Taylan Ulrich Bayırlı/Kammer
+;; Copyright © 2015 Taylan Ulrich Bayırlı/Kammer
+
+;; Author: Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
+;; Keywords: extensions lists partition partitioning
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; `partition' proper is in SRFI-1; we define alternative versions only.
+
+;;; Code:
+
+(define (%partition exclusive? list . procs)
+ (if (null? procs)
+ list
+ (let ((lists (make-list (+ 1 (length procs)) '())))
+ (for-each
+ (lambda (elt)
+ (let loop ((procs procs)
+ (lists lists)
+ (match? #f))
+ (if (null? procs)
+ (when (not match?)
+ (set-car! lists (cons elt (car lists))))
+ (if ((car procs) elt)
+ (begin (set-car! lists (cons elt (car lists)))
+ (when (not exclusive?)
+ (loop (cdr procs) (cdr lists) #t)))
+ (loop (cdr procs) (cdr lists) match?)))))
+ list)
+ (apply values (map reverse lists)))))
+
+(define (partition* list . procs)
+ "Partitions LIST via PROCS, returning PROCS + 1 many lists; the last list
+containing elements that didn't match any procedure. The ordering of each list
+obeys that of LIST. If there are elements matching multiple PROCS, it's
+unspecified in which one of the matching lists they appear."
+ (apply %partition #t list procs))
+
+(define (partition+ list . procs)
+ "This is like the `partition*' procedure, but elements matching multiple
+procedures appear in every corresponding list."
+ (apply %partition #f list procs))
+
+;;; partition.body.scm ends here
+(export
+ partition*
+ partition+
+ )
+(define-library (r7rs-extras partition)
+ (import (scheme base))
+ (include-library-declarations "partition.exports.sld")
+ (include "partition.body.scm"))
+;;; pushpop.body.scm --- push! and pop!
+
+;; Copyright © 2014 Taylan Ulrich Bayırlı/Kammer
+;; Copyright © 2015 Taylan Ulrich Bayırlı/Kammer
+
+;; Author: Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
+;; Keywords: extensions push pop
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Dead simple push! and pop!.
+
+;;; Code:
+
+(define-syntax push!
+ (syntax-rules ()
+ ((push! pair value)
+ (set! pair (cons value pair)))))
+
+(define-syntax pop!
+ (syntax-rules ()
+ ((pop! pair)
+ (let ((value (car pair)))
+ (set! pair (cdr pair))
+ value))))
+
+;;; pushpop.body.scm ends here
+(export
+ push!
+ pop!
+ )
+(define-library (r7rs-extras pushpop)
+ (import (scheme base))
+ (include-library-declarations "pushpop.exports.sld")
+ (include "pushpop.body.scm"))
+;;; generic-ref-set --- Generic accessor and modifier operators.
+
+;; Copyright © 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
+
+;; Permission is hereby granted, free of charge, to any person obtaining
+;; a copy of this software and associated documentation files (the
+;; "Software"), to deal in the Software without restriction, including
+;; without limitation the rights to use, copy, modify, merge, publish,
+;; distribute, sublicense, and/or sell copies of the Software, and to
+;; permit persons to whom the Software is furnished to do so, subject to
+;; the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+;;; Helpers
+
+(define-syntax push!
+ (syntax-rules ()
+ ((_ <list-var> <x>)
+ (set! <list-var> (cons <x> <list-var>)))))
+
+(define (alist->hashtable alist)
+ (let ((table (make-eqv-hashtable 100)))
+ (for-each (lambda (entry)
+ (hashtable-set! table (car entry) (cdr entry)))
+ alist)
+ table))
+
+(define (pair-ref pair key)
+ (cond
+ ((eqv? 'car key)
+ (car pair))
+ ((eqv? 'cdr key)
+ (cdr pair))
+ (else
+ (list-ref pair key))))
+
+(define (pair-set! pair key value)
+ (cond
+ ((eqv? 'car key)
+ (set-car! pair value))
+ ((eqv? 'cdr key)
+ (set-cdr! pair value))
+ (else
+ (list-set! pair key value))))
+
+;;; Record inspection support
+
+(cond-expand
+ ((or (library (srfi 99))
+ (library (rnrs records inspection))
+ (library (r6rs records inspection)))
+ (cond-expand
+ ((not (library (srfi 99)))
+ (define rtd-accessor record-accessor)
+ (define rtd-mutator record-mutator))
+ (else))
+ (define (record-ref record field)
+ (let* ((rtd (record-rtd record))
+ (accessor (rtd-accessor rtd field)))
+ (accessor record)))
+ (define (record-set! record field value)
+ (let* ((rtd (record-rtd record))
+ (mutator (rtd-mutator rtd field)))
+ (mutator record value)))
+ (define record-getter
+ (list (cons record? record-ref)))
+ (define record-setter
+ (list (cons record? record-set!)))
+ (define record-type
+ (list record?)))
+ (else
+ (define record-getter '())
+ (define record-setter '())
+ (define record-type '())))
+
+;;; SRFI-4 support
+
+;;; In some implementations, SRFI-4 vectors are also bytevectors. We accomodate
+;;; for those implementations by using generic bytevector-ref/set! procedures
+;;; which possibly dispatch to an SRFI-4 type's getter/setter, but also
+;;; inserting the SRFI-4 getters/setters into the top-level dispatch tables.
+
+(cond-expand
+ ((library (srfi 4))
+ (define srfi-4-getters
+ (list (cons s8vector? s8vector-ref)
+ (cons u8vector? u8vector-ref)
+ (cons s16vector? s16vector-ref)
+ (cons u16vector? u16vector-ref)
+ (cons s32vector? s32vector-ref)
+ (cons u32vector? u32vector-ref)
+ (cons s64vector? s64vector-ref)
+ (cons u64vector? u64vector-ref)))
+ (define srfi-4-setters
+ (list (cons s8vector? s8vector-set!)
+ (cons u8vector? u8vector-set!)
+ (cons s16vector? s16vector-set!)
+ (cons u16vector? u16vector-set!)
+ (cons s32vector? s32vector-set!)
+ (cons u32vector? u32vector-set!)
+ (cons s64vector? s64vector-set!)
+ (cons u64vector? u64vector-set!)))
+ (define srfi-4-types
+ (list s8vector? u8vector? s16vector? u16vector? s32vector? u32vector?
+ s64vector? u64vector?))
+ (define srfi-4-getters-table (alist->hashtable srfi-4-getters))
+ (define srfi-4-setters-table (alist->hashtable srfi-4-setters))
+ (define (bytevector-ref bytevector index)
+ (let* ((type (find (lambda (pred) (pred bytevector))) srfi-4-types)
+ (getter (if type
+ (ref srfi-4-getters-table type)
+ bytevector-u8-ref)))
+ (getter bytevector index)))
+ (define (bytevector-set! bytevector index value)
+ (let* ((type (find (lambda (pred) (pred bytevector))) srfi-4-types)
+ (setter (if type
+ (ref srfi-4-setters-table type)
+ bytevector-u8-set!)))
+ (setter bytevector index value))))
+ (else
+ (define srfi-4-getters '())
+ (define srfi-4-setters '())
+ (define srfi-4-types '())
+ (define bytevector-ref bytevector-u8-ref)
+ (define bytevector-set! bytevector-u8-set!)))
+
+;;; SRFI-111 boxes support
+
+(cond-expand
+ ((library (srfi 111))
+ (define (box-ref box _field)
+ (unbox box))
+ (define (box-set! box _field value)
+ (set-box! box value))
+ (define box-getter (list (cons box? box-ref)))
+ (define box-setter (list (cons box? box-set!)))
+ (define box-type (list box?)))
+ (else
+ (define box-getter '())
+ (define box-setter '())
+ (define box-type '())))
+
+;;; Main
+
+(define %ref
+ (case-lambda
+ ((object field)
+ (let ((getter (lookup-getter object))
+ (sparse? (sparse-type? object)))
+ (if sparse?
+ (let* ((not-found (cons #f #f))
+ (result (getter object field not-found)))
+ (if (eqv? result not-found)
+ (error "Object has no entry for field." object field)
+ result))
+ (getter object field))))
+ ((object field default)
+ (let ((getter (lookup-getter object)))
+ (getter object field default)))))
+
+(define (%ref* object field . fields)
+ (if (null? fields)
+ (%ref object field)
+ (apply %ref* (%ref object field) fields)))
+
+(define (%set! object field value)
+ (let ((setter (lookup-setter object)))
+ (setter object field value)))
+
+(define ref
+ (getter-with-setter
+ %ref
+ (lambda (object field value)
+ (%set! object field value))))
+
+(define ref*
+ (getter-with-setter
+ %ref*
+ (rec (set!* object field rest0 . rest)
+ (if (null? rest)
+ (%set! object field rest0)
+ (apply set!* (ref object field) rest0 rest)))))
+
+(define ~ ref*)
+
+(define $bracket-apply$ ref*)
+
+(define (lookup-getter object)
+ (or (hashtable-ref getter-table (type-of object) #f)
+ (error "No generic getter for object's type." object)))
+
+(define (lookup-setter object)
+ (or (hashtable-ref setter-table (type-of object) #f)
+ (error "No generic setter for object's type." object)))
+
+(define (sparse-type? object)
+ (memv (type-of object) sparse-types))
+
+(define (type-of object)
+ (find (lambda (pred) (pred object)) type-list))
+
+(define getter-table
+ (alist->hashtable
+ (append
+ (list (cons bytevector? bytevector-ref)
+ (cons hashtable? hashtable-ref)
+ (cons pair? pair-ref)
+ (cons string? string-ref)
+ (cons vector? vector-ref))
+ record-getter
+ srfi-4-getters
+ box-getter)))
+
+(define setter-table
+ (alist->hashtable
+ (append
+ (list (cons bytevector? bytevector-set!)
+ (cons hashtable? hashtable-set!)
+ (cons pair? pair-set!)
+ (cons string? string-set!)
+ (cons vector? vector-set!))
+ record-setter
+ srfi-4-setters
+ box-setter)))
+
+(define sparse-types
+ (list hashtable?))
+
+(define type-list
+ ;; Although the whole SRFI intrinsically neglects performance, we still use
+ ;; the micro-optimization of ordering this list roughly according to most
+ ;; likely match.
+ (append
+ (list hashtable? vector? pair? bytevector? string?)
+ srfi-4-types
+ box-type
+ ;; The record type must be placed last so specific record types (e.g. box)
+ ;; take precedence.
+ record-type
+ ;; Place those types we don't support really last.
+ (list boolean? char? eof-object? null? number? port? procedure? symbol?)))
+
+(define (register-getter-with-setter! type getter sparse?)
+ (push! type-list type)
+ (set! (~ getter-table type) getter)
+ (set! (~ setter-table type) (setter getter))
+ (when sparse?
+ (push! sparse-types type)))
+
+(cond-expand
+ ((not (or (library (srfi 99))
+ (library (rnrs records inspection))
+ (library (r6rs records inspection))))
+ (define-syntax define-record-type
+ (syntax-rules ()
+ ((_ <name> <constructor> <pred> <field> ...)
+ (begin
+ (%define-record-type <name> <constructor> <pred> <field> ...)
+ ;; Throw-away definition to not disturb an internal definitions
+ ;; sequence.
+ (define __throwaway
+ (begin
+ (register-getter-with-setter!
+ <pred>
+ (getter-with-setter (record-getter <field> ...)
+ (record-setter <field> ...))
+ #f)
+ ;; Return the implementation's preferred "unspecified" value.
+ (if #f #f)))))))
+
+ (define-syntax record-getter
+ (syntax-rules ()
+ ((_ (<field> <getter> . <rest>) ...)
+ (let ((getters (alist->hashtable (list (cons '<field> <getter>) ...))))
+ (lambda (record field)
+ (let ((getter (or (ref getters field #f)
+ (error "No such field of record." record field))))
+ (getter record)))))))
+
+ (define-syntax record-setter
+ (syntax-rules ()
+ ((_ . <rest>)
+ (%record-setter () . <rest>))))
+
+ (define-syntax %record-setter
+ (syntax-rules ()
+ ((_ <setters> (<field> <getter>) . <rest>)
+ (%record-setter <setters> . <rest>))
+ ((_ <setters> (<field> <getter> <setter>) . <rest>)
+ (%record-setter ((<field> <setter>) . <setters>) . <rest>))
+ ((_ ((<field> <setter>) ...))
+ (let ((setters (alist->hashtable (list (cons '<field> <setter>) ...))))
+ (lambda (record field value)
+ (let ((setter (or (ref setters field #f)
+ (error "No such assignable field of record."
+ record field))))
+ (setter record value)))))))))
+
+;;; generic-ref-set.body.scm ends here
+(define-library (srfi 123)
+ (export
+ ref ref* ~ register-getter-with-setter!
+ $bracket-apply$
+ set! setter getter-with-setter)
+ (import
+ (except (scheme base) set! define-record-type)
+ (scheme case-lambda)
+ (r6rs hashtables)
+ (srfi 1)
+ (srfi 17)
+ (srfi 31))
+ (cond-expand
+ ;; Favor SRFI-99.
+ ((library (srfi 99))
+ (import (srfi 99)))
+ ;; We assume that if there's the inspection library, there's also the
+ ;; syntactic and procedural libraries.
+ ((library (rnrs records inspection))
+ (import (rnrs records syntactic))
+ (import (rnrs records procedural))
+ (import (rnrs records inspection)))
+ ((library (r6rs records inspection))
+ (import (r6rs records syntactic))
+ (import (r6rs records procedural))
+ (import (r6rs records inspection)))
+ (else
+ (import (rename (only (scheme base) define-record-type)
+ (define-record-type %define-record-type)))
+ (export define-record-type)))
+ (cond-expand
+ ((library (srfi 4))
+ (import (srfi 4)))
+ (else))
+ (cond-expand
+ ((library (srfi 111))
+ (import (srfi 111)))
+ (else))
+ (include "123.body.scm"))
+;;; generic-ref-set --- Generic accessor and modifier operators.
+
+;; Copyright © 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
+
+;; Permission is hereby granted, free of charge, to any person obtaining
+;; a copy of this software and associated documentation files (the
+;; "Software"), to deal in the Software without restriction, including
+;; without limitation the rights to use, copy, modify, merge, publish,
+;; distribute, sublicense, and/or sell copies of the Software, and to
+;; permit persons to whom the Software is furnished to do so, subject to
+;; the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+(define-library (tests srfi-123)
+ (export run-tests)
+ (import (except (scheme base) define-record-type set!)
+ (r6rs hashtables)
+ (srfi 64)
+ (srfi 123))
+ (cond-expand
+ ((library (srfi 99))
+ (import (srfi 99)))
+ ((library (rnrs records inspection))
+ (import (rnrs records syntactic))
+ (import (rnrs records procedural)))
+ (import (rnrs records inspection))
+ ((library (r6rs records inspection))
+ (import (r6rs records syntactic))
+ (import (r6rs records procedural)))
+ (import (r6rs records inspection))
+ (else))
+ (cond-expand
+ ((library (srfi 4))
+ (import (srfi 4)))
+ (else
+ (begin
+ ;; Stub to silence compilers.
+ (define s16vector #f))))
+ (cond-expand
+ ((library (srfi 111))
+ (import (srfi 111)))
+ (else
+ (begin
+ ;; Stub to silence compilers.
+ (define box #f))))
+ (begin
+
+ (define-record-type <foo> (make-foo a b) foo?
+ (a foo-a set-foo-a!)
+ (b foo-b))
+
+ ;; The SRFI-99 sample implementation contains a bug where immutable fields
+ ;; are nevertheless mutable through the procedural API. Test whether we are
+ ;; on that implementation.
+ (cond-expand
+ ((library (srfi 99))
+ (define using-broken-srfi99
+ (guard (err (else #f))
+ (rtd-mutator <foo> 'b))))
+ (else
+ (define using-broken-srfi99 #f)))
+
+ (define (run-tests)
+ (let ((runner (test-runner-create)))
+ (parameterize ((test-runner-current runner))
+ (test-begin "SRFI-123")
+
+ (test-begin "ref")
+ (test-assert "bytevector" (= 1 (ref (bytevector 0 1 2) 1)))
+ (test-assert "hashtable" (let ((table (make-eqv-hashtable)))
+ (hashtable-set! table 'foo 0)
+ (= 0 (ref table 'foo))))
+ (test-assert "hashtable default" (let ((table (make-eqv-hashtable)))
+ (= 1 (ref table 0 1))))
+ (test-assert "pair" (= 1 (ref (cons 0 1) 'cdr)))
+ (test-assert "list" (= 1 (ref (list 0 1 2) 1)))
+ (test-assert "string" (char=? #\b (ref "abc" 1)))
+ (test-assert "vector" (= 1 (ref (vector 0 1 2) 1)))
+ (test-assert "record" (= 1 (ref (make-foo 0 1) 'b)))
+ (cond-expand
+ ((library (srfi 4)) (values))
+ (else (test-skip 1)))
+ (test-assert "srfi-4" (= 1 (ref (s16vector 0 1 2) 1)))
+ (cond-expand
+ ((library (srfi 111)) (values))
+ (else (test-skip 1)))
+ (test-assert "srfi-111" (= 1 (ref (box 1) '*)))
+ (test-end "ref")
+
+ (test-assert "ref*" (= 1 (ref* '(_ #(_ (0 . 1) _) _) 1 1 'cdr)))
+
+ (test-begin "ref setter")
+ (test-assert "bytevector" (let ((bv (bytevector 0 1 2)))
+ (set! (ref bv 1) 3)
+ (= 3 (ref bv 1))))
+ (test-assert "hashtable" (let ((ht (make-eqv-hashtable)))
+ (set! (ref ht 'foo) 0)
+ (= 0 (ref ht 'foo))))
+ (test-assert "pair" (let ((p (cons 0 1)))
+ (set! (ref p 'cdr) 2)
+ (= 2 (ref p 'cdr))))
+ (test-assert "list" (let ((l (list 0 1 2)))
+ (set! (ref l 1) 3)
+ (= 3 (ref l 1))))
+ (test-assert "string" (let ((s (string #\a #\b #\c)))
+ (set! (ref s 1) #\d)
+ (char=? #\d (ref s 1))))
+ (test-assert "vector" (let ((v (vector 0 1 2)))
+ (set! (ref v 1) 3)
+ (= 3 (ref v 1))))
+ (test-assert "record" (let ((r (make-foo 0 1)))
+ (set! (ref r 'a) 2)
+ (= 2 (ref r 'a))))
+ (when using-broken-srfi99
+ (test-expect-fail 1))
+ (test-assert "bad record assignment"
+ (not (guard (err (else #f)) (set! (ref (make-foo 0 1) 'b) 2) #t)))
+ (cond-expand
+ ((library (srfi 4)) (values))
+ (else (test-skip 1)))
+ (test-assert "srfi-4" (let ((s16v (s16vector 0 1 2)))
+ (set! (ref s16v 1) 3)
+ (= 3 (ref s16v 1))))
+ (cond-expand
+ ((library (srfi 111)) (values))
+ (else (test-skip 1)))
+ (test-assert "srfi-111" (let ((b (box 0)))
+ (set! (ref b '*) 1)
+ (= 1 (ref b '*))))
+ (test-end "ref setter")
+
+ (test-assert "ref* setter"
+ (let ((obj (list '_ (vector '_ (cons 0 1) '_) '_)))
+ (set! (ref* obj 1 1 'cdr) 2)
+ (= 2 (ref* obj 1 1 'cdr))))
+
+ (test-end "SRFI-123")
+ (and (= 0 (test-runner-xpass-count runner))
+ (= 0 (test-runner-fail-count runner))))))
+
+ ))
+(import (scheme base)
+ (scheme eval)
+ (scheme process-context))
+
+(if (eval '(run-tests) (environment '(tests srfi-123)))
+ (exit 0)
+ (exit 1))
+;;; Copyright 2015 William D Clinger.
+;;;
+;;; Permission to copy this software, in whole or in part, to use this
+;;; software for any lawful purpose, and to redistribute this software
+;;; is granted subject to the restriction that all copies made of this
+;;; software must include this copyright and permission notice in full.
+;;;
+;;; I also request that you send me a copy of any improvements that you
+;;; make to this software so that they may be incorporated within it to
+;;; the benefit of the Scheme community.
+;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; This R7RS code implements (rnrs hashtables) on top of SRFI 69.
+
+;;; Private stuff.
+
+;;; Although SRFI 69 is mostly written as though hash functions take
+;;; just one argument, its reference implementation routinely passes
+;;; a second argument to hash functions, and that arguably incorrect
+;;; behavior has undoubtedly found its way into many implementations
+;;; of SRFI 69.
+;;;
+;;; A unary hash function passed to R6RS make-hashtable is therefore
+;;; unlikely to work when passed to SRFI 69 make-hash-table. We need
+;;; to convert the unary hash function so it will accept a second
+;;; optional argument, and we also need to arrange for the original
+;;; unary hash function to be returned by hashtable-hash-function.
+;;;
+;;; We'd like to accomplish this while preserving interoperability
+;;; between R6RS hashtables and SRFI 69 hash tables. That argues
+;;; against implementing R6RS hashtables by records that encapsulate
+;;; a SRFI 69 hash table, which would otherwise be the easy way to
+;;; go about this.
+;;;
+;;; This association list implements a bidirectional mapping between
+;;; one-argument hash functions of R6RS and their representations as
+;;; two-argument hash functions that will work with SRFI 69.
+
+(define table-of-hash-functions '())
+
+;;; Given a unary hash function, returns a hash function that will
+;;; be acceptable to SRFI 69.
+
+(define (make-srfi-69-hash-function hash-function)
+ (lambda (x . rest)
+ (if (null? rest)
+ (hash-function x)
+ (modulo (hash-function x) (car rest)))))
+
+(define (r6rs->srfi69 hash-function)
+ (let ((probe (assoc hash-function table-of-hash-functions)))
+ (if probe
+ (cdr probe)
+ (let ((hasher (make-srfi-69-hash-function hash-function)))
+ (set! table-of-hash-functions
+ (cons (cons hash-function hasher)
+ table-of-hash-functions))
+ hasher))))
+
+(define (srfi69->r6rs hasher)
+ (define (loop table)
+ (cond ((null? table)
+ hasher)
+ ((equal? hasher (cdr (car table)))
+ (car (car table)))
+ (else
+ (loop (cdr table)))))
+ (loop table-of-hash-functions))
+
+;;; SRFI 69 doesn't define a hash function that's suitable for use
+;;; with the eqv? predicate, and we need one for make-eqv-hashtable.
+;;;
+;;; The R7RS eqv? predicate behaves the same as eq? for these types:
+;;;
+;;; symbols
+;;; booleans
+;;; empty list
+;;; pairs
+;;; records
+;;; non-empty strings
+;;; non-empty vectors
+;;; non-empty bytevectors
+;;;
+;;; eqv? might behave differently when its arguments are:
+;;;
+;;; procedures that behave the same but have equal location tags
+;;; numbers
+;;; characters
+;;; empty strings
+;;; empty vectors
+;;; empty bytevectors
+;;;
+;;; If eqv? and eq? behave differently on two arguments x and y,
+;;; eqv? returns true and eq? returns false.
+;;;
+;;; FIXME: There is no portable way to define a good hash function
+;;; that's compatible with eqv? on procedures and also runs in
+;;; constant time. This one is compatible with eqv? and runs in
+;;; constant time (on procedures), but isn't any good.
+
+;;; The main thing these numerical constants have in common is that
+;;; they're positive and fit in 24 bits.
+
+(define hash:procedure 9445898)
+(define hash:character 13048478)
+(define hash:empty-string 14079236)
+(define hash:empty-vector 1288342)
+(define hash:empty-bytevector 11753202)
+(define hash:inexact 1134643)
+(define hash:infinity+ 2725891)
+(define hash:infinity- 5984233)
+(define hash:nan 7537847)
+(define hash:complex 9999245)
+
+(define (hash-for-eqv x)
+ (cond ((procedure? x)
+ hash:procedure)
+ ((number? x)
+ (cond ((exact-integer? x)
+ x)
+ ((not (real? x))
+ (+ hash:complex (complex-hash x)))
+ ((exact? x)
+ (+ (numerator x) (denominator x)))
+ (else
+ (+ hash:inexact (inexact-hash x)))))
+ ((char? x)
+ (+ hash:character (char->integer x)))
+ ((eqv? x "")
+ hash:empty-string)
+ ((eqv? x '#())
+ hash:empty-vector)
+ ((eqv? x '#u8())
+ hash:empty-bytevector)
+ (else
+ (hash-by-identity x))))
+
+;;; The R6RS distinguishes mutable from immutable hashtables,
+;;; so we have to keep track of that somehow. Here we remember
+;;; all of the immutable hashtables within a SRFI 69 hash-table.
+;;;
+;;; FIXME: That means the storage occupied by an immutable
+;;; hashtable won't be reclaimed if it becomes otherwise
+;;; inaccessible.
+
+(define immutable-hashtables
+ (make-hash-table eqv? (r6rs->srfi69 hash-table-size)))
+
+(define (complain-if-immutable ht complainant)
+ (if (hash-table-ref/default immutable-hashtables ht #f)
+ (error (string-append (symbol->string complainant)
+ ": hashtable is immutable")
+ ht)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;
+;;; Exported procedures.
+;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; The R6RS make-eq-hashtable procedure is normally called with
+;;; no arguments, but an optional argument specifies the initial
+;;; capacity of the table. That optional argument, if present,
+;;; will be ignored by this implementation because it has no
+;;; counterpart in SRFI 69.
+
+(define (make-eq-hashtable . rest)
+ (make-hash-table eq? hash-by-identity))
+
+(define (make-eqv-hashtable . rest)
+ (make-hash-table eqv? (r6rs->srfi69 hash-for-eqv)))
+
+;;; As with make-eq-hashtable and make-eqv-hashtable, the optional
+;;; initial capacity will be ignored.
+
+(define (make-hashtable hash-function equiv . rest)
+ (make-hash-table equiv (r6rs->srfi69 hash-function)))
+
+(define (hashtable? x)
+ (hash-table? x))
+
+(define (hashtable-size ht)
+ (hash-table-size ht))
+
+(define (hashtable-ref ht key default)
+ (hash-table-ref/default ht key default))
+
+(define (hashtable-set! ht key obj)
+ (complain-if-immutable ht 'hashtable-set!)
+ (hash-table-set! ht key obj))
+
+(define (hashtable-delete! ht key)
+ (complain-if-immutable ht 'hashtable-delete!)
+ (hash-table-delete! ht key))
+
+(define (hashtable-contains? ht key)
+ (hash-table-exists? ht key))
+
+(define (hashtable-update! ht key proc default)
+ (complain-if-immutable ht 'hashtable-update!)
+ (hash-table-set! ht
+ key
+ (proc (hash-table-ref/default ht key default))))
+
+;;; By default, hashtable-copy returns an immutable hashtable.
+;;; The copy is mutable only if a second argument is passed and
+;;; that second argument is true.
+
+(define (hashtable-copy ht . rest)
+ (let ((mutable? (and (pair? rest) (car rest)))
+ (the-copy (hash-table-copy ht)))
+ (if (not mutable?)
+ (hash-table-set! immutable-hashtables the-copy #t))
+ the-copy))
+
+;;; As usual, the optional "initial" capacity is ignored.
+
+(define (hashtable-clear! ht . rest)
+ (complain-if-immutable ht 'hashtable-update!)
+ (hash-table-walk ht
+ (lambda (key value)
+ (hash-table-delete! ht key))))
+
+(define (hashtable-keys ht)
+ (list->vector (hash-table-keys ht)))
+
+(define (hashtable-entries ht)
+ (let* ((keys (hashtable-keys ht))
+ (vals (vector-map (lambda (key)
+ (hash-table-ref ht key))
+ keys)))
+ (values keys vals)))
+
+(define (hashtable-equivalence-function ht)
+ (hash-table-equivalence-function ht))
+
+(define (hashtable-hash-function ht)
+ (srfi69->r6rs (hash-table-hash-function ht)))
+
+(define (hashtable-mutable? ht)
+ (not (hash-table-ref/default immutable-hashtables ht #f)))
+
+(define (equal-hash obj)
+ (hash obj))
+
+;;; string-hash is exported by SRFI 69.
+;;; string-ci-hash is exported by SRFI 69.
+
+(define (r6rs:symbol-hash sym)
+ (hash-by-identity sym))
+
+;;; Reference implementation of SRFI 69, from
+;;; http://srfi.schemers.org/srfi-69/srfi-69.html
+
+;;; Copyright © Panu Kalliokoski (2005). All Rights Reserved.
+;;; Permission is hereby granted, free of charge, to any person
+;;; obtaining a copy of this software and associated documentation
+;;; files (the "Software"), to deal in the Software without
+;;; restriction, including without limitation the rights to use,
+;;; copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom
+;;; the Software is furnished to do so, subject to the following
+;;; conditions:
+;;;
+;;; The above copyright notice and this permission notice shall
+;;; be included in all copies or substantial portions of the Software.
+;;;
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+;;; KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+;;; WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+;;; PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+;;; OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+;;; OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+;;; OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+;;; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+;;; Modification history:
+;;;
+;;; In May 2015, William D Clinger modified this code for use in
+;;; R7RS systems, mainly so it could be used as a last resort in
+;;; the (r6rs hashtables) approximation to (rnrs hashtables).
+;;;
+;;; string-ci-hash was changed to use R7RS string-foldcase
+;;;
+;;; string-hash, symbol-hash, and %string-hash were changed
+;;; to eliminate a now-useless procedure call for each character
+;;;
+;;; whitespace was adjusted because it got messed up during
+;;; conversion from HTML to Scheme code
+
+(define *default-bound* (- (expt 2 29) 3))
+
+(define (%string-hash s bound)
+ (let ((hash 31)
+ (len (string-length s)))
+ (do ((index 0 (+ index 1)))
+ ((>= index len) (modulo hash bound))
+ (set! hash (modulo (+ (* 37 hash)
+ (char->integer (string-ref s index)))
+ *default-bound*)))))
+
+(define (string-hash s . maybe-bound)
+ (let ((bound (if (null? maybe-bound) *default-bound* (car maybe-bound))))
+ (%string-hash s bound)))
+
+(define (string-ci-hash s . maybe-bound)
+ (let ((bound (if (null? maybe-bound) *default-bound* (car maybe-bound))))
+ (%string-hash (string-foldcase s) bound)))
+
+(define (symbol-hash s . maybe-bound)
+ (let ((bound (if (null? maybe-bound) *default-bound* (car maybe-bound))))
+ (%string-hash (symbol->string s) bound)))
+
+(define (hash obj . maybe-bound)
+ (let ((bound (if (null? maybe-bound) *default-bound* (car maybe-bound))))
+ (cond ((integer? obj) (modulo obj bound))
+ ((string? obj) (string-hash obj bound))
+ ((symbol? obj) (symbol-hash obj bound))
+ ((real? obj) (modulo (+ (numerator obj) (denominator obj)) bound))
+ ((number? obj)
+ (modulo (+ (hash (real-part obj)) (* 3 (hash (imag-part obj))))
+ bound))
+ ((char? obj) (modulo (char->integer obj) bound))
+ ((vector? obj) (vector-hash obj bound))
+ ((pair? obj) (modulo (+ (hash (car obj)) (* 3 (hash (cdr obj))))
+ bound))
+ ((null? obj) 0)
+ ((not obj) 0)
+ ((procedure? obj) (error "hash: procedures cannot be hashed" obj))
+ (else 1))))
+
+(define hash-by-identity hash)
+
+(define (vector-hash v bound)
+ (let ((hashvalue 571)
+ (len (vector-length v)))
+ (do ((index 0 (+ index 1)))
+ ((>= index len) (modulo hashvalue bound))
+ (set! hashvalue (modulo (+ (* 257 hashvalue) (hash (vector-ref v index)))
+ *default-bound*)))))
+
+(define %make-hash-node cons)
+(define %hash-node-set-value! set-cdr!)
+(define %hash-node-key car)
+(define %hash-node-value cdr)
+
+(define-record-type <srfi-hash-table>
+ (%make-hash-table size hash compare associate entries)
+ hash-table?
+ (size hash-table-size hash-table-set-size!)
+ (hash hash-table-hash-function)
+ (compare hash-table-equivalence-function)
+ (associate hash-table-association-function)
+ (entries hash-table-entries hash-table-set-entries!))
+
+(define *default-table-size* 64)
+
+(define (appropriate-hash-function-for comparison)
+ (or (and (eq? comparison eq?) hash-by-identity)
+ (and (eq? comparison string=?) string-hash)
+ (and (eq? comparison string-ci=?) string-ci-hash)
+ hash))
+
+(define (make-hash-table . args)
+ (let* ((comparison (if (null? args) equal? (car args)))
+ (hash
+ (if (or (null? args) (null? (cdr args)))
+ (appropriate-hash-function-for comparison) (cadr args)))
+ (size
+ (if (or (null? args) (null? (cdr args)) (null? (cddr args)))
+ *default-table-size* (caddr args)))
+ (association
+ (or (and (eq? comparison eq?) assq)
+ (and (eq? comparison eqv?) assv)
+ (and (eq? comparison equal?) assoc)
+ (letrec
+ ((associate
+ (lambda (val alist)
+ (cond ((null? alist) #f)
+ ((comparison val (caar alist)) (car alist))
+ (else (associate val (cdr alist)))))))
+ associate))))
+ (%make-hash-table 0 hash comparison association (make-vector size '()))))
+
+(define (make-hash-table-maker comp hash)
+ (lambda args (apply make-hash-table (cons comp (cons hash args)))))
+(define make-symbol-hash-table
+ (make-hash-table-maker eq? symbol-hash))
+(define make-string-hash-table
+ (make-hash-table-maker string=? string-hash))
+(define make-string-ci-hash-table
+ (make-hash-table-maker string-ci=? string-ci-hash))
+(define make-integer-hash-table
+ (make-hash-table-maker = modulo))
+
+(define (%hash-table-hash hash-table key)
+ ((hash-table-hash-function hash-table)
+ key (vector-length (hash-table-entries hash-table))))
+
+(define (%hash-table-find entries associate hash key)
+ (associate key (vector-ref entries hash)))
+
+(define (%hash-table-add! entries hash key value)
+ (vector-set! entries hash
+ (cons (%make-hash-node key value)
+ (vector-ref entries hash))))
+
+(define (%hash-table-delete! entries compare hash key)
+ (let ((entrylist (vector-ref entries hash)))
+ (cond ((null? entrylist) #f)
+ ((compare key (caar entrylist))
+ (vector-set! entries hash (cdr entrylist)) #t)
+ (else
+ (let loop ((current (cdr entrylist)) (previous entrylist))
+ (cond ((null? current) #f)
+ ((compare key (caar current))
+ (set-cdr! previous (cdr current)) #t)
+ (else (loop (cdr current) current))))))))
+
+(define (%hash-table-walk proc entries)
+ (do ((index (- (vector-length entries) 1) (- index 1)))
+ ((< index 0)) (for-each proc (vector-ref entries index))))
+
+(define (%hash-table-maybe-resize! hash-table)
+ (let* ((old-entries (hash-table-entries hash-table))
+ (hash-length (vector-length old-entries)))
+ (if (> (hash-table-size hash-table) hash-length)
+ (let* ((new-length (* 2 hash-length))
+ (new-entries (make-vector new-length '()))
+ (hash (hash-table-hash-function hash-table)))
+ (%hash-table-walk
+ (lambda (node)
+ (%hash-table-add! new-entries
+ (hash (%hash-node-key node) new-length)
+ (%hash-node-key node) (%hash-node-value node)))
+ old-entries)
+ (hash-table-set-entries! hash-table new-entries)))))
+
+(define (hash-table-ref hash-table key . maybe-default)
+ (cond ((%hash-table-find (hash-table-entries hash-table)
+ (hash-table-association-function hash-table)
+ (%hash-table-hash hash-table key) key)
+ => %hash-node-value)
+ ((null? maybe-default)
+ (error "hash-table-ref: no value associated with" key))
+ (else ((car maybe-default)))))
+
+(define (hash-table-ref/default hash-table key default)
+ (hash-table-ref hash-table key (lambda () default)))
+
+(define (hash-table-set! hash-table key value)
+ (let ((hash (%hash-table-hash hash-table key))
+ (entries (hash-table-entries hash-table)))
+ (cond ((%hash-table-find entries
+ (hash-table-association-function hash-table)
+ hash key)
+ => (lambda (node) (%hash-node-set-value! node value)))
+ (else (%hash-table-add! entries hash key value)
+ (hash-table-set-size! hash-table
+ (+ 1 (hash-table-size hash-table)))
+ (%hash-table-maybe-resize! hash-table)))))
+
+(define (hash-table-update! hash-table key function . maybe-default)
+ (let ((hash (%hash-table-hash hash-table key))
+ (entries (hash-table-entries hash-table)))
+ (cond ((%hash-table-find entries
+ (hash-table-association-function hash-table)
+ hash key)
+ => (lambda (node)
+ (%hash-node-set-value!
+ node (function (%hash-node-value node)))))
+ ((null? maybe-default)
+ (error "hash-table-update!: no value exists for key" key))
+ (else (%hash-table-add! entries hash key
+ (function ((car maybe-default))))
+ (hash-table-set-size! hash-table
+ (+ 1 (hash-table-size hash-table)))
+ (%hash-table-maybe-resize! hash-table)))))
+
+(define (hash-table-update!/default hash-table key function default)
+ (hash-table-update! hash-table key function (lambda () default)))
+
+(define (hash-table-delete! hash-table key)
+ (if (%hash-table-delete! (hash-table-entries hash-table)
+ (hash-table-equivalence-function hash-table)
+ (%hash-table-hash hash-table key) key)
+ (hash-table-set-size! hash-table (- (hash-table-size hash-table) 1))))
+
+(define (hash-table-exists? hash-table key)
+ (and (%hash-table-find (hash-table-entries hash-table)
+ (hash-table-association-function hash-table)
+ (%hash-table-hash hash-table key) key) #t))
+
+(define (hash-table-walk hash-table proc)
+ (%hash-table-walk
+ (lambda (node) (proc (%hash-node-key node) (%hash-node-value node)))
+ (hash-table-entries hash-table)))
+
+(define (hash-table-fold hash-table f acc)
+ (hash-table-walk hash-table
+ (lambda (key value) (set! acc (f key value acc))))
+ acc)
+
+(define (alist->hash-table alist . args)
+ (let* ((comparison (if (null? args) equal? (car args)))
+ (hash
+ (if (or (null? args) (null? (cdr args)))
+ (appropriate-hash-function-for comparison) (cadr args)))
+ (size
+ (if (or (null? args) (null? (cdr args)) (null? (cddr args)))
+ (max *default-table-size* (* 2 (length alist))) (caddr args)))
+ (hash-table (make-hash-table comparison hash size)))
+ (for-each
+ (lambda (elem)
+ (hash-table-update!/default
+ hash-table (car elem) (lambda (x) x) (cdr elem)))
+ alist)
+ hash-table))
+
+(define (hash-table->alist hash-table)
+ (hash-table-fold hash-table
+ (lambda (key val acc) (cons (cons key val) acc)) '()))
+
+(define (hash-table-copy hash-table)
+ (let ((new (make-hash-table (hash-table-equivalence-function hash-table)
+ (hash-table-hash-function hash-table)
+ (max *default-table-size*
+ (* 2 (hash-table-size hash-table))))))
+ (hash-table-walk hash-table
+ (lambda (key value) (hash-table-set! new key value)))
+ new))
+
+(define (hash-table-merge! hash-table1 hash-table2)
+ (hash-table-walk
+ hash-table2
+ (lambda (key value) (hash-table-set! hash-table1 key value)))
+ hash-table1)
+
+(define (hash-table-keys hash-table)
+ (hash-table-fold hash-table (lambda (key val acc) (cons key acc)) '()))
+
+(define (hash-table-values hash-table)
+ (hash-table-fold hash-table (lambda (key val acc) (cons val acc)) '()))
+
+; eof
+;; Copyright 1991, 1994, 1998 William D Clinger
+;; Copyright 1998 Lars T Hansen
+;; Copyright 1984 - 1993 Lightship Software, Incorporated
+
+;; Permission to copy this software, in whole or in part, to use this
+;; software for any lawful purpose, and to redistribute this software
+;; is granted subject to the following restriction: Any publication
+;; or redistribution of this software, whether on its own or
+;; incorporated into other software, must bear the above copyright
+;; notices and the following legend:
+
+;; The Twobit compiler and the Larceny runtime system were
+;; developed by William Clinger and Lars Hansen with the
+;; assistance of Lightship Software and the College of Computer
+;; Science of Northeastern University. This acknowledges that
+;; Clinger et al remain the sole copyright holders to Twobit
+;; and Larceny and that no rights pursuant to that status are
+;; waived or conveyed.
+
+;; Twobit and Larceny are provided as is. The user specifically
+;; acknowledges that Northeastern University, William Clinger, Lars
+;; Hansen, and Lightship Software have not made any representations
+;; or warranty with regard to performance of Twobit and Larceny,
+;; their merchantability, or fitness for a particular purpose. Users
+;; further acknowledge that they have had the opportunity to inspect
+;; Twobit and Larceny and will hold harmless Northeastern University,
+;; William Clinger, Lars Hansen, and Lightship Software from any cost,
+;; liability, or expense arising from, or in any way related to the
+;; use of this software.
+
+(define-library (r6rs hashtables)
+
+ (export
+
+ make-eq-hashtable
+ make-eqv-hashtable
+ make-hashtable
+ hashtable?
+ hashtable-size
+ hashtable-ref
+ hashtable-set!
+ hashtable-delete!
+ hashtable-contains?
+ hashtable-update!
+ hashtable-copy
+ hashtable-clear!
+ hashtable-keys
+ hashtable-entries
+ hashtable-equivalence-function
+ hashtable-hash-function
+ hashtable-mutable?
+ equal-hash
+ string-hash
+ string-ci-hash
+ (rename r6rs:symbol-hash symbol-hash) ; see explanation below
+ )
+
+ (import (scheme base)
+ (scheme cxr))
+
+ ;; Hashing on inexact and complex numbers depends on whether the
+ ;; (scheme inexact) and (scheme complex) libraries are available.
+
+ (cond-expand
+
+ ((library (rnrs hashtables))) ; nothing to do
+
+ ((library (scheme inexact))
+ (import (scheme inexact))
+ (begin
+ (define (inexact-hash x)
+ (cond ((finite? x)
+ (hash-for-eqv (exact x)))
+ ((infinite? x)
+ (if (> x 0.0)
+ hash:infinity+
+ hash:infinity-))
+ (else
+ hash:nan)))))
+
+ (else
+ (begin
+ (define (inexact-hash x) 0))))
+
+ (cond-expand
+
+ ((and (library (rnrs hashtables))
+ (not (library (r6rs no-rnrs))))
+ ;; nothing to do
+ )
+
+ ((library (scheme complex))
+ (import (scheme complex))
+ (begin
+ (define (complex-hash z)
+ (+ (hash-for-eqv (real-part z))
+ (hash-for-eqv (imag-part z))))))
+
+ (else
+ (begin
+ (define (complex-hash z) 0))))
+
+ ;; If the (rnrs hashtables) library is available, import it.
+ ;; Otherwise use SRFI 69 if it's available.
+ ;; If SRFI 69 isn't available, use its reference implementation.
+ ;;
+ ;; The (r6rs hashtables) library must export symbol-hash, which
+ ;; has no equivalent among the procedures specified by SRFI 69.
+ ;; The SRFI 69 reference implementation does define symbol-hash,
+ ;; however, which has led to the current situation in which some
+ ;; implementations of (srfi 69) export symbol-hash but others
+ ;; don't. The R7RS says it's an error to import symbol-hash
+ ;; more than once with different bindings, or to redefine it
+ ;; if it's been imported, so this (r6rs hashtables) library
+ ;; defines r6rs:symbol-hash and renames it to symbol-hash only
+ ;; when it's exported.
+
+ (cond-expand
+
+ ((and (library (rnrs hashtables))
+ (not (library (r6rs no-rnrs))))
+ (import (rnrs hashtables))
+ (begin (define r6rs:symbol-hash symbol-hash)))
+
+ ((library (srfi 69 basic-hash-tables))
+ (import (srfi 69 basic-hash-tables))
+ (include "hashtables.atop69.scm"))
+
+ ((library (srfi 69))
+ (import (srfi 69))
+ (include "hashtables.atop69.scm"))
+
+ ((library (srfi 69 basic-hash-tables))
+ (import (srfi 69 basic-hash-tables))
+ (include "hashtables.atop69.scm"))
+
+ ((library (srfi 69))
+ (import (srfi 69))
+ (include "hashtables.atop69.scm"))
+
+ ((library (scheme char))
+ (import (scheme char))
+ (include "hashtables.body69.scm")
+ (include "hashtables.atop69.scm"))
+
+ (else
+ (begin (define (string-foldcase s) s)
+ (define (string-ci=? s1 s2)
+ (string=? s1 s2)))
+ (include "hashtables.body69.scm")
+ (include "hashtables.atop69.scm")))
+
+ )
+(define make-eq-hashtable
+ (case-lambda
+ (() (make-eq-hashtable #f #f))
+ ((capacity) (make-eq-hashtable capacity #f))
+ ((capacity weakness)
+ (when weakness
+ (error "No weak or ephemeral hashtables supported."))
+ (if capacity
+ (rnrs-make-eq-hashtable capacity)
+ (rnrs-make-eq-hashtable)))))
+
+(define make-eqv-hashtable
+ (case-lambda
+ (() (make-eqv-hashtable #f #f))
+ ((capacity) (make-eqv-hashtable capacity #f))
+ ((capacity weakness)
+ (when weakness
+ (error "No weak or ephemeral hashtables supported."))
+ (if capacity
+ (rnrs-make-eqv-hashtable capacity)
+ (rnrs-make-eqv-hashtable)))))
+
+(define make-hashtable
+ (case-lambda
+ ((hash equiv) (make-hashtable hash equiv #f #f))
+ ((hash equiv capacity) (make-hashtable hash equiv capacity #f))
+ ((hash equiv capacity weakness)
+ (cond
+ ((and (not hash) (eq? equiv eq?))
+ (make-eq-hashtable capacity weakness))
+ ((and (not hash) (eq? equiv eqv?))
+ (make-eqv-hashtable capacity weakness))
+ (else
+ (when weakness
+ (error "No weak or ephemeral hashtables supported."))
+ (let ((hash (if (pair? hash)
+ (car hash)
+ hash)))
+ (if capacity
+ (rnrs-make-hashtable hash equiv capacity)
+ (rnrs-make-hashtable hash equiv))))))))
+
+(define (alist->eq-hashtable . args)
+ (apply alist->hashtable #f eq? args))
+
+(define (alist->eqv-hashtable . args)
+ (apply alist->hashtable #f eqv? args))
+
+(define alist->hashtable
+ (case-lambda
+ ((hash equiv alist)
+ (alist->hashtable hash equiv #f #f alist))
+ ((hash equiv capacity alist)
+ (alist->hashtable hash equiv capacity #f alist))
+ ((hash equiv capacity weakness alist)
+ (let ((hashtable (make-hashtable hash equiv capacity weakness)))
+ (for-each (lambda (entry)
+ (hashtable-set! hashtable (car entry) (cdr entry)))
+ (reverse alist))
+ hashtable))))
+
+(define-enumeration weakness
+ (weak-key
+ weak-value
+ weak-key-and-value
+ ephemeral-key
+ ephemeral-value
+ ephemeral-key-and-value)
+ weakness-set)
+
+(define hashtable? rnrs-hashtable?)
+
+(define hashtable-size rnrs-hashtable-size)
+
+(define nil (cons #f #f))
+(define (nil? obj) (eq? obj nil))
+
+(define hashtable-ref
+ (case-lambda
+ ((hashtable key)
+ (let ((value (rnrs-hashtable-ref hashtable key nil)))
+ (if (nil? value)
+ (error "No such key in hashtable." hashtable key)
+ value)))
+ ((hashtable key default)
+ (rnrs-hashtable-ref hashtable key default))))
+
+(define hashtable-set! rnrs-hashtable-set!)
+
+(define hashtable-delete! rnrs-hashtable-delete!)
+
+(define hashtable-contains? rnrs-hashtable-contains?)
+
+(define (hashtable-lookup hashtable key)
+ (let ((value (rnrs-hashtable-ref hashtable key nil)))
+ (if (nil? value)
+ (values #f #f)
+ (values value #t))))
+
+(define hashtable-update!
+ (case-lambda
+ ((hashtable key proc) (hashtable-update! hashtable key proc nil))
+ ((hashtable key proc default)
+ (rnrs-hashtable-update!
+ hashtable key
+ (lambda (value)
+ (if (nil? value)
+ (error "No such key in hashtable." hashtable key)
+ (proc value)))
+ default))))
+
+;;; XXX This could be implemented at the platform level to eliminate the second
+;;; lookup for the key.
+(define (hashtable-intern! hashtable key default-proc)
+ (let ((value (rnrs-hashtable-ref hashtable key nil)))
+ (if (nil? value)
+ (let ((value (default-proc)))
+ (hashtable-set! hashtable key value)
+ value)
+ value)))
+
+(define hashtable-copy
+ (case-lambda
+ ((hashtable) (hashtable-copy hashtable #f #f))
+ ((hashtable mutable) (hashtable-copy hashtable mutable #f))
+ ((hashtable mutable weakness)
+ (when weakness
+ (error "No weak or ephemeral tables supported."))
+ (rnrs-hashtable-copy hashtable mutable))))
+
+(define hashtable-clear!
+ (case-lambda
+ ((hashtable) (rnrs-hashtable-clear! hashtable))
+ ((hashtable capacity)
+ (if capacity
+ (rnrs-hashtable-clear! hashtable capacity)
+ (rnrs-hashtable-clear! hashtable)))))
+
+(define hashtable-empty-copy
+ (case-lambda
+ ((hashtable) (hashtable-empty-copy hashtable #f))
+ ((hashtable capacity)
+ (make-hashtable (hashtable-hash-function hashtable)
+ (hashtable-equivalence-function hashtable)
+ (if (eq? #t capacity)
+ (hashtable-size hashtable)
+ capacity)
+ (hashtable-weakness hashtable)))))
+
+(define hashtable-keys rnrs-hashtable-keys)
+
+(define (hashtable-values hashtable)
+ (let-values (((keys values) (rnrs-hashtable-entries hashtable)))
+ values))
+
+(define hashtable-entries rnrs-hashtable-entries)
+
+(define (hashtable-key-list hashtable)
+ (hashtable-map->lset hashtable (lambda (key value) key)))
+
+(define (hashtable-value-list hashtable)
+ (hashtable-map->lset hashtable (lambda (key value) value)))
+
+(define (hashtable-entry-lists hashtable)
+ (let ((keys '())
+ (vals '()))
+ (hashtable-walk hashtable
+ (lambda (key val)
+ (set! keys (cons key keys))
+ (set! vals (cons val vals))))
+ (values keys vals)))
+
+;;; XXX The procedures hashtable-walk, hashtable-update-all!, hashtable-prune!,
+;;; and hashtable-sum should be implemented more efficiently at the platform
+;;; level. In particular, they should not allocate intermediate vectors or
+;;; lists to hold the keys or values that are being operated on.
+
+(define (hashtable-walk hashtable proc)
+ (let-values (((keys values) (rnrs-hashtable-entries hashtable)))
+ (vector-for-each proc keys values)))
+
+(define (hashtable-update-all! hashtable proc)
+ (let-values (((keys values) (hashtable-entries hashtable)))
+ (vector-for-each (lambda (key value)
+ (hashtable-set! hashtable key (proc key value)))
+ keys values)))
+
+(define (hashtable-prune! hashtable proc)
+ (let-values (((keys values) (hashtable-entries hashtable)))
+ (vector-for-each (lambda (key value)
+ (when (proc key value)
+ (hashtable-delete! hashtable key)))
+ keys values)))
+
+(define (hashtable-merge! hashtable-dest hashtable-source)
+ (hashtable-walk hashtable-source
+ (lambda (key value)
+ (hashtable-set! hashtable-dest key value)))
+ hashtable-dest)
+
+(define (hashtable-sum hashtable init proc)
+ (let-values (((keys vals) (hashtable-entry-lists hashtable)))
+ (fold proc init keys vals)))
+
+(define (hashtable-map->lset hashtable proc)
+ (hashtable-sum hashtable '()
+ (lambda (key value accumulator)
+ (cons (proc key value) accumulator))))
+
+;;; XXX If available, let-escape-continuation might be more efficient than
+;;; call/cc here.
+(define (hashtable-find hashtable proc)
+ (call/cc
+ (lambda (return)
+ (hashtable-walk hashtable
+ (lambda (key value)
+ (when (proc key value)
+ (return key value #t))))
+ (return #f #f #f))))
+
+(define (hashtable-empty? hashtable)
+ (zero? (hashtable-size hashtable)))
+
+;;; XXX A platform-level implementation could avoid allocating the constant true
+;;; function and the lookup for the key in the delete operation.
+(define (hashtable-pop! hashtable)
+ (if (hashtable-empty? hashtable)
+ (error "Cannot pop from empty hashtable." hashtable)
+ (let-values (((key value found?)
+ (hashtable-find hashtable (lambda (k v) #t))))
+ (hashtable-delete! hashtable key)
+ (values key value))))
+
+(define hashtable-inc!
+ (case-lambda
+ ((hashtable key) (hashtable-inc! hashtable key 1))
+ ((hashtable key number)
+ (hashtable-update! hashtable key (lambda (v) (+ v number)) 0))))
+
+(define hashtable-dec!
+ (case-lambda
+ ((hashtable key) (hashtable-dec! hashtable key 1))
+ ((hashtable key number)
+ (hashtable-update! hashtable key (lambda (v) (- v number)) 0))))
+
+(define hashtable-equivalence-function rnrs-hashtable-equivalence-function)
+
+(define hashtable-hash-function rnrs-hashtable-hash-function)
+
+(define (hashtable-weakness hashtable) #f)
+
+(define hashtable-mutable? rnrs-hashtable-mutable?)
+
+(define *hash-salt*
+ (let ((seed (get-environment-variable "SRFI_126_HASH_SEED")))
+ (if (or (not seed) (string=? seed ""))
+ (random-integer (greatest-fixnum))
+ (modulo
+ (fold (lambda (char result)
+ (+ (char->integer char) result))
+ 0
+ (string->list seed))
+ (greatest-fixnum)))))
+
+(define (hash-salt) *hash-salt*)
+
+(define equal-hash rnrs-equal-hash)
+
+(define string-hash rnrs-string-hash)
+
+(define string-ci-hash rnrs-string-ci-hash)
+
+(define symbol-hash rnrs-symbol-hash)
+
+;; Local Variables:
+;; eval: (put 'hashtable-walk 'scheme-indent-function 1)
+;; eval: (put 'hashtable-update-all! 'scheme-indent-function 1)
+;; eval: (put 'hashtable-prune! 'scheme-indent-function 1)
+;; eval: (put 'hashtable-sum 'scheme-indent-function 2)
+;; eval: (put 'hashtable-map->lset 'scheme-indent-function 1)
+;; eval: (put 'hashtable-find 'scheme-indent-function 1)
+;; End:
+(define-library (srfi 126)
+ (export
+ make-eq-hashtable make-eqv-hashtable make-hashtable
+ alist->eq-hashtable alist->eqv-hashtable alist->hashtable
+ weakness
+ hashtable?
+ hashtable-size
+ hashtable-ref hashtable-set! hashtable-delete!
+ hashtable-contains?
+ hashtable-lookup hashtable-update! hashtable-intern!
+ hashtable-copy hashtable-clear! hashtable-empty-copy
+ hashtable-keys hashtable-values hashtable-entries
+ hashtable-key-list hashtable-value-list hashtable-entry-lists
+ hashtable-walk hashtable-update-all! hashtable-prune! hashtable-merge!
+ hashtable-sum hashtable-map->lset hashtable-find
+ hashtable-empty? hashtable-pop! hashtable-inc! hashtable-dec!
+ hashtable-equivalence-function hashtable-hash-function hashtable-weakness
+ hashtable-mutable?
+ hash-salt equal-hash string-hash string-ci-hash symbol-hash)
+ (import
+ (scheme base)
+ (scheme case-lambda)
+ (scheme process-context)
+ (r6rs enums)
+ (prefix (r6rs hashtables) rnrs-)
+ (srfi 1)
+ (srfi 27))
+ (begin
+
+ ;; Smallest allowed in R6RS.
+ (define (greatest-fixnum) (expt 23 2))
+
+ ;; INCLUDE 126.body.scm
+(define make-eq-hashtable
+ (case-lambda
+ (() (make-eq-hashtable #f #f))
+ ((capacity) (make-eq-hashtable capacity #f))
+ ((capacity weakness)
+ (when weakness
+ (error "No weak or ephemeral hashtables supported."))
+ (if capacity
+ (rnrs-make-eq-hashtable capacity)
+ (rnrs-make-eq-hashtable)))))
+
+(define make-eqv-hashtable
+ (case-lambda
+ (() (make-eqv-hashtable #f #f))
+ ((capacity) (make-eqv-hashtable capacity #f))
+ ((capacity weakness)
+ (when weakness
+ (error "No weak or ephemeral hashtables supported."))
+ (if capacity
+ (rnrs-make-eqv-hashtable capacity)
+ (rnrs-make-eqv-hashtable)))))
+
+(define make-hashtable
+ (case-lambda
+ ((hash equiv) (make-hashtable hash equiv #f #f))
+ ((hash equiv capacity) (make-hashtable hash equiv capacity #f))
+ ((hash equiv capacity weakness)
+ (cond
+ ((and (not hash) (eq? equiv eq?))
+ (make-eq-hashtable capacity weakness))
+ ((and (not hash) (eq? equiv eqv?))
+ (make-eqv-hashtable capacity weakness))
+ (else
+ (when weakness
+ (error "No weak or ephemeral hashtables supported."))
+ (let ((hash (if (pair? hash)
+ (car hash)
+ hash)))
+ (if capacity
+ (rnrs-make-hashtable hash equiv capacity)
+ (rnrs-make-hashtable hash equiv))))))))
+
+(define (alist->eq-hashtable . args)
+ (apply alist->hashtable #f eq? args))
+
+(define (alist->eqv-hashtable . args)
+ (apply alist->hashtable #f eqv? args))
+
+(define alist->hashtable
+ (case-lambda
+ ((hash equiv alist)
+ (alist->hashtable hash equiv #f #f alist))
+ ((hash equiv capacity alist)
+ (alist->hashtable hash equiv capacity #f alist))
+ ((hash equiv capacity weakness alist)
+ (let ((hashtable (make-hashtable hash equiv capacity weakness)))
+ (for-each (lambda (entry)
+ (hashtable-set! hashtable (car entry) (cdr entry)))
+ (reverse alist))
+ hashtable))))
+
+(define-enumeration weakness
+ (weak-key
+ weak-value
+ weak-key-and-value
+ ephemeral-key
+ ephemeral-value
+ ephemeral-key-and-value)
+ weakness-set)
+
+(define hashtable? rnrs-hashtable?)
+
+(define hashtable-size rnrs-hashtable-size)
+
+(define nil (cons #f #f))
+(define (nil? obj) (eq? obj nil))
+
+(define hashtable-ref
+ (case-lambda
+ ((hashtable key)
+ (let ((value (rnrs-hashtable-ref hashtable key nil)))
+ (if (nil? value)
+ (error "No such key in hashtable." hashtable key)
+ value)))
+ ((hashtable key default)
+ (rnrs-hashtable-ref hashtable key default))))
+
+(define hashtable-set! rnrs-hashtable-set!)
+
+(define hashtable-delete! rnrs-hashtable-delete!)
+
+(define hashtable-contains? rnrs-hashtable-contains?)
+
+(define (hashtable-lookup hashtable key)
+ (let ((value (rnrs-hashtable-ref hashtable key nil)))
+ (if (nil? value)
+ (values #f #f)
+ (values value #t))))
+
+(define hashtable-update!
+ (case-lambda
+ ((hashtable key proc) (hashtable-update! hashtable key proc nil))
+ ((hashtable key proc default)
+ (rnrs-hashtable-update!
+ hashtable key
+ (lambda (value)
+ (if (nil? value)
+ (error "No such key in hashtable." hashtable key)
+ (proc value)))
+ default))))
+
+;;; XXX This could be implemented at the platform level to eliminate the second
+;;; lookup for the key.
+(define (hashtable-intern! hashtable key default-proc)
+ (let ((value (rnrs-hashtable-ref hashtable key nil)))
+ (if (nil? value)
+ (let ((value (default-proc)))
+ (hashtable-set! hashtable key value)
+ value)
+ value)))
+
+(define hashtable-copy
+ (case-lambda
+ ((hashtable) (hashtable-copy hashtable #f #f))
+ ((hashtable mutable) (hashtable-copy hashtable mutable #f))
+ ((hashtable mutable weakness)
+ (when weakness
+ (error "No weak or ephemeral tables supported."))
+ (rnrs-hashtable-copy hashtable mutable))))
+
+(define hashtable-clear!
+ (case-lambda
+ ((hashtable) (rnrs-hashtable-clear! hashtable))
+ ((hashtable capacity)
+ (if capacity
+ (rnrs-hashtable-clear! hashtable capacity)
+ (rnrs-hashtable-clear! hashtable)))))
+
+(define hashtable-empty-copy
+ (case-lambda
+ ((hashtable) (hashtable-empty-copy hashtable #f))
+ ((hashtable capacity)
+ (make-hashtable (hashtable-hash-function hashtable)
+ (hashtable-equivalence-function hashtable)
+ (if (eq? #t capacity)
+ (hashtable-size hashtable)
+ capacity)
+ (hashtable-weakness hashtable)))))
+
+(define hashtable-keys rnrs-hashtable-keys)
+
+(define (hashtable-values hashtable)
+ (let-values (((keys values) (rnrs-hashtable-entries hashtable)))
+ values))
+
+(define hashtable-entries rnrs-hashtable-entries)
+
+(define (hashtable-key-list hashtable)
+ (hashtable-map->lset hashtable (lambda (key value) key)))
+
+(define (hashtable-value-list hashtable)
+ (hashtable-map->lset hashtable (lambda (key value) value)))
+
+(define (hashtable-entry-lists hashtable)
+ (let ((keys '())
+ (vals '()))
+ (hashtable-walk hashtable
+ (lambda (key val)
+ (set! keys (cons key keys))
+ (set! vals (cons val vals))))
+ (values keys vals)))
+
+;;; XXX The procedures hashtable-walk, hashtable-update-all!, hashtable-prune!,
+;;; and hashtable-sum should be implemented more efficiently at the platform
+;;; level. In particular, they should not allocate intermediate vectors or
+;;; lists to hold the keys or values that are being operated on.
+
+(define (hashtable-walk hashtable proc)
+ (let-values (((keys values) (rnrs-hashtable-entries hashtable)))
+ (vector-for-each proc keys values)))
+
+(define (hashtable-update-all! hashtable proc)
+ (let-values (((keys values) (hashtable-entries hashtable)))
+ (vector-for-each (lambda (key value)
+ (hashtable-set! hashtable key (proc key value)))
+ keys values)))
+
+(define (hashtable-prune! hashtable proc)
+ (let-values (((keys values) (hashtable-entries hashtable)))
+ (vector-for-each (lambda (key value)
+ (when (proc key value)
+ (hashtable-delete! hashtable key)))
+ keys values)))
+
+(define (hashtable-merge! hashtable-dest hashtable-source)
+ (hashtable-walk hashtable-source
+ (lambda (key value)
+ (hashtable-set! hashtable-dest key value)))
+ hashtable-dest)
+
+(define (hashtable-sum hashtable init proc)
+ (let-values (((keys vals) (hashtable-entry-lists hashtable)))
+ (fold proc init keys vals)))
+
+(define (hashtable-map->lset hashtable proc)
+ (hashtable-sum hashtable '()
+ (lambda (key value accumulator)
+ (cons (proc key value) accumulator))))
+
+;;; XXX If available, let-escape-continuation might be more efficient than
+;;; call/cc here.
+(define (hashtable-find hashtable proc)
+ (call/cc
+ (lambda (return)
+ (hashtable-walk hashtable
+ (lambda (key value)
+ (when (proc key value)
+ (return key value #t))))
+ (return #f #f #f))))
+
+(define (hashtable-empty? hashtable)
+ (zero? (hashtable-size hashtable)))
+
+;;; XXX A platform-level implementation could avoid allocating the constant true
+;;; function and the lookup for the key in the delete operation.
+(define (hashtable-pop! hashtable)
+ (if (hashtable-empty? hashtable)
+ (error "Cannot pop from empty hashtable." hashtable)
+ (let-values (((key value found?)
+ (hashtable-find hashtable (lambda (k v) #t))))
+ (hashtable-delete! hashtable key)
+ (values key value))))
+
+(define hashtable-inc!
+ (case-lambda
+ ((hashtable key) (hashtable-inc! hashtable key 1))
+ ((hashtable key number)
+ (hashtable-update! hashtable key (lambda (v) (+ v number)) 0))))
+
+(define hashtable-dec!
+ (case-lambda
+ ((hashtable key) (hashtable-dec! hashtable key 1))
+ ((hashtable key number)
+ (hashtable-update! hashtable key (lambda (v) (- v number)) 0))))
+
+(define hashtable-equivalence-function rnrs-hashtable-equivalence-function)
+
+(define hashtable-hash-function rnrs-hashtable-hash-function)
+
+(define (hashtable-weakness hashtable) #f)
+
+(define hashtable-mutable? rnrs-hashtable-mutable?)
+
+(define *hash-salt*
+ (let ((seed (get-environment-variable "SRFI_126_HASH_SEED")))
+ (if (or (not seed) (string=? seed ""))
+ (random-integer (greatest-fixnum))
+ (modulo
+ (fold (lambda (char result)
+ (+ (char->integer char) result))
+ 0
+ (string->list seed))
+ (greatest-fixnum)))))
+
+(define (hash-salt) *hash-salt*)
+
+(define equal-hash rnrs-equal-hash)
+
+(define string-hash rnrs-string-hash)
+
+(define string-ci-hash rnrs-string-ci-hash)
+
+(define symbol-hash rnrs-symbol-hash)
+
+;; Local Variables:
+;; eval: (put 'hashtable-walk 'scheme-indent-function 1)
+;; eval: (put 'hashtable-update-all! 'scheme-indent-function 1)
+;; eval: (put 'hashtable-prune! 'scheme-indent-function 1)
+;; eval: (put 'hashtable-sum 'scheme-indent-function 2)
+;; eval: (put 'hashtable-map->lset 'scheme-indent-function 1)
+;; eval: (put 'hashtable-find 'scheme-indent-function 1)
+;; End:
+
+ ))
+;;; Guile implementation.
+
+(define-module (srfi srfi-126))
+
+(use-modules
+ (srfi srfi-1)
+ (srfi srfi-9)
+ (srfi srfi-9 gnu)
+ (srfi srfi-11)
+ (ice-9 hash-table)
+ (ice-9 control)
+ ((rnrs hashtables) #\select
+ (equal-hash string-hash string-ci-hash symbol-hash)))
+
+(export
+ make-eq-hashtable make-eqv-hashtable make-hashtable
+ alist->eq-hashtable alist->eqv-hashtable alist->hashtable
+ weakness
+ hashtable? hashtable-size
+ hashtable-ref hashtable-set! hashtable-delete! hashtable-contains?
+ hashtable-lookup hashtable-update! hashtable-intern!
+ hashtable-copy hashtable-clear! hashtable-empty-copy
+ hashtable-keys hashtable-values hashtable-entries
+ hashtable-key-list hashtable-value-list hashtable-entry-lists
+ hashtable-walk hashtable-update-all! hashtable-prune! hashtable-merge!
+ hashtable-sum hashtable-map->lset hashtable-find
+ hashtable-empty? hashtable-pop! hashtable-inc! hashtable-dec!
+ hashtable-equivalence-function hashtable-hash-function
+ hashtable-weakness hashtable-mutable?
+ hash-salt
+ )
+
+(re-export equal-hash string-hash string-ci-hash symbol-hash)
+
+(define-record-type <hashtable>
+ (%make-hashtable %table %hash %assoc hash equiv weakness mutable)
+ hashtable?
+ (%table %hashtable-table)
+ (%hash %hashtable-hash)
+ (%assoc %hashtable-assoc)
+ (hash hashtable-hash-function)
+ (equiv hashtable-equivalence-function)
+ (weakness hashtable-weakness)
+ (mutable hashtable-mutable? %hashtable-set-mutable!))
+
+(define nil (cons #f #f))
+(define (nil? obj) (eq? obj nil))
+
+(define (make-table capacity weakness)
+ (let ((capacity (or capacity 32)))
+ (case weakness
+ ((#f) (make-hash-table capacity))
+ ((weak-key) (make-weak-key-hash-table capacity))
+ ((weak-value) (make-weak-value-hash-table capacity))
+ ((weak-key-and-value) (make-doubly-weak-hash-table capacity))
+ (else (error "Hashtable weakness not supported." weakness)))))
+
+(define* (make-eq-hashtable #\optional capacity weakness)
+ (let ((table (make-table capacity weakness)))
+ (%make-hashtable table hashq assq #f eq? weakness #t)))
+
+(define* (make-eqv-hashtable #\optional capacity weakness)
+ (let ((table (make-table capacity weakness)))
+ (%make-hashtable table hashv assv #f eqv? weakness #t)))
+
+(define* (make-hashtable hash equiv #\optional capacity weakness)
+ (cond
+ ((and (not hash) (eq? equiv eq?))
+ (make-eq-hashtable capacity weakness))
+ ((and (not hash) (eq? equiv eqv?))
+ (make-eqv-hashtable capacity weakness))
+ (else
+ (let* ((table (make-table capacity weakness))
+ (hash (if (pair? hash)
+ (car hash)
+ hash))
+ (%hash (lambda (obj bound)
+ (modulo (hash obj) bound)))
+ (assoc (lambda (key alist)
+ (find (lambda (entry)
+ (equiv (car entry) key))
+ alist))))
+ (%make-hashtable table %hash assoc hash equiv weakness #t)))))
+
+(define (alist->eq-hashtable . args)
+ (apply alist->hashtable #f eq? args))
+
+(define (alist->eqv-hashtable . args)
+ (apply alist->hashtable #f eqv? args))
+
+(define alist->hashtable
+ (case-lambda
+ ((hash equiv alist)
+ (alist->hashtable hash equiv #f #f alist))
+ ((hash equiv capacity alist)
+ (alist->hashtable hash equiv capacity #f alist))
+ ((hash equiv capacity weakness alist)
+ (let ((ht (make-hashtable hash equiv capacity weakness)))
+ (for-each (lambda (entry)
+ (hashtable-set! ht (car entry) (cdr entry)))
+ (reverse alist))
+ ht))))
+
+(define-syntax weakness
+ (lambda (stx)
+ (syntax-case stx ()
+ ((_ <sym>)
+ (let ((sym (syntax->datum #'<sym>)))
+ (case sym
+ ((weak-key weak-value weak-key-and-value ephemeral-key
+ ephemeral-value ephemeral-key-and-value)
+ #''sym)
+ (else
+ (error "Bad weakness symbol." sym))))))))
+
+(define (hashtable-size ht)
+ (hash-count (const #t) (%hashtable-table ht)))
+
+(define* (%hashtable-ref ht key default)
+ (hashx-ref (%hashtable-hash ht) (%hashtable-assoc ht)
+ (%hashtable-table ht) key default))
+
+(define* (hashtable-ref ht key #\optional (default nil))
+ (let ((val (%hashtable-ref ht key default)))
+ (if (nil? val)
+ (error "No association for key in hashtable." key ht)
+ val)))
+
+(define (assert-mutable ht)
+ (when (not (hashtable-mutable? ht))
+ (error "Hashtable is immutable." ht)))
+
+(define (hashtable-set! ht key value)
+ (assert-mutable ht)
+ (hashx-set! (%hashtable-hash ht) (%hashtable-assoc ht)
+ (%hashtable-table ht) key value)
+ *unspecified*)
+
+(define (hashtable-delete! ht key)
+ (assert-mutable ht)
+ (hashx-remove! (%hashtable-hash ht) (%hashtable-assoc ht)
+ (%hashtable-table ht) key)
+ *unspecified*)
+
+(define (hashtable-contains? ht key)
+ (not (nil? (%hashtable-ref ht key nil))))
+
+(define (hashtable-lookup ht key)
+ (let ((val (%hashtable-ref ht key nil)))
+ (if (nil? val)
+ (values #f #f)
+ (values val #t))))
+
+(define* (hashtable-update! ht key updater #\optional (default nil))
+ (assert-mutable ht)
+ (let ((handle (hashx-create-handle!
+ (%hashtable-hash ht) (%hashtable-assoc ht)
+ (%hashtable-table ht) key nil)))
+ (if (eq? nil (cdr handle))
+ (if (nil? default)
+ (error "No association for key in hashtable." key ht)
+ (set-cdr! handle (updater default)))
+ (set-cdr! handle (updater (cdr handle))))
+ (cdr handle)))
+
+(define (hashtable-intern! ht key default-proc)
+ (assert-mutable ht)
+ (let ((handle (hashx-create-handle!
+ (%hashtable-hash ht) (%hashtable-assoc ht)
+ (%hashtable-table ht) key nil)))
+ (when (nil? (cdr handle))
+ (set-cdr! handle (default-proc)))
+ (cdr handle)))
+
+(define* (hashtable-copy ht #\optional mutable weakness)
+ (let ((copy (hashtable-empty-copy ht (hashtable-size ht) weakness)))
+ (hashtable-walk ht
+ (lambda (k v)
+ (hashtable-set! copy k v)))
+ (%hashtable-set-mutable! copy mutable)
+ copy))
+
+(define* (hashtable-clear! ht #\optional _capacity)
+ (assert-mutable ht)
+ (hash-clear! (%hashtable-table ht))
+ *unspecified*)
+
+(define* (hashtable-empty-copy ht #\optional capacity weakness)
+ (make-hashtable (hashtable-hash-function ht)
+ (hashtable-equivalence-function ht)
+ (case capacity
+ ((#f) #f)
+ ((#t) (hashtable-size ht))
+ (else capacity))
+ (or weakness (hashtable-weakness ht))))
+
+(define (hashtable-keys ht)
+ (let ((keys (make-vector (hashtable-size ht))))
+ (hashtable-sum ht 0
+ (lambda (k v i)
+ (vector-set! keys i k)
+ (+ i 1)))
+ keys))
+
+(define (hashtable-values ht)
+ (let ((vals (make-vector (hashtable-size ht))))
+ (hashtable-sum ht 0
+ (lambda (k v i)
+ (vector-set! vals i v)
+ (+ i 1)))
+ vals))
+
+(define (hashtable-entries ht)
+ (let ((keys (make-vector (hashtable-size ht)))
+ (vals (make-vector (hashtable-size ht))))
+ (hashtable-sum ht 0
+ (lambda (k v i)
+ (vector-set! keys i k)
+ (vector-set! vals i v)
+ (+ i 1)))
+ (values keys vals)))
+
+(define (hashtable-key-list ht)
+ (hashtable-map->lset ht (lambda (k v) k)))
+
+(define (hashtable-value-list ht)
+ (hashtable-map->lset ht (lambda (k v) v)))
+
+(define (hashtable-entry-lists ht)
+ (let ((keys&vals (cons '() '())))
+ (hashtable-walk ht
+ (lambda (k v)
+ (set-car! keys&vals (cons k (car keys&vals)))
+ (set-cdr! keys&vals (cons v (cdr keys&vals)))))
+ (car+cdr keys&vals)))
+
+(define (hashtable-walk ht proc)
+ (hash-for-each proc (%hashtable-table ht)))
+
+(define (hashtable-update-all! ht proc)
+ (assert-mutable ht)
+ (hash-for-each-handle
+ (lambda (handle)
+ (set-cdr! handle (proc (car handle) (cdr handle))))
+ (%hashtable-table ht)))
+
+(define (hashtable-prune! ht pred)
+ (assert-mutable ht)
+ (let ((keys (hashtable-sum ht '()
+ (lambda (k v keys-to-delete)
+ (if (pred k v)
+ (cons k keys-to-delete)
+ keys-to-delete)))))
+ (for-each (lambda (k)
+ (hashtable-delete! ht k))
+ keys)))
+
+(define (hashtable-merge! ht-dest ht-src)
+ (assert-mutable ht-dest)
+ (hashtable-walk ht-src
+ (lambda (k v)
+ (hashtable-set! ht-dest k v)))
+ ht-dest)
+
+(define (hashtable-sum ht init proc)
+ (hash-fold proc init (%hashtable-table ht)))
+
+(define (hashtable-map->lset ht proc)
+ (hash-map->list proc (%hashtable-table ht)))
+
+(define (hashtable-find ht pred)
+ (let/ec return
+ (hashtable-walk ht
+ (lambda (k v)
+ (when (pred k v)
+ (return k v #t))))
+ (return #f #f #f)))
+
+(define (hashtable-empty? ht)
+ (zero? (hashtable-size ht)))
+
+(define (hashtable-pop! ht)
+ (assert-mutable ht)
+ (when (hashtable-empty? ht)
+ (error "Cannot pop from empty hashtable." ht))
+ (let-values (((k v found?) (hashtable-find ht (const #t))))
+ (hashtable-delete! ht k)
+ (values k v)))
+
+(define* (hashtable-inc! ht k #\optional (x 1))
+ (assert-mutable ht)
+ (hashtable-update! ht k (lambda (v) (+ v x)) 0))
+
+(define* (hashtable-dec! ht k #\optional (x 1))
+ (assert-mutable ht)
+ (hashtable-update! ht k (lambda (v) (- v x)) 0))
+
+(define (hash-salt) 0)
+
+(set-record-type-printer!
+ <hashtable>
+ (lambda (ht port)
+ (with-output-to-port port
+ (lambda ()
+ (let ((equal-hash (@ (rnrs hashtables) equal-hash))
+ (string-hash (@ (rnrs hashtables) string-hash))
+ (string-ci-hash (@ (rnrs hashtables) string-ci-hash))
+ (symbol-hash (@ (rnrs hashtables) symbol-hash))
+ (hash (hashtable-hash-function ht))
+ (equiv (hashtable-equivalence-function ht))
+ (alist (hashtable-map->lset ht cons)))
+ (cond
+ ((and (not hash) (eq? equiv eq?))
+ (display "#hasheq")
+ (display alist))
+ ((and (not hash) (eq? equiv eqv?))
+ (display "#hasheqv")
+ (display alist))
+ (else
+ (display "#hash")
+ (cond
+ ((and (eq? hash (@ (rnrs hashtables) equal-hash)) (eq? equiv equal?))
+ (display alist))
+ ((and (eq? hash (@ (rnrs hashtables) string-hash)) (eq? equiv string=?))
+ (display (cons 'string alist)))
+ ((and (eq? hash string-ci-hash) (eq? equiv string-ci=?))
+ (display (cons 'string-ci alist)))
+ ((and (eq? hash symbol-hash) (eq? equiv eq?))
+ (display (cons 'symbol alist)))
+ (else
+ (display (cons 'custom alist)))))))))))
+
+(read-hash-extend
+ #\h
+ (lambda (char port)
+ (with-input-from-port port
+ (lambda ()
+ (let ((equal-hash (@ (rnrs hashtables) equal-hash))
+ (string-hash (@ (rnrs hashtables) string-hash))
+ (string-ci-hash (@ (rnrs hashtables) string-ci-hash))
+ (symbol-hash (@ (rnrs hashtables) symbol-hash))
+ (type (string-append "h" (symbol->string (read))))
+ (alist (read)))
+ (cond
+ ((string=? type "hasheq")
+ (alist->eq-hashtable alist))
+ ((string=? type "hasheqv")
+ (alist->eqv-hashtable alist))
+ (else
+ (when (not (string=? type "hash"))
+ (error "Unrecognized hash type." type))
+ (let* ((has-tag? (symbol? (car alist)))
+ (subtype (if has-tag?
+ (car alist)
+ "equal"))
+ (alist (if has-tag?
+ (cdr alist)
+ alist)))
+ (cond
+ ((string=? subtype "equal")
+ (alist->hashtable equal-hash equal? alist))
+ ((string=? subtype "string")
+ (alist->hashtable string-hash string=? alist))
+ ((string=? subtype "string-ci")
+ (alist->hashtable string-ci-hash string-ci=? alist))
+ ((string=? subtype "symbol")
+ (alist->hashtable symbol-hash eq? alist))
+ (else
+ (error "Unrecognized hash subtype." subtype)))))))))))
+
+;; Local Variables:
+;; eval: (put 'hashtable-walk 'scheme-indent-function 1)
+;; eval: (put 'hashtable-update-all! 'scheme-indent-function 1)
+;; eval: (put 'hashtable-prune! 'scheme-indent-function 1)
+;; eval: (put 'hashtable-sum 'scheme-indent-function 2)
+;; eval: (put 'hashtable-map->lset 'scheme-indent-function 1)
+;; eval: (put 'hashtable-find 'scheme-indent-function 1)
+;; End:
+;;; This doesn't test weakness, external representation, and quasiquote.
+
+(test-begin "SRFI-126")
+
+(test-group "constructors & inspection"
+ (test-group "eq"
+ (let ((tables (list (make-eq-hashtable)
+ (make-eq-hashtable 10)
+ (make-eq-hashtable #f #f)
+ (make-hashtable #f eq?)
+ (alist->eq-hashtable '((a . b) (c . d)))
+ (alist->eq-hashtable 10 '((a . b) (c . d)))
+ (alist->eq-hashtable #f #f '((a . b) (c . d))))))
+ (do ((tables tables (cdr tables))
+ (i 0 (+ i 1)))
+ ((null? tables))
+ (let ((table (car tables))
+ (label (number->string i)))
+ (test-assert label (hashtable? table))
+ (test-eq label #f (hashtable-hash-function table))
+ (test-eq label eq? (hashtable-equivalence-function table))
+ (test-eq label #f (hashtable-weakness table))
+ (test-assert label (hashtable-mutable? table))))))
+ (test-group "eqv"
+ (let ((tables (list (make-eqv-hashtable)
+ (make-eqv-hashtable 10)
+ (make-eqv-hashtable #f #f)
+ (make-hashtable #f eqv?)
+ (alist->eqv-hashtable '((a . b) (c . d)))
+ (alist->eqv-hashtable 10 '((a . b) (c . d)))
+ (alist->eqv-hashtable #f #f '((a . b) (c . d))))))
+ (do ((tables tables (cdr tables))
+ (i 0 (+ i 1)))
+ ((null? tables))
+ (let ((table (car tables))
+ (label (number->string i)))
+ (test-assert label (hashtable? table))
+ (test-eq label #f (hashtable-hash-function table))
+ (test-eq label eqv? (hashtable-equivalence-function table))
+ (test-eq label #f (hashtable-weakness table))
+ (test-assert label (hashtable-mutable? table))))))
+ (test-group "equal"
+ (let ((tables (list (make-hashtable equal-hash equal?)
+ (make-hashtable equal-hash equal? 10)
+ (make-hashtable equal-hash equal? #f #f)
+ (alist->hashtable equal-hash equal?
+ '((a . b) (c . d)))
+ (alist->hashtable equal-hash equal? 10
+ '((a . b) (c . d)))
+ (alist->hashtable equal-hash equal? #f #f
+ '((a . b) (c . d))))))
+ (do ((tables tables (cdr tables))
+ (i 0 (+ i 1)))
+ ((null? tables))
+ (let ((table (car tables))
+ (label (number->string i)))
+ (test-assert label (hashtable? table))
+ (test-eq label equal-hash (hashtable-hash-function table))
+ (test-eq label equal? (hashtable-equivalence-function table))
+ (test-eq label #f (hashtable-weakness table))
+ (test-assert label (hashtable-mutable? table))))
+ (let ((table (make-hashtable (cons equal-hash equal-hash) equal?)))
+ (let ((hash (hashtable-hash-function table)))
+ (test-assert (or (eq? equal-hash hash)
+ (and (eq? equal-hash (car hash))
+ (eq? equal-hash (cdr hash)))))))))
+ (test-group "alist"
+ (let ((tables (list (alist->eq-hashtable '((a . b) (a . c)))
+ (alist->eqv-hashtable '((a . b) (a . c)))
+ (alist->hashtable equal-hash equal?
+ '((a . b) (a . c))))))
+ (do ((tables tables (cdr tables))
+ (i 0 (+ i 1)))
+ ((null? tables))
+ (let ((table (car tables))
+ (label (number->string i)))
+ (test-eq label 'b (hashtable-ref table 'a)))))))
+
+(test-group "procedures"
+ (test-group "basics"
+ (let ((table (make-eq-hashtable)))
+ (test-group "ref"
+ (test-error (hashtable-ref table 'a))
+ (test-eq 'b (hashtable-ref table 'a 'b))
+ (test-assert (not (hashtable-contains? table 'a)))
+ (test-eqv 0 (hashtable-size table)))
+ (test-group "set"
+ (hashtable-set! table 'a 'c)
+ (test-eq 'c (hashtable-ref table 'a))
+ (test-eq 'c (hashtable-ref table 'a 'b))
+ (test-assert (hashtable-contains? table 'a))
+ (test-eqv 1 (hashtable-size table)))
+ (test-group "delete"
+ (hashtable-delete! table 'a)
+ (test-error (hashtable-ref table 'a))
+ (test-eq 'b (hashtable-ref table 'a 'b))
+ (test-assert (not (hashtable-contains? table 'a)))
+ (test-eqv 0 (hashtable-size table)))))
+ (test-group "advanced"
+ (let ((table (make-eq-hashtable)))
+ (test-group "lookup"
+ (let-values (((x found?) (hashtable-lookup table 'a)))
+ (test-assert (not found?))))
+ (test-group "update"
+ (test-error (hashtable-update! table 'a (lambda (x) (+ x 1))))
+ (hashtable-update! table 'a (lambda (x) (+ x 1)) 0)
+ (let-values (((x found?) (hashtable-lookup table 'a)))
+ (test-eqv 1 x)
+ (test-assert found?))
+ (hashtable-update! table 'a (lambda (x) (+ x 1)))
+ (let-values (((x found?) (hashtable-lookup table 'a)))
+ (test-eqv x 2)
+ (test-assert found?))
+ (hashtable-update! table 'a (lambda (x) (+ x 1)) 0)
+ (let-values (((x found?) (hashtable-lookup table 'a)))
+ (test-eqv x 3)
+ (test-assert found?)))
+ (test-group "intern"
+ (test-eqv 0 (hashtable-intern! table 'b (lambda () 0)))
+ (test-eqv 0 (hashtable-intern! table 'b (lambda () 1))))))
+ (test-group "copy/clear"
+ (let ((table (alist->hashtable equal-hash equal? '((a . b)))))
+ (test-group "copy"
+ (let ((table2 (hashtable-copy table)))
+ (test-eq equal-hash (hashtable-hash-function table2))
+ (test-eq equal? (hashtable-equivalence-function table2))
+ (test-eq 'b (hashtable-ref table2 'a))
+ (test-error (hashtable-set! table2 'a 'c)))
+ (let ((table2 (hashtable-copy table #f)))
+ (test-eq equal-hash (hashtable-hash-function table2))
+ (test-eq equal? (hashtable-equivalence-function table2))
+ (test-eq 'b (hashtable-ref table2 'a))
+ (test-error (hashtable-set! table2 'a 'c)))
+ (let ((table2 (hashtable-copy table #t)))
+ (test-eq equal-hash (hashtable-hash-function table2))
+ (test-eq equal? (hashtable-equivalence-function table2))
+ (test-eq 'b (hashtable-ref table2 'a))
+ (hashtable-set! table2 'a 'c)
+ (test-eq 'c (hashtable-ref table2 'a)))
+ (let ((table2 (hashtable-copy table #f #f)))
+ (test-eq equal-hash (hashtable-hash-function table2))
+ (test-eq equal? (hashtable-equivalence-function table2))
+ (test-eq #f (hashtable-weakness table2))))
+ (test-group "clear"
+ (let ((table2 (hashtable-copy table #t)))
+ (hashtable-clear! table2)
+ (test-eqv 0 (hashtable-size table2)))
+ (let ((table2 (hashtable-copy table #t)))
+ (hashtable-clear! table2 10)
+ (test-eqv 0 (hashtable-size table2))))
+ (test-group "empty-copy"
+ (let ((table2 (hashtable-empty-copy table)))
+ (test-eq equal-hash (hashtable-hash-function table2))
+ (test-eq equal? (hashtable-equivalence-function table2))
+ (test-eqv 0 (hashtable-size table2)))
+ (let ((table2 (hashtable-empty-copy table 10)))
+ (test-eq equal-hash (hashtable-hash-function table2))
+ (test-eq equal? (hashtable-equivalence-function table2))
+ (test-eqv 0 (hashtable-size table2))))))
+ (test-group "keys/values"
+ (let ((table (alist->eq-hashtable '((a . b) (c . d)))))
+ (test-assert (lset= eq? '(a c) (vector->list (hashtable-keys table))))
+ (test-assert (lset= eq? '(b d) (vector->list (hashtable-values table))))
+ (let-values (((keys values) (hashtable-entries table)))
+ (test-assert (lset= eq? '(a c) (vector->list keys)))
+ (test-assert (lset= eq? '(b d) (vector->list values))))
+ (test-assert (lset= eq? '(a c) (hashtable-key-list table)))
+ (test-assert (lset= eq? '(b d) (hashtable-value-list table)))
+ (let-values (((keys values) (hashtable-entry-lists table)))
+ (test-assert (lset= eq? '(a c) keys))
+ (test-assert (lset= eq? '(b d) values)))))
+ (test-group "iteration"
+ (test-group "walk"
+ (let ((keys '())
+ (values '()))
+ (hashtable-walk (alist->eq-hashtable '((a . b) (c . d)))
+ (lambda (k v)
+ (set! keys (cons k keys))
+ (set! values (cons v values))))
+ (test-assert (lset= eq? '(a c) keys))
+ (test-assert (lset= eq? '(b d) values))))
+ (test-group "update-all"
+ (let ((table (alist->eq-hashtable '((a . b) (c . d)))))
+ (hashtable-update-all! table
+ (lambda (k v)
+ (string->symbol (string-append (symbol->string v) "x"))))
+ (test-assert (lset= eq? '(a c) (hashtable-key-list table)))
+ (test-assert (lset= eq? '(bx dx) (hashtable-value-list table)))))
+ (test-group "prune"
+ (let ((table (alist->eq-hashtable '((a . b) (c . d)))))
+ (hashtable-prune! table (lambda (k v) (eq? k 'a)))
+ (test-assert (not (hashtable-contains? table 'a)))
+ (test-assert (hashtable-contains? table 'c))))
+ (test-group "merge"
+ (let ((table (alist->eq-hashtable '((a . b) (c . d))))
+ (table2 (alist->eq-hashtable '((a . x) (e . f)))))
+ (hashtable-merge! table table2)
+ (test-assert (lset= eq? '(a c e) (hashtable-key-list table)))
+ (test-assert (lset= eq? '(x d f) (hashtable-value-list table)))))
+ (test-group "sum"
+ (let ((table (alist->eq-hashtable '((a . b) (c . d)))))
+ (test-assert (lset= eq? '(a b c d)
+ (hashtable-sum table '()
+ (lambda (k v acc)
+ (lset-adjoin eq? acc k v)))))))
+ (test-group "map->lset"
+ (let ((table (alist->eq-hashtable '((a . b) (c . d)))))
+ (test-assert (lset= equal? '((a . b) (c . d))
+ (hashtable-map->lset table cons)))))
+ (test-group "find"
+ (let ((table (alist->eq-hashtable '((a . b) (c . d)))))
+ (let-values (((k v f?) (hashtable-find table
+ (lambda (k v)
+ (eq? k 'a)))))
+ (test-assert (and f? (eq? k 'a) (eq? v 'b))))
+ (let-values (((k v f?) (hashtable-find table (lambda (k v) #f))))
+ (test-assert (not f?)))))
+ (test-group "misc"
+ (test-group "empty?"
+ (test-assert (hashtable-empty? (alist->eq-hashtable '())))
+ (test-assert (not (hashtable-empty? (alist->eq-hashtable '((a . b)))))))
+ (test-group "pop!"
+ (test-error (hashtable-pop! (make-eq-hashtable)))
+ (let ((table (alist->eq-hashtable '((a . b)))))
+ (let-values (((k v) (hashtable-pop! table)))
+ (test-eq 'a k)
+ (test-eq 'b v)
+ (test-assert (hashtable-empty? table)))))
+ (test-group "inc!"
+ (let ((table (alist->eq-hashtable '((a . 0)))))
+ (hashtable-inc! table 'a)
+ (test-eqv 1 (hashtable-ref table 'a))
+ (hashtable-inc! table 'a 2)
+ (test-eqv 3 (hashtable-ref table 'a))))
+ (test-group "dec!"
+ (let ((table (alist->eq-hashtable '((a . 0)))))
+ (hashtable-dec! table 'a)
+ (test-eqv -1 (hashtable-ref table 'a))
+ (hashtable-dec! table 'a 2)
+ (test-eqv -3 (hashtable-ref table 'a)))))))
+
+(test-group "hashing"
+ (test-assert (and (exact-integer? (hash-salt))))
+ (test-assert (not (negative? (hash-salt))))
+ (test-assert (= (equal-hash (list "foo" 'bar 42))
+ (equal-hash (list "foo" 'bar 42))))
+ (test-assert (= (string-hash (string-copy "foo"))
+ (string-hash (string-copy "foo"))))
+ (test-assert (= (string-ci-hash (string-copy "foo"))
+ (string-ci-hash (string-copy "FOO"))))
+ (test-assert (= (symbol-hash (string->symbol "foo"))
+ (symbol-hash (string->symbol "foo")))))
+
+(test-end "SRFI-126")
+
+(display
+ (string-append
+ "\n"
+ "NOTE: On implementations using the (r6rs hashtables) library from Larceny,\n"
+ " 14 tests are expected to fail in relation to make-eq-hashtable and\n"
+ " make-eqv-hashtable returning hashtables whose hash functions are\n"
+ " exposed instead of being #f. We have no obvious way to detect this\n"
+ " within this portable test suite, hence no XFAIL results.\n"))
+
+;; Local Variables:
+;; eval: (put (quote test-group) (quote scheme-indent-function) 1)
+;; End:
+(import
+ (scheme base)
+ (scheme write)
+ (srfi 1)
+ (srfi 64)
+ (srfi 126))
+
+;; INCLUDE test-suite.body.scm
+;;; This doesn't test weakness, external representation, and quasiquote.
+
+(test-begin "SRFI-126")
+
+(test-group "constructors & inspection"
+ (test-group "eq"
+ (let ((tables (list (make-eq-hashtable)
+ (make-eq-hashtable 10)
+ (make-eq-hashtable #f #f)
+ (make-hashtable #f eq?)
+ (alist->eq-hashtable '((a . b) (c . d)))
+ (alist->eq-hashtable 10 '((a . b) (c . d)))
+ (alist->eq-hashtable #f #f '((a . b) (c . d))))))
+ (do ((tables tables (cdr tables))
+ (i 0 (+ i 1)))
+ ((null? tables))
+ (let ((table (car tables))
+ (label (number->string i)))
+ (test-assert label (hashtable? table))
+ (test-eq label #f (hashtable-hash-function table))
+ (test-eq label eq? (hashtable-equivalence-function table))
+ (test-eq label #f (hashtable-weakness table))
+ (test-assert label (hashtable-mutable? table))))))
+ (test-group "eqv"
+ (let ((tables (list (make-eqv-hashtable)
+ (make-eqv-hashtable 10)
+ (make-eqv-hashtable #f #f)
+ (make-hashtable #f eqv?)
+ (alist->eqv-hashtable '((a . b) (c . d)))
+ (alist->eqv-hashtable 10 '((a . b) (c . d)))
+ (alist->eqv-hashtable #f #f '((a . b) (c . d))))))
+ (do ((tables tables (cdr tables))
+ (i 0 (+ i 1)))
+ ((null? tables))
+ (let ((table (car tables))
+ (label (number->string i)))
+ (test-assert label (hashtable? table))
+ (test-eq label #f (hashtable-hash-function table))
+ (test-eq label eqv? (hashtable-equivalence-function table))
+ (test-eq label #f (hashtable-weakness table))
+ (test-assert label (hashtable-mutable? table))))))
+ (test-group "equal"
+ (let ((tables (list (make-hashtable equal-hash equal?)
+ (make-hashtable equal-hash equal? 10)
+ (make-hashtable equal-hash equal? #f #f)
+ (alist->hashtable equal-hash equal?
+ '((a . b) (c . d)))
+ (alist->hashtable equal-hash equal? 10
+ '((a . b) (c . d)))
+ (alist->hashtable equal-hash equal? #f #f
+ '((a . b) (c . d))))))
+ (do ((tables tables (cdr tables))
+ (i 0 (+ i 1)))
+ ((null? tables))
+ (let ((table (car tables))
+ (label (number->string i)))
+ (test-assert label (hashtable? table))
+ (test-eq label equal-hash (hashtable-hash-function table))
+ (test-eq label equal? (hashtable-equivalence-function table))
+ (test-eq label #f (hashtable-weakness table))
+ (test-assert label (hashtable-mutable? table))))
+ (let ((table (make-hashtable (cons equal-hash equal-hash) equal?)))
+ (let ((hash (hashtable-hash-function table)))
+ (test-assert (or (eq? equal-hash hash)
+ (and (eq? equal-hash (car hash))
+ (eq? equal-hash (cdr hash)))))))))
+ (test-group "alist"
+ (let ((tables (list (alist->eq-hashtable '((a . b) (a . c)))
+ (alist->eqv-hashtable '((a . b) (a . c)))
+ (alist->hashtable equal-hash equal?
+ '((a . b) (a . c))))))
+ (do ((tables tables (cdr tables))
+ (i 0 (+ i 1)))
+ ((null? tables))
+ (let ((table (car tables))
+ (label (number->string i)))
+ (test-eq label 'b (hashtable-ref table 'a)))))))
+
+(test-group "procedures"
+ (test-group "basics"
+ (let ((table (make-eq-hashtable)))
+ (test-group "ref"
+ (test-error (hashtable-ref table 'a))
+ (test-eq 'b (hashtable-ref table 'a 'b))
+ (test-assert (not (hashtable-contains? table 'a)))
+ (test-eqv 0 (hashtable-size table)))
+ (test-group "set"
+ (hashtable-set! table 'a 'c)
+ (test-eq 'c (hashtable-ref table 'a))
+ (test-eq 'c (hashtable-ref table 'a 'b))
+ (test-assert (hashtable-contains? table 'a))
+ (test-eqv 1 (hashtable-size table)))
+ (test-group "delete"
+ (hashtable-delete! table 'a)
+ (test-error (hashtable-ref table 'a))
+ (test-eq 'b (hashtable-ref table 'a 'b))
+ (test-assert (not (hashtable-contains? table 'a)))
+ (test-eqv 0 (hashtable-size table)))))
+ (test-group "advanced"
+ (let ((table (make-eq-hashtable)))
+ (test-group "lookup"
+ (let-values (((x found?) (hashtable-lookup table 'a)))
+ (test-assert (not found?))))
+ (test-group "update"
+ (test-error (hashtable-update! table 'a (lambda (x) (+ x 1))))
+ (hashtable-update! table 'a (lambda (x) (+ x 1)) 0)
+ (let-values (((x found?) (hashtable-lookup table 'a)))
+ (test-eqv 1 x)
+ (test-assert found?))
+ (hashtable-update! table 'a (lambda (x) (+ x 1)))
+ (let-values (((x found?) (hashtable-lookup table 'a)))
+ (test-eqv x 2)
+ (test-assert found?))
+ (hashtable-update! table 'a (lambda (x) (+ x 1)) 0)
+ (let-values (((x found?) (hashtable-lookup table 'a)))
+ (test-eqv x 3)
+ (test-assert found?)))
+ (test-group "intern"
+ (test-eqv 0 (hashtable-intern! table 'b (lambda () 0)))
+ (test-eqv 0 (hashtable-intern! table 'b (lambda () 1))))))
+ (test-group "copy/clear"
+ (let ((table (alist->hashtable equal-hash equal? '((a . b)))))
+ (test-group "copy"
+ (let ((table2 (hashtable-copy table)))
+ (test-eq equal-hash (hashtable-hash-function table2))
+ (test-eq equal? (hashtable-equivalence-function table2))
+ (test-eq 'b (hashtable-ref table2 'a))
+ (test-error (hashtable-set! table2 'a 'c)))
+ (let ((table2 (hashtable-copy table #f)))
+ (test-eq equal-hash (hashtable-hash-function table2))
+ (test-eq equal? (hashtable-equivalence-function table2))
+ (test-eq 'b (hashtable-ref table2 'a))
+ (test-error (hashtable-set! table2 'a 'c)))
+ (let ((table2 (hashtable-copy table #t)))
+ (test-eq equal-hash (hashtable-hash-function table2))
+ (test-eq equal? (hashtable-equivalence-function table2))
+ (test-eq 'b (hashtable-ref table2 'a))
+ (hashtable-set! table2 'a 'c)
+ (test-eq 'c (hashtable-ref table2 'a)))
+ (let ((table2 (hashtable-copy table #f #f)))
+ (test-eq equal-hash (hashtable-hash-function table2))
+ (test-eq equal? (hashtable-equivalence-function table2))
+ (test-eq #f (hashtable-weakness table2))))
+ (test-group "clear"
+ (let ((table2 (hashtable-copy table #t)))
+ (hashtable-clear! table2)
+ (test-eqv 0 (hashtable-size table2)))
+ (let ((table2 (hashtable-copy table #t)))
+ (hashtable-clear! table2 10)
+ (test-eqv 0 (hashtable-size table2))))
+ (test-group "empty-copy"
+ (let ((table2 (hashtable-empty-copy table)))
+ (test-eq equal-hash (hashtable-hash-function table2))
+ (test-eq equal? (hashtable-equivalence-function table2))
+ (test-eqv 0 (hashtable-size table2)))
+ (let ((table2 (hashtable-empty-copy table 10)))
+ (test-eq equal-hash (hashtable-hash-function table2))
+ (test-eq equal? (hashtable-equivalence-function table2))
+ (test-eqv 0 (hashtable-size table2))))))
+ (test-group "keys/values"
+ (let ((table (alist->eq-hashtable '((a . b) (c . d)))))
+ (test-assert (lset= eq? '(a c) (vector->list (hashtable-keys table))))
+ (test-assert (lset= eq? '(b d) (vector->list (hashtable-values table))))
+ (let-values (((keys values) (hashtable-entries table)))
+ (test-assert (lset= eq? '(a c) (vector->list keys)))
+ (test-assert (lset= eq? '(b d) (vector->list values))))
+ (test-assert (lset= eq? '(a c) (hashtable-key-list table)))
+ (test-assert (lset= eq? '(b d) (hashtable-value-list table)))
+ (let-values (((keys values) (hashtable-entry-lists table)))
+ (test-assert (lset= eq? '(a c) keys))
+ (test-assert (lset= eq? '(b d) values)))))
+ (test-group "iteration"
+ (test-group "walk"
+ (let ((keys '())
+ (values '()))
+ (hashtable-walk (alist->eq-hashtable '((a . b) (c . d)))
+ (lambda (k v)
+ (set! keys (cons k keys))
+ (set! values (cons v values))))
+ (test-assert (lset= eq? '(a c) keys))
+ (test-assert (lset= eq? '(b d) values))))
+ (test-group "update-all"
+ (let ((table (alist->eq-hashtable '((a . b) (c . d)))))
+ (hashtable-update-all! table
+ (lambda (k v)
+ (string->symbol (string-append (symbol->string v) "x"))))
+ (test-assert (lset= eq? '(a c) (hashtable-key-list table)))
+ (test-assert (lset= eq? '(bx dx) (hashtable-value-list table)))))
+ (test-group "prune"
+ (let ((table (alist->eq-hashtable '((a . b) (c . d)))))
+ (hashtable-prune! table (lambda (k v) (eq? k 'a)))
+ (test-assert (not (hashtable-contains? table 'a)))
+ (test-assert (hashtable-contains? table 'c))))
+ (test-group "merge"
+ (let ((table (alist->eq-hashtable '((a . b) (c . d))))
+ (table2 (alist->eq-hashtable '((a . x) (e . f)))))
+ (hashtable-merge! table table2)
+ (test-assert (lset= eq? '(a c e) (hashtable-key-list table)))
+ (test-assert (lset= eq? '(x d f) (hashtable-value-list table)))))
+ (test-group "sum"
+ (let ((table (alist->eq-hashtable '((a . b) (c . d)))))
+ (test-assert (lset= eq? '(a b c d)
+ (hashtable-sum table '()
+ (lambda (k v acc)
+ (lset-adjoin eq? acc k v)))))))
+ (test-group "map->lset"
+ (let ((table (alist->eq-hashtable '((a . b) (c . d)))))
+ (test-assert (lset= equal? '((a . b) (c . d))
+ (hashtable-map->lset table cons)))))
+ (test-group "find"
+ (let ((table (alist->eq-hashtable '((a . b) (c . d)))))
+ (let-values (((k v f?) (hashtable-find table
+ (lambda (k v)
+ (eq? k 'a)))))
+ (test-assert (and f? (eq? k 'a) (eq? v 'b))))
+ (let-values (((k v f?) (hashtable-find table (lambda (k v) #f))))
+ (test-assert (not f?)))))
+ (test-group "misc"
+ (test-group "empty?"
+ (test-assert (hashtable-empty? (alist->eq-hashtable '())))
+ (test-assert (not (hashtable-empty? (alist->eq-hashtable '((a . b)))))))
+ (test-group "pop!"
+ (test-error (hashtable-pop! (make-eq-hashtable)))
+ (let ((table (alist->eq-hashtable '((a . b)))))
+ (let-values (((k v) (hashtable-pop! table)))
+ (test-eq 'a k)
+ (test-eq 'b v)
+ (test-assert (hashtable-empty? table)))))
+ (test-group "inc!"
+ (let ((table (alist->eq-hashtable '((a . 0)))))
+ (hashtable-inc! table 'a)
+ (test-eqv 1 (hashtable-ref table 'a))
+ (hashtable-inc! table 'a 2)
+ (test-eqv 3 (hashtable-ref table 'a))))
+ (test-group "dec!"
+ (let ((table (alist->eq-hashtable '((a . 0)))))
+ (hashtable-dec! table 'a)
+ (test-eqv -1 (hashtable-ref table 'a))
+ (hashtable-dec! table 'a 2)
+ (test-eqv -3 (hashtable-ref table 'a)))))))
+
+(test-group "hashing"
+ (test-assert (and (exact-integer? (hash-salt))))
+ (test-assert (not (negative? (hash-salt))))
+ (test-assert (= (equal-hash (list "foo" 'bar 42))
+ (equal-hash (list "foo" 'bar 42))))
+ (test-assert (= (string-hash (string-copy "foo"))
+ (string-hash (string-copy "foo"))))
+ (test-assert (= (string-ci-hash (string-copy "foo"))
+ (string-ci-hash (string-copy "FOO"))))
+ (test-assert (= (symbol-hash (string->symbol "foo"))
+ (symbol-hash (string->symbol "foo")))))
+
+(test-end "SRFI-126")
+
+(display
+ (string-append
+ "\n"
+ "NOTE: On implementations using the (r6rs hashtables) library from Larceny,\n"
+ " 14 tests are expected to fail in relation to make-eq-hashtable and\n"
+ " make-eqv-hashtable returning hashtables whose hash functions are\n"
+ " exposed instead of being #f. We have no obvious way to detect this\n"
+ " within this portable test suite, hence no XFAIL results.\n"))
+
+;; Local Variables:
+;; eval: (put (quote test-group) (quote scheme-indent-function) 1)
+;; End:
+;; Copyright (c) 2005, 2006, 2007, 2012, 2013 Per Bothner
+;; Added "full" support for Chicken, Gauche, Guile and SISC.
+;; Alex Shinn, Copyright (c) 2005.
+;; Modified for Scheme Spheres by Álvaro Castro-Castilla, Copyright (c) 2012.
+;; Support for Guile 2 by Mark H Weaver <mhw@netris.org>, Copyright (c) 2014.
+;; Refactored by Taylan Ulrich Bayırlı/Kammer, Copyright (c) 2014, 2015.
+;;
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Note: to prevent producing massive amounts of code from the macro-expand
+;;; phase (which makes compile times suffer and may hit code size limits in some
+;;; systems), keep macro bodies minimal by delegating work to procedures.
+
+
+;;; Grouping
+
+(define (maybe-install-default-runner suite-name)
+ (when (not (test-runner-current))
+ (let* ((log-file (string-append suite-name ".srfi64.log"))
+ (runner (test-runner-simple log-file)))
+ (%test-runner-auto-installed! runner #t)
+ (test-runner-current runner))))
+
+(define (maybe-uninstall-default-runner)
+ (when (%test-runner-auto-installed? (test-runner-current))
+ (test-runner-current #f)))
+
+(define test-begin
+ (case-lambda
+ ((name)
+ (test-begin name #f))
+ ((name count)
+ (maybe-install-default-runner name)
+ (let ((r (test-runner-current)))
+ (let ((skip-list (%test-runner-skip-list r))
+ (skip-save (%test-runner-skip-save r))
+ (fail-list (%test-runner-fail-list r))
+ (fail-save (%test-runner-fail-save r))
+ (total-count (%test-runner-total-count r))
+ (count-list (%test-runner-count-list r))
+ (group-stack (test-runner-group-stack r)))
+ ((test-runner-on-group-begin r) r name count)
+ (%test-runner-skip-save! r (cons skip-list skip-save))
+ (%test-runner-fail-save! r (cons fail-list fail-save))
+ (%test-runner-count-list! r (cons (cons total-count count)
+ count-list))
+ (test-runner-group-stack! r (cons name group-stack)))))))
+
+(define test-end
+ (case-lambda
+ (()
+ (test-end #f))
+ ((name)
+ (let* ((r (test-runner-get))
+ (groups (test-runner-group-stack r)))
+ (test-result-clear r)
+ (when (null? groups)
+ (error "test-end not in a group"))
+ (when (and name (not (equal? name (car groups))))
+ ((test-runner-on-bad-end-name r) r name (car groups)))
+ (let* ((count-list (%test-runner-count-list r))
+ (expected-count (cdar count-list))
+ (saved-count (caar count-list))
+ (group-count (- (%test-runner-total-count r) saved-count)))
+ (when (and expected-count
+ (not (= expected-count group-count)))
+ ((test-runner-on-bad-count r) r group-count expected-count))
+ ((test-runner-on-group-end r) r)
+ (test-runner-group-stack! r (cdr (test-runner-group-stack r)))
+ (%test-runner-skip-list! r (car (%test-runner-skip-save r)))
+ (%test-runner-skip-save! r (cdr (%test-runner-skip-save r)))
+ (%test-runner-fail-list! r (car (%test-runner-fail-save r)))
+ (%test-runner-fail-save! r (cdr (%test-runner-fail-save r)))
+ (%test-runner-count-list! r (cdr count-list))
+ (when (null? (test-runner-group-stack r))
+ ((test-runner-on-final r) r)
+ (maybe-uninstall-default-runner)))))))
+
+(define-syntax test-group
+ (syntax-rules ()
+ ((_ <name> <body> . <body>*)
+ (%test-group <name> (lambda () <body> . <body>*)))))
+
+(define (%test-group name thunk)
+ (begin
+ (maybe-install-default-runner name)
+ (let ((runner (test-runner-get)))
+ (test-result-clear runner)
+ (test-result-set! runner 'name name)
+ (unless (test-skip? runner)
+ (dynamic-wind
+ (lambda () (test-begin name))
+ thunk
+ (lambda () (test-end name)))))))
+
+(define-syntax test-group-with-cleanup
+ (syntax-rules ()
+ ((_ <name> <body> <body>* ... <cleanup>)
+ (test-group <name>
+ (dynamic-wind (lambda () #f)
+ (lambda () <body> <body>* ...)
+ (lambda () <cleanup>))))))
+
+
+;;; Skipping, expected-failing, matching
+
+(define (test-skip . specs)
+ (let ((runner (test-runner-get)))
+ (%test-runner-skip-list!
+ runner (cons (apply test-match-all specs)
+ (%test-runner-skip-list runner)))))
+
+(define (test-skip? runner)
+ (let ((run-list (%test-runner-run-list runner))
+ (skip-list (%test-runner-skip-list runner)))
+ (or (and run-list (not (any-pred run-list runner)))
+ (any-pred skip-list runner))))
+
+(define (test-expect-fail . specs)
+ (let ((runner (test-runner-get)))
+ (%test-runner-fail-list!
+ runner (cons (apply test-match-all specs)
+ (%test-runner-fail-list runner)))))
+
+(define (test-match-any . specs)
+ (let ((preds (map make-pred specs)))
+ (lambda (runner)
+ (any-pred preds runner))))
+
+(define (test-match-all . specs)
+ (let ((preds (map make-pred specs)))
+ (lambda (runner)
+ (every-pred preds runner))))
+
+(define (make-pred spec)
+ (cond
+ ((procedure? spec)
+ spec)
+ ((integer? spec)
+ (test-match-nth 1 spec))
+ ((string? spec)
+ (test-match-name spec))
+ (else
+ (error "not a valid test specifier" spec))))
+
+(define test-match-nth
+ (case-lambda
+ ((n) (test-match-nth n 1))
+ ((n count)
+ (let ((i 0))
+ (lambda (runner)
+ (set! i (+ i 1))
+ (and (>= i n) (< i (+ n count))))))))
+
+(define (test-match-name name)
+ (lambda (runner)
+ (equal? name (test-runner-test-name runner))))
+
+;;; Beware: all predicates must be called because they might have side-effects;
+;;; no early returning or and/or short-circuiting of procedure calls allowed.
+
+(define (any-pred preds object)
+ (let loop ((matched? #f)
+ (preds preds))
+ (if (null? preds)
+ matched?
+ (let ((result ((car preds) object)))
+ (loop (or matched? result)
+ (cdr preds))))))
+
+(define (every-pred preds object)
+ (let loop ((failed? #f)
+ (preds preds))
+ (if (null? preds)
+ (not failed?)
+ (let ((result ((car preds) object)))
+ (loop (or failed? (not result))
+ (cdr preds))))))
+
+;;; Actual testing
+
+(define-syntax false-if-error
+ (syntax-rules ()
+ ((_ <expression> <runner>)
+ (guard (error
+ (else
+ (test-result-set! <runner> 'actual-error error)
+ #f))
+ <expression>))))
+
+(define (test-prelude source-info runner name form)
+ (test-result-clear runner)
+ (set-source-info! runner source-info)
+ (when name
+ (test-result-set! runner 'name name))
+ (test-result-set! runner 'source-form form)
+ (let ((skip? (test-skip? runner)))
+ (if skip?
+ (test-result-set! runner 'result-kind 'skip)
+ (let ((fail-list (%test-runner-fail-list runner)))
+ (when (any-pred fail-list runner)
+ ;; For later inspection only.
+ (test-result-set! runner 'result-kind 'xfail))))
+ ((test-runner-on-test-begin runner) runner)
+ (not skip?)))
+
+(define (test-postlude runner)
+ (let ((result-kind (test-result-kind runner)))
+ (case result-kind
+ ((pass)
+ (test-runner-pass-count! runner (+ 1 (test-runner-pass-count runner))))
+ ((fail)
+ (test-runner-fail-count! runner (+ 1 (test-runner-fail-count runner))))
+ ((xpass)
+ (test-runner-xpass-count! runner (+ 1 (test-runner-xpass-count runner))))
+ ((xfail)
+ (test-runner-xfail-count! runner (+ 1 (test-runner-xfail-count runner))))
+ ((skip)
+ (test-runner-skip-count! runner (+ 1 (test-runner-skip-count runner)))))
+ (%test-runner-total-count! runner (+ 1 (%test-runner-total-count runner)))
+ ((test-runner-on-test-end runner) runner)))
+
+(define (set-result-kind! runner pass?)
+ (test-result-set! runner 'result-kind
+ (if (eq? (test-result-kind runner) 'xfail)
+ (if pass? 'xpass 'xfail)
+ (if pass? 'pass 'fail))))
+
+;;; We need to use some trickery to get the source info right. The important
+;;; thing is to pass a syntax object that is a pair to `source-info', and make
+;;; sure this syntax object comes from user code and not from ourselves.
+
+(define-syntax test-assert
+ (syntax-rules ()
+ ((_ . <rest>)
+ (test-assert/source-info (source-info <rest>) . <rest>))))
+
+(define-syntax test-assert/source-info
+ (syntax-rules ()
+ ((_ <source-info> <expr>)
+ (test-assert/source-info <source-info> #f <expr>))
+ ((_ <source-info> <name> <expr>)
+ (%test-assert <source-info> <name> '<expr> (lambda () <expr>)))))
+
+(define (%test-assert source-info name form thunk)
+ (let ((runner (test-runner-get)))
+ (when (test-prelude source-info runner name form)
+ (let ((val (false-if-error (thunk) runner)))
+ (test-result-set! runner 'actual-value val)
+ (set-result-kind! runner val)))
+ (test-postlude runner)))
+
+(define-syntax test-compare
+ (syntax-rules ()
+ ((_ . <rest>)
+ (test-compare/source-info (source-info <rest>) . <rest>))))
+
+(define-syntax test-compare/source-info
+ (syntax-rules ()
+ ((_ <source-info> <compare> <expected> <expr>)
+ (test-compare/source-info <source-info> <compare> #f <expected> <expr>))
+ ((_ <source-info> <compare> <name> <expected> <expr>)
+ (%test-compare <source-info> <compare> <name> <expected> '<expr>
+ (lambda () <expr>)))))
+
+(define (%test-compare source-info compare name expected form thunk)
+ (let ((runner (test-runner-get)))
+ (when (test-prelude source-info runner name form)
+ (test-result-set! runner 'expected-value expected)
+ (let ((pass? (false-if-error
+ (let ((val (thunk)))
+ (test-result-set! runner 'actual-value val)
+ (compare expected val))
+ runner)))
+ (set-result-kind! runner pass?)))
+ (test-postlude runner)))
+
+(define-syntax test-equal
+ (syntax-rules ()
+ ((_ . <rest>)
+ (test-compare/source-info (source-info <rest>) equal? . <rest>))))
+
+(define-syntax test-eqv
+ (syntax-rules ()
+ ((_ . <rest>)
+ (test-compare/source-info (source-info <rest>) eqv? . <rest>))))
+
+(define-syntax test-eq
+ (syntax-rules ()
+ ((_ . <rest>)
+ (test-compare/source-info (source-info <rest>) eq? . <rest>))))
+
+(define (approx= margin)
+ (lambda (value expected)
+ (let ((rval (real-part value))
+ (ival (imag-part value))
+ (rexp (real-part expected))
+ (iexp (imag-part expected)))
+ (and (>= rval (- rexp margin))
+ (>= ival (- iexp margin))
+ (<= rval (+ rexp margin))
+ (<= ival (+ iexp margin))))))
+
+(define-syntax test-approximate
+ (syntax-rules ()
+ ((_ . <rest>)
+ (test-approximate/source-info (source-info <rest>) . <rest>))))
+
+(define-syntax test-approximate/source-info
+ (syntax-rules ()
+ ((_ <source-info> <expected> <expr> <error-margin>)
+ (test-approximate/source-info
+ <source-info> #f <expected> <expr> <error-margin>))
+ ((_ <source-info> <name> <expected> <expr> <error-margin>)
+ (test-compare/source-info
+ <source-info> (approx= <error-margin>) <name> <expected> <expr>))))
+
+(define (error-matches? error type)
+ (cond
+ ((eq? type #t)
+ #t)
+ ((condition-type? type)
+ (and (condition? error) (condition-has-type? error type)))
+ ((procedure? type)
+ (type error))
+ (else
+ (let ((runner (test-runner-get)))
+ ((%test-runner-on-bad-error-type runner) runner type error))
+ #f)))
+
+(define-syntax test-error
+ (syntax-rules ()
+ ((_ . <rest>)
+ (test-error/source-info (source-info <rest>) . <rest>))))
+
+(define-syntax test-error/source-info
+ (syntax-rules ()
+ ((_ <source-info> <expr>)
+ (test-error/source-info <source-info> #f #t <expr>))
+ ((_ <source-info> <error-type> <expr>)
+ (test-error/source-info <source-info> #f <error-type> <expr>))
+ ((_ <source-info> <name> <error-type> <expr>)
+ (%test-error <source-info> <name> <error-type> '<expr>
+ (lambda () <expr>)))))
+
+(define (%test-error source-info name error-type form thunk)
+ (let ((runner (test-runner-get)))
+ (when (test-prelude source-info runner name form)
+ (test-result-set! runner 'expected-error error-type)
+ (let ((pass? (guard (error (else (test-result-set!
+ runner 'actual-error error)
+ (error-matches? error error-type)))
+ (let ((val (thunk)))
+ (test-result-set! runner 'actual-value val))
+ #f)))
+ (set-result-kind! runner pass?)))
+ (test-postlude runner)))
+
+(define (default-module)
+ (cond-expand
+ (guile (current-module))
+ (else #f)))
+
+(define test-read-eval-string
+ (case-lambda
+ ((string)
+ (test-read-eval-string string (default-module)))
+ ((string env)
+ (let* ((port (open-input-string string))
+ (form (read port)))
+ (if (eof-object? (read-char port))
+ (if env
+ (eval form env)
+ (eval form))
+ (error "(not at eof)"))))))
+
+
+;;; Test runner control flow
+
+(define-syntax test-with-runner
+ (syntax-rules ()
+ ((_ <runner> <body> . <body>*)
+ (let ((saved-runner (test-runner-current)))
+ (dynamic-wind
+ (lambda () (test-runner-current <runner>))
+ (lambda () <body> . <body>*)
+ (lambda () (test-runner-current saved-runner)))))))
+
+(define (test-apply first . rest)
+ (let ((runner (if (test-runner? first)
+ first
+ (or (test-runner-current) (test-runner-create))))
+ (run-list (if (test-runner? first)
+ (drop-right rest 1)
+ (cons first (drop-right rest 1))))
+ (proc (last rest)))
+ (test-with-runner runner
+ (let ((saved-run-list (%test-runner-run-list runner)))
+ (%test-runner-run-list! runner run-list)
+ (proc)
+ (%test-runner-run-list! runner saved-run-list)))))
+
+
+;;; Indicate success/failure via exit status
+
+(define (test-exit)
+ (let ((runner (test-runner-current)))
+ (when (not runner)
+ (error "No test runner installed. Might have been auto-removed
+by test-end if you had not installed one explicitly."))
+ (if (and (zero? (test-runner-xpass-count runner))
+ (zero? (test-runner-fail-count runner)))
+ (exit 0)
+ (exit 1))))
+
+;;; execution.scm ends here
+;; Copyright (c) 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
+;;
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; In some systems, a macro use like (source-info ...), that resides in a
+;;; syntax-rules macro body, first gets inserted into the place where the
+;;; syntax-rules macro was used, and then the transformer of 'source-info' is
+;;; called with a syntax object that has the source location information of that
+;;; position. That works fine when the user calls e.g. (test-assert ...), whose
+;;; body contains (source-info ...); the user gets the source location of the
+;;; (test-assert ...) call as intended, and not the source location of the real
+;;; (source-info ...) call.
+
+;;; In other systems, *first* the (source-info ...) is processed to get its real
+;;; position, which is within the body of a syntax-rules macro like test-assert,
+;;; so no matter where the user calls (test-assert ...), they get source
+;;; location information of where we defined test-assert with the call to
+;;; (source-info ...) in its body. That's arguably more correct behavior,
+;;; although in this case it makes our job a bit harder; we need to get the
+;;; source location from an argument to 'source-info' instead.
+
+(define (canonical-syntax form arg)
+ (cond-expand
+ (kawa arg)
+ (guile-2 form)
+ (else #f)))
+
+(cond-expand
+ ((or kawa guile-2)
+ (define-syntax source-info
+ (lambda (stx)
+ (syntax-case stx ()
+ ((_ <x>)
+ (let* ((stx (canonical-syntax stx (syntax <x>)))
+ (file (syntax-source-file stx))
+ (line (syntax-source-line stx)))
+ (quasisyntax
+ (cons (unsyntax file) (unsyntax line)))))))))
+ (else
+ (define-syntax source-info
+ (syntax-rules ()
+ ((_ <x>)
+ #f)))))
+
+(define (syntax-source-file stx)
+ (cond-expand
+ (kawa
+ (syntax-source stx))
+ (guile-2
+ (let ((source (syntax-source stx)))
+ (and source (assq-ref source 'filename))))
+ (else
+ #f)))
+
+(define (syntax-source-line stx)
+ (cond-expand
+ (kawa
+ (syntax-line stx))
+ (guile-2
+ (let ((source (syntax-source stx)))
+ (and source (assq-ref source 'line))))
+ (else
+ #f)))
+
+(define (set-source-info! runner source-info)
+ (when source-info
+ (test-result-set! runner 'source-file (car source-info))
+ (test-result-set! runner 'source-line (cdr source-info))))
+
+;;; source-info.body.scm ends here
+;; Copyright (c) 2005, 2006, 2007, 2012, 2013 Per Bothner
+;; Added "full" support for Chicken, Gauche, Guile and SISC.
+;; Alex Shinn, Copyright (c) 2005.
+;; Modified for Scheme Spheres by Álvaro Castro-Castilla, Copyright (c) 2012.
+;; Support for Guile 2 by Mark H Weaver <mhw@netris.org>, Copyright (c) 2014.
+;; Refactored by Taylan Ulrich Bayırlı/Kammer, Copyright (c) 2014, 2015.
+;;
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Helpers
+
+(define (string-join strings delimiter)
+ (if (null? strings)
+ ""
+ (let loop ((result (car strings))
+ (rest (cdr strings)))
+ (if (null? rest)
+ result
+ (loop (string-append result delimiter (car rest))
+ (cdr rest))))))
+
+(define (truncate-string string length)
+ (define (newline->space c) (if (char=? #\newline c) #\space c))
+ (let* ((string (string-map newline->space string))
+ (fill "...")
+ (fill-len (string-length fill))
+ (string-len (string-length string)))
+ (if (<= string-len (+ length fill-len))
+ string
+ (let-values (((q r) (floor/ length 4)))
+ ;; Left part gets 3/4 plus the remainder.
+ (let ((left-end (+ (* q 3) r))
+ (right-start (- string-len q)))
+ (string-append (substring string 0 left-end)
+ fill
+ (substring string right-start string-len)))))))
+
+(define (print runner format-string . args)
+ (apply format #t format-string args)
+ (let ((port (%test-runner-log-port runner)))
+ (when port
+ (apply format port format-string args))))
+
+;;; Main
+
+(define test-runner-simple
+ (case-lambda
+ (()
+ (test-runner-simple #f))
+ ((log-file)
+ (let ((runner (test-runner-null)))
+ (test-runner-reset runner)
+ (test-runner-on-group-begin! runner test-on-group-begin-simple)
+ (test-runner-on-group-end! runner test-on-group-end-simple)
+ (test-runner-on-final! runner test-on-final-simple)
+ (test-runner-on-test-begin! runner test-on-test-begin-simple)
+ (test-runner-on-test-end! runner test-on-test-end-simple)
+ (test-runner-on-bad-count! runner test-on-bad-count-simple)
+ (test-runner-on-bad-end-name! runner test-on-bad-end-name-simple)
+ (%test-runner-on-bad-error-type! runner on-bad-error-type)
+ (%test-runner-log-file! runner log-file)
+ runner))))
+
+(when (not (test-runner-factory))
+ (test-runner-factory test-runner-simple))
+
+(define (test-on-group-begin-simple runner name count)
+ (when (null? (test-runner-group-stack runner))
+ (maybe-start-logging runner)
+ (print runner "Test suite begin: ~a~%" name)))
+
+(define (test-on-group-end-simple runner)
+ (let ((name (car (test-runner-group-stack runner))))
+ (when (= 1 (length (test-runner-group-stack runner)))
+ (print runner "Test suite end: ~a~%" name))))
+
+(define (test-on-final-simple runner)
+ (print runner "Passes: ~a\n" (test-runner-pass-count runner))
+ (print runner "Expected failures: ~a\n" (test-runner-xfail-count runner))
+ (print runner "Failures: ~a\n" (test-runner-fail-count runner))
+ (print runner "Unexpected passes: ~a\n" (test-runner-xpass-count runner))
+ (print runner "Skipped tests: ~a~%" (test-runner-skip-count runner))
+ (maybe-finish-logging runner))
+
+(define (maybe-start-logging runner)
+ (let ((log-file (%test-runner-log-file runner)))
+ (when log-file
+ ;; The possible race-condition here doesn't bother us.
+ (when (file-exists? log-file)
+ (delete-file log-file))
+ (%test-runner-log-port! runner (open-output-file log-file))
+ (print runner "Writing log file: ~a~%" log-file))))
+
+(define (maybe-finish-logging runner)
+ (let ((log-file (%test-runner-log-file runner)))
+ (when log-file
+ (print runner "Wrote log file: ~a~%" log-file)
+ (close-output-port (%test-runner-log-port runner)))))
+
+(define (test-on-test-begin-simple runner)
+ (values))
+
+(define (test-on-test-end-simple runner)
+ (let* ((result-kind (test-result-kind runner))
+ (result-kind-name (case result-kind
+ ((pass) "PASS") ((fail) "FAIL")
+ ((xpass) "XPASS") ((xfail) "XFAIL")
+ ((skip) "SKIP")))
+ (name (let ((name (test-runner-test-name runner)))
+ (if (string=? "" name)
+ (truncate-string
+ (format #f "~a" (test-result-ref runner 'source-form))
+ 30)
+ name)))
+ (label (string-join (append (test-runner-group-path runner)
+ (list name))
+ ": ")))
+ (print runner "[~a] ~a~%" result-kind-name label)
+ (when (memq result-kind '(fail xpass))
+ (let ((nil (cons #f #f)))
+ (define (found? value)
+ (not (eq? nil value)))
+ (define (maybe-print value message)
+ (when (found? value)
+ (print runner message value)))
+ (let ((file (test-result-ref runner 'source-file "(unknown file)"))
+ (line (test-result-ref runner 'source-line "(unknown line)"))
+ (expression (test-result-ref runner 'source-form))
+ (expected-value (test-result-ref runner 'expected-value nil))
+ (actual-value (test-result-ref runner 'actual-value nil))
+ (expected-error (test-result-ref runner 'expected-error nil))
+ (actual-error (test-result-ref runner 'actual-error nil)))
+ (print runner "~a:~a: ~s~%" file line expression)
+ (maybe-print expected-value "Expected value: ~s~%")
+ (maybe-print expected-error "Expected error: ~a~%")
+ (when (or (found? expected-value) (found? expected-error))
+ (maybe-print actual-value "Returned value: ~s~%"))
+ (maybe-print actual-error "Raised error: ~a~%")
+ (newline))))))
+
+(define (test-on-bad-count-simple runner count expected-count)
+ (print runner "*** Total number of tests was ~a but should be ~a. ***~%"
+ count expected-count)
+ (print runner
+ "*** Discrepancy indicates testsuite error or exceptions. ***~%"))
+
+(define (test-on-bad-end-name-simple runner begin-name end-name)
+ (error (format #f "Test-end \"~a\" does not match test-begin \"~a\"."
+ end-name begin-name)))
+
+(define (on-bad-error-type runner type error)
+ (print runner "WARNING: unknown error type predicate: ~a~%" type)
+ (print runner " error was: ~a~%" error))
+
+;;; test-runner-simple.scm ends here
+;; Copyright (c) 2005, 2006, 2007, 2012, 2013 Per Bothner
+;; Added "full" support for Chicken, Gauche, Guile and SISC.
+;; Alex Shinn, Copyright (c) 2005.
+;; Modified for Scheme Spheres by Álvaro Castro-Castilla, Copyright (c) 2012.
+;; Support for Guile 2 by Mark H Weaver <mhw@netris.org>, Copyright (c) 2014.
+;; Refactored by Taylan Ulrich Bayırlı/Kammer, Copyright (c) 2014, 2015.
+;;
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+
+;;; The data type
+
+(define-record-type <test-runner>
+ (make-test-runner) test-runner?
+
+ (result-alist test-result-alist test-result-alist!)
+
+ (pass-count test-runner-pass-count test-runner-pass-count!)
+ (fail-count test-runner-fail-count test-runner-fail-count!)
+ (xpass-count test-runner-xpass-count test-runner-xpass-count!)
+ (xfail-count test-runner-xfail-count test-runner-xfail-count!)
+ (skip-count test-runner-skip-count test-runner-skip-count!)
+ (total-count %test-runner-total-count %test-runner-total-count!)
+
+ ;; Stack (list) of (count-at-start . expected-count):
+ (count-list %test-runner-count-list %test-runner-count-list!)
+
+ ;; Normally #f, except when in a test-apply.
+ (run-list %test-runner-run-list %test-runner-run-list!)
+
+ (skip-list %test-runner-skip-list %test-runner-skip-list!)
+ (fail-list %test-runner-fail-list %test-runner-fail-list!)
+
+ (skip-save %test-runner-skip-save %test-runner-skip-save!)
+ (fail-save %test-runner-fail-save %test-runner-fail-save!)
+
+ (group-stack test-runner-group-stack test-runner-group-stack!)
+
+ ;; Note: on-test-begin and on-test-end are unrelated to the test-begin and
+ ;; test-end forms in the execution library. They're called at the
+ ;; beginning/end of each individual test, whereas the test-begin and test-end
+ ;; forms demarcate test groups.
+
+ (on-group-begin test-runner-on-group-begin test-runner-on-group-begin!)
+ (on-test-begin test-runner-on-test-begin test-runner-on-test-begin!)
+ (on-test-end test-runner-on-test-end test-runner-on-test-end!)
+ (on-group-end test-runner-on-group-end test-runner-on-group-end!)
+ (on-final test-runner-on-final test-runner-on-final!)
+ (on-bad-count test-runner-on-bad-count test-runner-on-bad-count!)
+ (on-bad-end-name test-runner-on-bad-end-name test-runner-on-bad-end-name!)
+
+ (on-bad-error-type %test-runner-on-bad-error-type
+ %test-runner-on-bad-error-type!)
+
+ (aux-value test-runner-aux-value test-runner-aux-value!)
+
+ (auto-installed %test-runner-auto-installed? %test-runner-auto-installed!)
+
+ (log-file %test-runner-log-file %test-runner-log-file!)
+ (log-port %test-runner-log-port %test-runner-log-port!))
+
+(define (test-runner-group-path runner)
+ (reverse (test-runner-group-stack runner)))
+
+(define (test-runner-reset runner)
+ (test-result-alist! runner '())
+ (test-runner-pass-count! runner 0)
+ (test-runner-fail-count! runner 0)
+ (test-runner-xpass-count! runner 0)
+ (test-runner-xfail-count! runner 0)
+ (test-runner-skip-count! runner 0)
+ (%test-runner-total-count! runner 0)
+ (%test-runner-count-list! runner '())
+ (%test-runner-run-list! runner #f)
+ (%test-runner-skip-list! runner '())
+ (%test-runner-fail-list! runner '())
+ (%test-runner-skip-save! runner '())
+ (%test-runner-fail-save! runner '())
+ (test-runner-group-stack! runner '()))
+
+(define (test-runner-null)
+ (define (test-null-callback . args) #f)
+ (let ((runner (make-test-runner)))
+ (test-runner-reset runner)
+ (test-runner-on-group-begin! runner test-null-callback)
+ (test-runner-on-group-end! runner test-null-callback)
+ (test-runner-on-final! runner test-null-callback)
+ (test-runner-on-test-begin! runner test-null-callback)
+ (test-runner-on-test-end! runner test-null-callback)
+ (test-runner-on-bad-count! runner test-null-callback)
+ (test-runner-on-bad-end-name! runner test-null-callback)
+ (%test-runner-on-bad-error-type! runner test-null-callback)
+ (%test-runner-auto-installed! runner #f)
+ (%test-runner-log-file! runner #f)
+ (%test-runner-log-port! runner #f)
+ runner))
+
+
+;;; State
+
+(define test-result-ref
+ (case-lambda
+ ((runner key)
+ (test-result-ref runner key #f))
+ ((runner key default)
+ (let ((entry (assq key (test-result-alist runner))))
+ (if entry (cdr entry) default)))))
+
+(define (test-result-set! runner key value)
+ (let* ((alist (test-result-alist runner))
+ (entry (assq key alist)))
+ (if entry
+ (set-cdr! entry value)
+ (test-result-alist! runner (cons (cons key value) alist)))))
+
+(define (test-result-remove runner key)
+ (test-result-alist! runner (remove (lambda (entry)
+ (eq? key (car entry)))
+ (test-result-alist runner))))
+
+(define (test-result-clear runner)
+ (test-result-alist! runner '()))
+
+(define (test-runner-test-name runner)
+ (or (test-result-ref runner 'name) ""))
+
+(define test-result-kind
+ (case-lambda
+ (() (test-result-kind (test-runner-get)))
+ ((runner) (test-result-ref runner 'result-kind))))
+
+(define test-passed?
+ (case-lambda
+ (() (test-passed? (test-runner-get)))
+ ((runner) (memq (test-result-kind runner) '(pass xpass)))))
+
+
+;;; Factory and current instance
+
+(define test-runner-factory (make-parameter #f))
+
+(define (test-runner-create) ((test-runner-factory)))
+
+(define test-runner-current (make-parameter #f))
+
+(define (test-runner-get)
+ (or (test-runner-current)
+ (error "test-runner not initialized - test-begin missing?")))
+
+;;; test-runner.scm ends here
+(define-module (srfi srfi-64)
+ #\export
+ (test-begin
+ test-end test-assert test-eqv test-eq test-equal
+ test-approximate test-assert test-error test-apply test-with-runner
+ test-match-nth test-match-all test-match-any test-match-name
+ test-skip test-expect-fail test-read-eval-string
+ test-runner-group-path test-group test-group-with-cleanup
+ test-exit
+ test-result-ref test-result-set! test-result-clear test-result-remove
+ test-result-kind test-passed?
+ test-runner? test-runner-reset test-runner-null
+ test-runner-simple test-runner-current test-runner-factory test-runner-get
+ test-runner-create test-runner-test-name
+ test-runner-pass-count test-runner-pass-count!
+ test-runner-fail-count test-runner-fail-count!
+ test-runner-xpass-count test-runner-xpass-count!
+ test-runner-xfail-count test-runner-xfail-count!
+ test-runner-skip-count test-runner-skip-count!
+ test-runner-group-stack test-runner-group-stack!
+ test-runner-on-test-begin test-runner-on-test-begin!
+ test-runner-on-test-end test-runner-on-test-end!
+ test-runner-on-group-begin test-runner-on-group-begin!
+ test-runner-on-group-end test-runner-on-group-end!
+ test-runner-on-final test-runner-on-final!
+ test-runner-on-bad-count test-runner-on-bad-count!
+ test-runner-on-bad-end-name test-runner-on-bad-end-name!
+ test-result-alist test-result-alist!
+ test-runner-aux-value test-runner-aux-value!
+ test-on-group-begin-simple test-on-group-end-simple
+ test-on-bad-count-simple test-on-bad-end-name-simple
+ test-on-final-simple test-on-test-end-simple
+ test-on-final-simple))
+
+(cond-expand-provide (current-module) '(srfi-64))
+
+(import
+ (only (rnrs exceptions) guard)
+ (srfi srfi-1)
+ (srfi srfi-9)
+ (srfi srfi-11)
+ (srfi srfi-35))
+(include-from-path "srfi/srfi-64/source-info.body.scm")
+(include-from-path "srfi/srfi-64/test-runner.body.scm")
+(include-from-path "srfi/srfi-64/test-runner-simple.body.scm")
+(include-from-path "srfi/srfi-64/execution.body.scm")
+(define-library (srfi-tests aux)
+ (export define-tests)
+ (import
+ (scheme base)
+ (scheme write)
+ (scheme case-lambda)
+ (srfi 64))
+ (begin
+ (define-syntax define-tests
+ (syntax-rules ()
+ ((_ proc-name suite-name form ...)
+ (define proc-name
+ (case-lambda
+ (() (proc-name (test-runner-create)))
+ ((runner)
+ (parameterize ((test-runner-current runner))
+ (test-begin suite-name)
+ form ...
+ (test-end suite-name)
+ (and (= 0 (test-runner-xpass-count runner))
+ (= 0 (test-runner-fail-count runner))))))))))
+ ))
+;; Copyright (C) Oleg Kiselyov (1998). All Rights Reserved.
+
+;; Made an R7RS library by Taylan Ulrich Bayırlı/Kammer, Copyright (C) 2014.
+
+;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;; of this software and associated documentation files (the "Software"), to deal
+;; in the Software without restriction, including without limitation the rights
+;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+;; copies of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be included in
+;; all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+(define-library (srfi-tests srfi-2)
+ (export run-tests)
+ (import
+ (scheme base)
+ (scheme eval)
+ (srfi 2)
+ (srfi 64)
+ (srfi-tests aux))
+ (begin
+
+ (define (test-eval form)
+ (eval form (environment '(scheme base) '(srfi 2))))
+
+ ;; We want to check whether 'form' has indeed wrong syntax. We eval it and
+ ;; check for any exception, which is our best approximation.
+ (define-syntax test-syntax-error
+ (syntax-rules ()
+ ((_ form)
+ (test-error (test-eval 'form)))))
+
+ (define-tests run-tests "SRFI-2"
+ (test-equal 1 (and-let* () 1))
+ (test-equal 2 (and-let* () 1 2))
+ (test-equal #t (and-let* ()))
+
+ (test-equal #f (let ((x #f)) (and-let* (x))))
+ (test-equal 1 (let ((x 1)) (and-let* (x))))
+ (test-equal #f (and-let* ((x #f))))
+ (test-equal 1 (and-let* ((x 1))))
+ (test-equal #f (and-let* ((#f) (x 1))))
+ (test-equal 1 (and-let* ((2) (x 1))))
+ ;; Gauche allows let-binding a constant, thus fails to signal an error on
+ ;; the following two tests.
+ (cond-expand
+ (gauche (test-expect-fail 2))
+ (else))
+ (test-syntax-error (and-let* (#f (x 1))))
+ (test-syntax-error (and-let* (2 (x 1))))
+ (test-equal 2 (and-let* ((x 1) (2))))
+ (test-equal #f (let ((x #f)) (and-let* (x) x)))
+ (test-equal "" (let ((x "")) (and-let* (x) x)))
+ (test-equal "" (let ((x "")) (and-let* (x))))
+ (test-equal 2 (let ((x 1)) (and-let* (x) (+ x 1))))
+ (test-equal #f (let ((x #f)) (and-let* (x) (+ x 1))))
+ (test-equal 2 (let ((x 1)) (and-let* (((positive? x))) (+ x 1))))
+ (test-equal #t (let ((x 1)) (and-let* (((positive? x))))))
+ (test-equal #f (let ((x 0)) (and-let* (((positive? x))) (+ x 1))))
+ (test-equal 3
+ (let ((x 1)) (and-let* (((positive? x)) (x (+ x 1))) (+ x 1))))
+ ;; This is marked as must-be-error in the original test suite; see
+ ;; comments in the implementation for our rationale for intentionally
+ ;; breaking off from the specification.
+ (test-equal 4
+ (let ((x 1))
+ (and-let* (((positive? x)) (x (+ x 1)) (x (+ x 1))) (+ x 1))))
+
+ (test-equal 2
+ (let ((x 1)) (and-let* (x ((positive? x))) (+ x 1))))
+ (test-equal 2
+ (let ((x 1)) (and-let* (((begin x)) ((positive? x))) (+ x 1))))
+ (test-equal #f
+ (let ((x 0)) (and-let* (x ((positive? x))) (+ x 1))))
+ (test-equal #f
+ (let ((x #f)) (and-let* (x ((positive? x))) (+ x 1))))
+ (test-equal #f
+ (let ((x #f)) (and-let* (((begin x)) ((positive? x))) (+ x 1))))
+
+ (test-equal #f
+ (let ((x 1)) (and-let* (x (y (- x 1)) ((positive? y))) (/ x y))))
+ (test-equal #f
+ (let ((x 0)) (and-let* (x (y (- x 1)) ((positive? y))) (/ x y))))
+ (test-equal #f
+ (let ((x #f)) (and-let* (x (y (- x 1)) ((positive? y))) (/ x y))))
+ (test-equal 3/2
+ (let ((x 3)) (and-let* (x (y (- x 1)) ((positive? y))) (/ x y)))))
+
+ ))
+(define-library (srfi-tests srfi-26)
+ (export run-tests)
+ (import
+ (scheme base)
+ (srfi 26)
+ (srfi 64)
+ (srfi-tests aux))
+ (begin
+ (define-tests run-tests "SRFI-26"
+ ;; cut
+ (test-equal '() ((cut list)))
+ (test-equal '() ((cut list <___>)))
+ (test-equal '(1) ((cut list 1)))
+ (test-equal '(1) ((cut list <>) 1))
+ (test-equal '(1) ((cut list <___>) 1))
+ (test-equal '(1 2) ((cut list 1 2)))
+ (test-equal '(1 2) ((cut list 1 <>) 2))
+ (test-equal '(1 2) ((cut list 1 <___>) 2))
+ (test-equal '(1 2 3 4) ((cut list 1 <___>) 2 3 4))
+ (test-equal '(1 2 3 4) ((cut list 1 <> 3 <>) 2 4))
+ (test-equal '(1 2 3 4 5 6) ((cut list 1 <> 3 <___>) 2 4 5 6))
+ (test-equal '(ok) (let* ((x 'wrong) (y (cut list x))) (set! x 'ok) (y)))
+ (test-equal 2 (let ((a 0))
+ (map (cut + (begin (set! a (+ a 1)) a) <>)
+ '(1 2))
+ a))
+ ;; cute
+ (test-equal '() ((cute list)))
+ (test-equal '() ((cute list <___>)))
+ (test-equal '(1) ((cute list 1)))
+ (test-equal '(1) ((cute list <>) 1))
+ (test-equal '(1) ((cute list <___>) 1))
+ (test-equal '(1 2) ((cute list 1 2)))
+ (test-equal '(1 2) ((cute list 1 <>) 2))
+ (test-equal '(1 2) ((cute list 1 <___>) 2))
+ (test-equal '(1 2 3 4) ((cute list 1 <___>) 2 3 4))
+ (test-equal '(1 2 3 4) ((cute list 1 <> 3 <>) 2 4))
+ (test-equal '(1 2 3 4 5 6) ((cute list 1 <> 3 <___>) 2 4 5 6))
+ (test-equal 1 (let ((a 0))
+ (map (cute + (begin (set! a (+ a 1)) a) <>)
+ '(1 2))
+ a)))))
+(define-library (srfi-tests srfi-31)
+ (export run-tests)
+ (import
+ (scheme base)
+ (scheme lazy)
+ (srfi 31)
+ (srfi 64)
+ (srfi-tests aux))
+ (begin
+ (define-tests run-tests "SRFI-31"
+ (test-eqv "factorial" 3628800
+ ((rec (! n)
+ (if (zero? n)
+ 1
+ (* n (! (- n 1)))))
+ 10))
+ (test-eqv "lazy stream" 'x
+ (car (force (cdr (force (cdr (rec xs (cons 'x (delay xs))))))))))))
+
+
+(define-library (srfi-tests srfi-54)
+ (export run-tests)
+ (import
+ (scheme base)
+ (scheme char)
+ (scheme write)
+ (srfi 54)
+ (srfi 64)
+ (srfi-tests aux))
+ (begin
+
+ (define-syntax value-and-output
+ (syntax-rules ()
+ ((_ expr)
+ (let ((port (open-output-string)))
+ (parameterize ((current-output-port port))
+ (let ((value expr))
+ (list value (get-output-string port))))))))
+
+ (define (string-reverse string)
+ (list->string (reverse (string->list string))))
+
+ (define-tests run-tests "SRFI-54"
+ (test-equal "130.00 " (cat 129.995 -10 2.))
+ (test-equal " 130.00" (cat 129.995 10 2.))
+ (test-equal " 129.98" (cat 129.985 10 2.))
+ (test-equal " 129.99" (cat 129.985001 10 2.))
+ (test-equal "#e130.00" (cat 129.995 2. 'exact))
+ (test-equal "129.00" (cat 129 -2.))
+ (test-equal "#e129.00" (cat 129 2.))
+ (test-equal "#e+0129.00" (cat 129 10 2. #\0 'sign))
+ (test-equal "*#e+129.00" (cat 129 10 2. #\* 'sign))
+ (test-equal "1/3" (cat 1/3))
+ (test-equal " #e0.33" (cat 1/3 10 2.))
+ (test-equal " 0.33" (cat 1/3 10 -2.))
+ (test-equal " 1,29.99,5" (cat 129.995 10 '(#\, 2)))
+ (test-equal " +129,995" (cat 129995 10 '(#\,) 'sign))
+ (test-equal "130" (cat (cat 129.995 0.) '(0 -1)))
+ ;; These produce different results on Chibi, but I don't know if that's a
+ ;; bug or whether the result is implementation-dependent.
+ ;; (test-equal "#i#o+307/2" (cat 99.5 10 'sign 'octal))
+ ;; (test-equal " #o+307/2" (cat 99.5 10 'sign 'octal 'exact))
+ (test-equal "#o+443" (cat #x123 'octal 'sign))
+ (test-equal "#e+291.00*" (cat #x123 -10 2. 'sign #\*))
+ ;; These produce different results on Larceny, but I don't know if that's
+ ;; a bug or whether the result is implementation-dependent.
+ ;; (test-equal "-1.234e+15+1.236e-15i" (cat -1.2345e+15+1.2355e-15i 3.))
+ ;; (test-equal "+1.234e+15" (cat 1.2345e+15 10 3. 'sign))
+ (test-equal "string " (cat "string" -10))
+ (test-equal " STRING" (cat "string" 10 (list string-upcase)))
+ (test-equal " RING" (cat "string" 10 (list string-upcase) '(-2)))
+ (test-equal " STING" (cat "string" 10 `(,string-upcase) '(2 3)))
+ (test-equal "GNIRTS" (cat "string" `(,string-reverse ,string-upcase)))
+ (test-equal " a" (cat #\a 10))
+ (test-equal " symbol" (cat 'symbol 10))
+ (test-equal "#(#\\a \"str\" s)" (cat '#(#\a "str" s)))
+ (test-equal "(#\\a \"str\" s)" (cat '(#\a "str" s)))
+ (test-equal '("(#\\a \"str\" s)" "(#\\a \"str\" s)")
+ (value-and-output (cat '(#\a "str" s) #t)))
+ (test-equal '("(#\\a \"str\" s)" "(#\\a \"str\" s)")
+ (value-and-output (cat '(#\a "str" s) (current-output-port))))
+ (test-equal "3s \"str\"" (cat 3 (cat 's) " " (cat "str" write)))
+ (test-equal '("3s \"str\"" "3s \"str\"")
+ (value-and-output (cat 3 #t (cat 's) " " (cat "str" write))))
+ (test-equal '("3s \"str\"" "s3s \"str\"")
+ (value-and-output (cat 3 #t (cat 's #t) " " (cat "str" write)))))
+
+ ))
+(import
+ (scheme base)
+ (scheme process-context)
+ (srfi 64))
+
+;;;
+;;; This is a test suite written in the notation of
+;;; SRFI-64, A Scheme API for test suites
+;;;
+
+(test-begin "SRFI 64 - Meta-Test Suite")
+
+;;;
+;;; Ironically, in order to set up the meta-test environment,
+;;; we have to invoke one of the most sophisticated features:
+;;; custom test runners
+;;;
+
+;;; The `prop-runner' invokes `thunk' in the context of a new
+;;; test runner, and returns the indicated properties of the
+;;; last-executed test result.
+
+(define (prop-runner props thunk)
+ (let ((r (test-runner-null))
+ (plist '()))
+ ;;
+ (test-runner-on-test-end!
+ r
+ (lambda (runner)
+ (set! plist (test-result-alist runner))))
+ ;;
+ (test-with-runner r (thunk))
+ ;; reorder the properties so they are in the order
+ ;; given by `props'. Note that any property listed in `props'
+ ;; that is not in the property alist will occur as #f
+ (map (lambda (k)
+ (assq k plist))
+ props)))
+
+;;; `on-test-runner' creates a null test runner and then
+;;; arranged for `visit' to be called with the runner
+;;; whenever a test is run. The results of the calls to
+;;; `visit' are returned in a list
+
+(define (on-test-runner thunk visit)
+ (let ((r (test-runner-null))
+ (results '()))
+ ;;
+ (test-runner-on-test-end!
+ r
+ (lambda (runner)
+ (set! results (cons (visit r) results))))
+ ;;
+ (test-with-runner r (thunk))
+ (reverse results)))
+
+;;;
+;;; The `triv-runner' invokes `thunk'
+;;; and returns a list of 6 lists, the first 5 of which
+;;; are a list of the names of the tests that, respectively,
+;;; PASS, FAIL, XFAIL, XPASS, and SKIP.
+;;; The last item is a list of counts.
+;;;
+
+(define (triv-runner thunk)
+ (let ((r (test-runner-null))
+ (accum-pass '())
+ (accum-fail '())
+ (accum-xfail '())
+ (accum-xpass '())
+ (accum-skip '()))
+ ;;
+ (test-runner-on-bad-count!
+ r
+ (lambda (runner count expected-count)
+ (error (string-append "bad count " (number->string count)
+ " but expected "
+ (number->string expected-count)))))
+ (test-runner-on-bad-end-name!
+ r
+ (lambda (runner begin end)
+ (error (string-append "bad end group name " end
+ " but expected " begin))))
+ (test-runner-on-test-end!
+ r
+ (lambda (runner)
+ (let ((n (test-runner-test-name runner)))
+ (case (test-result-kind runner)
+ ((pass) (set! accum-pass (cons n accum-pass)))
+ ((fail) (set! accum-fail (cons n accum-fail)))
+ ((xpass) (set! accum-xpass (cons n accum-xpass)))
+ ((xfail) (set! accum-xfail (cons n accum-xfail)))
+ ((skip) (set! accum-skip (cons n accum-skip)))))))
+ ;;
+ (test-with-runner r (thunk))
+ (list (reverse accum-pass) ; passed as expected
+ (reverse accum-fail) ; failed, but was expected to pass
+ (reverse accum-xfail) ; failed as expected
+ (reverse accum-xpass) ; passed, but was expected to fail
+ (reverse accum-skip) ; was not executed
+ (list (test-runner-pass-count r)
+ (test-runner-fail-count r)
+ (test-runner-xfail-count r)
+ (test-runner-xpass-count r)
+ (test-runner-skip-count r)))))
+
+(define (path-revealing-runner thunk)
+ (let ((r (test-runner-null))
+ (seq '()))
+ ;;
+ (test-runner-on-test-end!
+ r
+ (lambda (runner)
+ (set! seq (cons (list (test-runner-group-path runner)
+ (test-runner-test-name runner))
+ seq))))
+ (test-with-runner r (thunk))
+ (reverse seq)))
+
+;;;
+;;; Now we can start testing compliance with SRFI-64
+;;;
+
+(test-begin "1. Simple test-cases")
+
+(test-begin "1.1. test-assert")
+
+(define (t)
+ (triv-runner
+ (lambda ()
+ (test-assert "a" #t)
+ (test-assert "b" #f))))
+
+(test-equal
+ "1.1.1. Very simple"
+ '(("a") ("b") () () () (1 1 0 0 0))
+ (t))
+
+(test-equal
+ "1.1.2. A test with no name"
+ '(("a") ("") () () () (1 1 0 0 0))
+ (triv-runner (lambda () (test-assert "a" #t) (test-assert #f))))
+
+(test-equal
+ "1.1.3. Tests can have the same name"
+ '(("a" "a") () () () () (2 0 0 0 0))
+ (triv-runner (lambda () (test-assert "a" #t) (test-assert "a" #t))))
+
+(define (choke)
+ (vector-ref '#(1 2) 3))
+
+(test-equal
+ "1.1.4. One way to FAIL is to throw an error"
+ '(() ("a") () () () (0 1 0 0 0))
+ (triv-runner (lambda () (test-assert "a" (choke)))))
+
+(test-end);1.1
+
+(test-begin "1.2. test-eqv")
+
+(define (mean x y)
+ (/ (+ x y) 2.0))
+
+(test-equal
+ "1.2.1. Simple numerical equivalence"
+ '(("c") ("a" "b") () () () (1 2 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-eqv "a" (mean 3 5) 4)
+ (test-eqv "b" (mean 3 5) 4.5)
+ (test-eqv "c" (mean 3 5) 4.0))))
+
+(test-end);1.2
+
+(test-begin "1.3. test-approximate")
+
+(test-equal
+ "1.3.1. Simple numerical approximation"
+ '(("a" "c") ("b") () () () (2 1 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-approximate "a" (mean 3 5) 4 0.001)
+ (test-approximate "b" (mean 3 5) 4.5 0.001)
+ (test-approximate "c" (mean 3 5) 4.0 0.001))))
+
+(test-end);1.3
+
+(test-end "1. Simple test-cases")
+
+;;;
+;;;
+;;;
+
+(test-begin "2. Tests for catching errors")
+
+(test-begin "2.1. test-error")
+
+(test-equal
+ "2.1.1. Baseline test; PASS with no optional args"
+ '(("") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ ;; PASS
+ (test-error (vector-ref '#(1 2) 9)))))
+
+(test-equal
+ "2.1.2. Baseline test; FAIL with no optional args"
+ '(() ("") () () () (0 1 0 0 0))
+ (triv-runner
+ (lambda ()
+ ;; FAIL: the expr does not raise an error and `test-error' is
+ ;; claiming that it will, so this test should FAIL
+ (test-error (vector-ref '#(1 2) 0)))))
+
+(test-equal
+ "2.1.3. PASS with a test name and error type"
+ '(("a") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ ;; PASS
+ (test-error "a" #t (vector-ref '#(1 2) 9)))))
+
+(test-equal
+ "2.1.4. FAIL with a test name and error type"
+ '(() ("a") () () () (0 1 0 0 0))
+ (triv-runner
+ (lambda ()
+ ;; FAIL
+ (test-error "a" #t (vector-ref '#(1 2) 0)))))
+
+(test-equal
+ "2.1.5. PASS with an error type but no name"
+ '(("") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ ;; PASS
+ (test-error #t (vector-ref '#(1 2) 9)))))
+
+(test-equal
+ "2.1.6. FAIL with an error type but no name"
+ '(() ("") () () () (0 1 0 0 0))
+ (triv-runner
+ (lambda ()
+ ;; FAIL
+ (test-error #t (vector-ref '#(1 2) 0)))))
+
+(test-end "2.1. test-error")
+
+(test-end "2. Tests for catching errors")
+
+;;;
+;;;
+;;;
+
+(test-begin "3. Test groups and paths")
+
+(test-equal
+ "3.1. test-begin with unspecific test-end"
+ '(("b") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "b" #t)
+ (test-end))))
+
+(test-equal
+ "3.2. test-begin with name-matching test-end"
+ '(("b") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "b" #t)
+ (test-end "a"))))
+
+;;; since the error raised by `test-end' on a mismatch is not a test
+;;; error, we actually expect the triv-runner itself to fail
+
+(test-error
+ "3.3. test-begin with mismatched test-end"
+#t
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "b" #t)
+ (test-end "x"))))
+
+(test-equal
+ "3.4. test-begin with name and count"
+ '(("b" "c") () () () () (2 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a" 2)
+ (test-assert "b" #t)
+ (test-assert "c" #t)
+ (test-end "a"))))
+
+;; similarly here, a mismatched count is a lexical error
+;; and not a test failure...
+
+(test-error
+ "3.5. test-begin with mismatched count"
+ #t
+ (triv-runner
+ (lambda ()
+ (test-begin "a" 99)
+ (test-assert "b" #t)
+ (test-end "a"))))
+
+(test-equal
+ "3.6. introspecting on the group path"
+ '((() "w")
+ (("a" "b") "x")
+ (("a" "b") "y")
+ (("a") "z"))
+ ;;
+ ;; `path-revealing-runner' is designed to return a list
+ ;; of the tests executed, in order. Each entry is a list
+ ;; (GROUP-PATH TEST-NAME), and each GROUP-PATH is a list
+ ;; of test groups starting from the topmost
+ ;;
+ (path-revealing-runner
+ (lambda ()
+ (test-assert "w" #t)
+ (test-begin "a")
+ (test-begin "b")
+ (test-assert "x" #t)
+ (test-assert "y" #t)
+ (test-end)
+ (test-assert "z" #t))))
+
+
+(test-end "3. Test groups and paths")
+
+;;;
+;;;
+;;;
+
+(test-begin "4. Handling set-up and cleanup")
+
+(test-equal "4.1. Normal exit path"
+ '(in 1 2 out)
+ (let ((ex '()))
+ (define (do s)
+ (set! ex (cons s ex)))
+ ;;
+ (triv-runner
+ (lambda ()
+ (test-group-with-cleanup
+ "foo"
+ (do 'in)
+ (do 1)
+ (do 2)
+ (do 'out))))
+ (reverse ex)))
+
+(test-equal "4.2. Exception exit path"
+ '(in 1 out)
+ (let ((ex '()))
+ (define (do s)
+ (set! ex (cons s ex)))
+ ;;
+ ;; the outer runner is to run the `test-error' in, to
+ ;; catch the exception raised in the inner runner,
+ ;; since we don't want to depend on any other
+ ;; exception-catching support
+ ;;
+ (triv-runner
+ (lambda ()
+ (test-error
+ (triv-runner
+ (lambda ()
+ (test-group-with-cleanup
+ "foo"
+ (do 'in) (test-assert #t)
+ (do 1) (test-assert #t)
+ (choke) (test-assert #t)
+ (do 2) (test-assert #t)
+ (do 'out)))))))
+ (reverse ex)))
+
+(test-end "4. Handling set-up and cleanup")
+
+;;;
+;;;
+;;;
+
+(test-begin "5. Test specifiers")
+
+(test-begin "5.1. test-match-named")
+
+(test-equal "5.1.1. match test names"
+ '(("y") () () () ("x") (1 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-skip (test-match-name "x"))
+ (test-assert "x" #t)
+ (test-assert "y" #t))))
+
+(test-equal "5.1.2. but not group names"
+ '(("z") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-skip (test-match-name "x"))
+ (test-begin "x")
+ (test-assert "z" #t)
+ (test-end))))
+
+(test-end)
+
+(test-begin "5.2. test-match-nth")
+;; See also: [6.4. Short-circuit evaluation]
+
+(test-equal "5.2.1. skip the nth one after"
+ '(("v" "w" "y" "z") () () () ("x") (4 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-nth 2))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP
+ (test-assert "y" #t) ; 3
+ (test-assert "z" #t)))) ; 4
+
+(test-equal "5.2.2. skip m, starting at n"
+ '(("v" "w" "z") () () () ("x" "y") (3 0 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-nth 2 2))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP
+ (test-assert "y" #t) ; 3 SKIP
+ (test-assert "z" #t)))) ; 4
+
+(test-end)
+
+(test-begin "5.3. test-match-any")
+(test-equal "5.3.1. basic disjunction"
+ '(("v" "w" "z") () () () ("x" "y") (3 0 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-any (test-match-nth 3)
+ (test-match-name "x")))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP(NAME)
+ (test-assert "y" #t) ; 3 SKIP(COUNT)
+ (test-assert "z" #t)))) ; 4
+
+(test-equal "5.3.2. disjunction is commutative"
+ '(("v" "w" "z") () () () ("x" "y") (3 0 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-any (test-match-name "x")
+ (test-match-nth 3)))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP(NAME)
+ (test-assert "y" #t) ; 3 SKIP(COUNT)
+ (test-assert "z" #t)))) ; 4
+
+(test-end)
+
+(test-begin "5.4. test-match-all")
+(test-equal "5.4.1. basic conjunction"
+ '(("v" "w" "y" "z") () () () ("x") (4 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-all (test-match-nth 2 2)
+ (test-match-name "x")))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP(NAME) & SKIP(COUNT)
+ (test-assert "y" #t) ; 3 SKIP(COUNT)
+ (test-assert "z" #t)))) ; 4
+
+(test-equal "5.4.2. conjunction is commutative"
+ '(("v" "w" "y" "z") () () () ("x") (4 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-all (test-match-name "x")
+ (test-match-nth 2 2)))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP(NAME) & SKIP(COUNT)
+ (test-assert "y" #t) ; 3 SKIP(COUNT)
+ (test-assert "z" #t)))) ; 4
+
+(test-end)
+
+(test-end "5. Test specifiers")
+
+;;;
+;;;
+;;;
+
+(test-begin "6. Skipping selected tests")
+
+(test-equal
+ "6.1. Skip by specifier - match-name"
+ '(("x") () () () ("y") (1 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip (test-match-name "y"))
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end))))
+
+(test-equal
+ "6.2. Shorthand specifiers"
+ '(("x") () () () ("y") (1 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "y")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end))))
+
+(test-begin "6.3. Specifier Stack")
+
+(test-equal
+ "6.3.1. Clearing the Specifier Stack"
+ '(("x" "x") ("y") () () ("y") (2 1 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "y")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end)
+ (test-begin "b")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; FAIL
+ (test-end))))
+
+(test-equal
+ "6.3.2. Inheriting the Specifier Stack"
+ '(("x" "x") () () () ("y" "y") (2 0 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-skip "y")
+ (test-begin "a")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end)
+ (test-begin "b")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end))))
+
+(test-end);6.3
+
+(test-begin "6.4. Short-circuit evaluation")
+
+(test-equal
+ "6.4.1. In test-match-all"
+ '(("x") ("y" "x" "z") () () ("y") (1 3 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip (test-match-all "y" (test-match-nth 2)))
+ ;; let's label the substructure forms so we can
+ ;; see which one `test-match-nth' is going to skip
+ ;; ; # "y" 2 result
+ (test-assert "x" #t) ; 1 - #f #f PASS
+ (test-assert "y" #f) ; 2 - #t #t SKIP
+ (test-assert "y" #f) ; 3 - #t #f FAIL
+ (test-assert "x" #f) ; 4 - #f #f FAIL
+ (test-assert "z" #f) ; 5 - #f #f FAIL
+ (test-end))))
+
+(test-equal
+ "6.4.2. In separate skip-list entries"
+ '(("x") ("x" "z") () () ("y" "y") (1 2 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "y")
+ (test-skip (test-match-nth 2))
+ ;; let's label the substructure forms so we can
+ ;; see which one `test-match-nth' is going to skip
+ ;; ; # "y" 2 result
+ (test-assert "x" #t) ; 1 - #f #f PASS
+ (test-assert "y" #f) ; 2 - #t #t SKIP
+ (test-assert "y" #f) ; 3 - #t #f SKIP
+ (test-assert "x" #f) ; 4 - #f #f FAIL
+ (test-assert "z" #f) ; 5 - #f #f FAIL
+ (test-end))))
+
+(test-begin "6.4.3. Skipping test suites")
+
+(test-equal
+ "6.4.3.1. Introduced using 'test-begin'"
+ '(("x") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "b")
+ (test-begin "b") ; not skipped
+ (test-assert "x" #t)
+ (test-end "b")
+ (test-end "a"))))
+
+(test-expect-fail 1) ;; ???
+(test-equal
+ "6.4.3.2. Introduced using 'test-group'"
+ '(() () () () () (0 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "b")
+ (test-group
+ "b" ; skipped
+ (test-assert "x" #t))
+ (test-end "a"))))
+
+(test-equal
+ "6.4.3.3. Non-skipped 'test-group'"
+ '(("x") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "c")
+ (test-group "b" (test-assert "x" #t))
+ (test-end "a"))))
+
+(test-end) ; 6.4.3
+
+(test-end);6.4
+
+(test-end "6. Skipping selected tests")
+
+;;;
+;;;
+;;;
+
+(test-begin "7. Expected failures")
+
+(test-equal "7.1. Simple example"
+ '(() ("x") ("z") () () (0 1 1 0 0))
+ (triv-runner
+ (lambda ()
+ (test-assert "x" #f)
+ (test-expect-fail "z")
+ (test-assert "z" #f))))
+
+(test-equal "7.2. Expected exception"
+ '(() ("x") ("z") () () (0 1 1 0 0))
+ (triv-runner
+ (lambda ()
+ (test-assert "x" #f)
+ (test-expect-fail "z")
+ (test-assert "z" (choke)))))
+
+(test-equal "7.3. Unexpectedly PASS"
+ '(() () ("y") ("x") () (0 0 1 1 0))
+ (triv-runner
+ (lambda ()
+ (test-expect-fail "x")
+ (test-expect-fail "y")
+ (test-assert "x" #t)
+ (test-assert "y" #f))))
+
+
+
+(test-end "7. Expected failures")
+
+;;;
+;;;
+;;;
+
+(test-begin "8. Test-runner")
+
+;;;
+;;; Because we want this test suite to be accurate even
+;;; when the underlying implementation chooses to use, e.g.,
+;;; a global variable to implement what could be thread variables
+;;; or SRFI-39 parameter objects, we really need to save and restore
+;;; their state ourselves
+;;;
+(define (with-factory-saved thunk)
+ (let* ((saved (test-runner-factory))
+ (result (thunk)))
+ (test-runner-factory saved)
+ result))
+
+(test-begin "8.1. test-runner-current")
+(test-assert "8.1.1. automatically restored"
+ (let ((a 0)
+ (b 1)
+ (c 2))
+ ;
+ (triv-runner
+ (lambda ()
+ (set! a (test-runner-current))
+ ;;
+ (triv-runner
+ (lambda ()
+ (set! b (test-runner-current))))
+ ;;
+ (set! c (test-runner-current))))
+ ;;
+ (and (eq? a c)
+ (not (eq? a b)))))
+
+(test-end)
+
+(test-begin "8.2. test-runner-simple")
+(test-assert "8.2.1. default on-test hook"
+ (eq? (test-runner-on-test-end (test-runner-simple))
+ test-on-test-end-simple))
+(test-assert "8.2.2. default on-final hook"
+ (eq? (test-runner-on-final (test-runner-simple))
+ test-on-final-simple))
+(test-end)
+
+(test-begin "8.3. test-runner-factory")
+
+(test-assert "8.3.1. default factory"
+ (eq? (test-runner-factory) test-runner-simple))
+
+(test-assert "8.3.2. settable factory"
+ (with-factory-saved
+ (lambda ()
+ (test-runner-factory test-runner-null)
+ ;; we have no way, without bringing in other SRFIs,
+ ;; to make sure the following doesn't print anything,
+ ;; but it shouldn't:
+ (test-with-runner
+ (test-runner-create)
+ (lambda ()
+ (test-begin "a")
+ (test-assert #t) ; pass
+ (test-assert #f) ; fail
+ (test-assert (vector-ref '#(3) 10)) ; fail with error
+ (test-end "a")))
+ (eq? (test-runner-factory) test-runner-null))))
+
+(test-end)
+
+;;; This got tested about as well as it could in 8.3.2
+
+(test-begin "8.4. test-runner-create")
+(test-end)
+
+;;; This got tested about as well as it could in 8.3.2
+
+(test-begin "8.5. test-runner-factory")
+(test-end)
+
+(test-begin "8.6. test-apply")
+(test-equal "8.6.1. Simple (form 1) test-apply"
+ '(("w" "p" "v") () () () ("x") (3 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "w" #t)
+ (test-apply
+ (test-match-name "p")
+ (lambda ()
+ (test-begin "p")
+ (test-assert "x" #t)
+ (test-end)
+ (test-begin "z")
+ (test-assert "p" #t) ; only this one should execute in here
+ (test-end)))
+ (test-assert "v" #t))))
+
+(test-equal "8.6.2. Simple (form 2) test-apply"
+ '(("w" "p" "v") () () () ("x") (3 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "w" #t)
+ (test-apply
+ (test-runner-current)
+ (test-match-name "p")
+ (lambda ()
+ (test-begin "p")
+ (test-assert "x" #t)
+ (test-end)
+ (test-begin "z")
+ (test-assert "p" #t) ; only this one should execute in here
+ (test-end)))
+ (test-assert "v" #t))))
+
+(test-expect-fail 1) ;; depends on all test-match-nth being called.
+(test-equal "8.6.3. test-apply with skips"
+ '(("w" "q" "v") () () () ("x" "p" "x") (3 0 0 0 3))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "w" #t)
+ (test-skip (test-match-nth 2))
+ (test-skip (test-match-nth 4))
+ (test-apply
+ (test-runner-current)
+ (test-match-name "p")
+ (test-match-name "q")
+ (lambda ()
+ ; only execute if SKIP=no and APPLY=yes
+ (test-assert "x" #t) ; # 1 SKIP=no APPLY=no
+ (test-assert "p" #t) ; # 2 SKIP=yes APPLY=yes
+ (test-assert "q" #t) ; # 3 SKIP=no APPLY=yes
+ (test-assert "x" #f) ; # 4 SKIP=yes APPLY=no
+ 0))
+ (test-assert "v" #t))))
+
+;;; Unfortunately, since there is no way to UNBIND the current test runner,
+;;; there is no way to test the behavior of `test-apply' in the absence
+;;; of a current runner within our little meta-test framework.
+;;;
+;;; To test the behavior manually, you should be able to invoke:
+;;;
+;;; (test-apply "a" (lambda () (test-assert "a" #t)))
+;;;
+;;; from the top level (with SRFI 64 available) and it should create a
+;;; new, default (simple) test runner.
+
+(test-end)
+
+;;; This entire suite depends heavily on 'test-with-runner'. If it didn't
+;;; work, this suite would probably go down in flames
+(test-begin "8.7. test-with-runner")
+(test-end)
+
+;;; Again, this suite depends heavily on many of the test-runner
+;;; components. We'll just test those that aren't being exercised
+;;; by the meta-test framework
+(test-begin "8.8. test-runner components")
+
+(define (auxtrack-runner thunk)
+ (let ((r (test-runner-null)))
+ (test-runner-aux-value! r '())
+ (test-runner-on-test-end! r (lambda (r)
+ (test-runner-aux-value!
+ r
+ (cons (test-runner-test-name r)
+ (test-runner-aux-value r)))))
+ (test-with-runner r (thunk))
+ (reverse (test-runner-aux-value r))))
+
+(test-equal "8.8.1. test-runner-aux-value"
+ '("x" "" "y")
+ (auxtrack-runner
+ (lambda ()
+ (test-assert "x" #t)
+ (test-begin "a")
+ (test-assert #t)
+ (test-end)
+ (test-assert "y" #f))))
+
+(test-end) ; 8.8
+
+(test-end "8. Test-runner")
+
+(test-begin "9. Test Result Properties")
+
+(test-begin "9.1. test-result-alist")
+
+(define (symbol-alist? l)
+ (if (null? l)
+ #t
+ (and (pair? l)
+ (pair? (car l))
+ (symbol? (caar l))
+ (symbol-alist? (cdr l)))))
+
+;;; check the various syntactic forms
+
+(test-assert (symbol-alist?
+ (car (on-test-runner
+ (lambda ()
+ (test-assert #t))
+ (lambda (r)
+ (test-result-alist r))))))
+
+(test-assert (symbol-alist?
+ (car (on-test-runner
+ (lambda ()
+ (test-assert #t))
+ (lambda (r)
+ (test-result-alist r))))))
+
+;;; check to make sure the required properties are returned
+
+(test-equal '((result-kind . pass))
+ (prop-runner
+ '(result-kind)
+ (lambda ()
+ (test-assert #t)))
+ )
+
+(test-equal
+ '((result-kind . fail)
+ (expected-value . 2)
+ (actual-value . 3))
+ (prop-runner
+ '(result-kind expected-value actual-value)
+ (lambda ()
+ (test-equal 2 (+ 1 2)))))
+
+(test-end "9.1. test-result-alist")
+
+(test-begin "9.2. test-result-ref")
+
+(test-equal '(pass)
+ (on-test-runner
+ (lambda ()
+ (test-assert #t))
+ (lambda (r)
+ (test-result-ref r 'result-kind))))
+
+(test-equal '(pass)
+ (on-test-runner
+ (lambda ()
+ (test-assert #t))
+ (lambda (r)
+ (test-result-ref r 'result-kind))))
+
+(test-equal '(fail pass)
+ (on-test-runner
+ (lambda ()
+ (test-assert (= 1 2))
+ (test-assert (= 1 1)))
+ (lambda (r)
+ (test-result-ref r 'result-kind))))
+
+(test-end "9.2. test-result-ref")
+
+(test-begin "9.3. test-result-set!")
+
+(test-equal '(100 100)
+ (on-test-runner
+ (lambda ()
+ (test-assert (= 1 2))
+ (test-assert (= 1 1)))
+ (lambda (r)
+ (test-result-set! r 'foo 100)
+ (test-result-ref r 'foo))))
+
+(test-end "9.3. test-result-set!")
+
+(test-end "9. Test Result Properties")
+
+;;;
+;;;
+;;;
+
+;#| Time to stop having fun...
+;
+;(test-begin "9. For fun, some meta-test errors")
+;
+;(test-equal
+; "9.1. Really PASSes, but test like it should FAIL"
+; '(() ("b") () () ())
+; (triv-runner
+; (lambda ()
+; (test-assert "b" #t))))
+;
+;(test-expect-fail "9.2. Expect to FAIL and do so")
+;(test-expect-fail "9.3. Expect to FAIL but PASS")
+;(test-skip "9.4. SKIP this one")
+;
+;(test-assert "9.2. Expect to FAIL and do so" #f)
+;(test-assert "9.3. Expect to FAIL but PASS" #t)
+;(test-assert "9.4. SKIP this one" #t)
+;
+;(test-end)
+; |#
+
+(test-end "SRFI 64 - Meta-Test Suite")
+
+(let ((runner (test-runner-current)))
+ (unless (and (= 0 (test-runner-xpass-count runner))
+ (= 0 (test-runner-fail-count runner)))
+ (exit 1)))
+
+;;;
+;;;
+;;; This is a test suite written in the notation of
+;;; SRFI-64, A Scheme API for test suites
+;;;
+
+(test-begin "SRFI 64 - Meta-Test Suite")
+
+;;;
+;;; Ironically, in order to set up the meta-test environment,
+;;; we have to invoke one of the most sophisticated features:
+;;; custom test runners
+;;;
+
+;;; The `prop-runner' invokes `thunk' in the context of a new
+;;; test runner, and returns the indicated properties of the
+;;; last-executed test result.
+
+(define (prop-runner props thunk)
+ (let ((r (test-runner-null))
+ (plist '()))
+ ;;
+ (test-runner-on-test-end!
+ r
+ (lambda (runner)
+ (set! plist (test-result-alist runner))))
+ ;;
+ (test-with-runner r (thunk))
+ ;; reorder the properties so they are in the order
+ ;; given by `props'. Note that any property listed in `props'
+ ;; that is not in the property alist will occur as #f
+ (map (lambda (k)
+ (assq k plist))
+ props)))
+
+;;; `on-test-runner' creates a null test runner and then
+;;; arranged for `visit' to be called with the runner
+;;; whenever a test is run. The results of the calls to
+;;; `visit' are returned in a list
+
+(define (on-test-runner thunk visit)
+ (let ((r (test-runner-null))
+ (results '()))
+ ;;
+ (test-runner-on-test-end!
+ r
+ (lambda (runner)
+ (set! results (cons (visit r) results))))
+ ;;
+ (test-with-runner r (thunk))
+ (reverse results)))
+
+;;;
+;;; The `triv-runner' invokes `thunk'
+;;; and returns a list of 6 lists, the first 5 of which
+;;; are a list of the names of the tests that, respectively,
+;;; PASS, FAIL, XFAIL, XPASS, and SKIP.
+;;; The last item is a list of counts.
+;;;
+
+(define (triv-runner thunk)
+ (let ((r (test-runner-null))
+ (accum-pass '())
+ (accum-fail '())
+ (accum-xfail '())
+ (accum-xpass '())
+ (accum-skip '()))
+ ;;
+ (test-runner-on-bad-count!
+ r
+ (lambda (runner count expected-count)
+ (error (string-append "bad count " (number->string count)
+ " but expected "
+ (number->string expected-count)))))
+ (test-runner-on-bad-end-name!
+ r
+ (lambda (runner begin end)
+ (error (string-append "bad end group name " end
+ " but expected " begin))))
+ (test-runner-on-test-end!
+ r
+ (lambda (runner)
+ (let ((n (test-runner-test-name runner)))
+ (case (test-result-kind runner)
+ ((pass) (set! accum-pass (cons n accum-pass)))
+ ((fail) (set! accum-fail (cons n accum-fail)))
+ ((xpass) (set! accum-xpass (cons n accum-xpass)))
+ ((xfail) (set! accum-xfail (cons n accum-xfail)))
+ ((skip) (set! accum-skip (cons n accum-skip)))))))
+ ;;
+ (test-with-runner r (thunk))
+ (list (reverse accum-pass) ; passed as expected
+ (reverse accum-fail) ; failed, but was expected to pass
+ (reverse accum-xfail) ; failed as expected
+ (reverse accum-xpass) ; passed, but was expected to fail
+ (reverse accum-skip) ; was not executed
+ (list (test-runner-pass-count r)
+ (test-runner-fail-count r)
+ (test-runner-xfail-count r)
+ (test-runner-xpass-count r)
+ (test-runner-skip-count r)))))
+
+(define (path-revealing-runner thunk)
+ (let ((r (test-runner-null))
+ (seq '()))
+ ;;
+ (test-runner-on-test-end!
+ r
+ (lambda (runner)
+ (set! seq (cons (list (test-runner-group-path runner)
+ (test-runner-test-name runner))
+ seq))))
+ (test-with-runner r (thunk))
+ (reverse seq)))
+
+;;;
+;;; Now we can start testing compliance with SRFI-64
+;;;
+
+(test-begin "1. Simple test-cases")
+
+(test-begin "1.1. test-assert")
+
+(define (t)
+ (triv-runner
+ (lambda ()
+ (test-assert "a" #t)
+ (test-assert "b" #f))))
+
+(test-equal
+ "1.1.1. Very simple"
+ '(("a") ("b") () () () (1 1 0 0 0))
+ (t))
+
+(test-equal
+ "1.1.2. A test with no name"
+ '(("a") ("") () () () (1 1 0 0 0))
+ (triv-runner (lambda () (test-assert "a" #t) (test-assert #f))))
+
+(test-equal
+ "1.1.3. Tests can have the same name"
+ '(("a" "a") () () () () (2 0 0 0 0))
+ (triv-runner (lambda () (test-assert "a" #t) (test-assert "a" #t))))
+
+(define (choke)
+ (vector-ref '#(1 2) 3))
+
+(test-equal
+ "1.1.4. One way to FAIL is to throw an error"
+ '(() ("a") () () () (0 1 0 0 0))
+ (triv-runner (lambda () (test-assert "a" (choke)))))
+
+(test-end);1.1
+
+(test-begin "1.2. test-eqv")
+
+(define (mean x y)
+ (/ (+ x y) 2.0))
+
+(test-equal
+ "1.2.1. Simple numerical equivalence"
+ '(("c") ("a" "b") () () () (1 2 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-eqv "a" (mean 3 5) 4)
+ (test-eqv "b" (mean 3 5) 4.5)
+ (test-eqv "c" (mean 3 5) 4.0))))
+
+(test-end);1.2
+
+(test-end "1. Simple test-cases")
+
+;;;
+;;;
+;;;
+
+(test-begin "2. Tests for catching errors")
+
+(test-begin "2.1. test-error")
+
+(test-equal
+ "2.1.1. Baseline test; PASS with no optional args"
+ '(("") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ ;; PASS
+ (test-error (vector-ref '#(1 2) 9)))))
+
+(test-equal
+ "2.1.2. Baseline test; FAIL with no optional args"
+ '(() ("") () () () (0 1 0 0 0))
+ (triv-runner
+ (lambda ()
+ ;; FAIL: the expr does not raise an error and `test-error' is
+ ;; claiming that it will, so this test should FAIL
+ (test-error (vector-ref '#(1 2) 0)))))
+
+(test-equal
+ "2.1.3. PASS with a test name and error type"
+ '(("a") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ ;; PASS
+ (test-error "a" #t (vector-ref '#(1 2) 9)))))
+
+(test-end "2.1. test-error")
+
+(test-end "2. Tests for catching errors")
+
+;;;
+;;;
+;;;
+
+(test-begin "3. Test groups and paths")
+
+(test-equal
+ "3.1. test-begin with unspecific test-end"
+ '(("b") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "b" #t)
+ (test-end))))
+
+(test-equal
+ "3.2. test-begin with name-matching test-end"
+ '(("b") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "b" #t)
+ (test-end "a"))))
+
+;;; since the error raised by `test-end' on a mismatch is not a test
+;;; error, we actually expect the triv-runner itself to fail
+
+(test-error
+ "3.3. test-begin with mismatched test-end"
+#t
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "b" #t)
+ (test-end "x"))))
+
+(test-equal
+ "3.4. test-begin with name and count"
+ '(("b" "c") () () () () (2 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a" 2)
+ (test-assert "b" #t)
+ (test-assert "c" #t)
+ (test-end "a"))))
+
+;; similarly here, a mismatched count is a lexical error
+;; and not a test failure...
+
+(test-error
+ "3.5. test-begin with mismatched count"
+ #t
+ (triv-runner
+ (lambda ()
+ (test-begin "a" 99)
+ (test-assert "b" #t)
+ (test-end "a"))))
+
+(test-equal
+ "3.6. introspecting on the group path"
+ '((() "w")
+ (("a" "b") "x")
+ (("a" "b") "y")
+ (("a") "z"))
+ ;;
+ ;; `path-revealing-runner' is designed to return a list
+ ;; of the tests executed, in order. Each entry is a list
+ ;; (GROUP-PATH TEST-NAME), and each GROUP-PATH is a list
+ ;; of test groups starting from the topmost
+ ;;
+ (path-revealing-runner
+ (lambda ()
+ (test-assert "w" #t)
+ (test-begin "a")
+ (test-begin "b")
+ (test-assert "x" #t)
+ (test-assert "y" #t)
+ (test-end)
+ (test-assert "z" #t))))
+
+
+(test-end "3. Test groups and paths")
+
+;;;
+;;;
+;;;
+
+(test-begin "4. Handling set-up and cleanup")
+
+(test-equal "4.1. Normal exit path"
+ '(in 1 2 out)
+ (let ((ex '()))
+ (define (do s)
+ (set! ex (cons s ex)))
+ ;;
+ (triv-runner
+ (lambda ()
+ (test-group-with-cleanup
+ "foo"
+ (do 'in)
+ (do 1)
+ (do 2)
+ (do 'out))))
+ (reverse ex)))
+
+(test-equal "4.2. Exception exit path"
+ '(in 1 out)
+ (let ((ex '()))
+ (define (do s)
+ (set! ex (cons s ex)))
+ ;;
+ ;; the outer runner is to run the `test-error' in, to
+ ;; catch the exception raised in the inner runner,
+ ;; since we don't want to depend on any other
+ ;; exception-catching support
+ ;;
+ (triv-runner
+ (lambda ()
+ (test-error
+ (triv-runner
+ (lambda ()
+ (test-group-with-cleanup
+ "foo"
+ (do 'in) (test-assert #t)
+ (do 1) (test-assert #t)
+ (choke) (test-assert #t)
+ (do 2) (test-assert #t)
+ (do 'out)))))))
+ (reverse ex)))
+
+(test-end "4. Handling set-up and cleanup")
+
+;;;
+;;;
+;;;
+
+(test-begin "5. Test specifiers")
+
+(test-begin "5.1. test-match-named")
+
+(test-equal "5.1.1. match test names"
+ '(("y") () () () ("x") (1 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-skip (test-match-name "x"))
+ (test-assert "x" #t)
+ (test-assert "y" #t))))
+
+(test-equal "5.1.2. but not group names"
+ '(("z") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-skip (test-match-name "x"))
+ (test-begin "x")
+ (test-assert "z" #t)
+ (test-end))))
+
+(test-end)
+
+(test-begin "5.2. test-match-nth")
+;; See also: [6.4. Short-circuit evaluation]
+
+(test-equal "5.2.1. skip the nth one after"
+ '(("v" "w" "y" "z") () () () ("x") (4 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-nth 2))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP
+ (test-assert "y" #t) ; 3
+ (test-assert "z" #t)))) ; 4
+
+(test-equal "5.2.2. skip m, starting at n"
+ '(("v" "w" "z") () () () ("x" "y") (3 0 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-nth 2 2))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP
+ (test-assert "y" #t) ; 3 SKIP
+ (test-assert "z" #t)))) ; 4
+
+(test-end)
+
+(test-begin "5.3. test-match-any")
+(test-equal "5.3.1. basic disjunction"
+ '(("v" "w" "z") () () () ("x" "y") (3 0 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-any (test-match-nth 3)
+ (test-match-name "x")))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP(NAME)
+ (test-assert "y" #t) ; 3 SKIP(COUNT)
+ (test-assert "z" #t)))) ; 4
+
+(test-equal "5.3.2. disjunction is commutative"
+ '(("v" "w" "z") () () () ("x" "y") (3 0 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-any (test-match-name "x")
+ (test-match-nth 3)))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP(NAME)
+ (test-assert "y" #t) ; 3 SKIP(COUNT)
+ (test-assert "z" #t)))) ; 4
+
+(test-end)
+
+(test-begin "5.4. test-match-all")
+(test-equal "5.4.1. basic conjunction"
+ '(("v" "w" "y" "z") () () () ("x") (4 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-all (test-match-nth 2 2)
+ (test-match-name "x")))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP(NAME) & SKIP(COUNT)
+ (test-assert "y" #t) ; 3 SKIP(COUNT)
+ (test-assert "z" #t)))) ; 4
+
+(test-equal "5.4.2. conjunction is commutative"
+ '(("v" "w" "y" "z") () () () ("x") (4 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-all (test-match-name "x")
+ (test-match-nth 2 2)))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP(NAME) & SKIP(COUNT)
+ (test-assert "y" #t) ; 3 SKIP(COUNT)
+ (test-assert "z" #t)))) ; 4
+
+(test-end)
+
+(test-end "5. Test specifiers")
+
+;;;
+;;;
+;;;
+
+(test-begin "6. Skipping selected tests")
+
+(test-equal
+ "6.1. Skip by specifier - match-name"
+ '(("x") () () () ("y") (1 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip (test-match-name "y"))
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end))))
+
+(test-equal
+ "6.2. Shorthand specifiers"
+ '(("x") () () () ("y") (1 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "y")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end))))
+
+(test-begin "6.3. Specifier Stack")
+
+(test-equal
+ "6.3.1. Clearing the Specifier Stack"
+ '(("x" "x") ("y") () () ("y") (2 1 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "y")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end)
+ (test-begin "b")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; FAIL
+ (test-end))))
+
+(test-equal
+ "6.3.2. Inheriting the Specifier Stack"
+ '(("x" "x") () () () ("y" "y") (2 0 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-skip "y")
+ (test-begin "a")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end)
+ (test-begin "b")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end))))
+
+(test-end);6.3
+
+(test-begin "6.4. Short-circuit evaluation")
+
+(test-equal
+ "6.4.1. In test-match-all"
+ '(("x") ("y" "x" "z") () () ("y") (1 3 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip (test-match-all "y" (test-match-nth 2)))
+ ;; let's label the substructure forms so we can
+ ;; see which one `test-match-nth' is going to skip
+ ;; ; # "y" 2 result
+ (test-assert "x" #t) ; 1 - #f #f PASS
+ (test-assert "y" #f) ; 2 - #t #t SKIP
+ (test-assert "y" #f) ; 3 - #t #f FAIL
+ (test-assert "x" #f) ; 4 - #f #f FAIL
+ (test-assert "z" #f) ; 5 - #f #f FAIL
+ (test-end))))
+
+(test-equal
+ "6.4.2. In separate skip-list entries"
+ '(("x") ("x" "z") () () ("y" "y") (1 2 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "y")
+ (test-skip (test-match-nth 2))
+ ;; let's label the substructure forms so we can
+ ;; see which one `test-match-nth' is going to skip
+ ;; ; # "y" 2 result
+ (test-assert "x" #t) ; 1 - #f #f PASS
+ (test-assert "y" #f) ; 2 - #t #t SKIP
+ (test-assert "y" #f) ; 3 - #t #f SKIP
+ (test-assert "x" #f) ; 4 - #f #f FAIL
+ (test-assert "z" #f) ; 5 - #f #f FAIL
+ (test-end))))
+
+(test-begin "6.4.3. Skipping test suites")
+
+(test-equal
+ "6.4.3.1. Introduced using 'test-begin'"
+ '(("x") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "b")
+ (test-begin "b") ; not skipped
+ (test-assert "x" #t)
+ (test-end "b")
+ (test-end "a"))))
+
+(test-expect-fail 1) ;; ???
+(test-equal
+ "6.4.3.2. Introduced using 'test-group'"
+ '(() () () () () (0 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "b")
+ (test-group
+ "b" ; skipped
+ (test-assert "x" #t))
+ (test-end "a"))))
+
+(test-equal
+ "6.4.3.3. Non-skipped 'test-group'"
+ '(("x") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "c")
+ (test-group "b" (test-assert "x" #t))
+ (test-end "a"))))
+
+(test-end) ; 6.4.3
+
+(test-end);6.4
+
+(test-end "6. Skipping selected tests")
+
+;;;
+;;;
+;;;
+
+(test-begin "7. Expected failures")
+
+(test-equal "7.1. Simple example"
+ '(() ("x") ("z") () () (0 1 1 0 0))
+ (triv-runner
+ (lambda ()
+ (test-assert "x" #f)
+ (test-expect-fail "z")
+ (test-assert "z" #f))))
+
+(test-equal "7.2. Expected exception"
+ '(() ("x") ("z") () () (0 1 1 0 0))
+ (triv-runner
+ (lambda ()
+ (test-assert "x" #f)
+ (test-expect-fail "z")
+ (test-assert "z" (choke)))))
+
+(test-equal "7.3. Unexpectedly PASS"
+ '(() () ("y") ("x") () (0 0 1 1 0))
+ (triv-runner
+ (lambda ()
+ (test-expect-fail "x")
+ (test-expect-fail "y")
+ (test-assert "x" #t)
+ (test-assert "y" #f))))
+
+
+
+(test-end "7. Expected failures")
+
+;;;
+;;;
+;;;
+
+(test-begin "8. Test-runner")
+
+;;;
+;;; Because we want this test suite to be accurate even
+;;; when the underlying implementation chooses to use, e.g.,
+;;; a global variable to implement what could be thread variables
+;;; or SRFI-39 parameter objects, we really need to save and restore
+;;; their state ourselves
+;;;
+(define (with-factory-saved thunk)
+ (let* ((saved (test-runner-factory))
+ (result (thunk)))
+ (test-runner-factory saved)
+ result))
+
+(test-begin "8.1. test-runner-current")
+(test-assert "8.1.1. automatically restored"
+ (let ((a 0)
+ (b 1)
+ (c 2))
+ ;
+ (triv-runner
+ (lambda ()
+ (set! a (test-runner-current))
+ ;;
+ (triv-runner
+ (lambda ()
+ (set! b (test-runner-current))))
+ ;;
+ (set! c (test-runner-current))))
+ ;;
+ (and (eq? a c)
+ (not (eq? a b)))))
+
+(test-end)
+
+(test-begin "8.2. test-runner-simple")
+(test-assert "8.2.1. default on-test hook"
+ (eq? (test-runner-on-test-end (test-runner-simple))
+ test-on-test-end-simple))
+(test-assert "8.2.2. default on-final hook"
+ (eq? (test-runner-on-final (test-runner-simple))
+ test-on-final-simple))
+(test-end)
+
+(test-begin "8.3. test-runner-factory")
+
+(test-assert "8.3.1. default factory"
+ (eq? (test-runner-factory) test-runner-simple))
+
+(test-assert "8.3.2. settable factory"
+ (with-factory-saved
+ (lambda ()
+ (test-runner-factory test-runner-null)
+ ;; we have no way, without bringing in other SRFIs,
+ ;; to make sure the following doesn't print anything,
+ ;; but it shouldn't:
+ (test-with-runner
+ (test-runner-create)
+ (lambda ()
+ (test-begin "a")
+ (test-assert #t) ; pass
+ (test-assert #f) ; fail
+ (test-assert (vector-ref '#(3) 10)) ; fail with error
+ (test-end "a")))
+ (eq? (test-runner-factory) test-runner-null))))
+
+(test-end)
+
+;;; This got tested about as well as it could in 8.3.2
+
+(test-begin "8.4. test-runner-create")
+(test-end)
+
+;;; This got tested about as well as it could in 8.3.2
+
+(test-begin "8.5. test-runner-factory")
+(test-end)
+
+(test-begin "8.6. test-apply")
+(test-equal "8.6.1. Simple (form 1) test-apply"
+ '(("w" "p" "v") () () () ("x") (3 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "w" #t)
+ (test-apply
+ (test-match-name "p")
+ (lambda ()
+ (test-begin "p")
+ (test-assert "x" #t)
+ (test-end)
+ (test-begin "z")
+ (test-assert "p" #t) ; only this one should execute in here
+ (test-end)))
+ (test-assert "v" #t))))
+
+(test-equal "8.6.2. Simple (form 2) test-apply"
+ '(("w" "p" "v") () () () ("x") (3 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "w" #t)
+ (test-apply
+ (test-runner-current)
+ (test-match-name "p")
+ (lambda ()
+ (test-begin "p")
+ (test-assert "x" #t)
+ (test-end)
+ (test-begin "z")
+ (test-assert "p" #t) ; only this one should execute in here
+ (test-end)))
+ (test-assert "v" #t))))
+
+(test-expect-fail 1) ;; depends on all test-match-nth being called.
+(test-equal "8.6.3. test-apply with skips"
+ '(("w" "q" "v") () () () ("x" "p" "x") (3 0 0 0 3))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "w" #t)
+ (test-skip (test-match-nth 2))
+ (test-skip (test-match-nth 4))
+ (test-apply
+ (test-runner-current)
+ (test-match-name "p")
+ (test-match-name "q")
+ (lambda ()
+ ; only execute if SKIP=no and APPLY=yes
+ (test-assert "x" #t) ; # 1 SKIP=no APPLY=no
+ (test-assert "p" #t) ; # 2 SKIP=yes APPLY=yes
+ (test-assert "q" #t) ; # 3 SKIP=no APPLY=yes
+ (test-assert "x" #f) ; # 4 SKIP=yes APPLY=no
+ 0))
+ (test-assert "v" #t))))
+
+;;; Unfortunately, since there is no way to UNBIND the current test runner,
+;;; there is no way to test the behavior of `test-apply' in the absence
+;;; of a current runner within our little meta-test framework.
+;;;
+;;; To test the behavior manually, you should be able to invoke:
+;;;
+;;; (test-apply "a" (lambda () (test-assert "a" #t)))
+;;;
+;;; from the top level (with SRFI 64 available) and it should create a
+;;; new, default (simple) test runner.
+
+(test-end)
+
+;;; This entire suite depends heavily on 'test-with-runner'. If it didn't
+;;; work, this suite would probably go down in flames
+(test-begin "8.7. test-with-runner")
+(test-end)
+
+;;; Again, this suite depends heavily on many of the test-runner
+;;; components. We'll just test those that aren't being exercised
+;;; by the meta-test framework
+(test-begin "8.8. test-runner components")
+
+(define (auxtrack-runner thunk)
+ (let ((r (test-runner-null)))
+ (test-runner-aux-value! r '())
+ (test-runner-on-test-end! r (lambda (r)
+ (test-runner-aux-value!
+ r
+ (cons (test-runner-test-name r)
+ (test-runner-aux-value r)))))
+ (test-with-runner r (thunk))
+ (reverse (test-runner-aux-value r))))
+
+(test-equal "8.8.1. test-runner-aux-value"
+ '("x" "" "y")
+ (auxtrack-runner
+ (lambda ()
+ (test-assert "x" #t)
+ (test-begin "a")
+ (test-assert #t)
+ (test-end)
+ (test-assert "y" #f))))
+
+(test-end) ; 8.8
+
+(test-end "8. Test-runner")
+
+(test-begin "9. Test Result Properties")
+
+(test-begin "9.1. test-result-alist")
+
+(define (symbol-alist? l)
+ (if (null? l)
+ #t
+ (and (pair? l)
+ (pair? (car l))
+ (symbol? (caar l))
+ (symbol-alist? (cdr l)))))
+
+;;; check the various syntactic forms
+
+(test-assert (symbol-alist?
+ (car (on-test-runner
+ (lambda ()
+ (test-assert #t))
+ (lambda (r)
+ (test-result-alist r))))))
+
+(test-assert (symbol-alist?
+ (car (on-test-runner
+ (lambda ()
+ (test-assert #t))
+ (lambda (r)
+ (test-result-alist r))))))
+
+;;; check to make sure the required properties are returned
+
+(test-equal '((result-kind . pass))
+ (prop-runner
+ '(result-kind)
+ (lambda ()
+ (test-assert #t)))
+ )
+
+(test-equal
+ '((result-kind . fail)
+ (expected-value . 2)
+ (actual-value . 3))
+ (prop-runner
+ '(result-kind expected-value actual-value)
+ (lambda ()
+ (test-equal 2 (+ 1 2)))))
+
+(test-end "9.1. test-result-alist")
+
+(test-begin "9.2. test-result-ref")
+
+(test-equal '(pass)
+ (on-test-runner
+ (lambda ()
+ (test-assert #t))
+ (lambda (r)
+ (test-result-ref r 'result-kind))))
+
+(test-equal '(pass)
+ (on-test-runner
+ (lambda ()
+ (test-assert #t))
+ (lambda (r)
+ (test-result-ref r 'result-kind))))
+
+(test-equal '(fail pass)
+ (on-test-runner
+ (lambda ()
+ (test-assert (= 1 2))
+ (test-assert (= 1 1)))
+ (lambda (r)
+ (test-result-ref r 'result-kind))))
+
+(test-end "9.2. test-result-ref")
+
+(test-begin "9.3. test-result-set!")
+
+(test-equal '(100 100)
+ (on-test-runner
+ (lambda ()
+ (test-assert (= 1 2))
+ (test-assert (= 1 1)))
+ (lambda (r)
+ (test-result-set! r 'foo 100)
+ (test-result-ref r 'foo))))
+
+(test-end "9.3. test-result-set!")
+
+(test-end "9. Test Result Properties")
+
+;;;
+;;;
+;;;
+
+;#| Time to stop having fun...
+;
+;(test-begin "9. For fun, some meta-test errors")
+;
+;(test-equal
+; "9.1. Really PASSes, but test like it should FAIL"
+; '(() ("b") () () ())
+; (triv-runner
+; (lambda ()
+; (test-assert "b" #t))))
+;
+;(test-expect-fail "9.2. Expect to FAIL and do so")
+;(test-expect-fail "9.3. Expect to FAIL but PASS")
+;(test-skip "9.4. SKIP this one")
+;
+;(test-assert "9.2. Expect to FAIL and do so" #f)
+;(test-assert "9.3. Expect to FAIL but PASS" #t)
+;(test-assert "9.4. SKIP this one" #t)
+;
+;(test-end)
+; |#
+
+(test-end "SRFI 64 - Meta-Test Suite")
+
+;;;
+;;; Copyright (C) Philip L. Bewig (2007). All Rights Reserved.
+
+;;; Made an R7RS library by Taylan Ulrich Bayırlı/Kammer, Copyright (C) 2014.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+;;; Eval these in Emacs:
+;; (put 'stream-lambda 'scheme-indent-function 1)
+;; (put 'stream-let 'scheme-indent-function 2)
+
+(define-syntax define-stream
+ (syntax-rules ()
+ ((define-stream (name . formal) body0 body1 ...)
+ (define name (stream-lambda formal body0 body1 ...)))))
+
+(define (list->stream objs)
+ (define list->stream
+ (stream-lambda (objs)
+ (if (null? objs)
+ stream-null
+ (stream-cons (car objs) (list->stream (cdr objs))))))
+ (if (not (list? objs))
+ (error "non-list argument" objs)
+ (list->stream objs)))
+
+(define (port->stream . port)
+ (define port->stream
+ (stream-lambda (p)
+ (let ((c (read-char p)))
+ (if (eof-object? c)
+ stream-null
+ (stream-cons c (port->stream p))))))
+ (let ((p (if (null? port) (current-input-port) (car port))))
+ (if (not (input-port? p))
+ (error "non-input-port argument" p)
+ (port->stream p))))
+
+(define-syntax stream
+ (syntax-rules ()
+ ((stream) stream-null)
+ ((stream x y ...) (stream-cons x (stream y ...)))))
+
+(define (stream->list . args)
+ (let ((n (if (= 1 (length args)) #f (car args)))
+ (strm (if (= 1 (length args)) (car args) (cadr args))))
+ (cond
+ ((not (stream? strm)) (error "non-stream argument" strm))
+ ((and n (not (integer? n))) (error "non-integer count" n))
+ ((and n (negative? n)) (error "negative count" n))
+ (else (let loop ((n (if n n -1)) (strm strm))
+ (if (or (zero? n) (stream-null? strm))
+ '()
+ (cons (stream-car strm)
+ (loop (- n 1) (stream-cdr strm)))))))))
+
+(define (stream-append . strms)
+ (define stream-append
+ (stream-lambda (strms)
+ (cond
+ ((null? (cdr strms)) (car strms))
+ ((stream-null? (car strms)) (stream-append (cdr strms)))
+ (else (stream-cons (stream-car (car strms))
+ (stream-append (cons (stream-cdr (car strms))
+ (cdr strms))))))))
+ (cond
+ ((null? strms) stream-null)
+ ((find (lambda (x) (not (stream? x))) strms)
+ => (lambda (strm)
+ (error "non-stream argument" strm)))
+ (else (stream-append strms))))
+
+(define (stream-concat strms)
+ (define stream-concat
+ (stream-lambda (strms)
+ (cond
+ ((stream-null? strms) stream-null)
+ ((not (stream? (stream-car strms)))
+ (error "non-stream object in input stream" strms))
+ ((stream-null? (stream-car strms))
+ (stream-concat (stream-cdr strms)))
+ (else (stream-cons
+ (stream-car (stream-car strms))
+ (stream-concat
+ (stream-cons (stream-cdr (stream-car strms))
+ (stream-cdr strms))))))))
+ (if (not (stream? strms))
+ (error "non-stream argument" strms)
+ (stream-concat strms)))
+
+(define stream-constant
+ (stream-lambda objs
+ (cond
+ ((null? objs) stream-null)
+ ((null? (cdr objs)) (stream-cons (car objs)
+ (stream-constant (car objs))))
+ (else (stream-cons (car objs)
+ (apply stream-constant
+ (append (cdr objs) (list (car objs)))))))))
+
+(define (stream-drop n strm)
+ (define stream-drop
+ (stream-lambda (n strm)
+ (if (or (zero? n) (stream-null? strm))
+ strm
+ (stream-drop (- n 1) (stream-cdr strm)))))
+ (cond
+ ((not (integer? n)) (error "non-integer argument" n))
+ ((negative? n) (error "negative argument" n))
+ ((not (stream? strm)) (error "non-stream argument" strm))
+ (else (stream-drop n strm))))
+
+(define (stream-drop-while pred? strm)
+ (define stream-drop-while
+ (stream-lambda (strm)
+ (if (and (stream-pair? strm) (pred? (stream-car strm)))
+ (stream-drop-while (stream-cdr strm))
+ strm)))
+ (cond
+ ((not (procedure? pred?)) (error "non-procedural argument" pred?))
+ ((not (stream? strm)) (error "non-stream argument" strm))
+ (else (stream-drop-while strm))))
+
+(define (stream-filter pred? strm)
+ (define stream-filter
+ (stream-lambda (strm)
+ (cond
+ ((stream-null? strm) stream-null)
+ ((pred? (stream-car strm))
+ (stream-cons (stream-car strm) (stream-filter (stream-cdr strm))))
+ (else (stream-filter (stream-cdr strm))))))
+ (cond
+ ((not (procedure? pred?)) (error "non-procedural argument" pred?))
+ ((not (stream? strm)) (error "non-stream argument" strm))
+ (else (stream-filter strm))))
+
+(define (stream-fold proc base strm)
+ (cond
+ ((not (procedure? proc)) (error "non-procedural argument" proc))
+ ((not (stream? strm)) (error "non-stream argument" strm))
+ (else (let loop ((base base) (strm strm))
+ (if (stream-null? strm)
+ base
+ (loop (proc base (stream-car strm)) (stream-cdr strm)))))))
+
+(define (stream-for-each proc . strms)
+ (define (stream-for-each strms)
+ (if (not (find stream-null? strms))
+ (begin (apply proc (map stream-car strms))
+ (stream-for-each (map stream-cdr strms)))))
+ (cond
+ ((not (procedure? proc)) (error "non-procedural argument" proc))
+ ((null? strms) (error "no stream arguments"))
+ ((find (lambda (x) (not (stream? x))) strms)
+ => (lambda (strm)
+ (error "non-stream argument" strm)))
+ (else (stream-for-each strms))))
+
+(define (stream-from first . step)
+ (define stream-from
+ (stream-lambda (first delta)
+ (stream-cons first (stream-from (+ first delta) delta))))
+ (let ((delta (if (null? step) 1 (car step))))
+ (cond
+ ((not (number? first)) (error "non-numeric starting number" first))
+ ((not (number? delta)) (error "non-numeric step size" delta))
+ (else (stream-from first delta)))))
+
+(define (stream-iterate proc base)
+ (define stream-iterate
+ (stream-lambda (base)
+ (stream-cons base (stream-iterate (proc base)))))
+ (if (not (procedure? proc))
+ (error "non-procedural argument" proc)
+ (stream-iterate base)))
+
+(define (stream-length strm)
+ (if (not (stream? strm))
+ (error "non-stream argument" strm)
+ (let loop ((len 0) (strm strm))
+ (if (stream-null? strm)
+ len
+ (loop (+ len 1) (stream-cdr strm))))))
+
+(define-syntax stream-let
+ (syntax-rules ()
+ ((stream-let tag ((name val) ...) body1 body2 ...)
+ ((letrec ((tag (stream-lambda (name ...) body1 body2 ...)))
+ tag)
+ val ...))))
+
+(define (stream-map proc . strms)
+ (define stream-map
+ (stream-lambda (strms)
+ (if (find stream-null? strms)
+ stream-null
+ (stream-cons (apply proc (map stream-car strms))
+ (stream-map (map stream-cdr strms))))))
+ (cond
+ ((not (procedure? proc)) (error "non-procedural argument" proc))
+ ((null? strms) (error "no stream arguments"))
+ ((find (lambda (x) (not (stream? x))) strms)
+ => (lambda (strm)
+ (error "non-stream argument" strm)))
+ (else (stream-map strms))))
+
+(define-syntax stream-match
+ (syntax-rules ()
+ ((stream-match strm-expr clause ...)
+ (let ((strm strm-expr))
+ (cond
+ ((not (stream? strm)) (error "non-stream argument" strm))
+ ((stream-match-test strm clause) => car) ...
+ (else (error "pattern failure")))))))
+
+(define-syntax stream-match-test
+ (syntax-rules ()
+ ((stream-match-test strm (pattern fender expr))
+ (stream-match-pattern strm pattern () (and fender (list expr))))
+ ((stream-match-test strm (pattern expr))
+ (stream-match-pattern strm pattern () (list expr)))))
+
+(define-syntax stream-match-pattern
+ (syntax-rules (_)
+ ((stream-match-pattern strm () (binding ...) body)
+ (and (stream-null? strm) (let (binding ...) body)))
+ ((stream-match-pattern strm (_ . rest) (binding ...) body)
+ (and (stream-pair? strm)
+ (let ((strm (stream-cdr strm)))
+ (stream-match-pattern strm rest (binding ...) body))))
+ ((stream-match-pattern strm (var . rest) (binding ...) body)
+ (and (stream-pair? strm)
+ (let ((temp (stream-car strm)) (strm (stream-cdr strm)))
+ (stream-match-pattern strm rest ((var temp) binding ...) body))))
+ ((stream-match-pattern strm _ (binding ...) body)
+ (let (binding ...) body))
+ ((stream-match-pattern strm var (binding ...) body)
+ (let ((var strm) binding ...) body))))
+
+(define-syntax stream-of
+ (syntax-rules ()
+ ((_ expr rest ...)
+ (stream-of-aux expr stream-null rest ...))))
+
+(define-syntax stream-of-aux
+ (syntax-rules (in is)
+ ((stream-of-aux expr base)
+ (stream-cons expr base))
+ ((stream-of-aux expr base (var in stream) rest ...)
+ (stream-let loop ((strm stream))
+ (if (stream-null? strm)
+ base
+ (let ((var (stream-car strm)))
+ (stream-of-aux expr (loop (stream-cdr strm)) rest ...)))))
+ ((stream-of-aux expr base (var is exp) rest ...)
+ (let ((var exp)) (stream-of-aux expr base rest ...)))
+ ((stream-of-aux expr base pred? rest ...)
+ (if pred? (stream-of-aux expr base rest ...) base))))
+
+(define (stream-range first past . step)
+ (define stream-range
+ (stream-lambda (first past delta lt?)
+ (if (lt? first past)
+ (stream-cons first (stream-range (+ first delta) past delta lt?))
+ stream-null)))
+ (cond
+ ((not (number? first)) (error "non-numeric starting number" first))
+ ((not (number? past)) (error "non-numeric ending number" past))
+ (else (let ((delta (cond ((pair? step) (car step))
+ ((< first past) 1)
+ (else -1))))
+ (if (not (number? delta))
+ (error "non-numeric step size" delta)
+ (let ((lt? (if (< 0 delta) < >)))
+ (stream-range first past delta lt?)))))))
+
+(define (stream-ref strm n)
+ (cond
+ ((not (stream? strm)) (error "non-stream argument" strm))
+ ((not (integer? n)) (error "non-integer argument" n))
+ ((negative? n) (error "negative argument" n))
+ (else (let loop ((strm strm) (n n))
+ (cond
+ ((stream-null? strm) (error "beyond end of stream" strm))
+ ((zero? n) (stream-car strm))
+ (else (loop (stream-cdr strm) (- n 1))))))))
+
+(define (stream-reverse strm)
+ (define stream-reverse
+ (stream-lambda (strm rev)
+ (if (stream-null? strm)
+ rev
+ (stream-reverse (stream-cdr strm)
+ (stream-cons (stream-car strm) rev)))))
+ (if (not (stream? strm))
+ (error "non-stream argument" strm)
+ (stream-reverse strm stream-null)))
+
+(define (stream-scan proc base strm)
+ (define stream-scan
+ (stream-lambda (base strm)
+ (if (stream-null? strm)
+ (stream base)
+ (stream-cons base (stream-scan (proc base (stream-car strm))
+ (stream-cdr strm))))))
+ (cond
+ ((not (procedure? proc)) (error "non-procedural argument" proc))
+ ((not (stream? strm)) (error "non-stream argument" strm))
+ (else (stream-scan base strm))))
+
+(define (stream-take n strm)
+ (define stream-take
+ (stream-lambda (n strm)
+ (if (or (stream-null? strm) (zero? n))
+ stream-null
+ (stream-cons (stream-car strm)
+ (stream-take (- n 1) (stream-cdr strm))))))
+ (cond
+ ((not (stream? strm)) (error "non-stream argument" strm))
+ ((not (integer? n)) (error "non-integer argument" n))
+ ((negative? n) (error "negative argument" n))
+ (else (stream-take n strm))))
+
+(define (stream-take-while pred? strm)
+ (define stream-take-while
+ (stream-lambda (strm)
+ (cond
+ ((stream-null? strm) stream-null)
+ ((pred? (stream-car strm))
+ (stream-cons (stream-car strm)
+ (stream-take-while (stream-cdr strm))))
+ (else stream-null))))
+ (cond
+ ((not (stream? strm)) (error "non-stream argument" strm))
+ ((not (procedure? pred?)) (error "non-procedural argument" pred?))
+ (else (stream-take-while strm))))
+
+(define (stream-unfold mapper pred? generator base)
+ (define stream-unfold
+ (stream-lambda (base)
+ (if (pred? base)
+ (stream-cons (mapper base) (stream-unfold (generator base)))
+ stream-null)))
+ (cond
+ ((not (procedure? mapper)) (error "non-procedural mapper" mapper))
+ ((not (procedure? pred?)) (error "non-procedural pred?" pred?))
+ ((not (procedure? generator)) (error "non-procedural generator" generator))
+ (else (stream-unfold base))))
+
+(define (stream-unfolds gen seed)
+ (define (len-values gen seed)
+ (call-with-values
+ (lambda () (gen seed))
+ (lambda vs (- (length vs) 1))))
+ (define unfold-result-stream
+ (stream-lambda (gen seed)
+ (call-with-values
+ (lambda () (gen seed))
+ (lambda (next . results)
+ (stream-cons results (unfold-result-stream gen next))))))
+ (define result-stream->output-stream
+ (stream-lambda (result-stream i)
+ (let ((result (list-ref (stream-car result-stream) (- i 1))))
+ (cond
+ ((pair? result)
+ (stream-cons
+ (car result)
+ (result-stream->output-stream (stream-cdr result-stream) i)))
+ ((not result)
+ (result-stream->output-stream (stream-cdr result-stream) i))
+ ((null? result) stream-null)
+ (else (error "can't happen"))))))
+ (define (result-stream->output-streams result-stream)
+ (let loop ((i (len-values gen seed)) (outputs '()))
+ (if (zero? i)
+ (apply values outputs)
+ (loop (- i 1) (cons (result-stream->output-stream result-stream i)
+ outputs)))))
+ (if (not (procedure? gen))
+ (error "non-procedural argument" gen)
+ (result-stream->output-streams (unfold-result-stream gen seed))))
+
+(define (stream-zip . strms)
+ (define stream-zip
+ (stream-lambda (strms)
+ (if (find stream-null? strms)
+ stream-null
+ (stream-cons (map stream-car strms)
+ (stream-zip (map stream-cdr strms))))))
+ (cond
+ ((null? strms) (error "no stream arguments"))
+ ((find (lambda (x) (not (stream? x))) strms)
+ => (lambda (strm)
+ (error "non-stream argument" strm)))
+ (else (stream-zip strms))))
+(define-library (srfi 41 derived)
+ (export
+ stream-null stream-cons stream? stream-null? stream-pair? stream-car
+ stream-cdr stream-lambda define-stream list->stream port->stream stream
+ stream->list stream-append stream-concat stream-constant stream-drop
+ stream-drop-while stream-filter stream-fold stream-for-each stream-from
+ stream-iterate stream-length stream-let stream-map stream-match _
+ stream-of stream-range stream-ref stream-reverse stream-scan stream-take
+ stream-take-while stream-unfold stream-unfolds stream-zip
+ )
+ (import
+ (scheme base)
+ (srfi 1)
+ (srfi 41 primitive))
+ (include "derived.body.scm"))
+;;; Copyright (C) Philip L. Bewig (2007). All Rights Reserved.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+(library (streams derived)
+
+ (export stream-null stream-cons stream? stream-null? stream-pair? stream-car
+ stream-cdr stream-lambda define-stream list->stream port->stream stream
+ stream->list stream-append stream-concat stream-constant stream-drop
+ stream-drop-while stream-filter stream-fold stream-for-each stream-from
+ stream-iterate stream-length stream-let stream-map stream-match _
+ stream-of stream-range stream-ref stream-reverse stream-scan stream-take
+ stream-take-while stream-unfold stream-unfolds stream-zip)
+
+ (import (rnrs) (streams primitive))
+
+ (define-syntax define-stream
+ (syntax-rules ()
+ ((define-stream (name . formal) body0 body1 ...)
+ (define name (stream-lambda formal body0 body1 ...)))))
+
+ (define (list->stream objs)
+ (define list->stream
+ (stream-lambda (objs)
+ (if (null? objs)
+ stream-null
+ (stream-cons (car objs) (list->stream (cdr objs))))))
+ (if (not (list? objs))
+ (error 'list->stream "non-list argument")
+ (list->stream objs)))
+
+ (define (port->stream . port)
+ (define port->stream
+ (stream-lambda (p)
+ (let ((c (read-char p)))
+ (if (eof-object? c)
+ stream-null
+ (stream-cons c (port->stream p))))))
+ (let ((p (if (null? port) (current-input-port) (car port))))
+ (if (not (input-port? p))
+ (error 'port->stream "non-input-port argument")
+ (port->stream p))))
+
+ (define-syntax stream
+ (syntax-rules ()
+ ((stream) stream-null)
+ ((stream x y ...) (stream-cons x (stream y ...)))))
+
+ (define (stream->list . args)
+ (let ((n (if (= 1 (length args)) #f (car args)))
+ (strm (if (= 1 (length args)) (car args) (cadr args))))
+ (cond ((not (stream? strm)) (error 'stream->list "non-stream argument"))
+ ((and n (not (integer? n))) (error 'stream->list "non-integer count"))
+ ((and n (negative? n)) (error 'stream->list "negative count"))
+ (else (let loop ((n (if n n -1)) (strm strm))
+ (if (or (zero? n) (stream-null? strm))
+ '()
+ (cons (stream-car strm) (loop (- n 1) (stream-cdr strm)))))))))
+
+ (define (stream-append . strms)
+ (define stream-append
+ (stream-lambda (strms)
+ (cond ((null? (cdr strms)) (car strms))
+ ((stream-null? (car strms)) (stream-append (cdr strms)))
+ (else (stream-cons (stream-car (car strms))
+ (stream-append (cons (stream-cdr (car strms)) (cdr strms))))))))
+ (cond ((null? strms) stream-null)
+ ((exists (lambda (x) (not (stream? x))) strms)
+ (error 'stream-append "non-stream argument"))
+ (else (stream-append strms))))
+
+ (define (stream-concat strms)
+ (define stream-concat
+ (stream-lambda (strms)
+ (cond ((stream-null? strms) stream-null)
+ ((not (stream? (stream-car strms)))
+ (error 'stream-concat "non-stream object in input stream"))
+ ((stream-null? (stream-car strms))
+ (stream-concat (stream-cdr strms)))
+ (else (stream-cons
+ (stream-car (stream-car strms))
+ (stream-concat
+ (stream-cons (stream-cdr (stream-car strms)) (stream-cdr strms))))))))
+ (if (not (stream? strms))
+ (error 'stream-concat "non-stream argument")
+ (stream-concat strms)))
+
+ (define stream-constant
+ (stream-lambda objs
+ (cond ((null? objs) stream-null)
+ ((null? (cdr objs)) (stream-cons (car objs) (stream-constant (car objs))))
+ (else (stream-cons (car objs)
+ (apply stream-constant (append (cdr objs) (list (car objs)))))))))
+
+ (define (stream-drop n strm)
+ (define stream-drop
+ (stream-lambda (n strm)
+ (if (or (zero? n) (stream-null? strm))
+ strm
+ (stream-drop (- n 1) (stream-cdr strm)))))
+ (cond ((not (integer? n)) (error 'stream-drop "non-integer argument"))
+ ((negative? n) (error 'stream-drop "negative argument"))
+ ((not (stream? strm)) (error 'stream-drop "non-stream argument"))
+ (else (stream-drop n strm))))
+
+ (define (stream-drop-while pred? strm)
+ (define stream-drop-while
+ (stream-lambda (strm)
+ (if (and (stream-pair? strm) (pred? (stream-car strm)))
+ (stream-drop-while (stream-cdr strm))
+ strm)))
+ (cond ((not (procedure? pred?)) (error 'stream-drop-while "non-procedural argument"))
+ ((not (stream? strm)) (error 'stream-drop-while "non-stream argument"))
+ (else (stream-drop-while strm))))
+
+ (define (stream-filter pred? strm)
+ (define stream-filter
+ (stream-lambda (strm)
+ (cond ((stream-null? strm) stream-null)
+ ((pred? (stream-car strm))
+ (stream-cons (stream-car strm) (stream-filter (stream-cdr strm))))
+ (else (stream-filter (stream-cdr strm))))))
+ (cond ((not (procedure? pred?)) (error 'stream-filter "non-procedural argument"))
+ ((not (stream? strm)) (error 'stream-filter "non-stream argument"))
+ (else (stream-filter strm))))
+
+ (define (stream-fold proc base strm)
+ (cond ((not (procedure? proc)) (error 'stream-fold "non-procedural argument"))
+ ((not (stream? strm)) (error 'stream-fold "non-stream argument"))
+ (else (let loop ((base base) (strm strm))
+ (if (stream-null? strm)
+ base
+ (loop (proc base (stream-car strm)) (stream-cdr strm)))))))
+
+ (define (stream-for-each proc . strms)
+ (define (stream-for-each strms)
+ (if (not (exists stream-null? strms))
+ (begin (apply proc (map stream-car strms))
+ (stream-for-each (map stream-cdr strms)))))
+ (cond ((not (procedure? proc)) (error 'stream-for-each "non-procedural argument"))
+ ((null? strms) (error 'stream-for-each "no stream arguments"))
+ ((exists (lambda (x) (not (stream? x))) strms)
+ (error 'stream-for-each "non-stream argument"))
+ (else (stream-for-each strms))))
+
+ (define (stream-from first . step)
+ (define stream-from
+ (stream-lambda (first delta)
+ (stream-cons first (stream-from (+ first delta) delta))))
+ (let ((delta (if (null? step) 1 (car step))))
+ (cond ((not (number? first)) (error 'stream-from "non-numeric starting number"))
+ ((not (number? delta)) (error 'stream-from "non-numeric step size"))
+ (else (stream-from first delta)))))
+
+ (define (stream-iterate proc base)
+ (define stream-iterate
+ (stream-lambda (base)
+ (stream-cons base (stream-iterate (proc base)))))
+ (if (not (procedure? proc))
+ (error 'stream-iterate "non-procedural argument")
+ (stream-iterate base)))
+
+ (define (stream-length strm)
+ (if (not (stream? strm))
+ (error 'stream-length "non-stream argument")
+ (let loop ((len 0) (strm strm))
+ (if (stream-null? strm)
+ len
+ (loop (+ len 1) (stream-cdr strm))))))
+
+ (define-syntax stream-let
+ (syntax-rules ()
+ ((stream-let tag ((name val) ...) body1 body2 ...)
+ ((letrec ((tag (stream-lambda (name ...) body1 body2 ...))) tag) val ...))))
+
+ (define (stream-map proc . strms)
+ (define stream-map
+ (stream-lambda (strms)
+ (if (exists stream-null? strms)
+ stream-null
+ (stream-cons (apply proc (map stream-car strms))
+ (stream-map (map stream-cdr strms))))))
+ (cond ((not (procedure? proc)) (error 'stream-map "non-procedural argument"))
+ ((null? strms) (error 'stream-map "no stream arguments"))
+ ((exists (lambda (x) (not (stream? x))) strms)
+ (error 'stream-map "non-stream argument"))
+ (else (stream-map strms))))
+
+ (define-syntax stream-match
+ (syntax-rules ()
+ ((stream-match strm-expr clause ...)
+ (let ((strm strm-expr))
+ (cond
+ ((not (stream? strm)) (error 'stream-match "non-stream argument"))
+ ((stream-match-test strm clause) => car) ...
+ (else (error 'stream-match "pattern failure")))))))
+
+ (define-syntax stream-match-test
+ (syntax-rules ()
+ ((stream-match-test strm (pattern fender expr))
+ (stream-match-pattern strm pattern () (and fender (list expr))))
+ ((stream-match-test strm (pattern expr))
+ (stream-match-pattern strm pattern () (list expr)))))
+
+ (define-syntax stream-match-pattern
+ (lambda (x)
+ (define (wildcard? x)
+ (and (identifier? x)
+ (free-identifier=? x (syntax _))))
+ (syntax-case x ()
+ ((stream-match-pattern strm () (binding ...) body)
+ (syntax (and (stream-null? strm) (let (binding ...) body))))
+ ((stream-match-pattern strm (w? . rest) (binding ...) body)
+ (wildcard? #'w?)
+ (syntax (and (stream-pair? strm)
+ (let ((strm (stream-cdr strm)))
+ (stream-match-pattern strm rest (binding ...) body)))))
+ ((stream-match-pattern strm (var . rest) (binding ...) body)
+ (syntax (and (stream-pair? strm)
+ (let ((temp (stream-car strm)) (strm (stream-cdr strm)))
+ (stream-match-pattern strm rest ((var temp) binding ...) body)))))
+ ((stream-match-pattern strm w? (binding ...) body)
+ (wildcard? #'w?)
+ (syntax (let (binding ...) body)))
+ ((stream-match-pattern strm var (binding ...) body)
+ (syntax (let ((var strm) binding ...) body))))))
+
+ (define-syntax stream-of
+ (syntax-rules ()
+ ((_ expr rest ...)
+ (stream-of-aux expr stream-null rest ...))))
+
+ (define-syntax stream-of-aux
+ (syntax-rules (in is)
+ ((stream-of-aux expr base)
+ (stream-cons expr base))
+ ((stream-of-aux expr base (var in stream) rest ...)
+ (stream-let loop ((strm stream))
+ (if (stream-null? strm)
+ base
+ (let ((var (stream-car strm)))
+ (stream-of-aux expr (loop (stream-cdr strm)) rest ...)))))
+ ((stream-of-aux expr base (var is exp) rest ...)
+ (let ((var exp)) (stream-of-aux expr base rest ...)))
+ ((stream-of-aux expr base pred? rest ...)
+ (if pred? (stream-of-aux expr base rest ...) base))))
+
+ (define (stream-range first past . step)
+ (define stream-range
+ (stream-lambda (first past delta lt?)
+ (if (lt? first past)
+ (stream-cons first (stream-range (+ first delta) past delta lt?))
+ stream-null)))
+ (cond ((not (number? first)) (error 'stream-range "non-numeric starting number"))
+ ((not (number? past)) (error 'stream-range "non-numeric ending number"))
+ (else (let ((delta (cond ((pair? step) (car step)) ((< first past) 1) (else -1))))
+ (if (not (number? delta))
+ (error 'stream-range "non-numeric step size")
+ (let ((lt? (if (< 0 delta) < >)))
+ (stream-range first past delta lt?)))))))
+
+ (define (stream-ref strm n)
+ (cond ((not (stream? strm)) (error 'stream-ref "non-stream argument"))
+ ((not (integer? n)) (error 'stream-ref "non-integer argument"))
+ ((negative? n) (error 'stream-ref "negative argument"))
+ (else (let loop ((strm strm) (n n))
+ (cond ((stream-null? strm) (error 'stream-ref "beyond end of stream"))
+ ((zero? n) (stream-car strm))
+ (else (loop (stream-cdr strm) (- n 1))))))))
+
+ (define (stream-reverse strm)
+ (define stream-reverse
+ (stream-lambda (strm rev)
+ (if (stream-null? strm)
+ rev
+ (stream-reverse (stream-cdr strm) (stream-cons (stream-car strm) rev)))))
+ (if (not (stream? strm))
+ (error 'stream-reverse "non-stream argument")
+ (stream-reverse strm stream-null)))
+
+ (define (stream-scan proc base strm)
+ (define stream-scan
+ (stream-lambda (base strm)
+ (if (stream-null? strm)
+ (stream base)
+ (stream-cons base (stream-scan (proc base (stream-car strm)) (stream-cdr strm))))))
+ (cond ((not (procedure? proc)) (error 'stream-scan "non-procedural argument"))
+ ((not (stream? strm)) (error 'stream-scan "non-stream argument"))
+ (else (stream-scan base strm))))
+
+ (define (stream-take n strm)
+ (define stream-take
+ (stream-lambda (n strm)
+ (if (or (stream-null? strm) (zero? n))
+ stream-null
+ (stream-cons (stream-car strm) (stream-take (- n 1) (stream-cdr strm))))))
+ (cond ((not (stream? strm)) (error 'stream-take "non-stream argument"))
+ ((not (integer? n)) (error 'stream-take "non-integer argument"))
+ ((negative? n) (error 'stream-take "negative argument"))
+ (else (stream-take n strm))))
+
+ (define (stream-take-while pred? strm)
+ (define stream-take-while
+ (stream-lambda (strm)
+ (cond ((stream-null? strm) stream-null)
+ ((pred? (stream-car strm))
+ (stream-cons (stream-car strm) (stream-take-while (stream-cdr strm))))
+ (else stream-null))))
+ (cond ((not (stream? strm)) (error 'stream-take-while "non-stream argument"))
+ ((not (procedure? pred?)) (error 'stream-take-while "non-procedural argument"))
+ (else (stream-take-while strm))))
+
+ (define (stream-unfold mapper pred? generator base)
+ (define stream-unfold
+ (stream-lambda (base)
+ (if (pred? base)
+ (stream-cons (mapper base) (stream-unfold (generator base)))
+ stream-null)))
+ (cond ((not (procedure? mapper)) (error 'stream-unfold "non-procedural mapper"))
+ ((not (procedure? pred?)) (error 'stream-unfold "non-procedural pred?"))
+ ((not (procedure? generator)) (error 'stream-unfold "non-procedural generator"))
+ (else (stream-unfold base))))
+
+ (define (stream-unfolds gen seed)
+ (define (len-values gen seed)
+ (call-with-values
+ (lambda () (gen seed))
+ (lambda vs (- (length vs) 1))))
+ (define unfold-result-stream
+ (stream-lambda (gen seed)
+ (call-with-values
+ (lambda () (gen seed))
+ (lambda (next . results)
+ (stream-cons results (unfold-result-stream gen next))))))
+ (define result-stream->output-stream
+ (stream-lambda (result-stream i)
+ (let ((result (list-ref (stream-car result-stream) (- i 1))))
+ (cond ((pair? result)
+ (stream-cons
+ (car result)
+ (result-stream->output-stream (stream-cdr result-stream) i)))
+ ((not result)
+ (result-stream->output-stream (stream-cdr result-stream) i))
+ ((null? result) stream-null)
+ (else (error 'stream-unfolds "can't happen"))))))
+ (define (result-stream->output-streams result-stream)
+ (let loop ((i (len-values gen seed)) (outputs '()))
+ (if (zero? i)
+ (apply values outputs)
+ (loop (- i 1) (cons (result-stream->output-stream result-stream i) outputs)))))
+ (if (not (procedure? gen))
+ (error 'stream-unfolds "non-procedural argument")
+ (result-stream->output-streams (unfold-result-stream gen seed))))
+
+ (define (stream-zip . strms)
+ (define stream-zip
+ (stream-lambda (strms)
+ (if (exists stream-null? strms)
+ stream-null
+ (stream-cons (map stream-car strms) (stream-zip (map stream-cdr strms))))))
+ (cond ((null? strms) (error 'stream-zip "no stream arguments"))
+ ((exists (lambda (x) (not (stream? x))) strms)
+ (error 'stream-zip "non-stream argument"))
+ (else (stream-zip strms)))))
+;;; Copyright (C) Philip L. Bewig (2007). All Rights Reserved.
+
+;;; Made an R7RS library by Taylan Ulrich Bayırlı/Kammer, Copyright (C) 2014.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+(define-record-type <stream>
+ (make-stream promise)
+ stream?
+ (promise stream-promise stream-promise!))
+
+(define-syntax stream-lazy
+ (syntax-rules ()
+ ((stream-lazy expr)
+ (make-stream
+ (cons 'lazy (lambda () expr))))))
+
+(define (stream-eager expr)
+ (make-stream
+ (cons 'eager expr)))
+
+(define-syntax stream-delay
+ (syntax-rules ()
+ ((stream-delay expr)
+ (stream-lazy (stream-eager expr)))))
+
+(define (stream-force promise)
+ (let ((content (stream-promise promise)))
+ (case (car content)
+ ((eager) (cdr content))
+ ((lazy) (let* ((promise* ((cdr content)))
+ (content (stream-promise promise)))
+ (if (not (eqv? (car content) 'eager))
+ (begin (set-car! content (car (stream-promise promise*)))
+ (set-cdr! content (cdr (stream-promise promise*)))
+ (stream-promise! promise* content)))
+ (stream-force promise))))))
+
+(define stream-null (stream-delay (cons 'stream 'null)))
+
+(define-record-type <stream-pare>
+ (make-stream-pare kar kdr)
+ stream-pare?
+ (kar stream-kar)
+ (kdr stream-kdr))
+
+(define (stream-pair? obj)
+ (and (stream? obj) (stream-pare? (stream-force obj))))
+
+(define (stream-null? obj)
+ (and (stream? obj)
+ (eqv? (stream-force obj)
+ (stream-force stream-null))))
+
+(define-syntax stream-cons
+ (syntax-rules ()
+ ((stream-cons obj strm)
+ (stream-eager (make-stream-pare (stream-delay obj) (stream-lazy strm))))))
+
+(define (stream-car strm)
+ (cond ((not (stream? strm)) (error "non-stream" strm))
+ ((stream-null? strm) (error "null stream" strm))
+ (else (stream-force (stream-kar (stream-force strm))))))
+
+(define (stream-cdr strm)
+ (cond ((not (stream? strm)) (error "non-stream" strm))
+ ((stream-null? strm) (error "null stream" strm))
+ (else (stream-kdr (stream-force strm)))))
+
+(define-syntax stream-lambda
+ (syntax-rules ()
+ ((stream-lambda formals body0 body1 ...)
+ (lambda formals (stream-lazy (let () body0 body1 ...))))))
+(define-library (srfi 41 primitive)
+ (export
+ stream-null stream-cons stream? stream-null? stream-pair?
+ stream-car stream-cdr stream-lambda
+ )
+ (import (scheme base))
+ (include "primitive.body.scm"))
+;;; Copyright (C) Philip L. Bewig (2007). All Rights Reserved.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+(library (streams primitive)
+
+ (export stream-null stream-cons stream? stream-null? stream-pair?
+ stream-car stream-cdr stream-lambda)
+
+ (import (rnrs) (rnrs mutable-pairs))
+
+ (define-record-type (stream-type make-stream stream?)
+ (fields (mutable box stream-promise stream-promise!)))
+
+ (define-syntax stream-lazy
+ (syntax-rules ()
+ ((stream-lazy expr)
+ (make-stream
+ (cons 'lazy (lambda () expr))))))
+
+ (define (stream-eager expr)
+ (make-stream
+ (cons 'eager expr)))
+
+ (define-syntax stream-delay
+ (syntax-rules ()
+ ((stream-delay expr)
+ (stream-lazy (stream-eager expr)))))
+
+ (define (stream-force promise)
+ (let ((content (stream-promise promise)))
+ (case (car content)
+ ((eager) (cdr content))
+ ((lazy) (let* ((promise* ((cdr content)))
+ (content (stream-promise promise)))
+ (if (not (eqv? (car content) 'eager))
+ (begin (set-car! content (car (stream-promise promise*)))
+ (set-cdr! content (cdr (stream-promise promise*)))
+ (stream-promise! promise* content)))
+ (stream-force promise))))))
+
+ (define stream-null (stream-delay (cons 'stream 'null)))
+
+ (define-record-type (stream-pare-type make-stream-pare stream-pare?)
+ (fields (immutable kar stream-kar) (immutable kdr stream-kdr)))
+
+ (define (stream-pair? obj)
+ (and (stream? obj) (stream-pare? (stream-force obj))))
+
+ (define (stream-null? obj)
+ (and (stream? obj)
+ (eqv? (stream-force obj)
+ (stream-force stream-null))))
+
+ (define-syntax stream-cons
+ (syntax-rules ()
+ ((stream-cons obj strm)
+ (stream-eager (make-stream-pare (stream-delay obj) (stream-lazy strm))))))
+
+ (define (stream-car strm)
+ (cond ((not (stream? strm)) (error 'stream-car "non-stream"))
+ ((stream-null? strm) (error 'stream-car "null stream"))
+ (else (stream-force (stream-kar (stream-force strm))))))
+
+ (define (stream-cdr strm)
+ (cond ((not (stream? strm)) (error 'stream-cdr "non-stream"))
+ ((stream-null? strm) (error 'stream-cdr "null stream"))
+ (else (stream-kdr (stream-force strm)))))
+
+ (define-syntax stream-lambda
+ (syntax-rules ()
+ ((stream-lambda formals body0 body1 ...)
+ (lambda formals (stream-lazy (let () body0 body1 ...)))))))
+;; Copyright (c) 2005, 2006, 2007, 2012, 2013 Per Bothner
+;; Added "full" support for Chicken, Gauche, Guile and SISC.
+;; Alex Shinn, Copyright (c) 2005.
+;; Modified for Scheme Spheres by Álvaro Castro-Castilla, Copyright (c) 2012.
+;; Support for Guile 2 by Mark H Weaver <mhw@netris.org>, Copyright (c) 2014.
+;; Refactored by Taylan Ulrich Bayırlı/Kammer, Copyright (c) 2014, 2015.
+;;
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Note: to prevent producing massive amounts of code from the macro-expand
+;;; phase (which makes compile times suffer and may hit code size limits in some
+;;; systems), keep macro bodies minimal by delegating work to procedures.
+
+
+;;; Grouping
+
+(define (maybe-install-default-runner suite-name)
+ (when (not (test-runner-current))
+ (let* ((log-file (string-append suite-name ".srfi64.log"))
+ (runner (test-runner-simple log-file)))
+ (%test-runner-auto-installed! runner #t)
+ (test-runner-current runner))))
+
+(define (maybe-uninstall-default-runner)
+ (when (%test-runner-auto-installed? (test-runner-current))
+ (test-runner-current #f)))
+
+(define test-begin
+ (case-lambda
+ ((name)
+ (test-begin name #f))
+ ((name count)
+ (maybe-install-default-runner name)
+ (let ((r (test-runner-current)))
+ (let ((skip-list (%test-runner-skip-list r))
+ (skip-save (%test-runner-skip-save r))
+ (fail-list (%test-runner-fail-list r))
+ (fail-save (%test-runner-fail-save r))
+ (total-count (%test-runner-total-count r))
+ (count-list (%test-runner-count-list r))
+ (group-stack (test-runner-group-stack r)))
+ ((test-runner-on-group-begin r) r name count)
+ (%test-runner-skip-save! r (cons skip-list skip-save))
+ (%test-runner-fail-save! r (cons fail-list fail-save))
+ (%test-runner-count-list! r (cons (cons total-count count)
+ count-list))
+ (test-runner-group-stack! r (cons name group-stack)))))))
+
+(define test-end
+ (case-lambda
+ (()
+ (test-end #f))
+ ((name)
+ (let* ((r (test-runner-get))
+ (groups (test-runner-group-stack r)))
+ (test-result-clear r)
+ (when (null? groups)
+ (error "test-end not in a group"))
+ (when (and name (not (equal? name (car groups))))
+ ((test-runner-on-bad-end-name r) r name (car groups)))
+ (let* ((count-list (%test-runner-count-list r))
+ (expected-count (cdar count-list))
+ (saved-count (caar count-list))
+ (group-count (- (%test-runner-total-count r) saved-count)))
+ (when (and expected-count
+ (not (= expected-count group-count)))
+ ((test-runner-on-bad-count r) r group-count expected-count))
+ ((test-runner-on-group-end r) r)
+ (test-runner-group-stack! r (cdr (test-runner-group-stack r)))
+ (%test-runner-skip-list! r (car (%test-runner-skip-save r)))
+ (%test-runner-skip-save! r (cdr (%test-runner-skip-save r)))
+ (%test-runner-fail-list! r (car (%test-runner-fail-save r)))
+ (%test-runner-fail-save! r (cdr (%test-runner-fail-save r)))
+ (%test-runner-count-list! r (cdr count-list))
+ (when (null? (test-runner-group-stack r))
+ ((test-runner-on-final r) r)
+ (maybe-uninstall-default-runner)))))))
+
+(define-syntax test-group
+ (syntax-rules ()
+ ((_ <name> <body> . <body>*)
+ (%test-group <name> (lambda () <body> . <body>*)))))
+
+(define (%test-group name thunk)
+ (begin
+ (maybe-install-default-runner name)
+ (let ((runner (test-runner-get)))
+ (test-result-clear runner)
+ (test-result-set! runner 'name name)
+ (unless (test-skip? runner)
+ (dynamic-wind
+ (lambda () (test-begin name))
+ thunk
+ (lambda () (test-end name)))))))
+
+(define-syntax test-group-with-cleanup
+ (syntax-rules ()
+ ((_ <name> <body> <body>* ... <cleanup>)
+ (test-group <name>
+ (dynamic-wind (lambda () #f)
+ (lambda () <body> <body>* ...)
+ (lambda () <cleanup>))))))
+
+
+;;; Skipping, expected-failing, matching
+
+(define (test-skip . specs)
+ (let ((runner (test-runner-get)))
+ (%test-runner-skip-list!
+ runner (cons (apply test-match-all specs)
+ (%test-runner-skip-list runner)))))
+
+(define (test-skip? runner)
+ (let ((run-list (%test-runner-run-list runner))
+ (skip-list (%test-runner-skip-list runner)))
+ (or (and run-list (not (any-pred run-list runner)))
+ (any-pred skip-list runner))))
+
+(define (test-expect-fail . specs)
+ (let ((runner (test-runner-get)))
+ (%test-runner-fail-list!
+ runner (cons (apply test-match-all specs)
+ (%test-runner-fail-list runner)))))
+
+(define (test-match-any . specs)
+ (let ((preds (map make-pred specs)))
+ (lambda (runner)
+ (any-pred preds runner))))
+
+(define (test-match-all . specs)
+ (let ((preds (map make-pred specs)))
+ (lambda (runner)
+ (every-pred preds runner))))
+
+(define (make-pred spec)
+ (cond
+ ((procedure? spec)
+ spec)
+ ((integer? spec)
+ (test-match-nth 1 spec))
+ ((string? spec)
+ (test-match-name spec))
+ (else
+ (error "not a valid test specifier" spec))))
+
+(define test-match-nth
+ (case-lambda
+ ((n) (test-match-nth n 1))
+ ((n count)
+ (let ((i 0))
+ (lambda (runner)
+ (set! i (+ i 1))
+ (and (>= i n) (< i (+ n count))))))))
+
+(define (test-match-name name)
+ (lambda (runner)
+ (equal? name (test-runner-test-name runner))))
+
+;;; Beware: all predicates must be called because they might have side-effects;
+;;; no early returning or and/or short-circuiting of procedure calls allowed.
+
+(define (any-pred preds object)
+ (let loop ((matched? #f)
+ (preds preds))
+ (if (null? preds)
+ matched?
+ (let ((result ((car preds) object)))
+ (loop (or matched? result)
+ (cdr preds))))))
+
+(define (every-pred preds object)
+ (let loop ((failed? #f)
+ (preds preds))
+ (if (null? preds)
+ (not failed?)
+ (let ((result ((car preds) object)))
+ (loop (or failed? (not result))
+ (cdr preds))))))
+
+;;; Actual testing
+
+(define-syntax false-if-error
+ (syntax-rules ()
+ ((_ <expression> <runner>)
+ (guard (error
+ (else
+ (test-result-set! <runner> 'actual-error error)
+ #f))
+ <expression>))))
+
+(define (test-prelude source-info runner name form)
+ (test-result-clear runner)
+ (set-source-info! runner source-info)
+ (when name
+ (test-result-set! runner 'name name))
+ (test-result-set! runner 'source-form form)
+ (let ((skip? (test-skip? runner)))
+ (if skip?
+ (test-result-set! runner 'result-kind 'skip)
+ (let ((fail-list (%test-runner-fail-list runner)))
+ (when (any-pred fail-list runner)
+ ;; For later inspection only.
+ (test-result-set! runner 'result-kind 'xfail))))
+ ((test-runner-on-test-begin runner) runner)
+ (not skip?)))
+
+(define (test-postlude runner)
+ (let ((result-kind (test-result-kind runner)))
+ (case result-kind
+ ((pass)
+ (test-runner-pass-count! runner (+ 1 (test-runner-pass-count runner))))
+ ((fail)
+ (test-runner-fail-count! runner (+ 1 (test-runner-fail-count runner))))
+ ((xpass)
+ (test-runner-xpass-count! runner (+ 1 (test-runner-xpass-count runner))))
+ ((xfail)
+ (test-runner-xfail-count! runner (+ 1 (test-runner-xfail-count runner))))
+ ((skip)
+ (test-runner-skip-count! runner (+ 1 (test-runner-skip-count runner)))))
+ (%test-runner-total-count! runner (+ 1 (%test-runner-total-count runner)))
+ ((test-runner-on-test-end runner) runner)))
+
+(define (set-result-kind! runner pass?)
+ (test-result-set! runner 'result-kind
+ (if (eq? (test-result-kind runner) 'xfail)
+ (if pass? 'xpass 'xfail)
+ (if pass? 'pass 'fail))))
+
+;;; We need to use some trickery to get the source info right. The important
+;;; thing is to pass a syntax object that is a pair to `source-info', and make
+;;; sure this syntax object comes from user code and not from ourselves.
+
+(define-syntax test-assert
+ (syntax-rules ()
+ ((_ . <rest>)
+ (test-assert/source-info (source-info <rest>) . <rest>))))
+
+(define-syntax test-assert/source-info
+ (syntax-rules ()
+ ((_ <source-info> <expr>)
+ (test-assert/source-info <source-info> #f <expr>))
+ ((_ <source-info> <name> <expr>)
+ (%test-assert <source-info> <name> '<expr> (lambda () <expr>)))))
+
+(define (%test-assert source-info name form thunk)
+ (let ((runner (test-runner-get)))
+ (when (test-prelude source-info runner name form)
+ (let ((val (false-if-error (thunk) runner)))
+ (test-result-set! runner 'actual-value val)
+ (set-result-kind! runner val)))
+ (test-postlude runner)))
+
+(define-syntax test-compare
+ (syntax-rules ()
+ ((_ . <rest>)
+ (test-compare/source-info (source-info <rest>) . <rest>))))
+
+(define-syntax test-compare/source-info
+ (syntax-rules ()
+ ((_ <source-info> <compare> <expected> <expr>)
+ (test-compare/source-info <source-info> <compare> #f <expected> <expr>))
+ ((_ <source-info> <compare> <name> <expected> <expr>)
+ (%test-compare <source-info> <compare> <name> <expected> '<expr>
+ (lambda () <expr>)))))
+
+(define (%test-compare source-info compare name expected form thunk)
+ (let ((runner (test-runner-get)))
+ (when (test-prelude source-info runner name form)
+ (test-result-set! runner 'expected-value expected)
+ (let ((pass? (false-if-error
+ (let ((val (thunk)))
+ (test-result-set! runner 'actual-value val)
+ (compare expected val))
+ runner)))
+ (set-result-kind! runner pass?)))
+ (test-postlude runner)))
+
+(define-syntax test-equal
+ (syntax-rules ()
+ ((_ . <rest>)
+ (test-compare/source-info (source-info <rest>) equal? . <rest>))))
+
+(define-syntax test-eqv
+ (syntax-rules ()
+ ((_ . <rest>)
+ (test-compare/source-info (source-info <rest>) eqv? . <rest>))))
+
+(define-syntax test-eq
+ (syntax-rules ()
+ ((_ . <rest>)
+ (test-compare/source-info (source-info <rest>) eq? . <rest>))))
+
+(define (approx= margin)
+ (lambda (value expected)
+ (let ((rval (real-part value))
+ (ival (imag-part value))
+ (rexp (real-part expected))
+ (iexp (imag-part expected)))
+ (and (>= rval (- rexp margin))
+ (>= ival (- iexp margin))
+ (<= rval (+ rexp margin))
+ (<= ival (+ iexp margin))))))
+
+(define-syntax test-approximate
+ (syntax-rules ()
+ ((_ . <rest>)
+ (test-approximate/source-info (source-info <rest>) . <rest>))))
+
+(define-syntax test-approximate/source-info
+ (syntax-rules ()
+ ((_ <source-info> <expected> <expr> <error-margin>)
+ (test-approximate/source-info
+ <source-info> #f <expected> <expr> <error-margin>))
+ ((_ <source-info> <name> <expected> <expr> <error-margin>)
+ (test-compare/source-info
+ <source-info> (approx= <error-margin>) <name> <expected> <expr>))))
+
+(define (error-matches? error type)
+ (cond
+ ((eq? type #t)
+ #t)
+ ((condition-type? type)
+ (and (condition? error) (condition-has-type? error type)))
+ ((procedure? type)
+ (type error))
+ (else
+ (let ((runner (test-runner-get)))
+ ((%test-runner-on-bad-error-type runner) runner type error))
+ #f)))
+
+(define-syntax test-error
+ (syntax-rules ()
+ ((_ . <rest>)
+ (test-error/source-info (source-info <rest>) . <rest>))))
+
+(define-syntax test-error/source-info
+ (syntax-rules ()
+ ((_ <source-info> <expr>)
+ (test-error/source-info <source-info> #f #t <expr>))
+ ((_ <source-info> <error-type> <expr>)
+ (test-error/source-info <source-info> #f <error-type> <expr>))
+ ((_ <source-info> <name> <error-type> <expr>)
+ (%test-error <source-info> <name> <error-type> '<expr>
+ (lambda () <expr>)))))
+
+(define (%test-error source-info name error-type form thunk)
+ (let ((runner (test-runner-get)))
+ (when (test-prelude source-info runner name form)
+ (test-result-set! runner 'expected-error error-type)
+ (let ((pass? (guard (error (else (test-result-set!
+ runner 'actual-error error)
+ (error-matches? error error-type)))
+ (let ((val (thunk)))
+ (test-result-set! runner 'actual-value val))
+ #f)))
+ (set-result-kind! runner pass?)))
+ (test-postlude runner)))
+
+(define (default-module)
+ (cond-expand
+ (guile (current-module))
+ (else #f)))
+
+(define test-read-eval-string
+ (case-lambda
+ ((string)
+ (test-read-eval-string string (default-module)))
+ ((string env)
+ (let* ((port (open-input-string string))
+ (form (read port)))
+ (if (eof-object? (read-char port))
+ (if env
+ (eval form env)
+ (eval form))
+ (error "(not at eof)"))))))
+
+
+;;; Test runner control flow
+
+(define-syntax test-with-runner
+ (syntax-rules ()
+ ((_ <runner> <body> . <body>*)
+ (let ((saved-runner (test-runner-current)))
+ (dynamic-wind
+ (lambda () (test-runner-current <runner>))
+ (lambda () <body> . <body>*)
+ (lambda () (test-runner-current saved-runner)))))))
+
+(define (test-apply first . rest)
+ (let ((runner (if (test-runner? first)
+ first
+ (or (test-runner-current) (test-runner-create))))
+ (run-list (if (test-runner? first)
+ (drop-right rest 1)
+ (cons first (drop-right rest 1))))
+ (proc (last rest)))
+ (test-with-runner runner
+ (let ((saved-run-list (%test-runner-run-list runner)))
+ (%test-runner-run-list! runner run-list)
+ (proc)
+ (%test-runner-run-list! runner saved-run-list)))))
+
+
+;;; Indicate success/failure via exit status
+
+(define (test-exit)
+ (let ((runner (test-runner-current)))
+ (when (not runner)
+ (error "No test runner installed. Might have been auto-removed
+by test-end if you had not installed one explicitly."))
+ (if (and (zero? (test-runner-xpass-count runner))
+ (zero? (test-runner-fail-count runner)))
+ (exit 0)
+ (exit 1))))
+
+;;; execution.scm ends here
+(export
+ test-begin test-end test-group test-group-with-cleanup
+
+ test-skip test-expect-fail
+ test-match-name test-match-nth
+ test-match-all test-match-any
+
+ test-assert test-eqv test-eq test-equal test-approximate
+ test-error test-read-eval-string
+
+ test-apply test-with-runner
+
+ test-exit
+ )
+(define-library (srfi 64 execution)
+ (import
+ (scheme base)
+ (scheme case-lambda)
+ (scheme complex)
+ (scheme eval)
+ (scheme process-context)
+ (scheme read)
+ (srfi 1)
+ (srfi 35)
+ (srfi 48)
+ (srfi 64 source-info)
+ (srfi 64 test-runner)
+ (srfi 64 test-runner-simple))
+ (include-library-declarations "execution.exports.sld")
+ (include "execution.body.scm"))
+;; Copyright (c) 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
+;;
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; In some systems, a macro use like (source-info ...), that resides in a
+;;; syntax-rules macro body, first gets inserted into the place where the
+;;; syntax-rules macro was used, and then the transformer of 'source-info' is
+;;; called with a syntax object that has the source location information of that
+;;; position. That works fine when the user calls e.g. (test-assert ...), whose
+;;; body contains (source-info ...); the user gets the source location of the
+;;; (test-assert ...) call as intended, and not the source location of the real
+;;; (source-info ...) call.
+
+;;; In other systems, *first* the (source-info ...) is processed to get its real
+;;; position, which is within the body of a syntax-rules macro like test-assert,
+;;; so no matter where the user calls (test-assert ...), they get source
+;;; location information of where we defined test-assert with the call to
+;;; (source-info ...) in its body. That's arguably more correct behavior,
+;;; although in this case it makes our job a bit harder; we need to get the
+;;; source location from an argument to 'source-info' instead.
+
+(define (canonical-syntax form arg)
+ (cond-expand
+ (kawa arg)
+ (guile-2 form)
+ (else #f)))
+
+(cond-expand
+ ((or kawa guile-2)
+ (define-syntax source-info
+ (lambda (stx)
+ (syntax-case stx ()
+ ((_ <x>)
+ (let* ((stx (canonical-syntax stx (syntax <x>)))
+ (file (syntax-source-file stx))
+ (line (syntax-source-line stx)))
+ (quasisyntax
+ (cons (unsyntax file) (unsyntax line)))))))))
+ (else
+ (define-syntax source-info
+ (syntax-rules ()
+ ((_ <x>)
+ #f)))))
+
+(define (syntax-source-file stx)
+ (cond-expand
+ (kawa
+ (syntax-source stx))
+ (guile-2
+ (let ((source (syntax-source stx)))
+ (and source (assq-ref source 'filename))))
+ (else
+ #f)))
+
+(define (syntax-source-line stx)
+ (cond-expand
+ (kawa
+ (syntax-line stx))
+ (guile-2
+ (let ((source (syntax-source stx)))
+ (and source (assq-ref source 'line))))
+ (else
+ #f)))
+
+(define (set-source-info! runner source-info)
+ (when source-info
+ (test-result-set! runner 'source-file (car source-info))
+ (test-result-set! runner 'source-line (cdr source-info))))
+
+;;; source-info.body.scm ends here
+(define-library (srfi 64 source-info)
+ (import
+ (scheme base)
+ (srfi 64 test-runner))
+ (export source-info set-source-info!)
+ (include "source-info.body.scm"))
+;; Copyright (c) 2005, 2006, 2007, 2012, 2013 Per Bothner
+;; Added "full" support for Chicken, Gauche, Guile and SISC.
+;; Alex Shinn, Copyright (c) 2005.
+;; Modified for Scheme Spheres by Álvaro Castro-Castilla, Copyright (c) 2012.
+;; Support for Guile 2 by Mark H Weaver <mhw@netris.org>, Copyright (c) 2014.
+;; Refactored by Taylan Ulrich Bayırlı/Kammer, Copyright (c) 2014, 2015.
+;;
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Helpers
+
+(define (string-join strings delimiter)
+ (if (null? strings)
+ ""
+ (let loop ((result (car strings))
+ (rest (cdr strings)))
+ (if (null? rest)
+ result
+ (loop (string-append result delimiter (car rest))
+ (cdr rest))))))
+
+(define (truncate-string string length)
+ (define (newline->space c) (if (char=? #\newline c) #\space c))
+ (let* ((string (string-map newline->space string))
+ (fill "...")
+ (fill-len (string-length fill))
+ (string-len (string-length string)))
+ (if (<= string-len (+ length fill-len))
+ string
+ (let-values (((q r) (floor/ length 4)))
+ ;; Left part gets 3/4 plus the remainder.
+ (let ((left-end (+ (* q 3) r))
+ (right-start (- string-len q)))
+ (string-append (substring string 0 left-end)
+ fill
+ (substring string right-start string-len)))))))
+
+(define (print runner format-string . args)
+ (apply format #t format-string args)
+ (let ((port (%test-runner-log-port runner)))
+ (when port
+ (apply format port format-string args))))
+
+;;; Main
+
+(define test-runner-simple
+ (case-lambda
+ (()
+ (test-runner-simple #f))
+ ((log-file)
+ (let ((runner (test-runner-null)))
+ (test-runner-reset runner)
+ (test-runner-on-group-begin! runner test-on-group-begin-simple)
+ (test-runner-on-group-end! runner test-on-group-end-simple)
+ (test-runner-on-final! runner test-on-final-simple)
+ (test-runner-on-test-begin! runner test-on-test-begin-simple)
+ (test-runner-on-test-end! runner test-on-test-end-simple)
+ (test-runner-on-bad-count! runner test-on-bad-count-simple)
+ (test-runner-on-bad-end-name! runner test-on-bad-end-name-simple)
+ (%test-runner-on-bad-error-type! runner on-bad-error-type)
+ (%test-runner-log-file! runner log-file)
+ runner))))
+
+(when (not (test-runner-factory))
+ (test-runner-factory test-runner-simple))
+
+(define (test-on-group-begin-simple runner name count)
+ (when (null? (test-runner-group-stack runner))
+ (maybe-start-logging runner)
+ (print runner "Test suite begin: ~a~%" name)))
+
+(define (test-on-group-end-simple runner)
+ (let ((name (car (test-runner-group-stack runner))))
+ (when (= 1 (length (test-runner-group-stack runner)))
+ (print runner "Test suite end: ~a~%" name))))
+
+(define (test-on-final-simple runner)
+ (print runner "Passes: ~a\n" (test-runner-pass-count runner))
+ (print runner "Expected failures: ~a\n" (test-runner-xfail-count runner))
+ (print runner "Failures: ~a\n" (test-runner-fail-count runner))
+ (print runner "Unexpected passes: ~a\n" (test-runner-xpass-count runner))
+ (print runner "Skipped tests: ~a~%" (test-runner-skip-count runner))
+ (maybe-finish-logging runner))
+
+(define (maybe-start-logging runner)
+ (let ((log-file (%test-runner-log-file runner)))
+ (when log-file
+ ;; The possible race-condition here doesn't bother us.
+ (when (file-exists? log-file)
+ (delete-file log-file))
+ (%test-runner-log-port! runner (open-output-file log-file))
+ (print runner "Writing log file: ~a~%" log-file))))
+
+(define (maybe-finish-logging runner)
+ (let ((log-file (%test-runner-log-file runner)))
+ (when log-file
+ (print runner "Wrote log file: ~a~%" log-file)
+ (close-output-port (%test-runner-log-port runner)))))
+
+(define (test-on-test-begin-simple runner)
+ (values))
+
+(define (test-on-test-end-simple runner)
+ (let* ((result-kind (test-result-kind runner))
+ (result-kind-name (case result-kind
+ ((pass) "PASS") ((fail) "FAIL")
+ ((xpass) "XPASS") ((xfail) "XFAIL")
+ ((skip) "SKIP")))
+ (name (let ((name (test-runner-test-name runner)))
+ (if (string=? "" name)
+ (truncate-string
+ (format #f "~a" (test-result-ref runner 'source-form))
+ 30)
+ name)))
+ (label (string-join (append (test-runner-group-path runner)
+ (list name))
+ ": ")))
+ (print runner "[~a] ~a~%" result-kind-name label)
+ (when (memq result-kind '(fail xpass))
+ (let ((nil (cons #f #f)))
+ (define (found? value)
+ (not (eq? nil value)))
+ (define (maybe-print value message)
+ (when (found? value)
+ (print runner message value)))
+ (let ((file (test-result-ref runner 'source-file "(unknown file)"))
+ (line (test-result-ref runner 'source-line "(unknown line)"))
+ (expression (test-result-ref runner 'source-form))
+ (expected-value (test-result-ref runner 'expected-value nil))
+ (actual-value (test-result-ref runner 'actual-value nil))
+ (expected-error (test-result-ref runner 'expected-error nil))
+ (actual-error (test-result-ref runner 'actual-error nil)))
+ (print runner "~a:~a: ~s~%" file line expression)
+ (maybe-print expected-value "Expected value: ~s~%")
+ (maybe-print expected-error "Expected error: ~a~%")
+ (when (or (found? expected-value) (found? expected-error))
+ (maybe-print actual-value "Returned value: ~s~%"))
+ (maybe-print actual-error "Raised error: ~a~%")
+ (newline))))))
+
+(define (test-on-bad-count-simple runner count expected-count)
+ (print runner "*** Total number of tests was ~a but should be ~a. ***~%"
+ count expected-count)
+ (print runner
+ "*** Discrepancy indicates testsuite error or exceptions. ***~%"))
+
+(define (test-on-bad-end-name-simple runner begin-name end-name)
+ (error (format #f "Test-end \"~a\" does not match test-begin \"~a\"."
+ end-name begin-name)))
+
+(define (on-bad-error-type runner type error)
+ (print runner "WARNING: unknown error type predicate: ~a~%" type)
+ (print runner " error was: ~a~%" error))
+
+;;; test-runner-simple.scm ends here
+(export
+ test-runner-simple
+ ;; The following are exported so you can leverage their existing functionality
+ ;; when making more complex test runners.
+ test-on-group-begin-simple test-on-group-end-simple test-on-final-simple
+ test-on-test-begin-simple test-on-test-end-simple
+ test-on-bad-count-simple test-on-bad-end-name-simple
+ )
+(define-library (srfi 64 test-runner-simple)
+ (import
+ (scheme base)
+ (scheme case-lambda)
+ (scheme file)
+ (scheme write)
+ (srfi 48)
+ (srfi 64 test-runner))
+ (include-library-declarations "test-runner-simple.exports.sld")
+ (include "test-runner-simple.body.scm"))
+;; Copyright (c) 2005, 2006, 2007, 2012, 2013 Per Bothner
+;; Added "full" support for Chicken, Gauche, Guile and SISC.
+;; Alex Shinn, Copyright (c) 2005.
+;; Modified for Scheme Spheres by Álvaro Castro-Castilla, Copyright (c) 2012.
+;; Support for Guile 2 by Mark H Weaver <mhw@netris.org>, Copyright (c) 2014.
+;; Refactored by Taylan Ulrich Bayırlı/Kammer, Copyright (c) 2014, 2015.
+;;
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+
+;;; The data type
+
+(define-record-type <test-runner>
+ (make-test-runner) test-runner?
+
+ (result-alist test-result-alist test-result-alist!)
+
+ (pass-count test-runner-pass-count test-runner-pass-count!)
+ (fail-count test-runner-fail-count test-runner-fail-count!)
+ (xpass-count test-runner-xpass-count test-runner-xpass-count!)
+ (xfail-count test-runner-xfail-count test-runner-xfail-count!)
+ (skip-count test-runner-skip-count test-runner-skip-count!)
+ (total-count %test-runner-total-count %test-runner-total-count!)
+
+ ;; Stack (list) of (count-at-start . expected-count):
+ (count-list %test-runner-count-list %test-runner-count-list!)
+
+ ;; Normally #f, except when in a test-apply.
+ (run-list %test-runner-run-list %test-runner-run-list!)
+
+ (skip-list %test-runner-skip-list %test-runner-skip-list!)
+ (fail-list %test-runner-fail-list %test-runner-fail-list!)
+
+ (skip-save %test-runner-skip-save %test-runner-skip-save!)
+ (fail-save %test-runner-fail-save %test-runner-fail-save!)
+
+ (group-stack test-runner-group-stack test-runner-group-stack!)
+
+ ;; Note: on-test-begin and on-test-end are unrelated to the test-begin and
+ ;; test-end forms in the execution library. They're called at the
+ ;; beginning/end of each individual test, whereas the test-begin and test-end
+ ;; forms demarcate test groups.
+
+ (on-group-begin test-runner-on-group-begin test-runner-on-group-begin!)
+ (on-test-begin test-runner-on-test-begin test-runner-on-test-begin!)
+ (on-test-end test-runner-on-test-end test-runner-on-test-end!)
+ (on-group-end test-runner-on-group-end test-runner-on-group-end!)
+ (on-final test-runner-on-final test-runner-on-final!)
+ (on-bad-count test-runner-on-bad-count test-runner-on-bad-count!)
+ (on-bad-end-name test-runner-on-bad-end-name test-runner-on-bad-end-name!)
+
+ (on-bad-error-type %test-runner-on-bad-error-type
+ %test-runner-on-bad-error-type!)
+
+ (aux-value test-runner-aux-value test-runner-aux-value!)
+
+ (auto-installed %test-runner-auto-installed? %test-runner-auto-installed!)
+
+ (log-file %test-runner-log-file %test-runner-log-file!)
+ (log-port %test-runner-log-port %test-runner-log-port!))
+
+(define (test-runner-group-path runner)
+ (reverse (test-runner-group-stack runner)))
+
+(define (test-runner-reset runner)
+ (test-result-alist! runner '())
+ (test-runner-pass-count! runner 0)
+ (test-runner-fail-count! runner 0)
+ (test-runner-xpass-count! runner 0)
+ (test-runner-xfail-count! runner 0)
+ (test-runner-skip-count! runner 0)
+ (%test-runner-total-count! runner 0)
+ (%test-runner-count-list! runner '())
+ (%test-runner-run-list! runner #f)
+ (%test-runner-skip-list! runner '())
+ (%test-runner-fail-list! runner '())
+ (%test-runner-skip-save! runner '())
+ (%test-runner-fail-save! runner '())
+ (test-runner-group-stack! runner '()))
+
+(define (test-runner-null)
+ (define (test-null-callback . args) #f)
+ (let ((runner (make-test-runner)))
+ (test-runner-reset runner)
+ (test-runner-on-group-begin! runner test-null-callback)
+ (test-runner-on-group-end! runner test-null-callback)
+ (test-runner-on-final! runner test-null-callback)
+ (test-runner-on-test-begin! runner test-null-callback)
+ (test-runner-on-test-end! runner test-null-callback)
+ (test-runner-on-bad-count! runner test-null-callback)
+ (test-runner-on-bad-end-name! runner test-null-callback)
+ (%test-runner-on-bad-error-type! runner test-null-callback)
+ (%test-runner-auto-installed! runner #f)
+ (%test-runner-log-file! runner #f)
+ (%test-runner-log-port! runner #f)
+ runner))
+
+
+;;; State
+
+(define test-result-ref
+ (case-lambda
+ ((runner key)
+ (test-result-ref runner key #f))
+ ((runner key default)
+ (let ((entry (assq key (test-result-alist runner))))
+ (if entry (cdr entry) default)))))
+
+(define (test-result-set! runner key value)
+ (let* ((alist (test-result-alist runner))
+ (entry (assq key alist)))
+ (if entry
+ (set-cdr! entry value)
+ (test-result-alist! runner (cons (cons key value) alist)))))
+
+(define (test-result-remove runner key)
+ (test-result-alist! runner (remove (lambda (entry)
+ (eq? key (car entry)))
+ (test-result-alist runner))))
+
+(define (test-result-clear runner)
+ (test-result-alist! runner '()))
+
+(define (test-runner-test-name runner)
+ (or (test-result-ref runner 'name) ""))
+
+(define test-result-kind
+ (case-lambda
+ (() (test-result-kind (test-runner-get)))
+ ((runner) (test-result-ref runner 'result-kind))))
+
+(define test-passed?
+ (case-lambda
+ (() (test-passed? (test-runner-get)))
+ ((runner) (memq (test-result-kind runner) '(pass xpass)))))
+
+
+;;; Factory and current instance
+
+(define test-runner-factory (make-parameter #f))
+
+(define (test-runner-create) ((test-runner-factory)))
+
+(define test-runner-current (make-parameter #f))
+
+(define (test-runner-get)
+ (or (test-runner-current)
+ (error "test-runner not initialized - test-begin missing?")))
+
+;;; test-runner.scm ends here
+(export
+ ;; The data type
+ test-runner-null test-runner? test-runner-reset
+
+ test-result-alist test-result-alist!
+
+ test-runner-pass-count test-runner-pass-count!
+ test-runner-fail-count test-runner-fail-count!
+ test-runner-xpass-count test-runner-xpass-count!
+ test-runner-xfail-count test-runner-xfail-count!
+ test-runner-skip-count test-runner-skip-count!
+ %test-runner-total-count %test-runner-total-count!
+
+ %test-runner-count-list %test-runner-count-list!
+
+ %test-runner-run-list %test-runner-run-list!
+
+ %test-runner-skip-list %test-runner-skip-list!
+ %test-runner-fail-list %test-runner-fail-list!
+
+ %test-runner-skip-save %test-runner-skip-save!
+ %test-runner-fail-save %test-runner-fail-save!
+
+ test-runner-group-stack test-runner-group-stack!
+ test-runner-group-path
+
+ test-runner-on-test-begin test-runner-on-test-begin!
+ test-runner-on-test-end test-runner-on-test-end!
+ test-runner-on-group-begin test-runner-on-group-begin!
+ test-runner-on-group-end test-runner-on-group-end!
+ test-runner-on-final test-runner-on-final!
+ test-runner-on-bad-count test-runner-on-bad-count!
+ test-runner-on-bad-end-name test-runner-on-bad-end-name!
+
+ %test-runner-on-bad-error-type %test-runner-on-bad-error-type!
+
+ test-runner-aux-value test-runner-aux-value!
+
+ %test-runner-log-file %test-runner-log-file!
+ %test-runner-log-port %test-runner-log-port!
+
+ ;; State
+ test-result-ref test-result-set!
+ test-result-remove test-result-clear
+ test-runner-test-name test-result-kind test-passed?
+
+ ;; Factory and current instance
+ test-runner-factory test-runner-create
+ test-runner-current test-runner-get
+ )
+(define-library (srfi 64 test-runner)
+ (import
+ (scheme base)
+ (scheme case-lambda)
+ (srfi 1))
+ (include-library-declarations "test-runner.exports.sld")
+ (include "test-runner.body.scm"))
+;;; SRFI-1 list-processing library -*- Scheme -*-
+;;; Reference implementation
+;;;
+;;; Copyright (c) 1998, 1999 by Olin Shivers. You may do as you please with
+;;; this code as long as you do not remove this copyright notice or
+;;; hold me liable for its use. Please send bug reports to shivers@ai.mit.edu.
+;;; -Olin
+;;;
+;;; Made an R7RS library by Taylan Ulrich Bayırlı/Kammer, Copyright (c) 2014.
+
+;;; See 1.upstream.scm in the same repository for a bunch of comments which I
+;;; removed here because what they document does not necessarily correspond with
+;;; the code anymore. Diff with the same file to see changes in the code.
+
+;;; Constructors
+;;;;;;;;;;;;;;;;
+
+;;; Occasionally useful as a value to be passed to a fold or other
+;;; higher-order procedure.
+(define (xcons d a) (cons a d))
+
+;;;; Recursively copy every cons.
+;(define (tree-copy x)
+; (let recur ((x x))
+; (if (not (pair? x)) x
+; (cons (recur (car x)) (recur (cdr x))))))
+
+
+;(define (list . ans) ans) ; R4RS
+
+
+;;; Make a list of length LEN. Elt i is (PROC i) for 0 <= i < LEN.
+
+(define (list-tabulate len proc)
+ (check-arg (lambda (n) (and (integer? n) (>= n 0))) len list-tabulate)
+ (check-arg procedure? proc list-tabulate)
+ (do ((i (- len 1) (- i 1))
+ (ans '() (cons (proc i) ans)))
+ ((< i 0) ans)))
+
+;;; (cons* a1 a2 ... an) = (cons a1 (cons a2 (cons ... an)))
+;;; (cons* a1) = a1 (cons* a1 a2 ...) = (cons a1 (cons* a2 ...))
+;;;
+;;; (cons first (unfold not-pair? car cdr rest values))
+
+(define (cons* first . rest)
+ (let recur ((x first) (rest rest))
+ (if (pair? rest)
+ (cons x (recur (car rest) (cdr rest)))
+ x)))
+
+;;; IOTA count [start step] (start start+step ... start+(count-1)*step)
+
+(define (nonnegative? x)
+ (not (negative? x)))
+
+(define/opt (iota count (start 0) (step 1))
+ (check-arg integer? count iota)
+ (check-arg nonnegative? count iota)
+ (check-arg number? start iota)
+ (check-arg number? step iota)
+ (let loop ((n 0) (r '()))
+ (if (= n count)
+ (reverse r)
+ (loop (+ 1 n)
+ (cons (+ start (* n step)) r)))))
+
+;;; I thought these were lovely, but the public at large did not share my
+;;; enthusiasm...
+;;; :IOTA to (0 ... to-1)
+;;; :IOTA from to (from ... to-1)
+;;; :IOTA from to step (from from+step ...)
+
+;;; IOTA: to (1 ... to)
+;;; IOTA: from to (from+1 ... to)
+;;; IOTA: from to step (from+step from+2step ...)
+
+;(define (%parse-iota-args arg1 rest-args proc)
+; (let ((check (lambda (n) (check-arg integer? n proc))))
+; (check arg1)
+; (if (pair? rest-args)
+; (let ((arg2 (check (car rest-args)))
+; (rest (cdr rest-args)))
+; (if (pair? rest)
+; (let ((arg3 (check (car rest)))
+; (rest (cdr rest)))
+; (if (pair? rest) (error "Too many parameters" proc arg1 rest-args)
+; (values arg1 arg2 arg3)))
+; (values arg1 arg2 1)))
+; (values 0 arg1 1))))
+;
+;(define (iota: arg1 . rest-args)
+; (receive (from to step) (%parse-iota-args arg1 rest-args iota:)
+; (let* ((numsteps (floor (/ (- to from) step)))
+; (last-val (+ from (* step numsteps))))
+; (if (< numsteps 0) (error "Negative step count" iota: from to step))
+; (do ((steps-left numsteps (- steps-left 1))
+; (val last-val (- val step))
+; (ans '() (cons val ans)))
+; ((<= steps-left 0) ans)))))
+;
+;
+;(define (\:iota arg1 . rest-args)
+; (receive (from to step) (%parse-iota-args arg1 rest-args :iota)
+; (let* ((numsteps (ceiling (/ (- to from) step)))
+; (last-val (+ from (* step (- numsteps 1)))))
+; (if (< numsteps 0) (error "Negative step count" :iota from to step))
+; (do ((steps-left numsteps (- steps-left 1))
+; (val last-val (- val step))
+; (ans '() (cons val ans)))
+; ((<= steps-left 0) ans)))))
+
+
+
+(define (circular-list val1 . vals)
+ (let ((ans (cons val1 vals)))
+ (set-cdr! (last-pair ans) ans)
+ ans))
+
+;;; <proper-list> ::= () ; Empty proper list
+;;; | (cons <x> <proper-list>) ; Proper-list pair
+;;; Note that this definition rules out circular lists -- and this
+;;; function is required to detect this case and return false.
+
+(define (proper-list? x)
+ (let lp ((x x) (lag x))
+ (if (pair? x)
+ (let ((x (cdr x)))
+ (if (pair? x)
+ (let ((x (cdr x))
+ (lag (cdr lag)))
+ (and (not (eq? x lag)) (lp x lag)))
+ (null? x)))
+ (null? x))))
+
+
+;;; A dotted list is a finite list (possibly of length 0) terminated
+;;; by a non-nil value. Any non-cons, non-nil value (e.g., "foo" or 5)
+;;; is a dotted list of length 0.
+;;;
+;;; <dotted-list> ::= <non-nil,non-pair> ; Empty dotted list
+;;; | (cons <x> <dotted-list>) ; Proper-list pair
+
+(define (dotted-list? x)
+ (let lp ((x x) (lag x))
+ (if (pair? x)
+ (let ((x (cdr x)))
+ (if (pair? x)
+ (let ((x (cdr x))
+ (lag (cdr lag)))
+ (and (not (eq? x lag)) (lp x lag)))
+ (not (null? x))))
+ (not (null? x)))))
+
+(define (circular-list? x)
+ (let lp ((x x) (lag x))
+ (and (pair? x)
+ (let ((x (cdr x)))
+ (and (pair? x)
+ (let ((x (cdr x))
+ (lag (cdr lag)))
+ (or (eq? x lag) (lp x lag))))))))
+
+(define (not-pair? x) (not (pair? x))) ; Inline me.
+
+;;; This is a legal definition which is fast and sloppy:
+;;; (define null-list? not-pair?)
+;;; but we'll provide a more careful one:
+(define (null-list? l)
+ (cond ((pair? l) #f)
+ ((null? l) #t)
+ (else (error "null-list?: argument out of domain" l))))
+
+
+(define (list= = . lists)
+ (or (null? lists) ; special case
+
+ (let lp1 ((list-a (car lists)) (others (cdr lists)))
+ (or (null? others)
+ (let ((list-b (car others))
+ (others (cdr others)))
+ (if (eq? list-a list-b) ; EQ? => LIST=
+ (lp1 list-b others)
+ (let lp2 ((list-a list-a) (list-b list-b))
+ (if (null-list? list-a)
+ (and (null-list? list-b)
+ (lp1 list-b others))
+ (and (not (null-list? list-b))
+ (= (car list-a) (car list-b))
+ (lp2 (cdr list-a) (cdr list-b)))))))))))
+
+
+
+;;; R4RS, so commented out.
+;(define (length x) ; LENGTH may diverge or
+; (let lp ((x x) (len 0)) ; raise an error if X is
+; (if (pair? x) ; a circular list. This version
+; (lp (cdr x) (+ len 1)) ; diverges.
+; len)))
+
+(define (length+ x) ; Returns #f if X is circular.
+ (let lp ((x x) (lag x) (len 0))
+ (if (pair? x)
+ (let ((x (cdr x))
+ (len (+ len 1)))
+ (if (pair? x)
+ (let ((x (cdr x))
+ (lag (cdr lag))
+ (len (+ len 1)))
+ (and (not (eq? x lag)) (lp x lag len)))
+ len))
+ len)))
+
+(define (zip list1 . more-lists) (apply map list list1 more-lists))
+
+
+;;; Selectors
+;;;;;;;;;;;;;
+
+;;; R4RS non-primitives:
+;(define (caar x) (car (car x)))
+;(define (cadr x) (car (cdr x)))
+;(define (cdar x) (cdr (car x)))
+;(define (cddr x) (cdr (cdr x)))
+;
+;(define (caaar x) (caar (car x)))
+;(define (caadr x) (caar (cdr x)))
+;(define (cadar x) (cadr (car x)))
+;(define (caddr x) (cadr (cdr x)))
+;(define (cdaar x) (cdar (car x)))
+;(define (cdadr x) (cdar (cdr x)))
+;(define (cddar x) (cddr (car x)))
+;(define (cdddr x) (cddr (cdr x)))
+;
+;(define (caaaar x) (caaar (car x)))
+;(define (caaadr x) (caaar (cdr x)))
+;(define (caadar x) (caadr (car x)))
+;(define (caaddr x) (caadr (cdr x)))
+;(define (cadaar x) (cadar (car x)))
+;(define (cadadr x) (cadar (cdr x)))
+;(define (caddar x) (caddr (car x)))
+;(define (cadddr x) (caddr (cdr x)))
+;(define (cdaaar x) (cdaar (car x)))
+;(define (cdaadr x) (cdaar (cdr x)))
+;(define (cdadar x) (cdadr (car x)))
+;(define (cdaddr x) (cdadr (cdr x)))
+;(define (cddaar x) (cddar (car x)))
+;(define (cddadr x) (cddar (cdr x)))
+;(define (cdddar x) (cdddr (car x)))
+;(define (cddddr x) (cdddr (cdr x)))
+
+
+(define first car)
+(define second cadr)
+(define third caddr)
+(define fourth cadddr)
+(define (fifth x) (car (cddddr x)))
+(define (sixth x) (cadr (cddddr x)))
+(define (seventh x) (caddr (cddddr x)))
+(define (eighth x) (cadddr (cddddr x)))
+(define (ninth x) (car (cddddr (cddddr x))))
+(define (tenth x) (cadr (cddddr (cddddr x))))
+
+(define (car+cdr pair) (values (car pair) (cdr pair)))
+
+;;; take & drop
+
+(define (take lis k)
+ (check-arg integer? k take)
+ (let recur ((lis lis) (k k))
+ (if (zero? k) '()
+ (cons (car lis)
+ (recur (cdr lis) (- k 1))))))
+
+(define (drop lis k)
+ (check-arg integer? k drop)
+ (let iter ((lis lis) (k k))
+ (if (zero? k) lis (iter (cdr lis) (- k 1)))))
+
+(define (take! lis k)
+ (check-arg integer? k take!)
+ (if (zero? k) '()
+ (begin (set-cdr! (drop lis (- k 1)) '())
+ lis)))
+
+;;; TAKE-RIGHT and DROP-RIGHT work by getting two pointers into the list,
+;;; off by K, then chasing down the list until the lead pointer falls off
+;;; the end.
+
+(define (take-right lis k)
+ (check-arg integer? k take-right)
+ (let lp ((lag lis) (lead (drop lis k)))
+ (if (pair? lead)
+ (lp (cdr lag) (cdr lead))
+ lag)))
+
+(define (drop-right lis k)
+ (check-arg integer? k drop-right)
+ (let recur ((lag lis) (lead (drop lis k)))
+ (if (pair? lead)
+ (cons (car lag) (recur (cdr lag) (cdr lead)))
+ '())))
+
+;;; In this function, LEAD is actually K+1 ahead of LAG. This lets
+;;; us stop LAG one step early, in time to smash its cdr to ().
+(define (drop-right! lis k)
+ (check-arg integer? k drop-right!)
+ (let ((lead (drop lis k)))
+ (if (pair? lead)
+
+ (let lp ((lag lis) (lead (cdr lead))) ; Standard case
+ (if (pair? lead)
+ (lp (cdr lag) (cdr lead))
+ (begin (set-cdr! lag '())
+ lis)))
+
+ '()))) ; Special case dropping everything -- no cons to side-effect.
+
+;(define (list-ref lis i) (car (drop lis i))) ; R4RS
+
+;;; These use the APL convention, whereby negative indices mean
+;;; "from the right." I liked them, but they didn't win over the
+;;; SRFI reviewers.
+;;; K >= 0: Take and drop K elts from the front of the list.
+;;; K <= 0: Take and drop -K elts from the end of the list.
+
+;(define (take lis k)
+; (check-arg integer? k take)
+; (if (negative? k)
+; (list-tail lis (+ k (length lis)))
+; (let recur ((lis lis) (k k))
+; (if (zero? k) '()
+; (cons (car lis)
+; (recur (cdr lis) (- k 1)))))))
+;
+;(define (drop lis k)
+; (check-arg integer? k drop)
+; (if (negative? k)
+; (let recur ((lis lis) (nelts (+ k (length lis))))
+; (if (zero? nelts) '()
+; (cons (car lis)
+; (recur (cdr lis) (- nelts 1)))))
+; (list-tail lis k)))
+;
+;
+;(define (take! lis k)
+; (check-arg integer? k take!)
+; (cond ((zero? k) '())
+; ((positive? k)
+; (set-cdr! (list-tail lis (- k 1)) '())
+; lis)
+; (else (list-tail lis (+ k (length lis))))))
+;
+;(define (drop! lis k)
+; (check-arg integer? k drop!)
+; (if (negative? k)
+; (let ((nelts (+ k (length lis))))
+; (if (zero? nelts) '()
+; (begin (set-cdr! (list-tail lis (- nelts 1)) '())
+; lis)))
+; (list-tail lis k)))
+
+(define (split-at x k)
+ (check-arg integer? k split-at)
+ (let recur ((lis x) (k k))
+ (if (zero? k) (values '() lis)
+ (receive (prefix suffix) (recur (cdr lis) (- k 1))
+ (values (cons (car lis) prefix) suffix)))))
+
+(define (split-at! x k)
+ (check-arg integer? k split-at!)
+ (if (zero? k) (values '() x)
+ (let* ((prev (drop x (- k 1)))
+ (suffix (cdr prev)))
+ (set-cdr! prev '())
+ (values x suffix))))
+
+
+(define (last lis) (car (last-pair lis)))
+
+(define (last-pair lis)
+ (check-arg pair? lis last-pair)
+ (let lp ((lis lis))
+ (let ((tail (cdr lis)))
+ (if (pair? tail) (lp tail) lis))))
+
+
+;;; Unzippers -- 1 through 5
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (unzip1 lis) (map car lis))
+
+(define (unzip2 lis)
+ (let recur ((lis lis))
+ (if (null-list? lis) (values lis lis) ; Use NOT-PAIR? to handle
+ (let ((elt (car lis))) ; dotted lists.
+ (receive (a b) (recur (cdr lis))
+ (values (cons (car elt) a)
+ (cons (cadr elt) b)))))))
+
+(define (unzip3 lis)
+ (let recur ((lis lis))
+ (if (null-list? lis) (values lis lis lis)
+ (let ((elt (car lis)))
+ (receive (a b c) (recur (cdr lis))
+ (values (cons (car elt) a)
+ (cons (cadr elt) b)
+ (cons (caddr elt) c)))))))
+
+(define (unzip4 lis)
+ (let recur ((lis lis))
+ (if (null-list? lis) (values lis lis lis lis)
+ (let ((elt (car lis)))
+ (receive (a b c d) (recur (cdr lis))
+ (values (cons (car elt) a)
+ (cons (cadr elt) b)
+ (cons (caddr elt) c)
+ (cons (cadddr elt) d)))))))
+
+(define (unzip5 lis)
+ (let recur ((lis lis))
+ (if (null-list? lis) (values lis lis lis lis lis)
+ (let ((elt (car lis)))
+ (receive (a b c d e) (recur (cdr lis))
+ (values (cons (car elt) a)
+ (cons (cadr elt) b)
+ (cons (caddr elt) c)
+ (cons (cadddr elt) d)
+ (cons (car (cddddr elt)) e)))))))
+
+
+;;; append! append-reverse append-reverse! concatenate concatenate!
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (append! . lists)
+ ;; First, scan through lists looking for a non-empty one.
+ (let lp ((lists lists) (prev '()))
+ (if (not (pair? lists)) prev
+ (let ((first (car lists))
+ (rest (cdr lists)))
+ (if (not (pair? first)) (lp rest first)
+
+ ;; Now, do the splicing.
+ (let lp2 ((tail-cons (last-pair first))
+ (rest rest))
+ (if (pair? rest)
+ (let ((next (car rest))
+ (rest (cdr rest)))
+ (set-cdr! tail-cons next)
+ (lp2 (if (pair? next) (last-pair next) tail-cons)
+ rest))
+ first)))))))
+
+;;; APPEND is R4RS.
+;(define (append . lists)
+; (if (pair? lists)
+; (let recur ((list1 (car lists)) (lists (cdr lists)))
+; (if (pair? lists)
+; (let ((tail (recur (car lists) (cdr lists))))
+; (fold-right cons tail list1)) ; Append LIST1 & TAIL.
+; list1))
+; '()))
+
+;(define (append-reverse rev-head tail) (fold cons tail rev-head))
+
+;(define (append-reverse! rev-head tail)
+; (pair-fold (lambda (pair tail) (set-cdr! pair tail) pair)
+; tail
+; rev-head))
+
+;;; Hand-inline the FOLD and PAIR-FOLD ops for speed.
+
+(define (append-reverse rev-head tail)
+ (let lp ((rev-head rev-head) (tail tail))
+ (if (null-list? rev-head) tail
+ (lp (cdr rev-head) (cons (car rev-head) tail)))))
+
+(define (append-reverse! rev-head tail)
+ (let lp ((rev-head rev-head) (tail tail))
+ (if (null-list? rev-head) tail
+ (let ((next-rev (cdr rev-head)))
+ (set-cdr! rev-head tail)
+ (lp next-rev rev-head)))))
+
+
+(define (concatenate lists) (reduce-right append '() lists))
+(define (concatenate! lists) (reduce-right append! '() lists))
+
+;;; Fold/map internal utilities
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; These little internal utilities are used by the general
+;;; fold & mapper funs for the n-ary cases . It'd be nice if they got inlined.
+;;; One the other hand, the n-ary cases are painfully inefficient as it is.
+;;; An aggressive implementation should simply re-write these functions
+;;; for raw efficiency; I have written them for as much clarity, portability,
+;;; and simplicity as can be achieved.
+;;;
+;;; I use the dreaded call/cc to do local aborts. A good compiler could
+;;; handle this with extreme efficiency. An implementation that provides
+;;; a one-shot, non-persistent continuation grabber could help the compiler
+;;; out by using that in place of the call/cc's in these routines.
+;;;
+;;; These functions have funky definitions that are precisely tuned to
+;;; the needs of the fold/map procs -- for example, to minimize the number
+;;; of times the argument lists need to be examined.
+
+;;; Return (map cdr lists).
+;;; However, if any element of LISTS is empty, just abort and return '().
+(define (%cdrs lists)
+ (call-with-current-continuation
+ (lambda (abort)
+ (let recur ((lists lists))
+ (if (pair? lists)
+ (let ((lis (car lists)))
+ (if (null-list? lis) (abort '())
+ (cons (cdr lis) (recur (cdr lists)))))
+ '())))))
+
+(define (%cars+ lists last-elt) ; (append! (map car lists) (list last-elt))
+ (let recur ((lists lists))
+ (if (pair? lists) (cons (caar lists) (recur (cdr lists))) (list last-elt))))
+
+;;; LISTS is a (not very long) non-empty list of lists.
+;;; Return two lists: the cars & the cdrs of the lists.
+;;; However, if any of the lists is empty, just abort and return [() ()].
+
+(define (%cars+cdrs lists)
+ (call-with-current-continuation
+ (lambda (abort)
+ (let recur ((lists lists))
+ (if (pair? lists)
+ (receive (list other-lists) (car+cdr lists)
+ (if (null-list? list) (abort '() '()) ; LIST is empty -- bail out
+ (receive (a d) (car+cdr list)
+ (receive (cars cdrs) (recur other-lists)
+ (values (cons a cars) (cons d cdrs))))))
+ (values '() '()))))))
+
+;;; Like %CARS+CDRS, but we pass in a final elt tacked onto the end of the
+;;; cars list. What a hack.
+(define (%cars+cdrs+ lists cars-final)
+ (call-with-current-continuation
+ (lambda (abort)
+ (let recur ((lists lists))
+ (if (pair? lists)
+ (receive (list other-lists) (car+cdr lists)
+ (if (null-list? list) (abort '() '()) ; LIST is empty -- bail out
+ (receive (a d) (car+cdr list)
+ (receive (cars cdrs) (recur other-lists)
+ (values (cons a cars) (cons d cdrs))))))
+ (values (list cars-final) '()))))))
+
+;;; Like %CARS+CDRS, but blow up if any list is empty.
+(define (%cars+cdrs/no-test lists)
+ (let recur ((lists lists))
+ (if (pair? lists)
+ (receive (list other-lists) (car+cdr lists)
+ (receive (a d) (car+cdr list)
+ (receive (cars cdrs) (recur other-lists)
+ (values (cons a cars) (cons d cdrs)))))
+ (values '() '()))))
+
+
+;;; count
+;;;;;;;;;
+(define (count pred list1 . lists)
+ (check-arg procedure? pred count)
+ (if (pair? lists)
+
+ ;; N-ary case
+ (let lp ((list1 list1) (lists lists) (i 0))
+ (if (null-list? list1) i
+ (receive (as ds) (%cars+cdrs lists)
+ (if (null? as) i
+ (lp (cdr list1) ds
+ (if (apply pred (car list1) as) (+ i 1) i))))))
+
+ ;; Fast path
+ (let lp ((lis list1) (i 0))
+ (if (null-list? lis) i
+ (lp (cdr lis) (if (pred (car lis)) (+ i 1) i))))))
+
+
+;;; fold/unfold
+;;;;;;;;;;;;;;;
+
+(define/opt (unfold-right p f g seed (tail '()))
+ (check-arg procedure? p unfold-right)
+ (check-arg procedure? f unfold-right)
+ (check-arg procedure? g unfold-right)
+ (let lp ((seed seed) (ans tail))
+ (if (p seed) ans
+ (lp (g seed)
+ (cons (f seed) ans)))))
+
+
+(define/opt (unfold p f g seed (tail-gen #f))
+ (check-arg procedure? p unfold)
+ (check-arg procedure? f unfold)
+ (check-arg procedure? g unfold)
+ (check-arg procedure? tail-gen unfold)
+ (let recur ((seed seed))
+ (if (p seed)
+ (if tail-gen (tail-gen seed) '())
+ (cons (f seed) (recur (g seed))))))
+
+
+(define (fold kons knil lis1 . lists)
+ (check-arg procedure? kons fold)
+ (if (pair? lists)
+ (let lp ((lists (cons lis1 lists)) (ans knil)) ; N-ary case
+ (receive (cars+ans cdrs) (%cars+cdrs+ lists ans)
+ (if (null? cars+ans) ans ; Done.
+ (lp cdrs (apply kons cars+ans)))))
+
+ (let lp ((lis lis1) (ans knil)) ; Fast path
+ (if (null-list? lis) ans
+ (lp (cdr lis) (kons (car lis) ans))))))
+
+
+(define (fold-right kons knil lis1 . lists)
+ (check-arg procedure? kons fold-right)
+ (if (pair? lists)
+ (let recur ((lists (cons lis1 lists))) ; N-ary case
+ (let ((cdrs (%cdrs lists)))
+ (if (null? cdrs) knil
+ (apply kons (%cars+ lists (recur cdrs))))))
+
+ (let recur ((lis lis1)) ; Fast path
+ (if (null-list? lis) knil
+ (let ((head (car lis)))
+ (kons head (recur (cdr lis))))))))
+
+
+(define (pair-fold-right f zero lis1 . lists)
+ (check-arg procedure? f pair-fold-right)
+ (if (pair? lists)
+ (let recur ((lists (cons lis1 lists))) ; N-ary case
+ (let ((cdrs (%cdrs lists)))
+ (if (null? cdrs) zero
+ (apply f (append! lists (list (recur cdrs)))))))
+
+ (let recur ((lis lis1)) ; Fast path
+ (if (null-list? lis) zero (f lis (recur (cdr lis)))))))
+
+(define (pair-fold f zero lis1 . lists)
+ (check-arg procedure? f pair-fold)
+ (if (pair? lists)
+ (let lp ((lists (cons lis1 lists)) (ans zero)) ; N-ary case
+ (let ((tails (%cdrs lists)))
+ (if (null? tails) ans
+ (lp tails (apply f (append! lists (list ans)))))))
+
+ (let lp ((lis lis1) (ans zero))
+ (if (null-list? lis) ans
+ (let ((tail (cdr lis))) ; Grab the cdr now,
+ (lp tail (f lis ans))))))) ; in case F SET-CDR!s LIS.
+
+
+;;; REDUCE and REDUCE-RIGHT only use RIDENTITY in the empty-list case.
+;;; These cannot meaningfully be n-ary.
+
+(define (reduce f ridentity lis)
+ (check-arg procedure? f reduce)
+ (if (null-list? lis) ridentity
+ (fold f (car lis) (cdr lis))))
+
+(define (reduce-right f ridentity lis)
+ (check-arg procedure? f reduce-right)
+ (if (null-list? lis) ridentity
+ (let recur ((head (car lis)) (lis (cdr lis)))
+ (if (pair? lis)
+ (f head (recur (car lis) (cdr lis)))
+ head))))
+
+
+
+;;; Mappers: append-map append-map! pair-for-each map! filter-map map-in-order
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (append-map f lis1 . lists)
+ (really-append-map append-map append f lis1 lists))
+(define (append-map! f lis1 . lists)
+ (really-append-map append-map! append! f lis1 lists))
+
+(define (really-append-map who appender f lis1 lists)
+ (check-arg procedure? f who)
+ (if (pair? lists)
+ (receive (cars cdrs) (%cars+cdrs (cons lis1 lists))
+ (if (null? cars) '()
+ (let recur ((cars cars) (cdrs cdrs))
+ (let ((vals (apply f cars)))
+ (receive (cars2 cdrs2) (%cars+cdrs cdrs)
+ (if (null? cars2) vals
+ (appender vals (recur cars2 cdrs2))))))))
+
+ ;; Fast path
+ (if (null-list? lis1) '()
+ (let recur ((elt (car lis1)) (rest (cdr lis1)))
+ (let ((vals (f elt)))
+ (if (null-list? rest) vals
+ (appender vals (recur (car rest) (cdr rest)))))))))
+
+
+(define (pair-for-each proc lis1 . lists)
+ (check-arg procedure? proc pair-for-each)
+ (if (pair? lists)
+
+ (let lp ((lists (cons lis1 lists)))
+ (let ((tails (%cdrs lists)))
+ (if (pair? tails)
+ (begin (apply proc lists)
+ (lp tails)))))
+
+ ;; Fast path.
+ (let lp ((lis lis1))
+ (if (not (null-list? lis))
+ (let ((tail (cdr lis))) ; Grab the cdr now,
+ (proc lis) ; in case PROC SET-CDR!s LIS.
+ (lp tail))))))
+
+;;; We stop when LIS1 runs out, not when any list runs out.
+(define (map! f lis1 . lists)
+ (check-arg procedure? f map!)
+ (if (pair? lists)
+ (let lp ((lis1 lis1) (lists lists))
+ (if (not (null-list? lis1))
+ (receive (heads tails) (%cars+cdrs/no-test lists)
+ (set-car! lis1 (apply f (car lis1) heads))
+ (lp (cdr lis1) tails))))
+
+ ;; Fast path.
+ (pair-for-each (lambda (pair) (set-car! pair (f (car pair)))) lis1))
+ lis1)
+
+
+;;; Map F across L, and save up all the non-false results.
+(define (filter-map f lis1 . lists)
+ (check-arg procedure? f filter-map)
+ (if (pair? lists)
+ (let recur ((lists (cons lis1 lists)))
+ (receive (cars cdrs) (%cars+cdrs lists)
+ (if (pair? cars)
+ (cond ((apply f cars) => (lambda (x) (cons x (recur cdrs))))
+ (else (recur cdrs))) ; Tail call in this arm.
+ '())))
+
+ ;; Fast path.
+ (let recur ((lis lis1))
+ (if (null-list? lis) lis
+ (let ((tail (recur (cdr lis))))
+ (cond ((f (car lis)) => (lambda (x) (cons x tail)))
+ (else tail)))))))
+
+
+;;; Map F across lists, guaranteeing to go left-to-right.
+;;; NOTE: Some implementations of R5RS MAP are compliant with this spec;
+;;; in which case this procedure may simply be defined as a synonym for MAP.
+
+(define (map-in-order f lis1 . lists)
+ (check-arg procedure? f map-in-order)
+ (if (pair? lists)
+ (let recur ((lists (cons lis1 lists)))
+ (receive (cars cdrs) (%cars+cdrs lists)
+ (if (pair? cars)
+ (let ((x (apply f cars))) ; Do head first,
+ (cons x (recur cdrs))) ; then tail.
+ '())))
+
+ ;; Fast path.
+ (let recur ((lis lis1))
+ (if (null-list? lis) lis
+ (let ((tail (cdr lis))
+ (x (f (car lis)))) ; Do head first,
+ (cons x (recur tail))))))) ; then tail.
+
+
+;;; We extend MAP to handle arguments of unequal length.
+(define map map-in-order)
+
+
+;;; filter, remove, partition
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; FILTER, REMOVE, PARTITION and their destructive counterparts do not
+;;; disorder the elements of their argument.
+
+;; This FILTER shares the longest tail of L that has no deleted elements.
+;; If Scheme had multi-continuation calls, they could be made more efficient.
+
+(define (filter pred lis) ; Sleazing with EQ? makes this
+ (check-arg procedure? pred filter) ; one faster.
+ (let recur ((lis lis))
+ (if (null-list? lis) lis ; Use NOT-PAIR? to handle dotted lists.
+ (let ((head (car lis))
+ (tail (cdr lis)))
+ (if (pred head)
+ (let ((new-tail (recur tail))) ; Replicate the RECUR call so
+ (if (eq? tail new-tail) lis
+ (cons head new-tail)))
+ (recur tail)))))) ; this one can be a tail call.
+
+
+;;; Another version that shares longest tail.
+;(define (filter pred lis)
+; (receive (ans no-del?)
+; ;; (recur l) returns L with (pred x) values filtered.
+; ;; It also returns a flag NO-DEL? if the returned value
+; ;; is EQ? to L, i.e. if it didn't have to delete anything.
+; (let recur ((l l))
+; (if (null-list? l) (values l #t)
+; (let ((x (car l))
+; (tl (cdr l)))
+; (if (pred x)
+; (receive (ans no-del?) (recur tl)
+; (if no-del?
+; (values l #t)
+; (values (cons x ans) #f)))
+; (receive (ans no-del?) (recur tl) ; Delete X.
+; (values ans #f))))))
+; ans))
+
+
+
+;(define (filter! pred lis) ; Things are much simpler
+; (let recur ((lis lis)) ; if you are willing to
+; (if (pair? lis) ; push N stack frames & do N
+; (cond ((pred (car lis)) ; SET-CDR! writes, where N is
+; (set-cdr! lis (recur (cdr lis))); the length of the answer.
+; lis)
+; (else (recur (cdr lis))))
+; lis)))
+
+
+;;; This implementation of FILTER!
+;;; - doesn't cons, and uses no stack;
+;;; - is careful not to do redundant SET-CDR! writes, as writes to memory are
+;;; usually expensive on modern machines, and can be extremely expensive on
+;;; modern Schemes (e.g., ones that have generational GC's).
+;;; It just zips down contiguous runs of in and out elts in LIS doing the
+;;; minimal number of SET-CDR!s to splice the tail of one run of ins to the
+;;; beginning of the next.
+
+(define (filter! pred lis)
+ (check-arg procedure? pred filter!)
+ (let lp ((ans lis))
+ (cond ((null-list? ans) ans) ; Scan looking for
+ ((not (pred (car ans))) (lp (cdr ans))) ; first cons of result.
+
+ ;; ANS is the eventual answer.
+ ;; SCAN-IN: (CDR PREV) = LIS and (CAR PREV) satisfies PRED.
+ ;; Scan over a contiguous segment of the list that
+ ;; satisfies PRED.
+ ;; SCAN-OUT: (CAR PREV) satisfies PRED. Scan over a contiguous
+ ;; segment of the list that *doesn't* satisfy PRED.
+ ;; When the segment ends, patch in a link from PREV
+ ;; to the start of the next good segment, and jump to
+ ;; SCAN-IN.
+ (else (letrec ((scan-in (lambda (prev lis)
+ (if (pair? lis)
+ (if (pred (car lis))
+ (scan-in lis (cdr lis))
+ (scan-out prev (cdr lis))))))
+ (scan-out (lambda (prev lis)
+ (let lp ((lis lis))
+ (if (pair? lis)
+ (if (pred (car lis))
+ (begin (set-cdr! prev lis)
+ (scan-in lis (cdr lis)))
+ (lp (cdr lis)))
+ (set-cdr! prev lis))))))
+ (scan-in ans (cdr ans))
+ ans)))))
+
+
+
+;;; Answers share common tail with LIS where possible;
+;;; the technique is slightly subtle.
+
+(define (partition pred lis)
+ (check-arg procedure? pred partition)
+ (let recur ((lis lis))
+ (if (null-list? lis) (values lis lis) ; Use NOT-PAIR? to handle dotted lists.
+ (let ((elt (car lis))
+ (tail (cdr lis)))
+ (receive (in out) (recur tail)
+ (if (pred elt)
+ (values (if (pair? out) (cons elt in) lis) out)
+ (values in (if (pair? in) (cons elt out) lis))))))))
+
+
+
+;(define (partition! pred lis) ; Things are much simpler
+; (let recur ((lis lis)) ; if you are willing to
+; (if (null-list? lis) (values lis lis) ; push N stack frames & do N
+; (let ((elt (car lis))) ; SET-CDR! writes, where N is
+; (receive (in out) (recur (cdr lis)) ; the length of LIS.
+; (cond ((pred elt)
+; (set-cdr! lis in)
+; (values lis out))
+; (else (set-cdr! lis out)
+; (values in lis))))))))
+
+
+;;; This implementation of PARTITION!
+;;; - doesn't cons, and uses no stack;
+;;; - is careful not to do redundant SET-CDR! writes, as writes to memory are
+;;; usually expensive on modern machines, and can be extremely expensive on
+;;; modern Schemes (e.g., ones that have generational GC's).
+;;; It just zips down contiguous runs of in and out elts in LIS doing the
+;;; minimal number of SET-CDR!s to splice these runs together into the result
+;;; lists.
+
+(define (partition! pred lis)
+ (check-arg procedure? pred partition!)
+ (if (null-list? lis) (values lis lis)
+
+ ;; This pair of loops zips down contiguous in & out runs of the
+ ;; list, splicing the runs together. The invariants are
+ ;; SCAN-IN: (cdr in-prev) = LIS.
+ ;; SCAN-OUT: (cdr out-prev) = LIS.
+ (letrec ((scan-in (lambda (in-prev out-prev lis)
+ (let lp ((in-prev in-prev) (lis lis))
+ (if (pair? lis)
+ (if (pred (car lis))
+ (lp lis (cdr lis))
+ (begin (set-cdr! out-prev lis)
+ (scan-out in-prev lis (cdr lis))))
+ (set-cdr! out-prev lis))))) ; Done.
+
+ (scan-out (lambda (in-prev out-prev lis)
+ (let lp ((out-prev out-prev) (lis lis))
+ (if (pair? lis)
+ (if (pred (car lis))
+ (begin (set-cdr! in-prev lis)
+ (scan-in lis out-prev (cdr lis)))
+ (lp lis (cdr lis)))
+ (set-cdr! in-prev lis)))))) ; Done.
+
+ ;; Crank up the scan&splice loops.
+ (if (pred (car lis))
+ ;; LIS begins in-list. Search for out-list's first pair.
+ (let lp ((prev-l lis) (l (cdr lis)))
+ (cond ((not (pair? l)) (values lis l))
+ ((pred (car l)) (lp l (cdr l)))
+ (else (scan-out prev-l l (cdr l))
+ (values lis l)))) ; Done.
+
+ ;; LIS begins out-list. Search for in-list's first pair.
+ (let lp ((prev-l lis) (l (cdr lis)))
+ (cond ((not (pair? l)) (values l lis))
+ ((pred (car l))
+ (scan-in l prev-l (cdr l))
+ (values l lis)) ; Done.
+ (else (lp l (cdr l)))))))))
+
+
+;;; Inline us, please.
+(define (remove pred l) (filter (lambda (x) (not (pred x))) l))
+(define (remove! pred l) (filter! (lambda (x) (not (pred x))) l))
+
+
+
+;;; Here's the taxonomy for the DELETE/ASSOC/MEMBER functions.
+;;; (I don't actually think these are the world's most important
+;;; functions -- the procedural FILTER/REMOVE/FIND/FIND-TAIL variants
+;;; are far more general.)
+;;;
+;;; Function Action
+;;; ---------------------------------------------------------------------------
+;;; remove pred lis Delete by general predicate
+;;; delete x lis [=] Delete by element comparison
+;;;
+;;; find pred lis Search by general predicate
+;;; find-tail pred lis Search by general predicate
+;;; member x lis [=] Search by element comparison
+;;;
+;;; assoc key lis [=] Search alist by key comparison
+;;; alist-delete key alist [=] Alist-delete by key comparison
+
+(define/opt (delete x lis (= equal?))
+ (filter (lambda (y) (not (= x y))) lis))
+
+(define/opt (delete! x lis (= equal?))
+ (filter! (lambda (y) (not (= x y))) lis))
+
+;;; Extended from R4RS to take an optional comparison argument.
+(define/opt (member x lis (= equal?))
+ (find-tail (lambda (y) (= x y)) lis))
+
+;;; R4RS, hence we don't bother to define.
+;;; The MEMBER and then FIND-TAIL call should definitely
+;;; be inlined for MEMQ & MEMV.
+;(define (memq x lis) (member x lis eq?))
+;(define (memv x lis) (member x lis eqv?))
+
+
+;;; right-duplicate deletion
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; delete-duplicates delete-duplicates!
+;;;
+;;; Beware -- these are N^2 algorithms. To efficiently remove duplicates
+;;; in long lists, sort the list to bring duplicates together, then use a
+;;; linear-time algorithm to kill the dups. Or use an algorithm based on
+;;; element-marking. The former gives you O(n lg n), the latter is linear.
+
+(define/opt (delete-duplicates lis (elt= equal?))
+ (check-arg procedure? elt= delete-duplicates)
+ (let recur ((lis lis))
+ (if (null-list? lis) lis
+ (let* ((x (car lis))
+ (tail (cdr lis))
+ (new-tail (recur (delete x tail elt=))))
+ (if (eq? tail new-tail) lis (cons x new-tail))))))
+
+(define/opt (delete-duplicates! lis (elt= equal?))
+ (check-arg procedure? elt= delete-duplicates!)
+ (let recur ((lis lis))
+ (if (null-list? lis) lis
+ (let* ((x (car lis))
+ (tail (cdr lis))
+ (new-tail (recur (delete! x tail elt=))))
+ (if (eq? tail new-tail) lis (cons x new-tail))))))
+
+
+;;; alist stuff
+;;;;;;;;;;;;;;;
+
+;;; Extended from R4RS to take an optional comparison argument.
+(define/opt (assoc x lis (= equal?))
+ (find (lambda (entry) (= x (car entry))) lis))
+
+(define (alist-cons key datum alist) (cons (cons key datum) alist))
+
+(define (alist-copy alist)
+ (map (lambda (elt) (cons (car elt) (cdr elt)))
+ alist))
+
+(define/opt (alist-delete key alist (= equal?))
+ (filter (lambda (elt) (not (= key (car elt)))) alist))
+
+(define/opt (alist-delete! key alist (= equal?))
+ (filter! (lambda (elt) (not (= key (car elt)))) alist))
+
+
+;;; find find-tail take-while drop-while span break any every list-index
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (find pred list)
+ (cond ((find-tail pred list) => car)
+ (else #f)))
+
+(define (find-tail pred list)
+ (check-arg procedure? pred find-tail)
+ (let lp ((list list))
+ (and (not (null-list? list))
+ (if (pred (car list)) list
+ (lp (cdr list))))))
+
+(define (take-while pred lis)
+ (check-arg procedure? pred take-while)
+ (let recur ((lis lis))
+ (if (null-list? lis) '()
+ (let ((x (car lis)))
+ (if (pred x)
+ (cons x (recur (cdr lis)))
+ '())))))
+
+(define (drop-while pred lis)
+ (check-arg procedure? pred drop-while)
+ (let lp ((lis lis))
+ (if (null-list? lis) '()
+ (if (pred (car lis))
+ (lp (cdr lis))
+ lis))))
+
+(define (take-while! pred lis)
+ (check-arg procedure? pred take-while!)
+ (if (or (null-list? lis) (not (pred (car lis)))) '()
+ (begin (let lp ((prev lis) (rest (cdr lis)))
+ (if (pair? rest)
+ (let ((x (car rest)))
+ (if (pred x) (lp rest (cdr rest))
+ (set-cdr! prev '())))))
+ lis)))
+
+(define (span pred lis)
+ (check-arg procedure? pred span)
+ (let recur ((lis lis))
+ (if (null-list? lis) (values '() '())
+ (let ((x (car lis)))
+ (if (pred x)
+ (receive (prefix suffix) (recur (cdr lis))
+ (values (cons x prefix) suffix))
+ (values '() lis))))))
+
+(define (span! pred lis)
+ (check-arg procedure? pred span!)
+ (if (or (null-list? lis) (not (pred (car lis)))) (values '() lis)
+ (let ((suffix (let lp ((prev lis) (rest (cdr lis)))
+ (if (null-list? rest) rest
+ (let ((x (car rest)))
+ (if (pred x) (lp rest (cdr rest))
+ (begin (set-cdr! prev '())
+ rest)))))))
+ (values lis suffix))))
+
+
+(define (break pred lis) (span (lambda (x) (not (pred x))) lis))
+(define (break! pred lis) (span! (lambda (x) (not (pred x))) lis))
+
+(define (any pred lis1 . lists)
+ (check-arg procedure? pred any)
+ (if (pair? lists)
+
+ ;; N-ary case
+ (receive (heads tails) (%cars+cdrs (cons lis1 lists))
+ (and (pair? heads)
+ (let lp ((heads heads) (tails tails))
+ (receive (next-heads next-tails) (%cars+cdrs tails)
+ (if (pair? next-heads)
+ (or (apply pred heads) (lp next-heads next-tails))
+ (apply pred heads)))))) ; Last PRED app is tail call.
+
+ ;; Fast path
+ (and (not (null-list? lis1))
+ (let lp ((head (car lis1)) (tail (cdr lis1)))
+ (if (null-list? tail)
+ (pred head) ; Last PRED app is tail call.
+ (or (pred head) (lp (car tail) (cdr tail))))))))
+
+
+;(define (every pred list) ; Simple definition.
+; (let lp ((list list)) ; Doesn't return the last PRED value.
+; (or (not (pair? list))
+; (and (pred (car list))
+; (lp (cdr list))))))
+
+(define (every pred lis1 . lists)
+ (check-arg procedure? pred every)
+ (if (pair? lists)
+
+ ;; N-ary case
+ (receive (heads tails) (%cars+cdrs (cons lis1 lists))
+ (or (not (pair? heads))
+ (let lp ((heads heads) (tails tails))
+ (receive (next-heads next-tails) (%cars+cdrs tails)
+ (if (pair? next-heads)
+ (and (apply pred heads) (lp next-heads next-tails))
+ (apply pred heads)))))) ; Last PRED app is tail call.
+
+ ;; Fast path
+ (or (null-list? lis1)
+ (let lp ((head (car lis1)) (tail (cdr lis1)))
+ (if (null-list? tail)
+ (pred head) ; Last PRED app is tail call.
+ (and (pred head) (lp (car tail) (cdr tail))))))))
+
+(define (list-index pred lis1 . lists)
+ (check-arg procedure? pred list-index)
+ (if (pair? lists)
+
+ ;; N-ary case
+ (let lp ((lists (cons lis1 lists)) (n 0))
+ (receive (heads tails) (%cars+cdrs lists)
+ (and (pair? heads)
+ (if (apply pred heads) n
+ (lp tails (+ n 1))))))
+
+ ;; Fast path
+ (let lp ((lis lis1) (n 0))
+ (and (not (null-list? lis))
+ (if (pred (car lis)) n (lp (cdr lis) (+ n 1)))))))
+
+;;; Reverse
+;;;;;;;;;;;
+
+;R4RS, so not defined here.
+;(define (reverse lis) (fold cons '() lis))
+
+;(define (reverse! lis)
+; (pair-fold (lambda (pair tail) (set-cdr! pair tail) pair) '() lis))
+
+(define (reverse! lis)
+ (let lp ((lis lis) (ans '()))
+ (if (null-list? lis) ans
+ (let ((tail (cdr lis)))
+ (set-cdr! lis ans)
+ (lp tail lis)))))
+
+;;; Lists-as-sets
+;;;;;;;;;;;;;;;;;
+
+;;; This is carefully tuned code; do not modify casually.
+;;; - It is careful to share storage when possible;
+;;; - Side-effecting code tries not to perform redundant writes.
+;;; - It tries to avoid linear-time scans in special cases where constant-time
+;;; computations can be performed.
+;;; - It relies on similar properties from the other list-lib procs it calls.
+;;; For example, it uses the fact that the implementations of MEMBER and
+;;; FILTER in this source code share longest common tails between args
+;;; and results to get structure sharing in the lset procedures.
+
+(define (%lset2<= = lis1 lis2) (every (lambda (x) (member x lis2 =)) lis1))
+
+(define (lset<= = . lists)
+ (check-arg procedure? = lset<=)
+ (or (not (pair? lists)) ; 0-ary case
+ (let lp ((s1 (car lists)) (rest (cdr lists)))
+ (or (not (pair? rest))
+ (let ((s2 (car rest)) (rest (cdr rest)))
+ (and (or (eq? s2 s1) ; Fast path
+ (%lset2<= = s1 s2)) ; Real test
+ (lp s2 rest)))))))
+
+(define (lset= = . lists)
+ (check-arg procedure? = lset=)
+ (or (not (pair? lists)) ; 0-ary case
+ (let lp ((s1 (car lists)) (rest (cdr lists)))
+ (or (not (pair? rest))
+ (let ((s2 (car rest))
+ (rest (cdr rest)))
+ (and (or (eq? s1 s2) ; Fast path
+ (and (%lset2<= = s1 s2) (%lset2<= = s2 s1))) ; Real test
+ (lp s2 rest)))))))
+
+
+(define (lset-adjoin = lis . elts)
+ (check-arg procedure? = lset-adjoin)
+ (fold (lambda (elt ans) (if (member elt ans =) ans (cons elt ans)))
+ lis elts))
+
+
+(define (lset-union = . lists)
+ (check-arg procedure? = lset-union)
+ (reduce (lambda (lis ans) ; Compute ANS + LIS.
+ (cond ((null? lis) ans) ; Don't copy any lists
+ ((null? ans) lis) ; if we don't have to.
+ ((eq? lis ans) ans)
+ (else
+ (fold (lambda (elt ans) (if (any (lambda (x) (= x elt)) ans)
+ ans
+ (cons elt ans)))
+ ans lis))))
+ '() lists))
+
+(define (lset-union! = . lists)
+ (check-arg procedure? = lset-union!)
+ (reduce (lambda (lis ans) ; Splice new elts of LIS onto the front of ANS.
+ (cond ((null? lis) ans) ; Don't copy any lists
+ ((null? ans) lis) ; if we don't have to.
+ ((eq? lis ans) ans)
+ (else
+ (pair-fold (lambda (pair ans)
+ (let ((elt (car pair)))
+ (if (any (lambda (x) (= x elt)) ans)
+ ans
+ (begin (set-cdr! pair ans) pair))))
+ ans lis))))
+ '() lists))
+
+
+(define (lset-intersection = lis1 . lists)
+ (check-arg procedure? = lset-intersection)
+ (let ((lists (delete lis1 lists eq?))) ; Throw out any LIS1 vals.
+ (cond ((any null-list? lists) '()) ; Short cut
+ ((null? lists) lis1) ; Short cut
+ (else (filter (lambda (x)
+ (every (lambda (lis) (member x lis =)) lists))
+ lis1)))))
+
+(define (lset-intersection! = lis1 . lists)
+ (check-arg procedure? = lset-intersection!)
+ (let ((lists (delete lis1 lists eq?))) ; Throw out any LIS1 vals.
+ (cond ((any null-list? lists) '()) ; Short cut
+ ((null? lists) lis1) ; Short cut
+ (else (filter! (lambda (x)
+ (every (lambda (lis) (member x lis =)) lists))
+ lis1)))))
+
+
+(define (lset-difference = lis1 . lists)
+ (check-arg procedure? = lset-difference)
+ (let ((lists (filter pair? lists))) ; Throw out empty lists.
+ (cond ((null? lists) lis1) ; Short cut
+ ((memq lis1 lists) '()) ; Short cut
+ (else (filter (lambda (x)
+ (every (lambda (lis) (not (member x lis =)))
+ lists))
+ lis1)))))
+
+(define (lset-difference! = lis1 . lists)
+ (check-arg procedure? = lset-difference!)
+ (let ((lists (filter pair? lists))) ; Throw out empty lists.
+ (cond ((null? lists) lis1) ; Short cut
+ ((memq lis1 lists) '()) ; Short cut
+ (else (filter! (lambda (x)
+ (every (lambda (lis) (not (member x lis =)))
+ lists))
+ lis1)))))
+
+
+(define (lset-xor = . lists)
+ (check-arg procedure? = lset-xor)
+ (reduce (lambda (b a) ; Compute A xor B:
+ ;; Note that this code relies on the constant-time
+ ;; short-cuts provided by LSET-DIFF+INTERSECTION,
+ ;; LSET-DIFFERENCE & APPEND to provide constant-time short
+ ;; cuts for the cases A = (), B = (), and A eq? B. It takes
+ ;; a careful case analysis to see it, but it's carefully
+ ;; built in.
+
+ ;; Compute a-b and a^b, then compute b-(a^b) and
+ ;; cons it onto the front of a-b.
+ (receive (a-b a-int-b) (lset-diff+intersection = a b)
+ (cond ((null? a-b) (lset-difference = b a))
+ ((null? a-int-b) (append b a))
+ (else (fold (lambda (xb ans)
+ (if (member xb a-int-b =) ans (cons xb ans)))
+ a-b
+ b)))))
+ '() lists))
+
+
+(define (lset-xor! = . lists)
+ (check-arg procedure? = lset-xor!)
+ (reduce (lambda (b a) ; Compute A xor B:
+ ;; Note that this code relies on the constant-time
+ ;; short-cuts provided by LSET-DIFF+INTERSECTION,
+ ;; LSET-DIFFERENCE & APPEND to provide constant-time short
+ ;; cuts for the cases A = (), B = (), and A eq? B. It takes
+ ;; a careful case analysis to see it, but it's carefully
+ ;; built in.
+
+ ;; Compute a-b and a^b, then compute b-(a^b) and
+ ;; cons it onto the front of a-b.
+ (receive (a-b a-int-b) (lset-diff+intersection! = a b)
+ (cond ((null? a-b) (lset-difference! = b a))
+ ((null? a-int-b) (append! b a))
+ (else (pair-fold (lambda (b-pair ans)
+ (if (member (car b-pair) a-int-b =) ans
+ (begin (set-cdr! b-pair ans) b-pair)))
+ a-b
+ b)))))
+ '() lists))
+
+
+(define (lset-diff+intersection = lis1 . lists)
+ (check-arg procedure? = lset-diff+intersection)
+ (cond ((every null-list? lists) (values lis1 '())) ; Short cut
+ ((memq lis1 lists) (values '() lis1)) ; Short cut
+ (else (partition (lambda (elt)
+ (not (any (lambda (lis) (member elt lis =))
+ lists)))
+ lis1))))
+
+(define (lset-diff+intersection! = lis1 . lists)
+ (check-arg procedure? = lset-diff+intersection!)
+ (cond ((every null-list? lists) (values lis1 '())) ; Short cut
+ ((memq lis1 lists) (values '() lis1)) ; Short cut
+ (else (partition! (lambda (elt)
+ (not (any (lambda (lis) (member elt lis =))
+ lists)))
+ lis1))))
+(define-library (srfi 1)
+ (export
+ xcons list-tabulate cons*
+ proper-list? circular-list? dotted-list? not-pair? null-list? list=
+ circular-list length+
+ iota
+ first second third fourth fifth sixth seventh eighth ninth tenth
+ car+cdr
+ take drop
+ take-right drop-right
+ take! drop-right!
+ split-at split-at!
+ last last-pair
+ zip unzip1 unzip2 unzip3 unzip4 unzip5
+ count
+ append! append-reverse append-reverse! concatenate concatenate!
+ unfold fold pair-fold reduce
+ unfold-right fold-right pair-fold-right reduce-right
+ append-map append-map! map! pair-for-each filter-map map-in-order
+ filter partition remove
+ filter! partition! remove!
+ find find-tail any every list-index
+ take-while drop-while take-while!
+ span break span! break!
+ delete delete!
+ alist-cons alist-copy
+ delete-duplicates delete-duplicates!
+ alist-delete alist-delete!
+ reverse!
+ lset<= lset= lset-adjoin
+ lset-union lset-intersection lset-difference lset-xor lset-diff+intersection
+ lset-union! lset-intersection! lset-difference! lset-xor! lset-diff+intersection!
+ )
+ (import
+ (except (scheme base) map member assoc)
+ (scheme case-lambda)
+ (scheme cxr)
+ (srfi 8)
+ (srfi aux))
+ (begin
+ (define-check-arg check-arg))
+ (include "1.body.scm"))
+;;;;"array.scm" Arrays for Scheme
+; Copyright (C) 2001, 2003 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.
+
+;;@code{(require 'array)} or @code{(require 'srfi-63)}
+;;@ftindex array
+
+(define-record-type <array>
+ (array:construct dimensions scales offset store)
+ array:array?
+ (dimensions dimensions)
+ (scales scales)
+ (offset offset)
+ (store store))
+
+(define (array:dimensions array)
+ (cond ((vector? array) (list (vector-length array)))
+ ((string? array) (list (string-length array)))
+ (else (dimensions array))))
+
+(define (array:scales array)
+ (cond ((vector? array) '(1))
+ ((string? array) '(1))
+ (else (scales array))))
+
+(define (array:store array)
+ (cond ((vector? array) array)
+ ((string? array) array)
+ (else (store array))))
+
+(define (array:offset array)
+ (cond ((vector? array) 0)
+ ((string? array) 0)
+ (else (offset array))))
+
+;;@args obj
+;;Returns @code{#t} if the @1 is an array, and @code{#f} if not.
+(define (array? obj)
+ (or (vector? obj) (string? obj) (array:array? obj)))
+
+;;@noindent
+;;@emph{Note:} Arrays are not disjoint from other Scheme types.
+;;Vectors and possibly strings also satisfy @code{array?}.
+;;A disjoint array predicate can be written:
+;;
+;;@example
+;;(define (strict-array? obj)
+;; (and (array? obj) (not (string? obj)) (not (vector? obj))))
+;;@end example
+
+;;@body
+;;Returns @code{#t} if @1 and @2 have the same rank and dimensions and the
+;;corresponding elements of @1 and @2 are @code{equal?}.
+
+;;@body
+;;@0 recursively compares the contents of pairs, vectors, strings, and
+;;@emph{arrays}, applying @code{eqv?} on other objects such as numbers
+;;and symbols. A rule of thumb is that objects are generally @0 if
+;;they print the same. @0 may fail to terminate if its arguments are
+;;circular data structures.
+;;
+;;@example
+;;(equal? 'a 'a) @result{} #t
+;;(equal? '(a) '(a)) @result{} #t
+;;(equal? '(a (b) c)
+;; '(a (b) c)) @result{} #t
+;;(equal? "abc" "abc") @result{} #t
+;;(equal? 2 2) @result{} #t
+;;(equal? (make-vector 5 'a)
+;; (make-vector 5 'a)) @result{} #t
+;;(equal? (make-array (a:fixN32b 4) 5 3)
+;; (make-array (a:fixN32b 4) 5 3)) @result{} #t
+;;(equal? (make-array '#(foo) 3 3)
+;; (make-array '#(foo) 3 3)) @result{} #t
+;;(equal? (lambda (x) x)
+;; (lambda (y) y)) @result{} @emph{unspecified}
+;;@end example
+(define (equal? obj1 obj2)
+ (cond ((eqv? obj1 obj2) #t)
+ ((or (pair? obj1) (pair? obj2))
+ (and (pair? obj1) (pair? obj2)
+ (equal? (car obj1) (car obj2))
+ (equal? (cdr obj1) (cdr obj2))))
+ ((or (string? obj1) (string? obj2))
+ (and (string? obj1) (string? obj2)
+ (string=? obj1 obj2)))
+ ((or (vector? obj1) (vector? obj2))
+ (and (vector? obj1) (vector? obj2)
+ (equal? (vector-length obj1) (vector-length obj2))
+ (do ((idx (+ -1 (vector-length obj1)) (+ -1 idx)))
+ ((or (negative? idx)
+ (not (equal? (vector-ref obj1 idx)
+ (vector-ref obj2 idx))))
+ (negative? idx)))))
+ ((or (array? obj1) (array? obj2))
+ (and (array? obj1) (array? obj2)
+ (equal? (array:dimensions obj1) (array:dimensions obj2))
+ (equal? (array:store obj1) (array:store obj2))))
+ (else #f)))
+
+;;@body
+;;Returns the number of dimensions of @1. If @1 is not an array, 0 is
+;;returned.
+(define (array-rank obj)
+ (if (array? obj) (length (array:dimensions obj)) 0))
+
+;;@args array
+;;Returns a list of dimensions.
+;;
+;;@example
+;;(array-dimensions (make-array '#() 3 5))
+;; @result{} (3 5)
+;;@end example
+(define array-dimensions array:dimensions)
+
+;;@args prototype k1 @dots{}
+;;
+;;Creates and returns an array of type @1 with dimensions @2, @dots{}
+;;and filled with elements from @1. @1 must be an array, vector, or
+;;string. The implementation-dependent type of the returned array
+;;will be the same as the type of @1; except if that would be a vector
+;;or string with rank not equal to one, in which case some variety of
+;;array will be returned.
+;;
+;;If the @1 has no elements, then the initial contents of the returned
+;;array are unspecified. Otherwise, the returned array will be filled
+;;with the element at the origin of @1.
+(define (make-array prototype . dimensions)
+ (define tcnt (apply * dimensions))
+ (let ((store
+ (if (string? prototype)
+ (case (string-length prototype)
+ ((0) (make-string tcnt))
+ (else (make-string tcnt
+ (string-ref prototype 0))))
+ (let ((pdims (array:dimensions prototype)))
+ (case (apply * pdims)
+ ((0) (make-vector tcnt))
+ (else (make-vector tcnt
+ (apply array-ref prototype
+ (map (lambda (x) 0) pdims)))))))))
+ (define (loop dims scales)
+ (if (null? dims)
+ (array:construct dimensions (cdr scales) 0 store)
+ (loop (cdr dims) (cons (* (car dims) (car scales)) scales))))
+ (loop (reverse dimensions) '(1))))
+;;@args prototype k1 @dots{}
+;;@0 is an alias for @code{make-array}.
+(define create-array make-array)
+
+;;@args array mapper k1 @dots{}
+;;@0 can be used to create shared subarrays of other
+;;arrays. The @var{mapper} is a function that translates coordinates in
+;;the new array into coordinates in the old array. A @var{mapper} must be
+;;linear, and its range must stay within the bounds of the old array, but
+;;it can be otherwise arbitrary. A simple example:
+;;
+;;@example
+;;(define fred (make-array '#(#f) 8 8))
+;;(define freds-diagonal
+;; (make-shared-array fred (lambda (i) (list i i)) 8))
+;;(array-set! freds-diagonal 'foo 3)
+;;(array-ref fred 3 3)
+;; @result{} FOO
+;;(define freds-center
+;; (make-shared-array fred (lambda (i j) (list (+ 3 i) (+ 3 j)))
+;; 2 2))
+;;(array-ref freds-center 0 0)
+;; @result{} FOO
+;;@end example
+(define (make-shared-array array mapper . dimensions)
+ (define odl (array:scales array))
+ (define rank (length dimensions))
+ (define shape
+ (map (lambda (dim) (if (list? dim) dim (list 0 (+ -1 dim)))) dimensions))
+ (do ((idx (+ -1 rank) (+ -1 idx))
+ (uvt (append (cdr (vector->list (make-vector rank 0))) '(1))
+ (append (cdr uvt) '(0)))
+ (uvts '() (cons uvt uvts)))
+ ((negative? idx)
+ (let ((ker0 (apply + (map * odl (apply mapper uvt)))))
+ (array:construct
+ (map (lambda (dim) (+ 1 (- (cadr dim) (car dim)))) shape)
+ (map (lambda (uvt) (- (apply + (map * odl (apply mapper uvt))) ker0))
+ uvts)
+ (apply +
+ (array:offset array)
+ (map * odl (apply mapper (map car shape))))
+ (array:store array))))))
+
+;;@args rank proto list
+;;@3 must be a rank-nested list consisting of all the elements, in
+;;row-major order, of the array to be created.
+;;
+;;@0 returns an array of rank @1 and type @2 consisting of all the
+;;elements, in row-major order, of @3. When @1 is 0, @3 is the lone
+;;array element; not necessarily a list.
+;;
+;;@example
+;;(list->array 2 '#() '((1 2) (3 4)))
+;; @result{} #2A((1 2) (3 4))
+;;(list->array 0 '#() 3)
+;; @result{} #0A 3
+;;@end example
+(define (list->array rank proto lst)
+ (define dimensions
+ (do ((shp '() (cons (length row) shp))
+ (row lst (car lst))
+ (rnk (+ -1 rank) (+ -1 rnk)))
+ ((negative? rnk) (reverse shp))))
+ (let ((nra (apply make-array proto dimensions)))
+ (define (l2ra dims idxs row)
+ (cond ((null? dims)
+ (apply array-set! nra row (reverse idxs)))
+ (else
+ (if (not (eqv? (car dims) (length row)))
+ (error "Array not rectangular:" dims dimensions))
+ (do ((idx 0 (+ 1 idx))
+ (row row (cdr row)))
+ ((>= idx (car dims)))
+ (l2ra (cdr dims) (cons idx idxs) (car row))))))
+ (l2ra dimensions '() lst)
+ nra))
+
+;;@args array
+;;Returns a rank-nested list consisting of all the elements, in
+;;row-major order, of @1. In the case of a rank-0 array, @0 returns
+;;the single element.
+;;
+;;@example
+;;(array->list #2A((ho ho ho) (ho oh oh)))
+;; @result{} ((ho ho ho) (ho oh oh))
+;;(array->list #0A ho)
+;; @result{} ho
+;;@end example
+(define (array->list ra)
+ (define (ra2l dims idxs)
+ (if (null? dims)
+ (apply array-ref ra (reverse idxs))
+ (do ((lst '() (cons (ra2l (cdr dims) (cons idx idxs)) lst))
+ (idx (+ -1 (car dims)) (+ -1 idx)))
+ ((negative? idx) lst))))
+ (ra2l (array-dimensions ra) '()))
+
+;;@args vect proto dim1 @dots{}
+;;@1 must be a vector of length equal to the product of exact
+;;nonnegative integers @3, @dots{}.
+;;
+;;@0 returns an array of type @2 consisting of all the elements, in
+;;row-major order, of @1. In the case of a rank-0 array, @1 has a
+;;single element.
+;;
+;;@example
+;;(vector->array #(1 2 3 4) #() 2 2)
+;; @result{} #2A((1 2) (3 4))
+;;(vector->array '#(3) '#())
+;; @result{} #0A 3
+;;@end example
+(define (vector->array vect prototype . dimensions)
+ (define vdx (vector-length vect))
+ (if (not (eqv? vdx (apply * dimensions)))
+ (error "Vector length does not equal product of dimensions:"
+ vdx dimensions))
+ (let ((ra (apply make-array prototype dimensions)))
+ (define (v2ra dims idxs)
+ (cond ((null? dims)
+ (set! vdx (+ -1 vdx))
+ (apply array-set! ra (vector-ref vect vdx) (reverse idxs)))
+ (else
+ (do ((idx (+ -1 (car dims)) (+ -1 idx)))
+ ((negative? idx) vect)
+ (v2ra (cdr dims) (cons idx idxs))))))
+ (v2ra dimensions '())
+ ra))
+
+;;@args array
+;;Returns a new vector consisting of all the elements of @1 in
+;;row-major order.
+;;
+;;@example
+;;(array->vector #2A ((1 2)( 3 4)))
+;; @result{} #(1 2 3 4)
+;;(array->vector #0A ho)
+;; @result{} #(ho)
+;;@end example
+(define (array->vector ra)
+ (define dims (array-dimensions ra))
+ (let* ((vdx (apply * dims))
+ (vect (make-vector vdx)))
+ (define (ra2v dims idxs)
+ (if (null? dims)
+ (let ((val (apply array-ref ra (reverse idxs))))
+ (set! vdx (+ -1 vdx))
+ (vector-set! vect vdx val)
+ vect)
+ (do ((idx (+ -1 (car dims)) (+ -1 idx)))
+ ((negative? idx) vect)
+ (ra2v (cdr dims) (cons idx idxs)))))
+ (ra2v dims '())))
+
+(define (array:in-bounds? array indices)
+ (do ((bnds (array:dimensions array) (cdr bnds))
+ (idxs indices (cdr idxs)))
+ ((or (null? bnds)
+ (null? idxs)
+ (not (integer? (car idxs)))
+ (not (< -1 (car idxs) (car bnds))))
+ (and (null? bnds) (null? idxs)))))
+
+;;@args array index1 @dots{}
+;;Returns @code{#t} if its arguments would be acceptable to
+;;@code{array-ref}.
+(define (array-in-bounds? array . indices)
+ (array:in-bounds? array indices))
+
+;;@args array k1 @dots{}
+;;Returns the (@2, @dots{}) element of @1.
+(define (array-ref array . indices)
+ (define store (array:store array))
+ (or (array:in-bounds? array indices)
+ (error "Bad indices:" indices))
+ ((if (string? store) string-ref vector-ref)
+ store (apply + (array:offset array) (map * (array:scales array) indices))))
+
+;;@args array obj k1 @dots{}
+;;Stores @2 in the (@3, @dots{}) element of @1. The value returned
+;;by @0 is unspecified.
+(define (array-set! array obj . indices)
+ (define store (array:store array))
+ (or (array:in-bounds? array indices)
+ (error "Bad indices:" indices))
+ ((if (string? store) string-set! vector-set!)
+ store (apply + (array:offset array) (map * (array:scales array) indices))
+ obj))
+
+;;@noindent
+;;These functions return a prototypical uniform-array enclosing the
+;;optional argument (which must be of the correct type). If the
+;;uniform-array type is supported by the implementation, then it is
+;;returned; defaulting to the next larger precision type; resorting
+;;finally to vector.
+
+(define (make-prototype-checker name pred? creator)
+ (lambda args
+ (case (length args)
+ ((1) (if (pred? (car args))
+ (creator (car args))
+ (error "Incompatible type:" name (car args))))
+ ((0) (creator))
+ (else (error "Wrong number of arguments:" name args)))))
+
+(define (integer-bytes?? n)
+ (lambda (obj)
+ (and (integer? obj)
+ (exact? obj)
+ (or (negative? n) (not (negative? obj)))
+ (do ((num obj (quotient num 256))
+ (n (+ -1 (abs n)) (+ -1 n)))
+ ((or (zero? num) (negative? n))
+ (zero? num))))))
+
+;;@args z
+;;@args
+;;Returns an inexact 128.bit flonum complex uniform-array prototype.
+(define a:floc128b (make-prototype-checker 'a:floc128b complex? vector))
+;;@args z
+;;@args
+;;Returns an inexact 64.bit flonum complex uniform-array prototype.
+(define a:floc64b (make-prototype-checker 'a:floc64b complex? vector))
+;;@args z
+;;@args
+;;Returns an inexact 32.bit flonum complex uniform-array prototype.
+(define a:floc32b (make-prototype-checker 'a:floc32b complex? vector))
+;;@args z
+;;@args
+;;Returns an inexact 16.bit flonum complex uniform-array prototype.
+(define a:floc16b (make-prototype-checker 'a:floc16b complex? vector))
+
+;;@args z
+;;@args
+;;Returns an inexact 128.bit flonum real uniform-array prototype.
+(define a:flor128b (make-prototype-checker 'a:flor128b real? vector))
+;;@args z
+;;@args
+;;Returns an inexact 64.bit flonum real uniform-array prototype.
+(define a:flor64b (make-prototype-checker 'a:flor64b real? vector))
+;;@args z
+;;@args
+;;Returns an inexact 32.bit flonum real uniform-array prototype.
+(define a:flor32b (make-prototype-checker 'a:flor32b real? vector))
+;;@args z
+;;@args
+;;Returns an inexact 16.bit flonum real uniform-array prototype.
+(define a:flor16b (make-prototype-checker 'a:flor16b real? vector))
+
+;;@args z
+;;@args
+;;Returns an exact 128.bit decimal flonum rational uniform-array prototype.
+(define a:flor128b (make-prototype-checker 'a:flor128b real? vector))
+;;@args z
+;;@args
+;;Returns an exact 64.bit decimal flonum rational uniform-array prototype.
+(define a:flor64b (make-prototype-checker 'a:flor64b real? vector))
+;;@args z
+;;@args
+;;Returns an exact 32.bit decimal flonum rational uniform-array prototype.
+(define a:flor32b (make-prototype-checker 'a:flor32b real? vector))
+
+;;@args n
+;;@args
+;;Returns an exact binary fixnum uniform-array prototype with at least
+;;64 bits of precision.
+(define a:fixz64b (make-prototype-checker 'a:fixz64b (integer-bytes?? -8) vector))
+;;@args n
+;;@args
+;;Returns an exact binary fixnum uniform-array prototype with at least
+;;32 bits of precision.
+(define a:fixz32b (make-prototype-checker 'a:fixz32b (integer-bytes?? -4) vector))
+;;@args n
+;;@args
+;;Returns an exact binary fixnum uniform-array prototype with at least
+;;16 bits of precision.
+(define a:fixz16b (make-prototype-checker 'a:fixz16b (integer-bytes?? -2) vector))
+;;@args n
+;;@args
+;;Returns an exact binary fixnum uniform-array prototype with at least
+;;8 bits of precision.
+(define a:fixz8b (make-prototype-checker 'a:fixz8b (integer-bytes?? -1) vector))
+
+;;@args k
+;;@args
+;;Returns an exact non-negative binary fixnum uniform-array prototype with at
+;;least 64 bits of precision.
+(define a:fixn64b (make-prototype-checker 'a:fixn64b (integer-bytes?? 8) vector))
+;;@args k
+;;@args
+;;Returns an exact non-negative binary fixnum uniform-array prototype with at
+;;least 32 bits of precision.
+(define a:fixn32b (make-prototype-checker 'a:fixn32b (integer-bytes?? 4) vector))
+;;@args k
+;;@args
+;;Returns an exact non-negative binary fixnum uniform-array prototype with at
+;;least 16 bits of precision.
+(define a:fixn16b (make-prototype-checker 'a:fixn16b (integer-bytes?? 2) vector))
+;;@args k
+;;@args
+;;Returns an exact non-negative binary fixnum uniform-array prototype with at
+;;least 8 bits of precision.
+(define a:fixn8b (make-prototype-checker 'a:fixn8b (integer-bytes?? 1) vector))
+
+;;@args bool
+;;@args
+;;Returns a boolean uniform-array prototype.
+(define a:bool (make-prototype-checker 'a:bool boolean? vector))
+;;; SRFI-1 list-processing library -*- Scheme -*-
+;;; Reference implementation
+;;;
+;;; Copyright (c) 1998, 1999 by Olin Shivers. You may do as you please with
+;;; this code as long as you do not remove this copyright notice or
+;;; hold me liable for its use. Please send bug reports to shivers@ai.mit.edu.
+;;; -Olin
+
+;;; This is a library of list- and pair-processing functions. I wrote it after
+;;; carefully considering the functions provided by the libraries found in
+;;; R4RS/R5RS Scheme, MIT Scheme, Gambit, RScheme, MzScheme, slib, Common
+;;; Lisp, Bigloo, guile, T, APL and the SML standard basis. It is a pretty
+;;; rich toolkit, providing a superset of the functionality found in any of
+;;; the various Schemes I considered.
+
+;;; This implementation is intended as a portable reference implementation
+;;; for SRFI-1. See the porting notes below for more information.
+
+;;; Exported:
+;;; xcons tree-copy make-list list-tabulate cons* list-copy
+;;; proper-list? circular-list? dotted-list? not-pair? null-list? list=
+;;; circular-list length+
+;;; iota
+;;; first second third fourth fifth sixth seventh eighth ninth tenth
+;;; car+cdr
+;;; take drop
+;;; take-right drop-right
+;;; take! drop-right!
+;;; split-at split-at!
+;;; last last-pair
+;;; zip unzip1 unzip2 unzip3 unzip4 unzip5
+;;; count
+;;; append! append-reverse append-reverse! concatenate concatenate!
+;;; unfold fold pair-fold reduce
+;;; unfold-right fold-right pair-fold-right reduce-right
+;;; append-map append-map! map! pair-for-each filter-map map-in-order
+;;; filter partition remove
+;;; filter! partition! remove!
+;;; find find-tail any every list-index
+;;; take-while drop-while take-while!
+;;; span break span! break!
+;;; delete delete!
+;;; alist-cons alist-copy
+;;; delete-duplicates delete-duplicates!
+;;; alist-delete alist-delete!
+;;; reverse!
+;;; lset<= lset= lset-adjoin
+;;; lset-union lset-intersection lset-difference lset-xor lset-diff+intersection
+;;; lset-union! lset-intersection! lset-difference! lset-xor! lset-diff+intersection!
+;;;
+;;; In principle, the following R4RS list- and pair-processing procedures
+;;; are also part of this package's exports, although they are not defined
+;;; in this file:
+;;; Primitives: cons pair? null? car cdr set-car! set-cdr!
+;;; Non-primitives: list length append reverse cadr ... cddddr list-ref
+;;; memq memv assq assv
+;;; (The non-primitives are defined in this file, but commented out.)
+;;;
+;;; These R4RS procedures have extended definitions in SRFI-1 and are defined
+;;; in this file:
+;;; map for-each member assoc
+;;;
+;;; The remaining two R4RS list-processing procedures are not included:
+;;; list-tail (use drop)
+;;; list? (use proper-list?)
+
+
+;;; A note on recursion and iteration/reversal:
+;;; Many iterative list-processing algorithms naturally compute the elements
+;;; of the answer list in the wrong order (left-to-right or head-to-tail) from
+;;; the order needed to cons them into the proper answer (right-to-left, or
+;;; tail-then-head). One style or idiom of programming these algorithms, then,
+;;; loops, consing up the elements in reverse order, then destructively
+;;; reverses the list at the end of the loop. I do not do this. The natural
+;;; and efficient way to code these algorithms is recursively. This trades off
+;;; intermediate temporary list structure for intermediate temporary stack
+;;; structure. In a stack-based system, this improves cache locality and
+;;; lightens the load on the GC system. Don't stand on your head to iterate!
+;;; Recurse, where natural. Multiple-value returns make this even more
+;;; convenient, when the recursion/iteration has multiple state values.
+
+;;; Porting:
+;;; This is carefully tuned code; do not modify casually.
+;;; - It is careful to share storage when possible;
+;;; - Side-effecting code tries not to perform redundant writes.
+;;;
+;;; That said, a port of this library to a specific Scheme system might wish
+;;; to tune this code to exploit particulars of the implementation.
+;;; The single most important compiler-specific optimisation you could make
+;;; to this library would be to add rewrite rules or transforms to:
+;;; - transform applications of n-ary procedures (e.g. LIST=, CONS*, APPEND,
+;;; LSET-UNION) into multiple applications of a primitive two-argument
+;;; variant.
+;;; - transform applications of the mapping functions (MAP, FOR-EACH, FOLD,
+;;; ANY, EVERY) into open-coded loops. The killer here is that these
+;;; functions are n-ary. Handling the general case is quite inefficient,
+;;; requiring many intermediate data structures to be allocated and
+;;; discarded.
+;;; - transform applications of procedures that take optional arguments
+;;; into calls to variants that do not take optional arguments. This
+;;; eliminates unnecessary consing and parsing of the rest parameter.
+;;;
+;;; These transforms would provide BIG speedups. In particular, the n-ary
+;;; mapping functions are particularly slow and cons-intensive, and are good
+;;; candidates for tuning. I have coded fast paths for the single-list cases,
+;;; but what you really want to do is exploit the fact that the compiler
+;;; usually knows how many arguments are being passed to a particular
+;;; application of these functions -- they are usually explicitly called, not
+;;; passed around as higher-order values. If you can arrange to have your
+;;; compiler produce custom code or custom linkages based on the number of
+;;; arguments in the call, you can speed these functions up a *lot*. But this
+;;; kind of compiler technology no longer exists in the Scheme world as far as
+;;; I can see.
+;;;
+;;; Note that this code is, of course, dependent upon standard bindings for
+;;; the R5RS procedures -- i.e., it assumes that the variable CAR is bound
+;;; to the procedure that takes the car of a list. If your Scheme
+;;; implementation allows user code to alter the bindings of these procedures
+;;; in a manner that would be visible to these definitions, then there might
+;;; be trouble. You could consider horrible kludgery along the lines of
+;;; (define fact
+;;; (let ((= =) (- -) (* *))
+;;; (letrec ((real-fact (lambda (n)
+;;; (if (= n 0) 1 (* n (real-fact (- n 1)))))))
+;;; real-fact)))
+;;; Or you could consider shifting to a reasonable Scheme system that, say,
+;;; has a module system protecting code from this kind of lossage.
+;;;
+;;; This code does a fair amount of run-time argument checking. If your
+;;; Scheme system has a sophisticated compiler that can eliminate redundant
+;;; error checks, this is no problem. However, if not, these checks incur
+;;; some performance overhead -- and, in a safe Scheme implementation, they
+;;; are in some sense redundant: if we don't check to see that the PROC
+;;; parameter is a procedure, we'll find out anyway three lines later when
+;;; we try to call the value. It's pretty easy to rip all this argument
+;;; checking code out if it's inappropriate for your implementation -- just
+;;; nuke every call to CHECK-ARG.
+;;;
+;;; On the other hand, if you *do* have a sophisticated compiler that will
+;;; actually perform soft-typing and eliminate redundant checks (Rice's systems
+;;; being the only possible candidate of which I'm aware), leaving these checks
+;;; in can *help*, since their presence can be elided in redundant cases,
+;;; and in cases where they are needed, performing the checks early, at
+;;; procedure entry, can "lift" a check out of a loop.
+;;;
+;;; Finally, I have only checked the properties that can portably be checked
+;;; with R5RS Scheme -- and this is not complete. You may wish to alter
+;;; the CHECK-ARG parameter checks to perform extra, implementation-specific
+;;; checks, such as procedure arity for higher-order values.
+;;;
+;;; The code has only these non-R4RS dependencies:
+;;; A few calls to an ERROR procedure;
+;;; Uses of the R5RS multiple-value procedure VALUES and the m-v binding
+;;; RECEIVE macro (which isn't R5RS, but is a trivial macro).
+;;; Many calls to a parameter-checking procedure check-arg:
+;;; (define (check-arg pred val caller)
+;;; (let lp ((val val))
+;;; (if (pred val) val (lp (error "Bad argument" val pred caller)))))
+;;; A few uses of the LET-OPTIONAL and :OPTIONAL macros for parsing
+;;; optional arguments.
+;;;
+;;; Most of these procedures use the NULL-LIST? test to trigger the
+;;; base case in the inner loop or recursion. The NULL-LIST? function
+;;; is defined to be a careful one -- it raises an error if passed a
+;;; non-nil, non-pair value. The spec allows an implementation to use
+;;; a less-careful implementation that simply defines NULL-LIST? to
+;;; be NOT-PAIR?. This would speed up the inner loops of these procedures
+;;; at the expense of having them silently accept dotted lists.
+
+;;; A note on dotted lists:
+;;; I, personally, take the view that the only consistent view of lists
+;;; in Scheme is the view that *everything* is a list -- values such as
+;;; 3 or "foo" or 'bar are simply empty dotted lists. This is due to the
+;;; fact that Scheme actually has no true list type. It has a pair type,
+;;; and there is an *interpretation* of the trees built using this type
+;;; as lists.
+;;;
+;;; I lobbied to have these list-processing procedures hew to this
+;;; view, and accept any value as a list argument. I was overwhelmingly
+;;; overruled during the SRFI discussion phase. So I am inserting this
+;;; text in the reference lib and the SRFI spec as a sort of "minority
+;;; opinion" dissent.
+;;;
+;;; Many of the procedures in this library can be trivially redefined
+;;; to handle dotted lists, just by changing the NULL-LIST? base-case
+;;; check to NOT-PAIR?, meaning that any non-pair value is taken to be
+;;; an empty list. For most of these procedures, that's all that is
+;;; required.
+;;;
+;;; However, we have to do a little more work for some procedures that
+;;; *produce* lists from other lists. Were we to extend these procedures to
+;;; accept dotted lists, we would have to define how they terminate the lists
+;;; produced as results when passed a dotted list. I designed a coherent set
+;;; of termination rules for these cases; this was posted to the SRFI-1
+;;; discussion list. I additionally wrote an earlier version of this library
+;;; that implemented that spec. It has been discarded during later phases of
+;;; the definition and implementation of this library.
+;;;
+;;; The argument *against* defining these procedures to work on dotted
+;;; lists is that dotted lists are the rare, odd case, and that by
+;;; arranging for the procedures to handle them, we lose error checking
+;;; in the cases where a dotted list is passed by accident -- e.g., when
+;;; the programmer swaps a two arguments to a list-processing function,
+;;; one being a scalar and one being a list. For example,
+;;; (member '(1 3 5 7 9) 7)
+;;; This would quietly return #f if we extended MEMBER to accept dotted
+;;; lists.
+;;;
+;;; The SRFI discussion record contains more discussion on this topic.
+
+
+;;; Constructors
+;;;;;;;;;;;;;;;;
+
+;;; Occasionally useful as a value to be passed to a fold or other
+;;; higher-order procedure.
+(define (xcons d a) (cons a d))
+
+;;;; Recursively copy every cons.
+;(define (tree-copy x)
+; (let recur ((x x))
+; (if (not (pair? x)) x
+; (cons (recur (car x)) (recur (cdr x))))))
+
+;;; Make a list of length LEN.
+
+(define (make-list len . maybe-elt)
+ (check-arg (lambda (n) (and (integer? n) (>= n 0))) len make-list)
+ (let ((elt (cond ((null? maybe-elt) #f) ; Default value
+ ((null? (cdr maybe-elt)) (car maybe-elt))
+ (else (error "Too many arguments to MAKE-LIST"
+ (cons len maybe-elt))))))
+ (do ((i len (- i 1))
+ (ans '() (cons elt ans)))
+ ((<= i 0) ans))))
+
+
+;(define (list . ans) ans) ; R4RS
+
+
+;;; Make a list of length LEN. Elt i is (PROC i) for 0 <= i < LEN.
+
+(define (list-tabulate len proc)
+ (check-arg (lambda (n) (and (integer? n) (>= n 0))) len list-tabulate)
+ (check-arg procedure? proc list-tabulate)
+ (do ((i (- len 1) (- i 1))
+ (ans '() (cons (proc i) ans)))
+ ((< i 0) ans)))
+
+;;; (cons* a1 a2 ... an) = (cons a1 (cons a2 (cons ... an)))
+;;; (cons* a1) = a1 (cons* a1 a2 ...) = (cons a1 (cons* a2 ...))
+;;;
+;;; (cons first (unfold not-pair? car cdr rest values))
+
+(define (cons* first . rest)
+ (let recur ((x first) (rest rest))
+ (if (pair? rest)
+ (cons x (recur (car rest) (cdr rest)))
+ x)))
+
+;;; (unfold not-pair? car cdr lis values)
+
+(define (list-copy lis)
+ (let recur ((lis lis))
+ (if (pair? lis)
+ (cons (car lis) (recur (cdr lis)))
+ lis)))
+
+;;; IOTA count [start step] (start start+step ... start+(count-1)*step)
+
+(define (iota count . maybe-start+step)
+ (check-arg integer? count iota)
+ (if (< count 0) (error "Negative step count" iota count))
+ (let-optionals maybe-start+step ((start 0) (step 1))
+ (check-arg number? start iota)
+ (check-arg number? step iota)
+ (let loop ((n 0) (r '()))
+ (if (= n count)
+ (reverse r)
+ (loop (+ 1 n)
+ (cons (+ start (* n step)) r))))))
+
+;;; I thought these were lovely, but the public at large did not share my
+;;; enthusiasm...
+;;; :IOTA to (0 ... to-1)
+;;; :IOTA from to (from ... to-1)
+;;; :IOTA from to step (from from+step ...)
+
+;;; IOTA: to (1 ... to)
+;;; IOTA: from to (from+1 ... to)
+;;; IOTA: from to step (from+step from+2step ...)
+
+;(define (%parse-iota-args arg1 rest-args proc)
+; (let ((check (lambda (n) (check-arg integer? n proc))))
+; (check arg1)
+; (if (pair? rest-args)
+; (let ((arg2 (check (car rest-args)))
+; (rest (cdr rest-args)))
+; (if (pair? rest)
+; (let ((arg3 (check (car rest)))
+; (rest (cdr rest)))
+; (if (pair? rest) (error "Too many parameters" proc arg1 rest-args)
+; (values arg1 arg2 arg3)))
+; (values arg1 arg2 1)))
+; (values 0 arg1 1))))
+;
+;(define (iota: arg1 . rest-args)
+; (receive (from to step) (%parse-iota-args arg1 rest-args iota:)
+; (let* ((numsteps (floor (/ (- to from) step)))
+; (last-val (+ from (* step numsteps))))
+; (if (< numsteps 0) (error "Negative step count" iota: from to step))
+; (do ((steps-left numsteps (- steps-left 1))
+; (val last-val (- val step))
+; (ans '() (cons val ans)))
+; ((<= steps-left 0) ans)))))
+;
+;
+;(define (\:iota arg1 . rest-args)
+; (receive (from to step) (%parse-iota-args arg1 rest-args :iota)
+; (let* ((numsteps (ceiling (/ (- to from) step)))
+; (last-val (+ from (* step (- numsteps 1)))))
+; (if (< numsteps 0) (error "Negative step count" :iota from to step))
+; (do ((steps-left numsteps (- steps-left 1))
+; (val last-val (- val step))
+; (ans '() (cons val ans)))
+; ((<= steps-left 0) ans)))))
+
+
+
+(define (circular-list val1 . vals)
+ (let ((ans (cons val1 vals)))
+ (set-cdr! (last-pair ans) ans)
+ ans))
+
+;;; <proper-list> ::= () ; Empty proper list
+;;; | (cons <x> <proper-list>) ; Proper-list pair
+;;; Note that this definition rules out circular lists -- and this
+;;; function is required to detect this case and return false.
+
+(define (proper-list? x)
+ (let lp ((x x) (lag x))
+ (if (pair? x)
+ (let ((x (cdr x)))
+ (if (pair? x)
+ (let ((x (cdr x))
+ (lag (cdr lag)))
+ (and (not (eq? x lag)) (lp x lag)))
+ (null? x)))
+ (null? x))))
+
+
+;;; A dotted list is a finite list (possibly of length 0) terminated
+;;; by a non-nil value. Any non-cons, non-nil value (e.g., "foo" or 5)
+;;; is a dotted list of length 0.
+;;;
+;;; <dotted-list> ::= <non-nil,non-pair> ; Empty dotted list
+;;; | (cons <x> <dotted-list>) ; Proper-list pair
+
+(define (dotted-list? x)
+ (let lp ((x x) (lag x))
+ (if (pair? x)
+ (let ((x (cdr x)))
+ (if (pair? x)
+ (let ((x (cdr x))
+ (lag (cdr lag)))
+ (and (not (eq? x lag)) (lp x lag)))
+ (not (null? x))))
+ (not (null? x)))))
+
+(define (circular-list? x)
+ (let lp ((x x) (lag x))
+ (and (pair? x)
+ (let ((x (cdr x)))
+ (and (pair? x)
+ (let ((x (cdr x))
+ (lag (cdr lag)))
+ (or (eq? x lag) (lp x lag))))))))
+
+(define (not-pair? x) (not (pair? x))) ; Inline me.
+
+;;; This is a legal definition which is fast and sloppy:
+;;; (define null-list? not-pair?)
+;;; but we'll provide a more careful one:
+(define (null-list? l)
+ (cond ((pair? l) #f)
+ ((null? l) #t)
+ (else (error "null-list?: argument out of domain" l))))
+
+
+(define (list= = . lists)
+ (or (null? lists) ; special case
+
+ (let lp1 ((list-a (car lists)) (others (cdr lists)))
+ (or (null? others)
+ (let ((list-b (car others))
+ (others (cdr others)))
+ (if (eq? list-a list-b) ; EQ? => LIST=
+ (lp1 list-b others)
+ (let lp2 ((list-a list-a) (list-b list-b))
+ (if (null-list? list-a)
+ (and (null-list? list-b)
+ (lp1 list-b others))
+ (and (not (null-list? list-b))
+ (= (car list-a) (car list-b))
+ (lp2 (cdr list-a) (cdr list-b)))))))))))
+
+
+
+;;; R4RS, so commented out.
+;(define (length x) ; LENGTH may diverge or
+; (let lp ((x x) (len 0)) ; raise an error if X is
+; (if (pair? x) ; a circular list. This version
+; (lp (cdr x) (+ len 1)) ; diverges.
+; len)))
+
+(define (length+ x) ; Returns #f if X is circular.
+ (let lp ((x x) (lag x) (len 0))
+ (if (pair? x)
+ (let ((x (cdr x))
+ (len (+ len 1)))
+ (if (pair? x)
+ (let ((x (cdr x))
+ (lag (cdr lag))
+ (len (+ len 1)))
+ (and (not (eq? x lag)) (lp x lag len)))
+ len))
+ len)))
+
+(define (zip list1 . more-lists) (apply map list list1 more-lists))
+
+
+;;; Selectors
+;;;;;;;;;;;;;
+
+;;; R4RS non-primitives:
+;(define (caar x) (car (car x)))
+;(define (cadr x) (car (cdr x)))
+;(define (cdar x) (cdr (car x)))
+;(define (cddr x) (cdr (cdr x)))
+;
+;(define (caaar x) (caar (car x)))
+;(define (caadr x) (caar (cdr x)))
+;(define (cadar x) (cadr (car x)))
+;(define (caddr x) (cadr (cdr x)))
+;(define (cdaar x) (cdar (car x)))
+;(define (cdadr x) (cdar (cdr x)))
+;(define (cddar x) (cddr (car x)))
+;(define (cdddr x) (cddr (cdr x)))
+;
+;(define (caaaar x) (caaar (car x)))
+;(define (caaadr x) (caaar (cdr x)))
+;(define (caadar x) (caadr (car x)))
+;(define (caaddr x) (caadr (cdr x)))
+;(define (cadaar x) (cadar (car x)))
+;(define (cadadr x) (cadar (cdr x)))
+;(define (caddar x) (caddr (car x)))
+;(define (cadddr x) (caddr (cdr x)))
+;(define (cdaaar x) (cdaar (car x)))
+;(define (cdaadr x) (cdaar (cdr x)))
+;(define (cdadar x) (cdadr (car x)))
+;(define (cdaddr x) (cdadr (cdr x)))
+;(define (cddaar x) (cddar (car x)))
+;(define (cddadr x) (cddar (cdr x)))
+;(define (cdddar x) (cdddr (car x)))
+;(define (cddddr x) (cdddr (cdr x)))
+
+
+(define first car)
+(define second cadr)
+(define third caddr)
+(define fourth cadddr)
+(define (fifth x) (car (cddddr x)))
+(define (sixth x) (cadr (cddddr x)))
+(define (seventh x) (caddr (cddddr x)))
+(define (eighth x) (cadddr (cddddr x)))
+(define (ninth x) (car (cddddr (cddddr x))))
+(define (tenth x) (cadr (cddddr (cddddr x))))
+
+(define (car+cdr pair) (values (car pair) (cdr pair)))
+
+;;; take & drop
+
+(define (take lis k)
+ (check-arg integer? k take)
+ (let recur ((lis lis) (k k))
+ (if (zero? k) '()
+ (cons (car lis)
+ (recur (cdr lis) (- k 1))))))
+
+(define (drop lis k)
+ (check-arg integer? k drop)
+ (let iter ((lis lis) (k k))
+ (if (zero? k) lis (iter (cdr lis) (- k 1)))))
+
+(define (take! lis k)
+ (check-arg integer? k take!)
+ (if (zero? k) '()
+ (begin (set-cdr! (drop lis (- k 1)) '())
+ lis)))
+
+;;; TAKE-RIGHT and DROP-RIGHT work by getting two pointers into the list,
+;;; off by K, then chasing down the list until the lead pointer falls off
+;;; the end.
+
+(define (take-right lis k)
+ (check-arg integer? k take-right)
+ (let lp ((lag lis) (lead (drop lis k)))
+ (if (pair? lead)
+ (lp (cdr lag) (cdr lead))
+ lag)))
+
+(define (drop-right lis k)
+ (check-arg integer? k drop-right)
+ (let recur ((lag lis) (lead (drop lis k)))
+ (if (pair? lead)
+ (cons (car lag) (recur (cdr lag) (cdr lead)))
+ '())))
+
+;;; In this function, LEAD is actually K+1 ahead of LAG. This lets
+;;; us stop LAG one step early, in time to smash its cdr to ().
+(define (drop-right! lis k)
+ (check-arg integer? k drop-right!)
+ (let ((lead (drop lis k)))
+ (if (pair? lead)
+
+ (let lp ((lag lis) (lead (cdr lead))) ; Standard case
+ (if (pair? lead)
+ (lp (cdr lag) (cdr lead))
+ (begin (set-cdr! lag '())
+ lis)))
+
+ '()))) ; Special case dropping everything -- no cons to side-effect.
+
+;(define (list-ref lis i) (car (drop lis i))) ; R4RS
+
+;;; These use the APL convention, whereby negative indices mean
+;;; "from the right." I liked them, but they didn't win over the
+;;; SRFI reviewers.
+;;; K >= 0: Take and drop K elts from the front of the list.
+;;; K <= 0: Take and drop -K elts from the end of the list.
+
+;(define (take lis k)
+; (check-arg integer? k take)
+; (if (negative? k)
+; (list-tail lis (+ k (length lis)))
+; (let recur ((lis lis) (k k))
+; (if (zero? k) '()
+; (cons (car lis)
+; (recur (cdr lis) (- k 1)))))))
+;
+;(define (drop lis k)
+; (check-arg integer? k drop)
+; (if (negative? k)
+; (let recur ((lis lis) (nelts (+ k (length lis))))
+; (if (zero? nelts) '()
+; (cons (car lis)
+; (recur (cdr lis) (- nelts 1)))))
+; (list-tail lis k)))
+;
+;
+;(define (take! lis k)
+; (check-arg integer? k take!)
+; (cond ((zero? k) '())
+; ((positive? k)
+; (set-cdr! (list-tail lis (- k 1)) '())
+; lis)
+; (else (list-tail lis (+ k (length lis))))))
+;
+;(define (drop! lis k)
+; (check-arg integer? k drop!)
+; (if (negative? k)
+; (let ((nelts (+ k (length lis))))
+; (if (zero? nelts) '()
+; (begin (set-cdr! (list-tail lis (- nelts 1)) '())
+; lis)))
+; (list-tail lis k)))
+
+(define (split-at x k)
+ (check-arg integer? k split-at)
+ (let recur ((lis x) (k k))
+ (if (zero? k) (values '() lis)
+ (receive (prefix suffix) (recur (cdr lis) (- k 1))
+ (values (cons (car lis) prefix) suffix)))))
+
+(define (split-at! x k)
+ (check-arg integer? k split-at!)
+ (if (zero? k) (values '() x)
+ (let* ((prev (drop x (- k 1)))
+ (suffix (cdr prev)))
+ (set-cdr! prev '())
+ (values x suffix))))
+
+
+(define (last lis) (car (last-pair lis)))
+
+(define (last-pair lis)
+ (check-arg pair? lis last-pair)
+ (let lp ((lis lis))
+ (let ((tail (cdr lis)))
+ (if (pair? tail) (lp tail) lis))))
+
+
+;;; Unzippers -- 1 through 5
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (unzip1 lis) (map car lis))
+
+(define (unzip2 lis)
+ (let recur ((lis lis))
+ (if (null-list? lis) (values lis lis) ; Use NOT-PAIR? to handle
+ (let ((elt (car lis))) ; dotted lists.
+ (receive (a b) (recur (cdr lis))
+ (values (cons (car elt) a)
+ (cons (cadr elt) b)))))))
+
+(define (unzip3 lis)
+ (let recur ((lis lis))
+ (if (null-list? lis) (values lis lis lis)
+ (let ((elt (car lis)))
+ (receive (a b c) (recur (cdr lis))
+ (values (cons (car elt) a)
+ (cons (cadr elt) b)
+ (cons (caddr elt) c)))))))
+
+(define (unzip4 lis)
+ (let recur ((lis lis))
+ (if (null-list? lis) (values lis lis lis lis)
+ (let ((elt (car lis)))
+ (receive (a b c d) (recur (cdr lis))
+ (values (cons (car elt) a)
+ (cons (cadr elt) b)
+ (cons (caddr elt) c)
+ (cons (cadddr elt) d)))))))
+
+(define (unzip5 lis)
+ (let recur ((lis lis))
+ (if (null-list? lis) (values lis lis lis lis lis)
+ (let ((elt (car lis)))
+ (receive (a b c d e) (recur (cdr lis))
+ (values (cons (car elt) a)
+ (cons (cadr elt) b)
+ (cons (caddr elt) c)
+ (cons (cadddr elt) d)
+ (cons (car (cddddr elt)) e)))))))
+
+
+;;; append! append-reverse append-reverse! concatenate concatenate!
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (append! . lists)
+ ;; First, scan through lists looking for a non-empty one.
+ (let lp ((lists lists) (prev '()))
+ (if (not (pair? lists)) prev
+ (let ((first (car lists))
+ (rest (cdr lists)))
+ (if (not (pair? first)) (lp rest first)
+
+ ;; Now, do the splicing.
+ (let lp2 ((tail-cons (last-pair first))
+ (rest rest))
+ (if (pair? rest)
+ (let ((next (car rest))
+ (rest (cdr rest)))
+ (set-cdr! tail-cons next)
+ (lp2 (if (pair? next) (last-pair next) tail-cons)
+ rest))
+ first)))))))
+
+;;; APPEND is R4RS.
+;(define (append . lists)
+; (if (pair? lists)
+; (let recur ((list1 (car lists)) (lists (cdr lists)))
+; (if (pair? lists)
+; (let ((tail (recur (car lists) (cdr lists))))
+; (fold-right cons tail list1)) ; Append LIST1 & TAIL.
+; list1))
+; '()))
+
+;(define (append-reverse rev-head tail) (fold cons tail rev-head))
+
+;(define (append-reverse! rev-head tail)
+; (pair-fold (lambda (pair tail) (set-cdr! pair tail) pair)
+; tail
+; rev-head))
+
+;;; Hand-inline the FOLD and PAIR-FOLD ops for speed.
+
+(define (append-reverse rev-head tail)
+ (let lp ((rev-head rev-head) (tail tail))
+ (if (null-list? rev-head) tail
+ (lp (cdr rev-head) (cons (car rev-head) tail)))))
+
+(define (append-reverse! rev-head tail)
+ (let lp ((rev-head rev-head) (tail tail))
+ (if (null-list? rev-head) tail
+ (let ((next-rev (cdr rev-head)))
+ (set-cdr! rev-head tail)
+ (lp next-rev rev-head)))))
+
+
+(define (concatenate lists) (reduce-right append '() lists))
+(define (concatenate! lists) (reduce-right append! '() lists))
+
+;;; Fold/map internal utilities
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; These little internal utilities are used by the general
+;;; fold & mapper funs for the n-ary cases . It'd be nice if they got inlined.
+;;; One the other hand, the n-ary cases are painfully inefficient as it is.
+;;; An aggressive implementation should simply re-write these functions
+;;; for raw efficiency; I have written them for as much clarity, portability,
+;;; and simplicity as can be achieved.
+;;;
+;;; I use the dreaded call/cc to do local aborts. A good compiler could
+;;; handle this with extreme efficiency. An implementation that provides
+;;; a one-shot, non-persistent continuation grabber could help the compiler
+;;; out by using that in place of the call/cc's in these routines.
+;;;
+;;; These functions have funky definitions that are precisely tuned to
+;;; the needs of the fold/map procs -- for example, to minimize the number
+;;; of times the argument lists need to be examined.
+
+;;; Return (map cdr lists).
+;;; However, if any element of LISTS is empty, just abort and return '().
+(define (%cdrs lists)
+ (call-with-current-continuation
+ (lambda (abort)
+ (let recur ((lists lists))
+ (if (pair? lists)
+ (let ((lis (car lists)))
+ (if (null-list? lis) (abort '())
+ (cons (cdr lis) (recur (cdr lists)))))
+ '())))))
+
+(define (%cars+ lists last-elt) ; (append! (map car lists) (list last-elt))
+ (let recur ((lists lists))
+ (if (pair? lists) (cons (caar lists) (recur (cdr lists))) (list last-elt))))
+
+;;; LISTS is a (not very long) non-empty list of lists.
+;;; Return two lists: the cars & the cdrs of the lists.
+;;; However, if any of the lists is empty, just abort and return [() ()].
+
+(define (%cars+cdrs lists)
+ (call-with-current-continuation
+ (lambda (abort)
+ (let recur ((lists lists))
+ (if (pair? lists)
+ (receive (list other-lists) (car+cdr lists)
+ (if (null-list? list) (abort '() '()) ; LIST is empty -- bail out
+ (receive (a d) (car+cdr list)
+ (receive (cars cdrs) (recur other-lists)
+ (values (cons a cars) (cons d cdrs))))))
+ (values '() '()))))))
+
+;;; Like %CARS+CDRS, but we pass in a final elt tacked onto the end of the
+;;; cars list. What a hack.
+(define (%cars+cdrs+ lists cars-final)
+ (call-with-current-continuation
+ (lambda (abort)
+ (let recur ((lists lists))
+ (if (pair? lists)
+ (receive (list other-lists) (car+cdr lists)
+ (if (null-list? list) (abort '() '()) ; LIST is empty -- bail out
+ (receive (a d) (car+cdr list)
+ (receive (cars cdrs) (recur other-lists)
+ (values (cons a cars) (cons d cdrs))))))
+ (values (list cars-final) '()))))))
+
+;;; Like %CARS+CDRS, but blow up if any list is empty.
+(define (%cars+cdrs/no-test lists)
+ (let recur ((lists lists))
+ (if (pair? lists)
+ (receive (list other-lists) (car+cdr lists)
+ (receive (a d) (car+cdr list)
+ (receive (cars cdrs) (recur other-lists)
+ (values (cons a cars) (cons d cdrs)))))
+ (values '() '()))))
+
+
+;;; count
+;;;;;;;;;
+(define (count pred list1 . lists)
+ (check-arg procedure? pred count)
+ (if (pair? lists)
+
+ ;; N-ary case
+ (let lp ((list1 list1) (lists lists) (i 0))
+ (if (null-list? list1) i
+ (receive (as ds) (%cars+cdrs lists)
+ (if (null? as) i
+ (lp (cdr list1) ds
+ (if (apply pred (car list1) as) (+ i 1) i))))))
+
+ ;; Fast path
+ (let lp ((lis list1) (i 0))
+ (if (null-list? lis) i
+ (lp (cdr lis) (if (pred (car lis)) (+ i 1) i))))))
+
+
+;;; fold/unfold
+;;;;;;;;;;;;;;;
+
+(define (unfold-right p f g seed . maybe-tail)
+ (check-arg procedure? p unfold-right)
+ (check-arg procedure? f unfold-right)
+ (check-arg procedure? g unfold-right)
+ (let lp ((seed seed) (ans (#\:optional maybe-tail '())))
+ (if (p seed) ans
+ (lp (g seed)
+ (cons (f seed) ans)))))
+
+
+(define (unfold p f g seed . maybe-tail-gen)
+ (check-arg procedure? p unfold)
+ (check-arg procedure? f unfold)
+ (check-arg procedure? g unfold)
+ (if (pair? maybe-tail-gen)
+
+ (let ((tail-gen (car maybe-tail-gen)))
+ (if (pair? (cdr maybe-tail-gen))
+ (apply error "Too many arguments" unfold p f g seed maybe-tail-gen)
+
+ (let recur ((seed seed))
+ (if (p seed) (tail-gen seed)
+ (cons (f seed) (recur (g seed)))))))
+
+ (let recur ((seed seed))
+ (if (p seed) '()
+ (cons (f seed) (recur (g seed)))))))
+
+
+(define (fold kons knil lis1 . lists)
+ (check-arg procedure? kons fold)
+ (if (pair? lists)
+ (let lp ((lists (cons lis1 lists)) (ans knil)) ; N-ary case
+ (receive (cars+ans cdrs) (%cars+cdrs+ lists ans)
+ (if (null? cars+ans) ans ; Done.
+ (lp cdrs (apply kons cars+ans)))))
+
+ (let lp ((lis lis1) (ans knil)) ; Fast path
+ (if (null-list? lis) ans
+ (lp (cdr lis) (kons (car lis) ans))))))
+
+
+(define (fold-right kons knil lis1 . lists)
+ (check-arg procedure? kons fold-right)
+ (if (pair? lists)
+ (let recur ((lists (cons lis1 lists))) ; N-ary case
+ (let ((cdrs (%cdrs lists)))
+ (if (null? cdrs) knil
+ (apply kons (%cars+ lists (recur cdrs))))))
+
+ (let recur ((lis lis1)) ; Fast path
+ (if (null-list? lis) knil
+ (let ((head (car lis)))
+ (kons head (recur (cdr lis))))))))
+
+
+(define (pair-fold-right f zero lis1 . lists)
+ (check-arg procedure? f pair-fold-right)
+ (if (pair? lists)
+ (let recur ((lists (cons lis1 lists))) ; N-ary case
+ (let ((cdrs (%cdrs lists)))
+ (if (null? cdrs) zero
+ (apply f (append! lists (list (recur cdrs)))))))
+
+ (let recur ((lis lis1)) ; Fast path
+ (if (null-list? lis) zero (f lis (recur (cdr lis)))))))
+
+(define (pair-fold f zero lis1 . lists)
+ (check-arg procedure? f pair-fold)
+ (if (pair? lists)
+ (let lp ((lists (cons lis1 lists)) (ans zero)) ; N-ary case
+ (let ((tails (%cdrs lists)))
+ (if (null? tails) ans
+ (lp tails (apply f (append! lists (list ans)))))))
+
+ (let lp ((lis lis1) (ans zero))
+ (if (null-list? lis) ans
+ (let ((tail (cdr lis))) ; Grab the cdr now,
+ (lp tail (f lis ans))))))) ; in case F SET-CDR!s LIS.
+
+
+;;; REDUCE and REDUCE-RIGHT only use RIDENTITY in the empty-list case.
+;;; These cannot meaningfully be n-ary.
+
+(define (reduce f ridentity lis)
+ (check-arg procedure? f reduce)
+ (if (null-list? lis) ridentity
+ (fold f (car lis) (cdr lis))))
+
+(define (reduce-right f ridentity lis)
+ (check-arg procedure? f reduce-right)
+ (if (null-list? lis) ridentity
+ (let recur ((head (car lis)) (lis (cdr lis)))
+ (if (pair? lis)
+ (f head (recur (car lis) (cdr lis)))
+ head))))
+
+
+
+;;; Mappers: append-map append-map! pair-for-each map! filter-map map-in-order
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (append-map f lis1 . lists)
+ (really-append-map append-map append f lis1 lists))
+(define (append-map! f lis1 . lists)
+ (really-append-map append-map! append! f lis1 lists))
+
+(define (really-append-map who appender f lis1 lists)
+ (check-arg procedure? f who)
+ (if (pair? lists)
+ (receive (cars cdrs) (%cars+cdrs (cons lis1 lists))
+ (if (null? cars) '()
+ (let recur ((cars cars) (cdrs cdrs))
+ (let ((vals (apply f cars)))
+ (receive (cars2 cdrs2) (%cars+cdrs cdrs)
+ (if (null? cars2) vals
+ (appender vals (recur cars2 cdrs2))))))))
+
+ ;; Fast path
+ (if (null-list? lis1) '()
+ (let recur ((elt (car lis1)) (rest (cdr lis1)))
+ (let ((vals (f elt)))
+ (if (null-list? rest) vals
+ (appender vals (recur (car rest) (cdr rest)))))))))
+
+
+(define (pair-for-each proc lis1 . lists)
+ (check-arg procedure? proc pair-for-each)
+ (if (pair? lists)
+
+ (let lp ((lists (cons lis1 lists)))
+ (let ((tails (%cdrs lists)))
+ (if (pair? tails)
+ (begin (apply proc lists)
+ (lp tails)))))
+
+ ;; Fast path.
+ (let lp ((lis lis1))
+ (if (not (null-list? lis))
+ (let ((tail (cdr lis))) ; Grab the cdr now,
+ (proc lis) ; in case PROC SET-CDR!s LIS.
+ (lp tail))))))
+
+;;; We stop when LIS1 runs out, not when any list runs out.
+(define (map! f lis1 . lists)
+ (check-arg procedure? f map!)
+ (if (pair? lists)
+ (let lp ((lis1 lis1) (lists lists))
+ (if (not (null-list? lis1))
+ (receive (heads tails) (%cars+cdrs/no-test lists)
+ (set-car! lis1 (apply f (car lis1) heads))
+ (lp (cdr lis1) tails))))
+
+ ;; Fast path.
+ (pair-for-each (lambda (pair) (set-car! pair (f (car pair)))) lis1))
+ lis1)
+
+
+;;; Map F across L, and save up all the non-false results.
+(define (filter-map f lis1 . lists)
+ (check-arg procedure? f filter-map)
+ (if (pair? lists)
+ (let recur ((lists (cons lis1 lists)))
+ (receive (cars cdrs) (%cars+cdrs lists)
+ (if (pair? cars)
+ (cond ((apply f cars) => (lambda (x) (cons x (recur cdrs))))
+ (else (recur cdrs))) ; Tail call in this arm.
+ '())))
+
+ ;; Fast path.
+ (let recur ((lis lis1))
+ (if (null-list? lis) lis
+ (let ((tail (recur (cdr lis))))
+ (cond ((f (car lis)) => (lambda (x) (cons x tail)))
+ (else tail)))))))
+
+
+;;; Map F across lists, guaranteeing to go left-to-right.
+;;; NOTE: Some implementations of R5RS MAP are compliant with this spec;
+;;; in which case this procedure may simply be defined as a synonym for MAP.
+
+(define (map-in-order f lis1 . lists)
+ (check-arg procedure? f map-in-order)
+ (if (pair? lists)
+ (let recur ((lists (cons lis1 lists)))
+ (receive (cars cdrs) (%cars+cdrs lists)
+ (if (pair? cars)
+ (let ((x (apply f cars))) ; Do head first,
+ (cons x (recur cdrs))) ; then tail.
+ '())))
+
+ ;; Fast path.
+ (let recur ((lis lis1))
+ (if (null-list? lis) lis
+ (let ((tail (cdr lis))
+ (x (f (car lis)))) ; Do head first,
+ (cons x (recur tail))))))) ; then tail.
+
+
+;;; We extend MAP to handle arguments of unequal length.
+(define map map-in-order)
+
+
+;;; filter, remove, partition
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; FILTER, REMOVE, PARTITION and their destructive counterparts do not
+;;; disorder the elements of their argument.
+
+;; This FILTER shares the longest tail of L that has no deleted elements.
+;; If Scheme had multi-continuation calls, they could be made more efficient.
+
+(define (filter pred lis) ; Sleazing with EQ? makes this
+ (check-arg procedure? pred filter) ; one faster.
+ (let recur ((lis lis))
+ (if (null-list? lis) lis ; Use NOT-PAIR? to handle dotted lists.
+ (let ((head (car lis))
+ (tail (cdr lis)))
+ (if (pred head)
+ (let ((new-tail (recur tail))) ; Replicate the RECUR call so
+ (if (eq? tail new-tail) lis
+ (cons head new-tail)))
+ (recur tail)))))) ; this one can be a tail call.
+
+
+;;; Another version that shares longest tail.
+;(define (filter pred lis)
+; (receive (ans no-del?)
+; ;; (recur l) returns L with (pred x) values filtered.
+; ;; It also returns a flag NO-DEL? if the returned value
+; ;; is EQ? to L, i.e. if it didn't have to delete anything.
+; (let recur ((l l))
+; (if (null-list? l) (values l #t)
+; (let ((x (car l))
+; (tl (cdr l)))
+; (if (pred x)
+; (receive (ans no-del?) (recur tl)
+; (if no-del?
+; (values l #t)
+; (values (cons x ans) #f)))
+; (receive (ans no-del?) (recur tl) ; Delete X.
+; (values ans #f))))))
+; ans))
+
+
+
+;(define (filter! pred lis) ; Things are much simpler
+; (let recur ((lis lis)) ; if you are willing to
+; (if (pair? lis) ; push N stack frames & do N
+; (cond ((pred (car lis)) ; SET-CDR! writes, where N is
+; (set-cdr! lis (recur (cdr lis))); the length of the answer.
+; lis)
+; (else (recur (cdr lis))))
+; lis)))
+
+
+;;; This implementation of FILTER!
+;;; - doesn't cons, and uses no stack;
+;;; - is careful not to do redundant SET-CDR! writes, as writes to memory are
+;;; usually expensive on modern machines, and can be extremely expensive on
+;;; modern Schemes (e.g., ones that have generational GC's).
+;;; It just zips down contiguous runs of in and out elts in LIS doing the
+;;; minimal number of SET-CDR!s to splice the tail of one run of ins to the
+;;; beginning of the next.
+
+(define (filter! pred lis)
+ (check-arg procedure? pred filter!)
+ (let lp ((ans lis))
+ (cond ((null-list? ans) ans) ; Scan looking for
+ ((not (pred (car ans))) (lp (cdr ans))) ; first cons of result.
+
+ ;; ANS is the eventual answer.
+ ;; SCAN-IN: (CDR PREV) = LIS and (CAR PREV) satisfies PRED.
+ ;; Scan over a contiguous segment of the list that
+ ;; satisfies PRED.
+ ;; SCAN-OUT: (CAR PREV) satisfies PRED. Scan over a contiguous
+ ;; segment of the list that *doesn't* satisfy PRED.
+ ;; When the segment ends, patch in a link from PREV
+ ;; to the start of the next good segment, and jump to
+ ;; SCAN-IN.
+ (else (letrec ((scan-in (lambda (prev lis)
+ (if (pair? lis)
+ (if (pred (car lis))
+ (scan-in lis (cdr lis))
+ (scan-out prev (cdr lis))))))
+ (scan-out (lambda (prev lis)
+ (let lp ((lis lis))
+ (if (pair? lis)
+ (if (pred (car lis))
+ (begin (set-cdr! prev lis)
+ (scan-in lis (cdr lis)))
+ (lp (cdr lis)))
+ (set-cdr! prev lis))))))
+ (scan-in ans (cdr ans))
+ ans)))))
+
+
+
+;;; Answers share common tail with LIS where possible;
+;;; the technique is slightly subtle.
+
+(define (partition pred lis)
+ (check-arg procedure? pred partition)
+ (let recur ((lis lis))
+ (if (null-list? lis) (values lis lis) ; Use NOT-PAIR? to handle dotted lists.
+ (let ((elt (car lis))
+ (tail (cdr lis)))
+ (receive (in out) (recur tail)
+ (if (pred elt)
+ (values (if (pair? out) (cons elt in) lis) out)
+ (values in (if (pair? in) (cons elt out) lis))))))))
+
+
+
+;(define (partition! pred lis) ; Things are much simpler
+; (let recur ((lis lis)) ; if you are willing to
+; (if (null-list? lis) (values lis lis) ; push N stack frames & do N
+; (let ((elt (car lis))) ; SET-CDR! writes, where N is
+; (receive (in out) (recur (cdr lis)) ; the length of LIS.
+; (cond ((pred elt)
+; (set-cdr! lis in)
+; (values lis out))
+; (else (set-cdr! lis out)
+; (values in lis))))))))
+
+
+;;; This implementation of PARTITION!
+;;; - doesn't cons, and uses no stack;
+;;; - is careful not to do redundant SET-CDR! writes, as writes to memory are
+;;; usually expensive on modern machines, and can be extremely expensive on
+;;; modern Schemes (e.g., ones that have generational GC's).
+;;; It just zips down contiguous runs of in and out elts in LIS doing the
+;;; minimal number of SET-CDR!s to splice these runs together into the result
+;;; lists.
+
+(define (partition! pred lis)
+ (check-arg procedure? pred partition!)
+ (if (null-list? lis) (values lis lis)
+
+ ;; This pair of loops zips down contiguous in & out runs of the
+ ;; list, splicing the runs together. The invariants are
+ ;; SCAN-IN: (cdr in-prev) = LIS.
+ ;; SCAN-OUT: (cdr out-prev) = LIS.
+ (letrec ((scan-in (lambda (in-prev out-prev lis)
+ (let lp ((in-prev in-prev) (lis lis))
+ (if (pair? lis)
+ (if (pred (car lis))
+ (lp lis (cdr lis))
+ (begin (set-cdr! out-prev lis)
+ (scan-out in-prev lis (cdr lis))))
+ (set-cdr! out-prev lis))))) ; Done.
+
+ (scan-out (lambda (in-prev out-prev lis)
+ (let lp ((out-prev out-prev) (lis lis))
+ (if (pair? lis)
+ (if (pred (car lis))
+ (begin (set-cdr! in-prev lis)
+ (scan-in lis out-prev (cdr lis)))
+ (lp lis (cdr lis)))
+ (set-cdr! in-prev lis)))))) ; Done.
+
+ ;; Crank up the scan&splice loops.
+ (if (pred (car lis))
+ ;; LIS begins in-list. Search for out-list's first pair.
+ (let lp ((prev-l lis) (l (cdr lis)))
+ (cond ((not (pair? l)) (values lis l))
+ ((pred (car l)) (lp l (cdr l)))
+ (else (scan-out prev-l l (cdr l))
+ (values lis l)))) ; Done.
+
+ ;; LIS begins out-list. Search for in-list's first pair.
+ (let lp ((prev-l lis) (l (cdr lis)))
+ (cond ((not (pair? l)) (values l lis))
+ ((pred (car l))
+ (scan-in l prev-l (cdr l))
+ (values l lis)) ; Done.
+ (else (lp l (cdr l)))))))))
+
+
+;;; Inline us, please.
+(define (remove pred l) (filter (lambda (x) (not (pred x))) l))
+(define (remove! pred l) (filter! (lambda (x) (not (pred x))) l))
+
+
+
+;;; Here's the taxonomy for the DELETE/ASSOC/MEMBER functions.
+;;; (I don't actually think these are the world's most important
+;;; functions -- the procedural FILTER/REMOVE/FIND/FIND-TAIL variants
+;;; are far more general.)
+;;;
+;;; Function Action
+;;; ---------------------------------------------------------------------------
+;;; remove pred lis Delete by general predicate
+;;; delete x lis [=] Delete by element comparison
+;;;
+;;; find pred lis Search by general predicate
+;;; find-tail pred lis Search by general predicate
+;;; member x lis [=] Search by element comparison
+;;;
+;;; assoc key lis [=] Search alist by key comparison
+;;; alist-delete key alist [=] Alist-delete by key comparison
+
+(define (delete x lis . maybe-=)
+ (let ((= (#\:optional maybe-= equal?)))
+ (filter (lambda (y) (not (= x y))) lis)))
+
+(define (delete! x lis . maybe-=)
+ (let ((= (#\:optional maybe-= equal?)))
+ (filter! (lambda (y) (not (= x y))) lis)))
+
+;;; Extended from R4RS to take an optional comparison argument.
+(define (member x lis . maybe-=)
+ (let ((= (#\:optional maybe-= equal?)))
+ (find-tail (lambda (y) (= x y)) lis)))
+
+;;; R4RS, hence we don't bother to define.
+;;; The MEMBER and then FIND-TAIL call should definitely
+;;; be inlined for MEMQ & MEMV.
+;(define (memq x lis) (member x lis eq?))
+;(define (memv x lis) (member x lis eqv?))
+
+
+;;; right-duplicate deletion
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; delete-duplicates delete-duplicates!
+;;;
+;;; Beware -- these are N^2 algorithms. To efficiently remove duplicates
+;;; in long lists, sort the list to bring duplicates together, then use a
+;;; linear-time algorithm to kill the dups. Or use an algorithm based on
+;;; element-marking. The former gives you O(n lg n), the latter is linear.
+
+(define (delete-duplicates lis . maybe-=)
+ (let ((elt= (#\:optional maybe-= equal?)))
+ (check-arg procedure? elt= delete-duplicates)
+ (let recur ((lis lis))
+ (if (null-list? lis) lis
+ (let* ((x (car lis))
+ (tail (cdr lis))
+ (new-tail (recur (delete x tail elt=))))
+ (if (eq? tail new-tail) lis (cons x new-tail)))))))
+
+(define (delete-duplicates! lis maybe-=)
+ (let ((elt= (#\:optional maybe-= equal?)))
+ (check-arg procedure? elt= delete-duplicates!)
+ (let recur ((lis lis))
+ (if (null-list? lis) lis
+ (let* ((x (car lis))
+ (tail (cdr lis))
+ (new-tail (recur (delete! x tail elt=))))
+ (if (eq? tail new-tail) lis (cons x new-tail)))))))
+
+
+;;; alist stuff
+;;;;;;;;;;;;;;;
+
+;;; Extended from R4RS to take an optional comparison argument.
+(define (assoc x lis . maybe-=)
+ (let ((= (#\:optional maybe-= equal?)))
+ (find (lambda (entry) (= x (car entry))) lis)))
+
+(define (alist-cons key datum alist) (cons (cons key datum) alist))
+
+(define (alist-copy alist)
+ (map (lambda (elt) (cons (car elt) (cdr elt)))
+ alist))
+
+(define (alist-delete key alist . maybe-=)
+ (let ((= (#\:optional maybe-= equal?)))
+ (filter (lambda (elt) (not (= key (car elt)))) alist)))
+
+(define (alist-delete! key alist . maybe-=)
+ (let ((= (#\:optional maybe-= equal?)))
+ (filter! (lambda (elt) (not (= key (car elt)))) alist)))
+
+
+;;; find find-tail take-while drop-while span break any every list-index
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (find pred list)
+ (cond ((find-tail pred list) => car)
+ (else #f)))
+
+(define (find-tail pred list)
+ (check-arg procedure? pred find-tail)
+ (let lp ((list list))
+ (and (not (null-list? list))
+ (if (pred (car list)) list
+ (lp (cdr list))))))
+
+(define (take-while pred lis)
+ (check-arg procedure? pred take-while)
+ (let recur ((lis lis))
+ (if (null-list? lis) '()
+ (let ((x (car lis)))
+ (if (pred x)
+ (cons x (recur (cdr lis)))
+ '())))))
+
+(define (drop-while pred lis)
+ (check-arg procedure? pred drop-while)
+ (let lp ((lis lis))
+ (if (null-list? lis) '()
+ (if (pred (car lis))
+ (lp (cdr lis))
+ lis))))
+
+(define (take-while! pred lis)
+ (check-arg procedure? pred take-while!)
+ (if (or (null-list? lis) (not (pred (car lis)))) '()
+ (begin (let lp ((prev lis) (rest (cdr lis)))
+ (if (pair? rest)
+ (let ((x (car rest)))
+ (if (pred x) (lp rest (cdr rest))
+ (set-cdr! prev '())))))
+ lis)))
+
+(define (span pred lis)
+ (check-arg procedure? pred span)
+ (let recur ((lis lis))
+ (if (null-list? lis) (values '() '())
+ (let ((x (car lis)))
+ (if (pred x)
+ (receive (prefix suffix) (recur (cdr lis))
+ (values (cons x prefix) suffix))
+ (values '() lis))))))
+
+(define (span! pred lis)
+ (check-arg procedure? pred span!)
+ (if (or (null-list? lis) (not (pred (car lis)))) (values '() lis)
+ (let ((suffix (let lp ((prev lis) (rest (cdr lis)))
+ (if (null-list? rest) rest
+ (let ((x (car rest)))
+ (if (pred x) (lp rest (cdr rest))
+ (begin (set-cdr! prev '())
+ rest)))))))
+ (values lis suffix))))
+
+
+(define (break pred lis) (span (lambda (x) (not (pred x))) lis))
+(define (break! pred lis) (span! (lambda (x) (not (pred x))) lis))
+
+(define (any pred lis1 . lists)
+ (check-arg procedure? pred any)
+ (if (pair? lists)
+
+ ;; N-ary case
+ (receive (heads tails) (%cars+cdrs (cons lis1 lists))
+ (and (pair? heads)
+ (let lp ((heads heads) (tails tails))
+ (receive (next-heads next-tails) (%cars+cdrs tails)
+ (if (pair? next-heads)
+ (or (apply pred heads) (lp next-heads next-tails))
+ (apply pred heads)))))) ; Last PRED app is tail call.
+
+ ;; Fast path
+ (and (not (null-list? lis1))
+ (let lp ((head (car lis1)) (tail (cdr lis1)))
+ (if (null-list? tail)
+ (pred head) ; Last PRED app is tail call.
+ (or (pred head) (lp (car tail) (cdr tail))))))))
+
+
+;(define (every pred list) ; Simple definition.
+; (let lp ((list list)) ; Doesn't return the last PRED value.
+; (or (not (pair? list))
+; (and (pred (car list))
+; (lp (cdr list))))))
+
+(define (every pred lis1 . lists)
+ (check-arg procedure? pred every)
+ (if (pair? lists)
+
+ ;; N-ary case
+ (receive (heads tails) (%cars+cdrs (cons lis1 lists))
+ (or (not (pair? heads))
+ (let lp ((heads heads) (tails tails))
+ (receive (next-heads next-tails) (%cars+cdrs tails)
+ (if (pair? next-heads)
+ (and (apply pred heads) (lp next-heads next-tails))
+ (apply pred heads)))))) ; Last PRED app is tail call.
+
+ ;; Fast path
+ (or (null-list? lis1)
+ (let lp ((head (car lis1)) (tail (cdr lis1)))
+ (if (null-list? tail)
+ (pred head) ; Last PRED app is tail call.
+ (and (pred head) (lp (car tail) (cdr tail))))))))
+
+(define (list-index pred lis1 . lists)
+ (check-arg procedure? pred list-index)
+ (if (pair? lists)
+
+ ;; N-ary case
+ (let lp ((lists (cons lis1 lists)) (n 0))
+ (receive (heads tails) (%cars+cdrs lists)
+ (and (pair? heads)
+ (if (apply pred heads) n
+ (lp tails (+ n 1))))))
+
+ ;; Fast path
+ (let lp ((lis lis1) (n 0))
+ (and (not (null-list? lis))
+ (if (pred (car lis)) n (lp (cdr lis) (+ n 1)))))))
+
+;;; Reverse
+;;;;;;;;;;;
+
+;R4RS, so not defined here.
+;(define (reverse lis) (fold cons '() lis))
+
+;(define (reverse! lis)
+; (pair-fold (lambda (pair tail) (set-cdr! pair tail) pair) '() lis))
+
+(define (reverse! lis)
+ (let lp ((lis lis) (ans '()))
+ (if (null-list? lis) ans
+ (let ((tail (cdr lis)))
+ (set-cdr! lis ans)
+ (lp tail lis)))))
+
+;;; Lists-as-sets
+;;;;;;;;;;;;;;;;;
+
+;;; This is carefully tuned code; do not modify casually.
+;;; - It is careful to share storage when possible;
+;;; - Side-effecting code tries not to perform redundant writes.
+;;; - It tries to avoid linear-time scans in special cases where constant-time
+;;; computations can be performed.
+;;; - It relies on similar properties from the other list-lib procs it calls.
+;;; For example, it uses the fact that the implementations of MEMBER and
+;;; FILTER in this source code share longest common tails between args
+;;; and results to get structure sharing in the lset procedures.
+
+(define (%lset2<= = lis1 lis2) (every (lambda (x) (member x lis2 =)) lis1))
+
+(define (lset<= = . lists)
+ (check-arg procedure? = lset<=)
+ (or (not (pair? lists)) ; 0-ary case
+ (let lp ((s1 (car lists)) (rest (cdr lists)))
+ (or (not (pair? rest))
+ (let ((s2 (car rest)) (rest (cdr rest)))
+ (and (or (eq? s2 s1) ; Fast path
+ (%lset2<= = s1 s2)) ; Real test
+ (lp s2 rest)))))))
+
+(define (lset= = . lists)
+ (check-arg procedure? = lset=)
+ (or (not (pair? lists)) ; 0-ary case
+ (let lp ((s1 (car lists)) (rest (cdr lists)))
+ (or (not (pair? rest))
+ (let ((s2 (car rest))
+ (rest (cdr rest)))
+ (and (or (eq? s1 s2) ; Fast path
+ (and (%lset2<= = s1 s2) (%lset2<= = s2 s1))) ; Real test
+ (lp s2 rest)))))))
+
+
+(define (lset-adjoin = lis . elts)
+ (check-arg procedure? = lset-adjoin)
+ (fold (lambda (elt ans) (if (member elt ans =) ans (cons elt ans)))
+ lis elts))
+
+
+(define (lset-union = . lists)
+ (check-arg procedure? = lset-union)
+ (reduce (lambda (lis ans) ; Compute ANS + LIS.
+ (cond ((null? lis) ans) ; Don't copy any lists
+ ((null? ans) lis) ; if we don't have to.
+ ((eq? lis ans) ans)
+ (else
+ (fold (lambda (elt ans) (if (any (lambda (x) (= x elt)) ans)
+ ans
+ (cons elt ans)))
+ ans lis))))
+ '() lists))
+
+(define (lset-union! = . lists)
+ (check-arg procedure? = lset-union!)
+ (reduce (lambda (lis ans) ; Splice new elts of LIS onto the front of ANS.
+ (cond ((null? lis) ans) ; Don't copy any lists
+ ((null? ans) lis) ; if we don't have to.
+ ((eq? lis ans) ans)
+ (else
+ (pair-fold (lambda (pair ans)
+ (let ((elt (car pair)))
+ (if (any (lambda (x) (= x elt)) ans)
+ ans
+ (begin (set-cdr! pair ans) pair))))
+ ans lis))))
+ '() lists))
+
+
+(define (lset-intersection = lis1 . lists)
+ (check-arg procedure? = lset-intersection)
+ (let ((lists (delete lis1 lists eq?))) ; Throw out any LIS1 vals.
+ (cond ((any null-list? lists) '()) ; Short cut
+ ((null? lists) lis1) ; Short cut
+ (else (filter (lambda (x)
+ (every (lambda (lis) (member x lis =)) lists))
+ lis1)))))
+
+(define (lset-intersection! = lis1 . lists)
+ (check-arg procedure? = lset-intersection!)
+ (let ((lists (delete lis1 lists eq?))) ; Throw out any LIS1 vals.
+ (cond ((any null-list? lists) '()) ; Short cut
+ ((null? lists) lis1) ; Short cut
+ (else (filter! (lambda (x)
+ (every (lambda (lis) (member x lis =)) lists))
+ lis1)))))
+
+
+(define (lset-difference = lis1 . lists)
+ (check-arg procedure? = lset-difference)
+ (let ((lists (filter pair? lists))) ; Throw out empty lists.
+ (cond ((null? lists) lis1) ; Short cut
+ ((memq lis1 lists) '()) ; Short cut
+ (else (filter (lambda (x)
+ (every (lambda (lis) (not (member x lis =)))
+ lists))
+ lis1)))))
+
+(define (lset-difference! = lis1 . lists)
+ (check-arg procedure? = lset-difference!)
+ (let ((lists (filter pair? lists))) ; Throw out empty lists.
+ (cond ((null? lists) lis1) ; Short cut
+ ((memq lis1 lists) '()) ; Short cut
+ (else (filter! (lambda (x)
+ (every (lambda (lis) (not (member x lis =)))
+ lists))
+ lis1)))))
+
+
+(define (lset-xor = . lists)
+ (check-arg procedure? = lset-xor)
+ (reduce (lambda (b a) ; Compute A xor B:
+ ;; Note that this code relies on the constant-time
+ ;; short-cuts provided by LSET-DIFF+INTERSECTION,
+ ;; LSET-DIFFERENCE & APPEND to provide constant-time short
+ ;; cuts for the cases A = (), B = (), and A eq? B. It takes
+ ;; a careful case analysis to see it, but it's carefully
+ ;; built in.
+
+ ;; Compute a-b and a^b, then compute b-(a^b) and
+ ;; cons it onto the front of a-b.
+ (receive (a-b a-int-b) (lset-diff+intersection = a b)
+ (cond ((null? a-b) (lset-difference = b a))
+ ((null? a-int-b) (append b a))
+ (else (fold (lambda (xb ans)
+ (if (member xb a-int-b =) ans (cons xb ans)))
+ a-b
+ b)))))
+ '() lists))
+
+
+(define (lset-xor! = . lists)
+ (check-arg procedure? = lset-xor!)
+ (reduce (lambda (b a) ; Compute A xor B:
+ ;; Note that this code relies on the constant-time
+ ;; short-cuts provided by LSET-DIFF+INTERSECTION,
+ ;; LSET-DIFFERENCE & APPEND to provide constant-time short
+ ;; cuts for the cases A = (), B = (), and A eq? B. It takes
+ ;; a careful case analysis to see it, but it's carefully
+ ;; built in.
+
+ ;; Compute a-b and a^b, then compute b-(a^b) and
+ ;; cons it onto the front of a-b.
+ (receive (a-b a-int-b) (lset-diff+intersection! = a b)
+ (cond ((null? a-b) (lset-difference! = b a))
+ ((null? a-int-b) (append! b a))
+ (else (pair-fold (lambda (b-pair ans)
+ (if (member (car b-pair) a-int-b =) ans
+ (begin (set-cdr! b-pair ans) b-pair)))
+ a-b
+ b)))))
+ '() lists))
+
+
+(define (lset-diff+intersection = lis1 . lists)
+ (check-arg procedure? = lset-diff+intersection)
+ (cond ((every null-list? lists) (values lis1 '())) ; Short cut
+ ((memq lis1 lists) (values '() lis1)) ; Short cut
+ (else (partition (lambda (elt)
+ (not (any (lambda (lis) (member elt lis =))
+ lists)))
+ lis1))))
+
+(define (lset-diff+intersection! = lis1 . lists)
+ (check-arg procedure? = lset-diff+intersection!)
+ (cond ((every null-list? lists) (values lis1 '())) ; Short cut
+ ((memq lis1 lists) (values '() lis1)) ; Short cut
+ (else (partition! (lambda (elt)
+ (not (any (lambda (lis) (member elt lis =))
+ lists)))
+ lis1))))
+;;; Copyright (C) John Cowan 2013. All Rights Reserved.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+(define-library (srfi 111)
+ (export box box? unbox set-box!)
+ (import (scheme base))
+ (begin
+ (define-record-type <box>
+ (box value)
+ box?
+ (value unbox set-box!))))
+;; Copyright (C) Taylan Ulrich Bayırlı/Kammer (2015). All Rights Reserved.
+
+;; Permission is hereby granted, free of charge, to any person obtaining
+;; a copy of this software and associated documentation files (the
+;; "Software"), to deal in the Software without restriction, including
+;; without limitation the rights to use, copy, modify, merge, publish,
+;; distribute, sublicense, and/or sell copies of the Software, and to
+;; permit persons to whom the Software is furnished to do so, subject to
+;; the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+(define-library (srfi 17)
+ (export set! setter getter-with-setter)
+ (import
+ (rename (scheme base) (set! %set!))
+ (srfi 1))
+ (begin
+
+ (define-syntax set!
+ (syntax-rules ()
+ ((_ (getter arg ...) val)
+ ((setter getter) arg ... val))
+ ((_ var val)
+ (%set! var val))))
+
+ (define setter
+ (let ((setters `((,car . ,set-car!)
+ (,cdr . ,set-cdr!)
+ (,caar . ,(lambda (p v) (set-car! (car p) v)))
+ (,cadr . ,(lambda (p v) (set-car! (cdr p) v)))
+ (,cdar . ,(lambda (p v) (set-cdr! (car p) v)))
+ (,cddr . ,(lambda (p v) (set-cdr! (cdr p) v)))
+ (,list-ref . ,list-set!)
+ (,vector-ref . ,vector-set!)
+ (,string-ref . ,string-set!)
+ (,bytevector-u8-ref . ,bytevector-u8-set!))))
+ (letrec ((setter
+ (lambda (proc)
+ (let ((probe (assv proc setters)))
+ (if probe
+ (cdr probe)
+ (error "No setter for " proc)))))
+ (set-setter!
+ (lambda (proc setter)
+ (set! setters (cons (cons proc setter) setters)))))
+ (set-setter! setter set-setter!)
+ setter)))
+
+ (define (getter-with-setter get set)
+ (let ((proc (lambda args (apply get args))))
+ (set! (setter proc) set)
+ proc))
+
+ ))
+;; Copyright (C) Taylan Ulrich Bayırlı/Kammer (2015). All Rights Reserved.
+
+;; Permission is hereby granted, free of charge, to any person obtaining
+;; a copy of this software and associated documentation files (the
+;; "Software"), to deal in the Software without restriction, including
+;; without limitation the rights to use, copy, modify, merge, publish,
+;; distribute, sublicense, and/or sell copies of the Software, and to
+;; permit persons to whom the Software is furnished to do so, subject to
+;; the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+;;; The SRFI claims that having the same variable appear multiple times is an
+;;; error in let* and so also in and-let*. In fact let* allows rebinding the
+;;; same variable, so we also allow it here.
+
+(define-library (srfi 2)
+ (export and-let*)
+ (import (scheme base))
+ (begin
+ (define-syntax and-let*
+ (syntax-rules ()
+
+ ;; Handle zero-clauses special-case.
+ ((_ () . body)
+ (begin #t . body))
+
+ ;; Reduce clauses down to one regardless of body.
+ ((_ ((var expr) rest . rest*) . body)
+ (let ((var expr))
+ (and var (and-let* (rest . rest*) . body))))
+ ((_ ((expr) rest . rest*) . body)
+ (and expr (and-let* (rest . rest*) . body)))
+ ((_ (var rest . rest*) . body)
+ (begin
+ (let ((var #f)) #f) ;(identifier? var)
+ (and var (and-let* (rest . rest*) . body))))
+
+ ;; Handle 1-clause cases without a body.
+ ((_ ((var expr)))
+ expr)
+ ((_ ((expr)))
+ expr)
+ ((_ (var))
+ (begin
+ (let ((var #f)) #f) ;(identifier? var)
+ var))
+
+ ;; Handle 1-clause cases with a body.
+ ((_ ((var expr)) . body)
+ (let ((var expr))
+ (and var (begin . body))))
+ ((_ ((expr)) . body)
+ (and expr (begin . body)))
+ ((_ (var) . body)
+ (begin
+ (let ((var #f)) #f) ;(identifier? var)
+ (and var (begin . body))))))))
+;;; Copyright (C) André van Tonder (2004). All Rights Reserved.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+;============================================================================================
+; IMPLEMENTATION:
+;
+; Andre van Tonder, 2004.
+;
+;============================================================================================
+
+(define-syntax define-record-type
+ (syntax-rules ()
+ ((define-record-type . body)
+ (parse-declaration #f . body))))
+
+(define-syntax define-record-scheme
+ (syntax-rules ()
+ ((define-record-scheme . body)
+ (parse-declaration #t . body))))
+
+(define-syntax parse-declaration
+ (syntax-rules ()
+ ((parse-declaration is-scheme? (name super ...) constructor-clause predicate field-clause ...)
+ (build-record 0 constructor-clause (super ...) (field-clause ...) name predicate is-scheme?))
+ ((parse-declaration is-scheme? (name super ...) constructor-clause)
+ (parse-declaration is-scheme? (name super ...) constructor-clause #f))
+ ((parse-declaration is-scheme? (name super ...))
+ (parse-declaration is-scheme? (name super ...) #f #f))
+ ((parse-declaration is-scheme? name . rest)
+ (parse-declaration is-scheme? (name) . rest))))
+
+(define-syntax record-update!
+ (syntax-rules ()
+ ((record-update! record name (label exp) ...)
+ (meta
+ `(let ((r record))
+ ((meta ,(name ("setter") label)) r exp)
+ ...
+ r)))))
+
+(define-syntax record-update
+ (syntax-rules ()
+ ((record-update record name (label exp) ...)
+ (name ("is-scheme?")
+ (meta
+ `(let ((new ((meta ,(name ("copier"))) record)))
+ (record-update! new name (label exp) ...)))
+ (record-compose (name record) (name (label exp) ...))))))
+
+(define-syntax record-compose
+ (syntax-rules ()
+ ((record-compose (export-name (label exp) ...))
+ (export-name (label exp) ...))
+ ((record-compose (import-name record) ... (export-name (label exp) ...))
+ (help-compose 1 (import-name record) ... (export-name (label exp) ...)))))
+
+(define-syntax help-compose
+ (syntax-rules ()
+ ((help-compose 1 (import-name record) import ... (export-name (label exp) ...))
+ (meta
+ `(help-compose 2
+ (meta ,(intersection
+ (meta ,(export-name ("labels")))
+ (meta ,(remove-from (meta ,(import-name ("labels")))
+ (label ...)
+ if-free=))
+ if-free=))
+ (import-name record)
+ import ...
+ (export-name (label exp) ...))))
+ ((help-compose 2 (copy-label ...) (import-name record) import ... (export-name . bindings))
+ (meta
+ `(let ((r record))
+ (record-compose import ...
+ (export-name (copy-label ((meta ,(import-name ("getter") copy-label)) r))
+ ...
+ . bindings)))))))
+
+(define-syntax build-record
+ (syntax-rules ()
+ ((build-record 0 (constructor . pos-labels) . rest) ; extract positional labels from constructor clause
+ (build-record 1 (constructor . pos-labels) pos-labels . rest)) ;
+ ((build-record 0 constructor . rest) ;
+ (build-record 1 (constructor . #f) () . rest)) ;
+ ((build-record 1 constructor-clause (pos-label ...) (super ...)
+ ((label . accessors) ...) . rest)
+ (meta
+ `(build-record 2
+ constructor-clause
+ (meta ,(union (meta ,(super ("labels"))) ; compute union of labels from supers,
+ ... ; constructor clause and field clauses
+ (pos-label ...)
+ (label ...)
+ top:if-free=))
+ ((label . accessors) ...)
+ (meta ,(union (meta ,(super ("supers"))) ; compute transitive union of supers
+ ...
+ top:if-free=))
+ . rest)))
+ ((build-record 2 (constructor . pos-labels) labels . rest) ; insert default constructor labels if not given
+ (syntax-if pos-labels
+ (build-record 3 (constructor . pos-labels) labels . rest)
+ (build-record 3 (constructor . labels) labels . rest)))
+ ((build-record 3 constructor-clause labels ((label . accessors) ...) . rest)
+ (meta
+ `(build-record 4
+ (meta ,(remove-from labels ; separate the labels that do not appear in a
+ (label ...) ; field clause for next step
+ top:if-free=))
+ ((label . accessors) ...)
+ constructor-clause
+ labels
+ . rest)))
+ ((build-record 4
+ (undeclared-label ...)
+ (field-clause ...)
+ (constructor . pos-labels)
+ labels
+ supers
+ name
+ predicate
+ is-scheme?)
+ (meta
+ `(build-record 5 ; generate identifiers for constructor, predicate
+ is-scheme? ; getters and setters as needed
+ name
+ supers
+ supers
+ labels
+ (meta ,(to-identifier constructor))
+ (meta ,(add-temporaries pos-labels)) ; needed for constructor below
+ (meta ,(to-identifier predicate))
+ (meta ,(augment-field field-clause))
+ ...
+ (undeclared-label (meta ,(generate-identifier))
+ (meta ,(generate-identifier)))
+ ...)))
+ ((build-record 5
+ is-scheme?
+ name
+ (super ...)
+ supers
+ (label ...)
+ constructor
+ ((pos-label pos-temp) ...)
+ predicate
+ (field-label getter setter)
+ ...)
+
+ (begin
+ (syntax-if is-scheme?
+
+ (begin
+ (define-generic (predicate x) (lambda (x) #f))
+ (define-generic (getter x))
+ ...
+ (define-generic (setter x v))
+ ...
+ (define-generic (copy x)))
+
+ (begin
+ (srfi-9:define-record-type internal-name
+ (maker field-label ...)
+ predicate
+ (field-label getter setter) ...)
+
+ (define constructor
+ (lambda (pos-temp ...)
+ (populate 1 maker (field-label ...) (pos-label pos-temp) ...)))
+
+ (extend-predicates supers predicate)
+ (extend-accessors supers field-label predicate getter setter)
+ ...
+
+ (define (copy x)
+ (maker (getter x) ...))
+ (extend-copiers supers copy predicate)
+
+ (define-method (show (r predicate))
+ (list 'name
+ (list 'field-label (getter r))
+ ...))))
+
+ (define-syntax name
+ (syntax-rules (field-label ...)
+ ((name ("is-scheme?") sk fk) (syntax-if is-scheme? sk fk))
+ ((name ("predicate") k) (syntax-apply k predicate))
+ ((name ("supers") k) (syntax-apply k (super ... name)))
+ ((name ("labels") k) (syntax-apply k (label ...)))
+ ((name ("pos-labels") k) (syntax-apply k (pos-label ...)))
+ ((name ("getter") field-label k) (syntax-apply k getter))
+ ...
+ ((name ("getter") other k) (syntax-apply k #f))
+ ((name ("setter") field-label k) (syntax-apply k setter))
+ ...
+ ((name ("setter") other k) (syntax-apply k #f))
+ ((name ("copier") k) (syntax-apply k copy))
+ ((name . bindings) (populate 1 maker (field-label ...) . bindings))))))))
+
+
+(define-syntax to-identifier
+ (syntax-rules ()
+ ((to-identifier #f k) (syntax-apply k generated-identifier))
+ ((to-identifier id k) (syntax-apply k id))))
+
+(define-syntax augment-field
+ (syntax-rules ()
+ ((augment-field (label) k) (syntax-apply k (label generated-getter generated-setter)))
+ ((augment-field (label getter) k) (meta `(label (meta ,(to-identifier getter)) generated-setter) k))
+ ((augment-field (label getter setter) k) (meta `(label (meta ,(to-identifier getter))
+ (meta ,(to-identifier setter))) k))))
+
+(define-syntax extend-predicates
+ (syntax-rules ()
+ ((extend-predicates (super ...) predicate)
+ (begin
+ (meta
+ `(define-method (meta ,(super ("predicate")))
+ (predicate)
+ (x)
+ any?))
+ ...))))
+
+(define-syntax extend-copiers
+ (syntax-rules ()
+ ((extend-copiers (super ...) copy predicate)
+ (begin
+ (meta
+ `(define-method (meta ,(super ("copier")))
+ (predicate)
+ (x)
+ copy))
+ ...))))
+
+(define-syntax extend-accessors
+ (syntax-rules ()
+ ((extend-accessors (super ...) label predicate selector modifier)
+ (meta
+ `(begin
+ (syntax-if (meta ,(super ("getter") label))
+ (define-method (meta ,(super ("getter") label))
+ (predicate)
+ (x)
+ selector)
+ (begin))
+ ...
+ (syntax-if (meta ,(super ("setter") label))
+ (define-method (meta ,(super ("setter") label))
+ (predicate any?)
+ (x v)
+ modifier)
+ (begin))
+ ...)))))
+
+(define-syntax populate
+ (syntax-rules ()
+ ((populate 1 maker labels . bindings)
+ (meta
+ `(populate 2 maker
+ (meta ,(order labels bindings ('<undefined>))))))
+ ((populate 2 maker ((label exp) ...))
+ (maker exp ...))))
+
+(define-syntax order
+ (syntax-rules ()
+ ((order (label ...) ((label* . binding) ...) default k)
+ (meta
+ `(if-empty? (meta ,(remove-from (label* ...)
+ (label ...)
+ if-free=))
+ (order "emit" (label ...) ((label* . binding) ...) default k)
+ (syntax-error "Illegal labels in" ((label* . binding) ...)
+ "Legal labels are" (label ...)))))
+ ((order "emit" (label ...) bindings default k)
+ (meta
+ `((label . (meta ,(syntax-lookup label
+ bindings
+ if-free=
+ default)))
+ ...)
+ k))))
+
+
+;============================================================================================
+; Simple generic functions:
+
+(define-syntax define-generic
+ (syntax-rules ()
+ ((define-generic (name arg ...))
+ (define-generic (name arg ...)
+ (lambda (arg ...) (error "Inapplicable method:" 'name
+ "Arguments:" (show arg) ... ))))
+ ((define-generic (name arg ...) proc)
+ (define name (make-generic (arg ...) proc)))))
+
+(define-syntax define-method
+ (syntax-rules ()
+ ((define-method (generic (arg pred?) ...) . body)
+ (define-method generic (pred? ...) (arg ...) (lambda (arg ...) . body)))
+ ((define-method generic (pred? ...) (arg ...) procedure)
+ (let ((next ((generic) 'get-proc))
+ (proc procedure))
+ (((generic) 'set-proc)
+ (lambda (arg ...)
+ (if (and (pred? arg) ...)
+ (proc arg ...)
+ (next arg ...))))))))
+
+(define-syntax make-generic
+ (syntax-rules ()
+ ((make-generic (arg arg+ ...) default-proc)
+ (let ((proc default-proc))
+ (case-lambda
+ ((arg arg+ ...)
+ (proc arg arg+ ...))
+ (()
+ (lambda (msg)
+ (case msg
+ ((get-proc) proc)
+ ((set-proc) (lambda (new)
+ (set! proc new)))))))))))
+
+(define-generic (show x)
+ (lambda (x) x))
+
+(define (any? x) #t)
+
+
+;============================================================================================
+; Syntax utilities:
+
+(define-syntax syntax-error
+ (syntax-rules ()))
+
+(define-syntax syntax-apply
+ (syntax-rules ()
+ ((syntax-apply (f . args) exp ...)
+ (f exp ... . args))))
+
+(define-syntax syntax-cons
+ (syntax-rules ()
+ ((syntax-cons x rest k)
+ (syntax-apply k (x . rest)))))
+
+(define-syntax syntax-cons-after
+ (syntax-rules ()
+ ((syntax-cons-after rest x k)
+ (syntax-apply k (x . rest)))))
+
+(define-syntax if-empty?
+ (syntax-rules ()
+ ((if-empty? () sk fk) sk)
+ ((if-empty? (h . t) sk fk) fk)))
+
+(define-syntax add-temporaries
+ (syntax-rules ()
+ ((add-temporaries lst k) (add-temporaries lst () k))
+ ((add-temporaries () lst-temps k) (syntax-apply k lst-temps))
+ ((add-temporaries (h . t) (done ...) k) (add-temporaries t (done ... (h temp)) k))))
+
+(define-syntax if-free=
+ (syntax-rules ()
+ ((if-free= x y kt kf)
+ (let-syntax
+ ((test (syntax-rules (x)
+ ((test x kt* kf*) kt*)
+ ((test z kt* kf*) kf*))))
+ (test y kt kf)))))
+
+(define-syntax top:if-free=
+ (syntax-rules ()
+ ((top:if-free= x y kt kf)
+ (begin
+ (define-syntax if-free=:test
+ (syntax-rules (x)
+ ((if-free=:test x kt* kf*) kt*)
+ ((if-free=:test z kt* kf*) kf*)))
+ (if-free=:test y kt kf)))))
+
+(define-syntax meta
+ (syntax-rules (meta quasiquote unquote)
+ ((meta `(meta ,(function argument ...)) k)
+ (meta `(argument ...) (syntax-apply-to function k)))
+ ((meta `(a . b) k)
+ (meta `a (descend-right b k)))
+ ((meta `whatever k) (syntax-apply k whatever))
+ ((meta `arg)
+ (meta `arg (syntax-id)))))
+
+(define-syntax syntax-apply-to
+ (syntax-rules ()
+ ((syntax-apply-to (argument ...) function k)
+ (function argument ... k))))
+
+(define-syntax descend-right
+ (syntax-rules ()
+ ((descend-right evaled b k)
+ (meta `b (syntax-cons-after evaled k)))))
+
+(define-syntax syntax-id
+ (syntax-rules ()
+ ((syntax-id arg) arg)))
+
+(define-syntax remove-duplicates
+ (syntax-rules ()
+ ((remove-duplicates lst compare? k)
+ (remove-duplicates lst () compare? k))
+ ((remove-duplicates () done compare? k)
+ (syntax-apply k done))
+ ((remove-duplicates (h . t) (d ...) compare? k)
+ (if-member? h (d ...) compare?
+ (remove-duplicates t (d ...) compare? k)
+ (remove-duplicates t (d ... h) compare? k)))))
+
+(define-syntax syntax-filter
+ (syntax-rules ()
+ ((syntax-filter () (if-p? arg ...) k)
+ (syntax-apply k ()))
+ ((syntax-filter (h . t) (if-p? arg ...) k)
+ (if-p? h arg ...
+ (syntax-filter t (if-p? arg ...) (syntax-cons-after h k))
+ (syntax-filter t (if-p? arg ...) k)))))
+
+(define-syntax if-member?
+ (syntax-rules ()
+ ((if-member? x () compare? sk fk)
+ fk)
+ ((if-member? x (h . t) compare? sk fk)
+ (compare? x h
+ sk
+ (if-member? x t compare? sk fk)))))
+
+(define-syntax union
+ (syntax-rules ()
+ ((union (x ...) ... compare? k)
+ (remove-duplicates (x ... ...) compare? k))))
+
+(define-syntax intersection
+ (syntax-rules ()
+ ((intersection list1 list2 compare? k)
+ (syntax-filter list1 (if-member? list2 compare?) k))))
+
+(define-syntax remove-from
+ (syntax-rules ()
+ ((remove-from list1 list2 compare? k)
+ (syntax-filter list1 (if-not-member? list2 compare?) k))))
+
+(define-syntax if-not-member?
+ (syntax-rules ()
+ ((if-not-member? x list compare? sk fk)
+ (if-member? x list compare? fk sk))))
+
+(define-syntax generate-identifier
+ (syntax-rules ()
+ ((generate-identifier k) (syntax-apply k generated-identifier))))
+
+(define-syntax syntax-if
+ (syntax-rules ()
+ ((syntax-if #f sk fk) fk)
+ ((syntax-if other sk fk) sk)))
+
+(define-syntax syntax-lookup
+ (syntax-rules ()
+ ((syntax-lookup label () compare fail k)
+ (syntax-apply k fail))
+ ((syntax-lookup label ((label* . value) . bindings) compare fail k)
+ (compare label label*
+ (syntax-apply k value)
+ (syntax-lookup label bindings compare fail k)))))
+;;; array as-srfi-9-record
+;;; 2001 Jussi Piitulainen
+
+;;; Untested.
+
+(define-record-type
+ array:srfi-9-record-type-descriptor
+ (array:make vec ind shp)
+ array:array?
+ (vec array:vector)
+ (ind array:index)
+ (shp array:shape))
+(define-library (srfi 60)
+ (export
+ ;; Bitwise Operations
+ logand
+ bitwise-and
+ logior
+ bitwise-ior
+ logxor
+ bitwise-xor
+ lognot
+ bitwise-not
+ bitwise-if
+ bitwise-merge
+ logtest
+ any-bits-set?
+
+ ;; Integer Properties
+ logcount
+ bit-count
+ integer-length
+ log2-binary-factors
+ first-set-bit
+
+ ;; Bit Within Word
+ logbit?
+ bit-set?
+ copy-bit
+
+ ;; Field of Bits
+ bit-field
+ copy-bit-field
+ ash
+ arithmetic-shift
+ rotate-bit-field
+ reverse-bit-field
+
+ ;; Bits as Booleans
+ integer->list
+ list->integer
+ booleans->integer
+ )
+ (import (scheme base))
+ (include "60.upstream.scm"))
+;;; Copyright (C) Jussi Piitulainen (2001). All Rights Reserved.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+(define (array-ref a . xs)
+ (or (array:array? a)
+ (error "not an array"))
+ (let ((shape (array:shape a)))
+ (if (null? xs)
+ (array:check-indices "array-ref" xs shape)
+ (let ((x (car xs)))
+ (if (vector? x)
+ (array:check-index-vector "array-ref" x shape)
+ (if (integer? x)
+ (array:check-indices "array-ref" xs shape)
+ (if (array:array? x)
+ (array:check-index-actor "array-ref" x shape)
+ (error "not an index object"))))))
+ (vector-ref
+ (array:vector a)
+ (if (null? xs)
+ (vector-ref (array:index a) 0)
+ (let ((x (car xs)))
+ (if (vector? x)
+ (array:index/vector
+ (quotient (vector-length shape) 2)
+ (array:index a)
+ x)
+ (if (integer? x)
+ (array:vector-index (array:index a) xs)
+ (if (array:array? x)
+ (array:index/array
+ (quotient (vector-length shape) 2)
+ (array:index a)
+ (array:vector x)
+ (array:index x))
+ (error "array-ref: bad index object")))))))))
+
+(define (array-set! a x . xs)
+ (or (array:array? a)
+ (error "array-set!: not an array"))
+ (let ((shape (array:shape a)))
+ (if (null? xs)
+ (array:check-indices "array-set!" '() shape)
+ (if (vector? x)
+ (array:check-index-vector "array-set!" x shape)
+ (if (integer? x)
+ (array:check-indices.o "array-set!" (cons x xs) shape)
+ (if (array:array? x)
+ (array:check-index-actor "array-set!" x shape)
+ (error "not an index object")))))
+ (if (null? xs)
+ (vector-set! (array:vector a) (vector-ref (array:index a) 0) x)
+ (if (vector? x)
+ (vector-set! (array:vector a)
+ (array:index/vector
+ (quotient (vector-length shape) 2)
+ (array:index a)
+ x)
+ (car xs))
+ (if (integer? x)
+ (let ((v (array:vector a))
+ (i (array:index a))
+ (r (quotient (vector-length shape) 2)))
+ (do ((sum (* (vector-ref i 0) x)
+ (+ sum (* (vector-ref i k) (car ks))))
+ (ks xs (cdr ks))
+ (k 1 (+ k 1)))
+ ((= k r)
+ (vector-set! v (+ sum (vector-ref i k)) (car ks)))))
+ (if (array:array? x)
+ (vector-set! (array:vector a)
+ (array:index/array
+ (quotient (vector-length shape) 2)
+ (array:index a)
+ (array:vector x)
+ (array:index x))
+ (car xs))
+ (error (string-append
+ "array-set!: bad index object: "
+ (array:thing->string x)))))))))
+(define-library (srfi 63)
+ (export
+ array?
+ equal?
+ array-rank
+ array-dimensions
+ make-array
+ make-shared-array
+ list->array
+ array->list
+ vector->array
+ array->vector
+ array-in-bounds?
+ array-ref
+ array-set!
+ a:floc128b
+ a:floc64b
+ a:floc32b
+ a:floc16b
+ a:flor128b
+ a:flor64b
+ a:flor32b
+ a:flor16b
+ a:fixz64b
+ a:fixz32b
+ a:fixz16b
+ a:fixz8b
+ a:fixn64b
+ a:fixn32b
+ a:fixn16b
+ a:fixn8b
+ a:bool
+ )
+ (import (except (scheme base) equal?))
+ (include "63.body.scm"))
+;;; array
+;;; 1997 - 2001 Jussi Piitulainen
+
+;;; --- Intro ---
+
+;;; This interface to arrays is based on Alan Bawden's array.scm of
+;;; 1993 (earlier version in the Internet Repository and another
+;;; version in SLIB). This is a complete rewrite, to be consistent
+;;; with the rest of Scheme and to make arrays independent of lists.
+
+;;; Some modifications are due to discussion in srfi-25 mailing list.
+
+;;; (array? obj)
+;;; (make-array shape [obj]) changed arguments
+;;; (shape bound ...) new
+;;; (array shape obj ...) new
+;;; (array-rank array) changed name back
+;;; (array-start array dimension) new
+;;; (array-end array dimension) new
+;;; (array-ref array k ...)
+;;; (array-ref array index) new variant
+;;; (array-set! array k ... obj) changed argument order
+;;; (array-set! array index obj) new variant
+;;; (share-array array shape proc) changed arguments
+
+;;; All other variables in this file have names in "array:".
+
+;;; Should there be a way to make arrays with initial values mapped
+;;; from indices? Sure. The current "initial object" is lame.
+;;;
+;;; Removed (array-shape array) from here. There is a new version
+;;; in arlib though.
+
+;;; --- Representation type dependencies ---
+
+;;; The mapping from array indices to the index to the underlying vector
+;;; is whatever array:optimize returns. The file "opt" provides three
+;;; representations:
+;;;
+;;; mbda) mapping is a procedure that allows an optional argument
+;;; tter) mapping is two procedures that takes exactly the indices
+;;; ctor) mapping is a vector of a constant term and coefficients
+;;;
+;;; Choose one in "opt" to make the optimizer. Then choose the matching
+;;; implementation of array-ref and array-set!.
+;;;
+;;; These should be made macros to inline them. Or have a good compiler
+;;; and plant the package as a module.
+
+;;; 1. Pick an optimizer.
+;;; 2. Pick matching index representation.
+;;; 3. Pick a record implementation; as-procedure is generic; syntax inlines.
+;;; 3. This file is otherwise portable.
+
+;;; --- Portable R5RS (R4RS and multiple values) ---
+
+;;; (array? obj)
+;;; returns #t if `obj' is an array and #t or #f otherwise.
+
+(define (array? obj)
+ (array:array? obj))
+
+;;; (make-array shape)
+;;; (make-array shape obj)
+;;; makes array of `shape' with each cell containing `obj' initially.
+
+(define (make-array shape . rest)
+ (or (array:good-shape? shape)
+ (error "make-array: shape is not a shape"))
+ (apply array:make-array shape rest))
+
+(define (array:make-array shape . rest)
+ (let ((size (array:size shape)))
+ (array:make
+ (if (pair? rest)
+ (apply (lambda (o) (make-vector size o)) rest)
+ (make-vector size))
+ (if (= size 0)
+ (array:optimize-empty
+ (vector-ref (array:shape shape) 1))
+ (array:optimize
+ (array:make-index shape)
+ (vector-ref (array:shape shape) 1)))
+ (array:shape->vector shape))))
+
+;;; (shape bound ...)
+;;; makes a shape. Bounds must be an even number of exact, pairwise
+;;; non-decreasing integers. Note that any such array can be a shape.
+
+(define (shape . bounds)
+ (let ((v (list->vector bounds)))
+ (or (even? (vector-length v))
+ (error (string-append "shape: uneven number of bounds: "
+ (array:list->string bounds))))
+ (let ((shp (array:make
+ v
+ (if (pair? bounds)
+ (array:shape-index)
+ (array:empty-shape-index))
+ (vector 0 (quotient (vector-length v) 2)
+ 0 2))))
+ (or (array:good-shape? shp)
+ (error (string-append "shape: bounds are not pairwise "
+ "non-decreasing exact integers: "
+ (array:list->string bounds))))
+ shp)))
+
+;;; (array shape obj ...)
+;;; is analogous to `vector'.
+
+(define (array shape . elts)
+ (or (array:good-shape? shape)
+ (error (string-append "array: shape " (array:thing->string shape)
+ " is not a shape")))
+ (let ((size (array:size shape)))
+ (let ((vector (list->vector elts)))
+ (or (= (vector-length vector) size)
+ (error (string-append "array: an array of shape "
+ (array:shape-vector->string
+ (array:vector shape))
+ " has "
+ (number->string size)
+ " elements but got "
+ (number->string (vector-length vector))
+ " values: "
+ (array:list->string elts))))
+ (array:make
+ vector
+ (if (= size 0)
+ (array:optimize-empty
+ (vector-ref (array:shape shape) 1))
+ (array:optimize
+ (array:make-index shape)
+ (vector-ref (array:shape shape) 1)))
+ (array:shape->vector shape)))))
+
+;;; (array-rank array)
+;;; returns the number of dimensions of `array'.
+
+(define (array-rank array)
+ (quotient (vector-length (array:shape array)) 2))
+
+;;; (array-start array k)
+;;; returns the lower bound index of array along dimension k. This is
+;;; the least valid index along that dimension if the dimension is not
+;;; empty.
+
+(define (array-start array d)
+ (vector-ref (array:shape array) (+ d d)))
+
+;;; (array-end array k)
+;;; returns the upper bound index of array along dimension k. This is
+;;; not a valid index. If the dimension is empty, this is the same as
+;;; the lower bound along it.
+
+(define (array-end array d)
+ (vector-ref (array:shape array) (+ d d 1)))
+
+;;; (share-array array shape proc)
+;;; makes an array that shares elements of `array' at shape `shape'.
+;;; The arguments to `proc' are indices of the result. The values of
+;;; `proc' are indices of `array'.
+
+;;; Todo: in the error message, should recognise the mapping and show it.
+
+(define (share-array array subshape f)
+ (or (array:good-shape? subshape)
+ (error (string-append "share-array: shape "
+ (array:thing->string subshape)
+ " is not a shape")))
+ (let ((subsize (array:size subshape)))
+ (or (array:good-share? subshape subsize f (array:shape array))
+ (error (string-append "share-array: subshape "
+ (array:shape-vector->string
+ (array:vector subshape))
+ " does not map into supershape "
+ (array:shape-vector->string
+ (array:shape array))
+ " under mapping "
+ (array:map->string
+ f
+ (vector-ref (array:shape subshape) 1)))))
+ (let ((g (array:index array)))
+ (array:make
+ (array:vector array)
+ (if (= subsize 0)
+ (array:optimize-empty
+ (vector-ref (array:shape subshape) 1))
+ (array:optimize
+ (lambda ks
+ (call-with-values
+ (lambda () (apply f ks))
+ (lambda ks (array:vector-index g ks))))
+ (vector-ref (array:shape subshape) 1)))
+ (array:shape->vector subshape)))))
+
+;;; --- Hrmph ---
+
+;;; (array:share/index! ...)
+;;; reuses a user supplied index object when recognising the
+;;; mapping. The mind balks at the very nasty side effect that
+;;; exposes the implementation. So this is not in the spec.
+;;; But letting index objects in at all creates a pressure
+;;; to go the whole hog. Arf.
+
+;;; Use array:optimize-empty for an empty array to get a
+;;; clearly invalid vector index.
+
+;;; Surely it's perverse to use an actor for index here? But
+;;; the possibility is provided for completeness.
+
+(define (array:share/index! array subshape proc index)
+ (array:make
+ (array:vector array)
+ (if (= (array:size subshape) 0)
+ (array:optimize-empty
+ (quotient (vector-length (array:shape array)) 2))
+ ((if (vector? index)
+ array:optimize/vector
+ array:optimize/actor)
+ (lambda (subindex)
+ (let ((superindex (proc subindex)))
+ (if (vector? superindex)
+ (array:index/vector
+ (quotient (vector-length (array:shape array)) 2)
+ (array:index array)
+ superindex)
+ (array:index/array
+ (quotient (vector-length (array:shape array)) 2)
+ (array:index array)
+ (array:vector superindex)
+ (array:index superindex)))))
+ index))
+ (array:shape->vector subshape)))
+
+(define (array:optimize/vector f v)
+ (let ((r (vector-length v)))
+ (do ((k 0 (+ k 1)))
+ ((= k r))
+ (vector-set! v k 0))
+ (let ((n0 (f v))
+ (cs (make-vector (+ r 1)))
+ (apply (array:applier-to-vector (+ r 1))))
+ (vector-set! cs 0 n0)
+ (let wok ((k 0))
+ (if (< k r)
+ (let ((k1 (+ k 1)))
+ (vector-set! v k 1)
+ (let ((nk (- (f v) n0)))
+ (vector-set! v k 0)
+ (vector-set! cs k1 nk)
+ (wok k1)))))
+ (apply (array:maker r) cs))))
+
+(define (array:optimize/actor f a)
+ (let ((r (array-end a 0))
+ (v (array:vector a))
+ (i (array:index a)))
+ (do ((k 0 (+ k 1)))
+ ((= k r))
+ (vector-set! v (array:actor-index i k) 0))
+ (let ((n0 (f a))
+ (cs (make-vector (+ r 1)))
+ (apply (array:applier-to-vector (+ r 1))))
+ (vector-set! cs 0 n0)
+ (let wok ((k 0))
+ (if (< k r)
+ (let ((k1 (+ k 1))
+ (t (array:actor-index i k)))
+ (vector-set! v t 1)
+ (let ((nk (- (f a) n0)))
+ (vector-set! v t 0)
+ (vector-set! cs k1 nk)
+ (wok k1)))))
+ (apply (array:maker r) cs))))
+
+;;; --- Internals ---
+
+(define (array:shape->vector shape)
+ (let ((idx (array:index shape))
+ (shv (array:vector shape))
+ (rnk (vector-ref (array:shape shape) 1)))
+ (let ((vec (make-vector (* rnk 2))))
+ (do ((k 0 (+ k 1)))
+ ((= k rnk)
+ vec)
+ (vector-set! vec (+ k k)
+ (vector-ref shv (array:shape-vector-index idx k 0)))
+ (vector-set! vec (+ k k 1)
+ (vector-ref shv (array:shape-vector-index idx k 1)))))))
+
+;;; (array:size shape)
+;;; returns the number of elements in arrays of shape `shape'.
+
+(define (array:size shape)
+ (let ((idx (array:index shape))
+ (shv (array:vector shape))
+ (rnk (vector-ref (array:shape shape) 1)))
+ (do ((k 0 (+ k 1))
+ (s 1 (* s
+ (- (vector-ref shv (array:shape-vector-index idx k 1))
+ (vector-ref shv (array:shape-vector-index idx k 0))))))
+ ((= k rnk) s))))
+
+;;; (array:make-index shape)
+;;; returns an index function for arrays of shape `shape'. This is a
+;;; runtime composition of several variable arity procedures, to be
+;;; passed to array:optimize for recognition as an affine function of
+;;; as many variables as there are dimensions in arrays of this shape.
+
+(define (array:make-index shape)
+ (let ((idx (array:index shape))
+ (shv (array:vector shape))
+ (rnk (vector-ref (array:shape shape) 1)))
+ (do ((f (lambda () 0)
+ (lambda (k . ks)
+ (+ (* s (- k (vector-ref
+ shv
+ (array:shape-vector-index idx (- j 1) 0))))
+ (apply f ks))))
+ (s 1 (* s (- (vector-ref
+ shv
+ (array:shape-vector-index idx (- j 1) 1))
+ (vector-ref
+ shv
+ (array:shape-vector-index idx (- j 1) 0)))))
+ (j rnk (- j 1)))
+ ((= j 0)
+ f))))
+
+
+;;; --- Error checking ---
+
+;;; (array:good-shape? shape)
+;;; returns true if `shape' is an array of the right shape and its
+;;; elements are exact integers that pairwise bound intervals `[lo..hi)´.
+
+(define (array:good-shape? shape)
+ (and (array:array? shape)
+ (let ((u (array:shape shape))
+ (v (array:vector shape))
+ (x (array:index shape)))
+ (and (= (vector-length u) 4)
+ (= (vector-ref u 0) 0)
+ (= (vector-ref u 2) 0)
+ (= (vector-ref u 3) 2))
+ (let ((p (vector-ref u 1)))
+ (do ((k 0 (+ k 1))
+ (true #t (let ((lo (vector-ref
+ v
+ (array:shape-vector-index x k 0)))
+ (hi (vector-ref
+ v
+ (array:shape-vector-index x k 1))))
+ (and true
+ (integer? lo)
+ (exact? lo)
+ (integer? hi)
+ (exact? hi)
+ (<= lo hi)))))
+ ((= k p) true))))))
+
+;;; (array:good-share? subv subsize mapping superv)
+;;; returns true if the extreme indices in the subshape vector map
+;;; into the bounds in the supershape vector.
+
+;;; If some interval in `subv' is empty, then `subv' is empty and its
+;;; image under `f' is empty and it is trivially alright. One must
+;;; not call `f', though.
+
+(define (array:good-share? subshape subsize f super)
+ (or (zero? subsize)
+ (letrec
+ ((sub (array:vector subshape))
+ (dex (array:index subshape))
+ (ck (lambda (k ks)
+ (if (zero? k)
+ (call-with-values
+ (lambda () (apply f ks))
+ (lambda qs (array:good-indices? qs super)))
+ (and (ck (- k 1)
+ (cons (vector-ref
+ sub
+ (array:shape-vector-index
+ dex
+ (- k 1)
+ 0))
+ ks))
+ (ck (- k 1)
+ (cons (- (vector-ref
+ sub
+ (array:shape-vector-index
+ dex
+ (- k 1)
+ 1))
+ 1)
+ ks)))))))
+ (let ((rnk (vector-ref (array:shape subshape) 1)))
+ (or (array:unchecked-share-depth? rnk)
+ (ck rnk '()))))))
+
+;;; Check good-share on 10 dimensions at most. The trouble is,
+;;; the cost of this check is exponential in the number of dimensions.
+
+(define (array:unchecked-share-depth? rank)
+ (if (> rank 10)
+ (begin
+ (display `(warning unchecked depth in share
+ ,rank subdimensions))
+ (newline)
+ #t)
+ #f))
+
+;;; (array:check-indices caller indices shape-vector)
+;;; (array:check-indices.o caller indices shape-vector)
+;;; (array:check-index-vector caller index-vector shape-vector)
+;;; return if the index is in bounds, else signal error.
+;;;
+;;; Shape-vector is the internal representation, with
+;;; b and e for dimension k at 2k and 2k + 1.
+
+(define (array:check-indices who ks shv)
+ (or (array:good-indices? ks shv)
+ (error (array:not-in who ks shv))))
+
+(define (array:check-indices.o who ks shv)
+ (or (array:good-indices.o? ks shv)
+ (error (array:not-in who (reverse (cdr (reverse ks))) shv))))
+
+(define (array:check-index-vector who ks shv)
+ (or (array:good-index-vector? ks shv)
+ (error (array:not-in who (vector->list ks) shv))))
+
+(define (array:check-index-actor who ks shv)
+ (let ((shape (array:shape ks)))
+ (or (and (= (vector-length shape) 2)
+ (= (vector-ref shape 0) 0))
+ (error "not an actor"))
+ (or (array:good-index-actor?
+ (vector-ref shape 1)
+ (array:vector ks)
+ (array:index ks)
+ shv)
+ (array:not-in who (do ((k (vector-ref shape 1) (- k 1))
+ (m '() (cons (vector-ref
+ (array:vector ks)
+ (array:actor-index
+ (array:index ks)
+ (- k 1)))
+ m)))
+ ((= k 0) m))
+ shv))))
+
+(define (array:good-indices? ks shv)
+ (let ((d2 (vector-length shv)))
+ (do ((kp ks (if (pair? kp)
+ (cdr kp)))
+ (k 0 (+ k 2))
+ (true #t (and true (pair? kp)
+ (array:good-index? (car kp) shv k))))
+ ((= k d2)
+ (and true (null? kp))))))
+
+(define (array:good-indices.o? ks.o shv)
+ (let ((d2 (vector-length shv)))
+ (do ((kp ks.o (if (pair? kp)
+ (cdr kp)))
+ (k 0 (+ k 2))
+ (true #t (and true (pair? kp)
+ (array:good-index? (car kp) shv k))))
+ ((= k d2)
+ (and true (pair? kp) (null? (cdr kp)))))))
+
+(define (array:good-index-vector? ks shv)
+ (let ((r2 (vector-length shv)))
+ (and (= (* 2 (vector-length ks)) r2)
+ (do ((j 0 (+ j 1))
+ (k 0 (+ k 2))
+ (true #t (and true
+ (array:good-index? (vector-ref ks j) shv k))))
+ ((= k r2) true)))))
+
+(define (array:good-index-actor? r v i shv)
+ (and (= (* 2 r) (vector-length shv))
+ (do ((j 0 (+ j 1))
+ (k 0 (+ k 2))
+ (true #t (and true
+ (array:good-index? (vector-ref
+ v
+ (array:actor-index i j))
+ shv
+ k))))
+ ((= j r) true))))
+
+;;; (array:good-index? index shape-vector 2d)
+;;; returns true if index is within bounds for dimension 2d/2.
+
+(define (array:good-index? w shv k)
+ (and (integer? w)
+ (exact? w)
+ (<= (vector-ref shv k) w)
+ (< w (vector-ref shv (+ k 1)))))
+
+(define (array:not-in who ks shv)
+ (let ((index (array:list->string ks))
+ (bounds (array:shape-vector->string shv)))
+ (error (string-append who
+ ": index " index
+ " not in bounds " bounds))))
+
+(define (array:list->string ks)
+ (do ((index "" (string-append index (array:thing->string (car ks)) " "))
+ (ks ks (cdr ks)))
+ ((null? ks) index)))
+
+(define (array:shape-vector->string shv)
+ (do ((bounds "" (string-append bounds
+ "["
+ (number->string (vector-ref shv t))
+ ".."
+ (number->string (vector-ref shv (+ t 1)))
+ ")"
+ " "))
+ (t 0 (+ t 2)))
+ ((= t (vector-length shv)) bounds)))
+
+(define (array:thing->string thing)
+ (cond
+ ((number? thing) (number->string thing))
+ ((symbol? thing) (string-append "#<symbol>" (symbol->string thing)))
+ ((char? thing) "#<char>")
+ ((string? thing) "#<string>")
+ ((list? thing) (string-append "#" (number->string (length thing))
+ "<list>"))
+
+ ((pair? thing) "#<pair>")
+ ((array? thing) "#<array>")
+ ((vector? thing) (string-append "#" (number->string
+ (vector-length thing))
+ "<vector>"))
+ ((procedure? thing) "#<procedure>")
+ (else
+ (case thing
+ ((()) "()")
+ ((#t) "#t")
+ ((#f) "#f")
+ (else
+ "#<whatsit>")))))
+
+;;; And to grok an affine map, vector->vector type. Column k of arr
+;;; will contain coefficients n0 ... nm of 1 k1 ... km for kth value.
+;;;
+;;; These are for the error message when share fails.
+
+(define (array:index-ref ind k)
+ (if (vector? ind)
+ (vector-ref ind k)
+ (vector-ref
+ (array:vector ind)
+ (array:actor-index (array:index ind) k))))
+
+(define (array:index-set! ind k o)
+ (if (vector? ind)
+ (vector-set! ind k o)
+ (vector-set!
+ (array:vector ind)
+ (array:actor-index (array:index ind) k)
+ o)))
+
+(define (array:index-length ind)
+ (if (vector? ind)
+ (vector-length ind)
+ (vector-ref (array:shape ind) 1)))
+
+(define (array:map->string proc r)
+ (let* ((m (array:grok/arguments proc r))
+ (s (vector-ref (array:shape m) 3)))
+ (do ((i "" (string-append i c "k" (number->string k)))
+ (c "" ", ")
+ (k 1 (+ k 1)))
+ ((< r k)
+ (do ((o "" (string-append o c (array:map-column->string m r k)))
+ (c "" ", ")
+ (k 0 (+ k 1)))
+ ((= k s)
+ (string-append i " => " o)))))))
+
+(define (array:map-column->string m r k)
+ (let ((v (array:vector m))
+ (i (array:index m)))
+ (let ((n0 (vector-ref v (array:vector-index i (list 0 k)))))
+ (let wok ((j 1)
+ (e (if (= n0 0) "" (number->string n0))))
+ (if (<= j r)
+ (let ((nj (vector-ref v (array:vector-index i (list j k)))))
+ (if (= nj 0)
+ (wok (+ j 1) e)
+ (let* ((nj (if (= nj 1) ""
+ (if (= nj -1) "-"
+ (string-append (number->string nj)
+ " "))))
+ (njkj (string-append nj "k" (number->string j))))
+ (if (string=? e "")
+ (wok (+ j 1) njkj)
+ (wok (+ j 1) (string-append e " + " njkj))))))
+ (if (string=? e "") "0" e))))))
+
+(define (array:grok/arguments proc r)
+ (array:grok/index!
+ (lambda (vec)
+ (call-with-values
+ (lambda ()
+ (array:apply-to-vector r proc vec))
+ vector))
+ (make-vector r)))
+
+(define (array:grok/index! proc in)
+ (let ((m (array:index-length in)))
+ (do ((k 0 (+ k 1)))
+ ((= k m))
+ (array:index-set! in k 0))
+ (let* ((n0 (proc in))
+ (n (array:index-length n0)))
+ (let ((arr (make-array (shape 0 (+ m 1) 0 n)))) ; (*)
+ (do ((k 0 (+ k 1)))
+ ((= k n))
+ (array-set! arr 0 k (array:index-ref n0 k))) ; (**)
+ (do ((j 0 (+ j 1)))
+ ((= j m))
+ (array:index-set! in j 1)
+ (let ((nj (proc in)))
+ (array:index-set! in j 0)
+ (do ((k 0 (+ k 1)))
+ ((= k n))
+ (array-set! arr (+ j 1) k (- (array:index-ref nj k) ; (**)
+ (array:index-ref n0 k))))))
+ arr))))
+;; (*) Should not use `make-array' and `shape' here
+;; (**) Should not use `array-set!' here
+;; Should use something internal to the library instead: either lower
+;; level code (preferable but complex) or alternative names to these same.
+;; Copyright (C) John David Stone (1999). All Rights Reserved.
+
+;; Made an R7RS library by Taylan Ulrich Bayırlı/Kammer, Copyright (C) 2014.
+
+;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;; of this software and associated documentation files (the "Software"), to deal
+;; in the Software without restriction, including without limitation the rights
+;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+;; copies of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be included in
+;; all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+(define-library (srfi 8)
+ (export receive)
+ (import (scheme base))
+ (begin
+ (define-syntax receive
+ (syntax-rules ()
+ ((receive formals expression body ...)
+ (call-with-values (lambda () expression)
+ (lambda formals body ...)))))))
+;;; Copyright (C) Jussi Piitulainen (2001). All Rights Reserved.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+(begin
+ (define array:opt-args '(ctor (4)))
+ (define (array:optimize f r)
+ (case r
+ ((0) (let ((n0 (f))) (array:0 n0)))
+ ((1) (let ((n0 (f 0))) (array:1 n0 (- (f 1) n0))))
+ ((2)
+ (let ((n0 (f 0 0)))
+ (array:2 n0 (- (f 1 0) n0) (- (f 0 1) n0))))
+ ((3)
+ (let ((n0 (f 0 0 0)))
+ (array:3
+ n0
+ (- (f 1 0 0) n0)
+ (- (f 0 1 0) n0)
+ (- (f 0 0 1) n0))))
+ (else
+ (let ((v
+ (do ((k 0 (+ k 1)) (v '() (cons 0 v)))
+ ((= k r) v))))
+ (let ((n0 (apply f v)))
+ (apply
+ array:n
+ n0
+ (array:coefficients f n0 v v)))))))
+ (define (array:optimize-empty r)
+ (let ((x (make-vector (+ r 1) 0)))
+ (vector-set! x r -1)
+ x))
+ (define (array:coefficients f n0 vs vp)
+ (case vp
+ ((()) '())
+ (else
+ (set-car! vp 1)
+ (let ((n (- (apply f vs) n0)))
+ (set-car! vp 0)
+ (cons n (array:coefficients f n0 vs (cdr vp)))))))
+ (define (array:vector-index x ks)
+ (do ((sum 0 (+ sum (* (vector-ref x k) (car ks))))
+ (ks ks (cdr ks))
+ (k 0 (+ k 1)))
+ ((null? ks) (+ sum (vector-ref x k)))))
+ (define (array:shape-index) '#(2 1 0))
+ (define (array:empty-shape-index) '#(0 0 -1))
+ (define (array:shape-vector-index x r k)
+ (+
+ (* (vector-ref x 0) r)
+ (* (vector-ref x 1) k)
+ (vector-ref x 2)))
+ (define (array:actor-index x k)
+ (+ (* (vector-ref x 0) k) (vector-ref x 1)))
+ (define (array:0 n0) (vector n0))
+ (define (array:1 n0 n1) (vector n1 n0))
+ (define (array:2 n0 n1 n2) (vector n1 n2 n0))
+ (define (array:3 n0 n1 n2 n3) (vector n1 n2 n3 n0))
+ (define (array:n n0 n1 n2 n3 n4 . ns)
+ (apply vector n1 n2 n3 n4 (append ns (list n0))))
+ (define (array:maker r)
+ (case r
+ ((0) array:0)
+ ((1) array:1)
+ ((2) array:2)
+ ((3) array:3)
+ (else array:n)))
+ (define array:indexer/vector
+ (let ((em
+ (vector
+ (lambda (x i) (+ (vector-ref x 0)))
+ (lambda (x i)
+ (+
+ (* (vector-ref x 0) (vector-ref i 0))
+ (vector-ref x 1)))
+ (lambda (x i)
+ (+
+ (* (vector-ref x 0) (vector-ref i 0))
+ (* (vector-ref x 1) (vector-ref i 1))
+ (vector-ref x 2)))
+ (lambda (x i)
+ (+
+ (* (vector-ref x 0) (vector-ref i 0))
+ (* (vector-ref x 1) (vector-ref i 1))
+ (* (vector-ref x 2) (vector-ref i 2))
+ (vector-ref x 3)))
+ (lambda (x i)
+ (+
+ (* (vector-ref x 0) (vector-ref i 0))
+ (* (vector-ref x 1) (vector-ref i 1))
+ (* (vector-ref x 2) (vector-ref i 2))
+ (* (vector-ref x 3) (vector-ref i 3))
+ (vector-ref x 4)))
+ (lambda (x i)
+ (+
+ (* (vector-ref x 0) (vector-ref i 0))
+ (* (vector-ref x 1) (vector-ref i 1))
+ (* (vector-ref x 2) (vector-ref i 2))
+ (* (vector-ref x 3) (vector-ref i 3))
+ (* (vector-ref x 4) (vector-ref i 4))
+ (vector-ref x 5)))
+ (lambda (x i)
+ (+
+ (* (vector-ref x 0) (vector-ref i 0))
+ (* (vector-ref x 1) (vector-ref i 1))
+ (* (vector-ref x 2) (vector-ref i 2))
+ (* (vector-ref x 3) (vector-ref i 3))
+ (* (vector-ref x 4) (vector-ref i 4))
+ (* (vector-ref x 5) (vector-ref i 5))
+ (vector-ref x 6)))
+ (lambda (x i)
+ (+
+ (* (vector-ref x 0) (vector-ref i 0))
+ (* (vector-ref x 1) (vector-ref i 1))
+ (* (vector-ref x 2) (vector-ref i 2))
+ (* (vector-ref x 3) (vector-ref i 3))
+ (* (vector-ref x 4) (vector-ref i 4))
+ (* (vector-ref x 5) (vector-ref i 5))
+ (* (vector-ref x 6) (vector-ref i 6))
+ (vector-ref x 7)))
+ (lambda (x i)
+ (+
+ (* (vector-ref x 0) (vector-ref i 0))
+ (* (vector-ref x 1) (vector-ref i 1))
+ (* (vector-ref x 2) (vector-ref i 2))
+ (* (vector-ref x 3) (vector-ref i 3))
+ (* (vector-ref x 4) (vector-ref i 4))
+ (* (vector-ref x 5) (vector-ref i 5))
+ (* (vector-ref x 6) (vector-ref i 6))
+ (* (vector-ref x 7) (vector-ref i 7))
+ (vector-ref x 8)))
+ (lambda (x i)
+ (+
+ (* (vector-ref x 0) (vector-ref i 0))
+ (* (vector-ref x 1) (vector-ref i 1))
+ (* (vector-ref x 2) (vector-ref i 2))
+ (* (vector-ref x 3) (vector-ref i 3))
+ (* (vector-ref x 4) (vector-ref i 4))
+ (* (vector-ref x 5) (vector-ref i 5))
+ (* (vector-ref x 6) (vector-ref i 6))
+ (* (vector-ref x 7) (vector-ref i 7))
+ (* (vector-ref x 8) (vector-ref i 8))
+ (vector-ref x 9)))))
+ (it
+ (lambda (w)
+ (lambda (x i)
+ (+
+ (* (vector-ref x 0) (vector-ref i 0))
+ (* (vector-ref x 1) (vector-ref i 1))
+ (* (vector-ref x 2) (vector-ref i 2))
+ (* (vector-ref x 3) (vector-ref i 3))
+ (* (vector-ref x 4) (vector-ref i 4))
+ (* (vector-ref x 5) (vector-ref i 5))
+ (* (vector-ref x 6) (vector-ref i 6))
+ (* (vector-ref x 7) (vector-ref i 7))
+ (* (vector-ref x 8) (vector-ref i 8))
+ (* (vector-ref x 9) (vector-ref i 9))
+ (do ((xi
+ 0
+ (+
+ (* (vector-ref x u) (vector-ref i u))
+ xi))
+ (u (- w 1) (- u 1)))
+ ((< u 10) xi))
+ (vector-ref x w))))))
+ (lambda (r) (if (< r 10) (vector-ref em r) (it r)))))
+ (define array:indexer/array
+ (let ((em
+ (vector
+ (lambda (x v i) (+ (vector-ref x 0)))
+ (lambda (x v i)
+ (+
+ (*
+ (vector-ref x 0)
+ (vector-ref v (array:actor-index i 0)))
+ (vector-ref x 1)))
+ (lambda (x v i)
+ (+
+ (*
+ (vector-ref x 0)
+ (vector-ref v (array:actor-index i 0)))
+ (*
+ (vector-ref x 1)
+ (vector-ref v (array:actor-index i 1)))
+ (vector-ref x 2)))
+ (lambda (x v i)
+ (+
+ (*
+ (vector-ref x 0)
+ (vector-ref v (array:actor-index i 0)))
+ (*
+ (vector-ref x 1)
+ (vector-ref v (array:actor-index i 1)))
+ (*
+ (vector-ref x 2)
+ (vector-ref v (array:actor-index i 2)))
+ (vector-ref x 3)))
+ (lambda (x v i)
+ (+
+ (*
+ (vector-ref x 0)
+ (vector-ref v (array:actor-index i 0)))
+ (*
+ (vector-ref x 1)
+ (vector-ref v (array:actor-index i 1)))
+ (*
+ (vector-ref x 2)
+ (vector-ref v (array:actor-index i 2)))
+ (*
+ (vector-ref x 3)
+ (vector-ref v (array:actor-index i 3)))
+ (vector-ref x 4)))
+ (lambda (x v i)
+ (+
+ (*
+ (vector-ref x 0)
+ (vector-ref v (array:actor-index i 0)))
+ (*
+ (vector-ref x 1)
+ (vector-ref v (array:actor-index i 1)))
+ (*
+ (vector-ref x 2)
+ (vector-ref v (array:actor-index i 2)))
+ (*
+ (vector-ref x 3)
+ (vector-ref v (array:actor-index i 3)))
+ (*
+ (vector-ref x 4)
+ (vector-ref v (array:actor-index i 4)))
+ (vector-ref x 5)))
+ (lambda (x v i)
+ (+
+ (*
+ (vector-ref x 0)
+ (vector-ref v (array:actor-index i 0)))
+ (*
+ (vector-ref x 1)
+ (vector-ref v (array:actor-index i 1)))
+ (*
+ (vector-ref x 2)
+ (vector-ref v (array:actor-index i 2)))
+ (*
+ (vector-ref x 3)
+ (vector-ref v (array:actor-index i 3)))
+ (*
+ (vector-ref x 4)
+ (vector-ref v (array:actor-index i 4)))
+ (*
+ (vector-ref x 5)
+ (vector-ref v (array:actor-index i 5)))
+ (vector-ref x 6)))
+ (lambda (x v i)
+ (+
+ (*
+ (vector-ref x 0)
+ (vector-ref v (array:actor-index i 0)))
+ (*
+ (vector-ref x 1)
+ (vector-ref v (array:actor-index i 1)))
+ (*
+ (vector-ref x 2)
+ (vector-ref v (array:actor-index i 2)))
+ (*
+ (vector-ref x 3)
+ (vector-ref v (array:actor-index i 3)))
+ (*
+ (vector-ref x 4)
+ (vector-ref v (array:actor-index i 4)))
+ (*
+ (vector-ref x 5)
+ (vector-ref v (array:actor-index i 5)))
+ (*
+ (vector-ref x 6)
+ (vector-ref v (array:actor-index i 6)))
+ (vector-ref x 7)))
+ (lambda (x v i)
+ (+
+ (*
+ (vector-ref x 0)
+ (vector-ref v (array:actor-index i 0)))
+ (*
+ (vector-ref x 1)
+ (vector-ref v (array:actor-index i 1)))
+ (*
+ (vector-ref x 2)
+ (vector-ref v (array:actor-index i 2)))
+ (*
+ (vector-ref x 3)
+ (vector-ref v (array:actor-index i 3)))
+ (*
+ (vector-ref x 4)
+ (vector-ref v (array:actor-index i 4)))
+ (*
+ (vector-ref x 5)
+ (vector-ref v (array:actor-index i 5)))
+ (*
+ (vector-ref x 6)
+ (vector-ref v (array:actor-index i 6)))
+ (*
+ (vector-ref x 7)
+ (vector-ref v (array:actor-index i 7)))
+ (vector-ref x 8)))
+ (lambda (x v i)
+ (+
+ (*
+ (vector-ref x 0)
+ (vector-ref v (array:actor-index i 0)))
+ (*
+ (vector-ref x 1)
+ (vector-ref v (array:actor-index i 1)))
+ (*
+ (vector-ref x 2)
+ (vector-ref v (array:actor-index i 2)))
+ (*
+ (vector-ref x 3)
+ (vector-ref v (array:actor-index i 3)))
+ (*
+ (vector-ref x 4)
+ (vector-ref v (array:actor-index i 4)))
+ (*
+ (vector-ref x 5)
+ (vector-ref v (array:actor-index i 5)))
+ (*
+ (vector-ref x 6)
+ (vector-ref v (array:actor-index i 6)))
+ (*
+ (vector-ref x 7)
+ (vector-ref v (array:actor-index i 7)))
+ (*
+ (vector-ref x 8)
+ (vector-ref v (array:actor-index i 8)))
+ (vector-ref x 9)))))
+ (it
+ (lambda (w)
+ (lambda (x v i)
+ (+
+ (*
+ (vector-ref x 0)
+ (vector-ref v (array:actor-index i 0)))
+ (*
+ (vector-ref x 1)
+ (vector-ref v (array:actor-index i 1)))
+ (*
+ (vector-ref x 2)
+ (vector-ref v (array:actor-index i 2)))
+ (*
+ (vector-ref x 3)
+ (vector-ref v (array:actor-index i 3)))
+ (*
+ (vector-ref x 4)
+ (vector-ref v (array:actor-index i 4)))
+ (*
+ (vector-ref x 5)
+ (vector-ref v (array:actor-index i 5)))
+ (*
+ (vector-ref x 6)
+ (vector-ref v (array:actor-index i 6)))
+ (*
+ (vector-ref x 7)
+ (vector-ref v (array:actor-index i 7)))
+ (*
+ (vector-ref x 8)
+ (vector-ref v (array:actor-index i 8)))
+ (*
+ (vector-ref x 9)
+ (vector-ref v (array:actor-index i 9)))
+ (do ((xi
+ 0
+ (+
+ (*
+ (vector-ref x u)
+ (vector-ref
+ v
+ (array:actor-index i u)))
+ xi))
+ (u (- w 1) (- u 1)))
+ ((< u 10) xi))
+ (vector-ref x w))))))
+ (lambda (r) (if (< r 10) (vector-ref em r) (it r)))))
+ (define array:applier-to-vector
+ (let ((em
+ (vector
+ (lambda (p v) (p))
+ (lambda (p v) (p (vector-ref v 0)))
+ (lambda (p v)
+ (p (vector-ref v 0) (vector-ref v 1)))
+ (lambda (p v)
+ (p
+ (vector-ref v 0)
+ (vector-ref v 1)
+ (vector-ref v 2)))
+ (lambda (p v)
+ (p
+ (vector-ref v 0)
+ (vector-ref v 1)
+ (vector-ref v 2)
+ (vector-ref v 3)))
+ (lambda (p v)
+ (p
+ (vector-ref v 0)
+ (vector-ref v 1)
+ (vector-ref v 2)
+ (vector-ref v 3)
+ (vector-ref v 4)))
+ (lambda (p v)
+ (p
+ (vector-ref v 0)
+ (vector-ref v 1)
+ (vector-ref v 2)
+ (vector-ref v 3)
+ (vector-ref v 4)
+ (vector-ref v 5)))
+ (lambda (p v)
+ (p
+ (vector-ref v 0)
+ (vector-ref v 1)
+ (vector-ref v 2)
+ (vector-ref v 3)
+ (vector-ref v 4)
+ (vector-ref v 5)
+ (vector-ref v 6)))
+ (lambda (p v)
+ (p
+ (vector-ref v 0)
+ (vector-ref v 1)
+ (vector-ref v 2)
+ (vector-ref v 3)
+ (vector-ref v 4)
+ (vector-ref v 5)
+ (vector-ref v 6)
+ (vector-ref v 7)))
+ (lambda (p v)
+ (p
+ (vector-ref v 0)
+ (vector-ref v 1)
+ (vector-ref v 2)
+ (vector-ref v 3)
+ (vector-ref v 4)
+ (vector-ref v 5)
+ (vector-ref v 6)
+ (vector-ref v 7)
+ (vector-ref v 8)))))
+ (it
+ (lambda (r)
+ (lambda (p v)
+ (apply
+ p
+ (vector-ref v 0)
+ (vector-ref v 1)
+ (vector-ref v 2)
+ (vector-ref v 3)
+ (vector-ref v 4)
+ (vector-ref v 5)
+ (vector-ref v 6)
+ (vector-ref v 7)
+ (vector-ref v 8)
+ (vector-ref v 9)
+ (do ((k r (- k 1))
+ (r
+ '()
+ (cons (vector-ref v (- k 1)) r)))
+ ((= k 10) r)))))))
+ (lambda (r) (if (< r 10) (vector-ref em r) (it r)))))
+ (define array:applier-to-actor
+ (let ((em
+ (vector
+ (lambda (p a) (p))
+ (lambda (p a) (p (array-ref a 0)))
+ (lambda (p a)
+ (p (array-ref a 0) (array-ref a 1)))
+ (lambda (p a)
+ (p
+ (array-ref a 0)
+ (array-ref a 1)
+ (array-ref a 2)))
+ (lambda (p a)
+ (p
+ (array-ref a 0)
+ (array-ref a 1)
+ (array-ref a 2)
+ (array-ref a 3)))
+ (lambda (p a)
+ (p
+ (array-ref a 0)
+ (array-ref a 1)
+ (array-ref a 2)
+ (array-ref a 3)
+ (array-ref a 4)))
+ (lambda (p a)
+ (p
+ (array-ref a 0)
+ (array-ref a 1)
+ (array-ref a 2)
+ (array-ref a 3)
+ (array-ref a 4)
+ (array-ref a 5)))
+ (lambda (p a)
+ (p
+ (array-ref a 0)
+ (array-ref a 1)
+ (array-ref a 2)
+ (array-ref a 3)
+ (array-ref a 4)
+ (array-ref a 5)
+ (array-ref a 6)))
+ (lambda (p a)
+ (p
+ (array-ref a 0)
+ (array-ref a 1)
+ (array-ref a 2)
+ (array-ref a 3)
+ (array-ref a 4)
+ (array-ref a 5)
+ (array-ref a 6)
+ (array-ref a 7)))
+ (lambda (p a)
+ (p
+ (array-ref a 0)
+ (array-ref a 1)
+ (array-ref a 2)
+ (array-ref a 3)
+ (array-ref a 4)
+ (array-ref a 5)
+ (array-ref a 6)
+ (array-ref a 7)
+ (array-ref a 8)))))
+ (it
+ (lambda (r)
+ (lambda (p a)
+ (apply
+ a
+ (array-ref a 0)
+ (array-ref a 1)
+ (array-ref a 2)
+ (array-ref a 3)
+ (array-ref a 4)
+ (array-ref a 5)
+ (array-ref a 6)
+ (array-ref a 7)
+ (array-ref a 8)
+ (array-ref a 9)
+ (do ((k r (- k 1))
+ (r '() (cons (array-ref a (- k 1)) r)))
+ ((= k 10) r)))))))
+ (lambda (r)
+ "These are high level, hiding implementation at call site."
+ (if (< r 10) (vector-ref em r) (it r)))))
+ (define array:applier-to-backing-vector
+ (let ((em
+ (vector
+ (lambda (p ai av) (p))
+ (lambda (p ai av)
+ (p (vector-ref av (array:actor-index ai 0))))
+ (lambda (p ai av)
+ (p
+ (vector-ref av (array:actor-index ai 0))
+ (vector-ref av (array:actor-index ai 1))))
+ (lambda (p ai av)
+ (p
+ (vector-ref av (array:actor-index ai 0))
+ (vector-ref av (array:actor-index ai 1))
+ (vector-ref av (array:actor-index ai 2))))
+ (lambda (p ai av)
+ (p
+ (vector-ref av (array:actor-index ai 0))
+ (vector-ref av (array:actor-index ai 1))
+ (vector-ref av (array:actor-index ai 2))
+ (vector-ref av (array:actor-index ai 3))))
+ (lambda (p ai av)
+ (p
+ (vector-ref av (array:actor-index ai 0))
+ (vector-ref av (array:actor-index ai 1))
+ (vector-ref av (array:actor-index ai 2))
+ (vector-ref av (array:actor-index ai 3))
+ (vector-ref av (array:actor-index ai 4))))
+ (lambda (p ai av)
+ (p
+ (vector-ref av (array:actor-index ai 0))
+ (vector-ref av (array:actor-index ai 1))
+ (vector-ref av (array:actor-index ai 2))
+ (vector-ref av (array:actor-index ai 3))
+ (vector-ref av (array:actor-index ai 4))
+ (vector-ref av (array:actor-index ai 5))))
+ (lambda (p ai av)
+ (p
+ (vector-ref av (array:actor-index ai 0))
+ (vector-ref av (array:actor-index ai 1))
+ (vector-ref av (array:actor-index ai 2))
+ (vector-ref av (array:actor-index ai 3))
+ (vector-ref av (array:actor-index ai 4))
+ (vector-ref av (array:actor-index ai 5))
+ (vector-ref av (array:actor-index ai 6))))
+ (lambda (p ai av)
+ (p
+ (vector-ref av (array:actor-index ai 0))
+ (vector-ref av (array:actor-index ai 1))
+ (vector-ref av (array:actor-index ai 2))
+ (vector-ref av (array:actor-index ai 3))
+ (vector-ref av (array:actor-index ai 4))
+ (vector-ref av (array:actor-index ai 5))
+ (vector-ref av (array:actor-index ai 6))
+ (vector-ref av (array:actor-index ai 7))))
+ (lambda (p ai av)
+ (p
+ (vector-ref av (array:actor-index ai 0))
+ (vector-ref av (array:actor-index ai 1))
+ (vector-ref av (array:actor-index ai 2))
+ (vector-ref av (array:actor-index ai 3))
+ (vector-ref av (array:actor-index ai 4))
+ (vector-ref av (array:actor-index ai 5))
+ (vector-ref av (array:actor-index ai 6))
+ (vector-ref av (array:actor-index ai 7))
+ (vector-ref av (array:actor-index ai 8))))))
+ (it
+ (lambda (r)
+ (lambda (p ai av)
+ (apply
+ p
+ (vector-ref av (array:actor-index ai 0))
+ (vector-ref av (array:actor-index ai 1))
+ (vector-ref av (array:actor-index ai 2))
+ (vector-ref av (array:actor-index ai 3))
+ (vector-ref av (array:actor-index ai 4))
+ (vector-ref av (array:actor-index ai 5))
+ (vector-ref av (array:actor-index ai 6))
+ (vector-ref av (array:actor-index ai 7))
+ (vector-ref av (array:actor-index ai 8))
+ (vector-ref av (array:actor-index ai 9))
+ (do ((k r (- k 1))
+ (r
+ '()
+ (cons
+ (vector-ref
+ av
+ (array:actor-index ai (- k 1)))
+ r)))
+ ((= k 10) r)))))))
+ (lambda (r)
+ "These are low level, exposing implementation at call site."
+ (if (< r 10) (vector-ref em r) (it r)))))
+ (define (array:index/vector r x v)
+ ((array:indexer/vector r) x v))
+ (define (array:index/array r x av ai)
+ ((array:indexer/array r) x av ai))
+ (define (array:apply-to-vector r p v)
+ ((array:applier-to-vector r) p v))
+ (define (array:apply-to-actor r p a)
+ ((array:applier-to-actor r) p a)))
+(define-library (srfi 25)
+ (export
+ array?
+ make-array
+ shape
+ array
+ array-rank
+ array-start
+ array-end
+ array-ref
+ array-set!
+ share-array
+ )
+ (import
+ (scheme base)
+ (scheme write))
+ (include "25.as-srfi-9-record.upstream.scm")
+ (include "25.ix-ctor.upstream.scm")
+ (include "25.op-ctor.upstream.scm")
+ (include "25.main.upstream.scm"))
+(define-library (srfi 26)
+ (export cut cute)
+ (import (scheme base))
+ (include "26.upstream.scm"))
+;;;;"array.scm" Arrays for Scheme
+; Copyright (C) 2001, 2003 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.
+
+;;@code{(require 'array)} or @code{(require 'srfi-63)}
+;;@ftindex array
+
+(require 'record)
+
+(define array:rtd
+ (make-record-type "array"
+ '(dimensions
+ scales ;list of dimension scales
+ offset ;exact integer
+ store ;data
+ )))
+
+(define array:dimensions
+ (let ((dimensions (record-accessor array:rtd 'dimensions)))
+ (lambda (array)
+ (cond ((vector? array) (list (vector-length array)))
+ ((string? array) (list (string-length array)))
+ (else (dimensions array))))))
+
+(define array:scales
+ (let ((scales (record-accessor array:rtd 'scales)))
+ (lambda (obj)
+ (cond ((string? obj) '(1))
+ ((vector? obj) '(1))
+ (else (scales obj))))))
+
+(define array:store
+ (let ((store (record-accessor array:rtd 'store)))
+ (lambda (obj)
+ (cond ((string? obj) obj)
+ ((vector? obj) obj)
+ (else (store obj))))))
+
+(define array:offset
+ (let ((offset (record-accessor array:rtd 'offset)))
+ (lambda (obj)
+ (cond ((string? obj) 0)
+ ((vector? obj) 0)
+ (else (offset obj))))))
+
+(define array:construct
+ (record-constructor array:rtd '(dimensions scales offset store)))
+
+;;@args obj
+;;Returns @code{#t} if the @1 is an array, and @code{#f} if not.
+(define array?
+ (let ((array:array? (record-predicate array:rtd)))
+ (lambda (obj) (or (string? obj) (vector? obj) (array:array? obj)))))
+
+;;@noindent
+;;@emph{Note:} Arrays are not disjoint from other Scheme types.
+;;Vectors and possibly strings also satisfy @code{array?}.
+;;A disjoint array predicate can be written:
+;;
+;;@example
+;;(define (strict-array? obj)
+;; (and (array? obj) (not (string? obj)) (not (vector? obj))))
+;;@end example
+
+;;@body
+;;Returns @code{#t} if @1 and @2 have the same rank and dimensions and the
+;;corresponding elements of @1 and @2 are @code{equal?}.
+
+;;@body
+;;@0 recursively compares the contents of pairs, vectors, strings, and
+;;@emph{arrays}, applying @code{eqv?} on other objects such as numbers
+;;and symbols. A rule of thumb is that objects are generally @0 if
+;;they print the same. @0 may fail to terminate if its arguments are
+;;circular data structures.
+;;
+;;@example
+;;(equal? 'a 'a) @result{} #t
+;;(equal? '(a) '(a)) @result{} #t
+;;(equal? '(a (b) c)
+;; '(a (b) c)) @result{} #t
+;;(equal? "abc" "abc") @result{} #t
+;;(equal? 2 2) @result{} #t
+;;(equal? (make-vector 5 'a)
+;; (make-vector 5 'a)) @result{} #t
+;;(equal? (make-array (A:fixN32b 4) 5 3)
+;; (make-array (A:fixN32b 4) 5 3)) @result{} #t
+;;(equal? (make-array '#(foo) 3 3)
+;; (make-array '#(foo) 3 3)) @result{} #t
+;;(equal? (lambda (x) x)
+;; (lambda (y) y)) @result{} @emph{unspecified}
+;;@end example
+(define (equal? obj1 obj2)
+ (cond ((eqv? obj1 obj2) #t)
+ ((or (pair? obj1) (pair? obj2))
+ (and (pair? obj1) (pair? obj2)
+ (equal? (car obj1) (car obj2))
+ (equal? (cdr obj1) (cdr obj2))))
+ ((or (string? obj1) (string? obj2))
+ (and (string? obj1) (string? obj2)
+ (string=? obj1 obj2)))
+ ((or (vector? obj1) (vector? obj2))
+ (and (vector? obj1) (vector? obj2)
+ (equal? (vector-length obj1) (vector-length obj2))
+ (do ((idx (+ -1 (vector-length obj1)) (+ -1 idx)))
+ ((or (negative? idx)
+ (not (equal? (vector-ref obj1 idx)
+ (vector-ref obj2 idx))))
+ (negative? idx)))))
+ ((or (array? obj1) (array? obj2))
+ (and (array? obj1) (array? obj2)
+ (equal? (array:dimensions obj1) (array:dimensions obj2))
+ (equal? (array:store obj1) (array:store obj2))))
+ (else #f)))
+
+;;@body
+;;Returns the number of dimensions of @1. If @1 is not an array, 0 is
+;;returned.
+(define (array-rank obj)
+ (if (array? obj) (length (array:dimensions obj)) 0))
+
+;;@args array
+;;Returns a list of dimensions.
+;;
+;;@example
+;;(array-dimensions (make-array '#() 3 5))
+;; @result{} (3 5)
+;;@end example
+(define array-dimensions array:dimensions)
+
+;;@args prototype k1 @dots{}
+;;
+;;Creates and returns an array of type @1 with dimensions @2, @dots{}
+;;and filled with elements from @1. @1 must be an array, vector, or
+;;string. The implementation-dependent type of the returned array
+;;will be the same as the type of @1; except if that would be a vector
+;;or string with rank not equal to one, in which case some variety of
+;;array will be returned.
+;;
+;;If the @1 has no elements, then the initial contents of the returned
+;;array are unspecified. Otherwise, the returned array will be filled
+;;with the element at the origin of @1.
+(define (make-array prototype . dimensions)
+ (define tcnt (apply * dimensions))
+ (let ((store
+ (if (string? prototype)
+ (case (string-length prototype)
+ ((0) (make-string tcnt))
+ (else (make-string tcnt
+ (string-ref prototype 0))))
+ (let ((pdims (array:dimensions prototype)))
+ (case (apply * pdims)
+ ((0) (make-vector tcnt))
+ (else (make-vector tcnt
+ (apply array-ref prototype
+ (map (lambda (x) 0) pdims)))))))))
+ (define (loop dims scales)
+ (if (null? dims)
+ (array:construct dimensions (cdr scales) 0 store)
+ (loop (cdr dims) (cons (* (car dims) (car scales)) scales))))
+ (loop (reverse dimensions) '(1))))
+;;@args prototype k1 @dots{}
+;;@0 is an alias for @code{make-array}.
+(define create-array make-array)
+
+;;@args array mapper k1 @dots{}
+;;@0 can be used to create shared subarrays of other
+;;arrays. The @var{mapper} is a function that translates coordinates in
+;;the new array into coordinates in the old array. A @var{mapper} must be
+;;linear, and its range must stay within the bounds of the old array, but
+;;it can be otherwise arbitrary. A simple example:
+;;
+;;@example
+;;(define fred (make-array '#(#f) 8 8))
+;;(define freds-diagonal
+;; (make-shared-array fred (lambda (i) (list i i)) 8))
+;;(array-set! freds-diagonal 'foo 3)
+;;(array-ref fred 3 3)
+;; @result{} FOO
+;;(define freds-center
+;; (make-shared-array fred (lambda (i j) (list (+ 3 i) (+ 3 j)))
+;; 2 2))
+;;(array-ref freds-center 0 0)
+;; @result{} FOO
+;;@end example
+(define (make-shared-array array mapper . dimensions)
+ (define odl (array:scales array))
+ (define rank (length dimensions))
+ (define shape
+ (map (lambda (dim) (if (list? dim) dim (list 0 (+ -1 dim)))) dimensions))
+ (do ((idx (+ -1 rank) (+ -1 idx))
+ (uvt (append (cdr (vector->list (make-vector rank 0))) '(1))
+ (append (cdr uvt) '(0)))
+ (uvts '() (cons uvt uvts)))
+ ((negative? idx)
+ (let ((ker0 (apply + (map * odl (apply mapper uvt)))))
+ (array:construct
+ (map (lambda (dim) (+ 1 (- (cadr dim) (car dim)))) shape)
+ (map (lambda (uvt) (- (apply + (map * odl (apply mapper uvt))) ker0))
+ uvts)
+ (apply +
+ (array:offset array)
+ (map * odl (apply mapper (map car shape))))
+ (array:store array))))))
+
+;;@args rank proto list
+;;@3 must be a rank-nested list consisting of all the elements, in
+;;row-major order, of the array to be created.
+;;
+;;@0 returns an array of rank @1 and type @2 consisting of all the
+;;elements, in row-major order, of @3. When @1 is 0, @3 is the lone
+;;array element; not necessarily a list.
+;;
+;;@example
+;;(list->array 2 '#() '((1 2) (3 4)))
+;; @result{} #2A((1 2) (3 4))
+;;(list->array 0 '#() 3)
+;; @result{} #0A 3
+;;@end example
+(define (list->array rank proto lst)
+ (define dimensions
+ (do ((shp '() (cons (length row) shp))
+ (row lst (car lst))
+ (rnk (+ -1 rank) (+ -1 rnk)))
+ ((negative? rnk) (reverse shp))))
+ (let ((nra (apply make-array proto dimensions)))
+ (define (l2ra dims idxs row)
+ (cond ((null? dims)
+ (apply array-set! nra row (reverse idxs)))
+ ((if (not (eqv? (car dims) (length row)))
+ (slib:error 'list->array
+ 'non-rectangular 'array dims dimensions))
+ (do ((idx 0 (+ 1 idx))
+ (row row (cdr row)))
+ ((>= idx (car dims)))
+ (l2ra (cdr dims) (cons idx idxs) (car row))))))
+ (l2ra dimensions '() lst)
+ nra))
+
+;;@args array
+;;Returns a rank-nested list consisting of all the elements, in
+;;row-major order, of @1. In the case of a rank-0 array, @0 returns
+;;the single element.
+;;
+;;@example
+;;(array->list #2A((ho ho ho) (ho oh oh)))
+;; @result{} ((ho ho ho) (ho oh oh))
+;;(array->list #0A ho)
+;; @result{} ho
+;;@end example
+(define (array->list ra)
+ (define (ra2l dims idxs)
+ (if (null? dims)
+ (apply array-ref ra (reverse idxs))
+ (do ((lst '() (cons (ra2l (cdr dims) (cons idx idxs)) lst))
+ (idx (+ -1 (car dims)) (+ -1 idx)))
+ ((negative? idx) lst))))
+ (ra2l (array-dimensions ra) '()))
+
+;;@args vect proto dim1 @dots{}
+;;@1 must be a vector of length equal to the product of exact
+;;nonnegative integers @3, @dots{}.
+;;
+;;@0 returns an array of type @2 consisting of all the elements, in
+;;row-major order, of @1. In the case of a rank-0 array, @1 has a
+;;single element.
+;;
+;;@example
+;;(vector->array #(1 2 3 4) #() 2 2)
+;; @result{} #2A((1 2) (3 4))
+;;(vector->array '#(3) '#())
+;; @result{} #0A 3
+;;@end example
+(define (vector->array vect prototype . dimensions)
+ (define vdx (vector-length vect))
+ (if (not (eqv? vdx (apply * dimensions)))
+ (slib:error 'vector->array vdx '<> (cons '* dimensions)))
+ (let ((ra (apply make-array prototype dimensions)))
+ (define (v2ra dims idxs)
+ (cond ((null? dims)
+ (set! vdx (+ -1 vdx))
+ (apply array-set! ra (vector-ref vect vdx) (reverse idxs)))
+ (else
+ (do ((idx (+ -1 (car dims)) (+ -1 idx)))
+ ((negative? idx) vect)
+ (v2ra (cdr dims) (cons idx idxs))))))
+ (v2ra dimensions '())
+ ra))
+
+;;@args array
+;;Returns a new vector consisting of all the elements of @1 in
+;;row-major order.
+;;
+;;@example
+;;(array->vector #2A ((1 2)( 3 4)))
+;; @result{} #(1 2 3 4)
+;;(array->vector #0A ho)
+;; @result{} #(ho)
+;;@end example
+(define (array->vector ra)
+ (define dims (array-dimensions ra))
+ (let* ((vdx (apply * dims))
+ (vect (make-vector vdx)))
+ (define (ra2v dims idxs)
+ (if (null? dims)
+ (let ((val (apply array-ref ra (reverse idxs))))
+ (set! vdx (+ -1 vdx))
+ (vector-set! vect vdx val)
+ vect)
+ (do ((idx (+ -1 (car dims)) (+ -1 idx)))
+ ((negative? idx) vect)
+ (ra2v (cdr dims) (cons idx idxs)))))
+ (ra2v dims '())))
+
+(define (array:in-bounds? array indices)
+ (do ((bnds (array:dimensions array) (cdr bnds))
+ (idxs indices (cdr idxs)))
+ ((or (null? bnds)
+ (null? idxs)
+ (not (integer? (car idxs)))
+ (not (< -1 (car idxs) (car bnds))))
+ (and (null? bnds) (null? idxs)))))
+
+;;@args array index1 @dots{}
+;;Returns @code{#t} if its arguments would be acceptable to
+;;@code{array-ref}.
+(define (array-in-bounds? array . indices)
+ (array:in-bounds? array indices))
+
+;;@args array k1 @dots{}
+;;Returns the (@2, @dots{}) element of @1.
+(define (array-ref array . indices)
+ (define store (array:store array))
+ (or (array:in-bounds? array indices)
+ (slib:error 'array-ref 'bad-indices indices))
+ ((if (string? store) string-ref vector-ref)
+ store (apply + (array:offset array) (map * (array:scales array) indices))))
+
+;;@args array obj k1 @dots{}
+;;Stores @2 in the (@3, @dots{}) element of @1. The value returned
+;;by @0 is unspecified.
+(define (array-set! array obj . indices)
+ (define store (array:store array))
+ (or (array:in-bounds? array indices)
+ (slib:error 'array-set! 'bad-indices indices))
+ ((if (string? store) string-set! vector-set!)
+ store (apply + (array:offset array) (map * (array:scales array) indices))
+ obj))
+
+;;@noindent
+;;These functions return a prototypical uniform-array enclosing the
+;;optional argument (which must be of the correct type). If the
+;;uniform-array type is supported by the implementation, then it is
+;;returned; defaulting to the next larger precision type; resorting
+;;finally to vector.
+
+(define (make-prototype-checker name pred? creator)
+ (lambda args
+ (case (length args)
+ ((1) (if (pred? (car args))
+ (creator (car args))
+ (slib:error name 'incompatible 'type (car args))))
+ ((0) (creator))
+ (else (slib:error name 'wrong 'number 'of 'args args)))))
+
+(define (integer-bytes?? n)
+ (lambda (obj)
+ (and (integer? obj)
+ (exact? obj)
+ (or (negative? n) (not (negative? obj)))
+ (do ((num obj (quotient num 256))
+ (n (+ -1 (abs n)) (+ -1 n)))
+ ((or (zero? num) (negative? n))
+ (zero? num))))))
+
+;;@args z
+;;@args
+;;Returns an inexact 128.bit flonum complex uniform-array prototype.
+(define A:floC128b (make-prototype-checker 'A:floC128b complex? vector))
+;;@args z
+;;@args
+;;Returns an inexact 64.bit flonum complex uniform-array prototype.
+(define A:floC64b (make-prototype-checker 'A:floC64b complex? vector))
+;;@args z
+;;@args
+;;Returns an inexact 32.bit flonum complex uniform-array prototype.
+(define A:floC32b (make-prototype-checker 'A:floC32b complex? vector))
+;;@args z
+;;@args
+;;Returns an inexact 16.bit flonum complex uniform-array prototype.
+(define A:floC16b (make-prototype-checker 'A:floC16b complex? vector))
+
+;;@args z
+;;@args
+;;Returns an inexact 128.bit flonum real uniform-array prototype.
+(define A:floR128b (make-prototype-checker 'A:floR128b real? vector))
+;;@args z
+;;@args
+;;Returns an inexact 64.bit flonum real uniform-array prototype.
+(define A:floR64b (make-prototype-checker 'A:floR64b real? vector))
+;;@args z
+;;@args
+;;Returns an inexact 32.bit flonum real uniform-array prototype.
+(define A:floR32b (make-prototype-checker 'A:floR32b real? vector))
+;;@args z
+;;@args
+;;Returns an inexact 16.bit flonum real uniform-array prototype.
+(define A:floR16b (make-prototype-checker 'A:floR16b real? vector))
+
+;;@args z
+;;@args
+;;Returns an exact 128.bit decimal flonum rational uniform-array prototype.
+(define A:floR128b (make-prototype-checker 'A:floR128b real? vector))
+;;@args z
+;;@args
+;;Returns an exact 64.bit decimal flonum rational uniform-array prototype.
+(define A:floR64b (make-prototype-checker 'A:floR64b real? vector))
+;;@args z
+;;@args
+;;Returns an exact 32.bit decimal flonum rational uniform-array prototype.
+(define A:floR32b (make-prototype-checker 'A:floR32b real? vector))
+
+;;@args n
+;;@args
+;;Returns an exact binary fixnum uniform-array prototype with at least
+;;64 bits of precision.
+(define A:fixZ64b (make-prototype-checker 'A:fixZ64b (integer-bytes?? -8) vector))
+;;@args n
+;;@args
+;;Returns an exact binary fixnum uniform-array prototype with at least
+;;32 bits of precision.
+(define A:fixZ32b (make-prototype-checker 'A:fixZ32b (integer-bytes?? -4) vector))
+;;@args n
+;;@args
+;;Returns an exact binary fixnum uniform-array prototype with at least
+;;16 bits of precision.
+(define A:fixZ16b (make-prototype-checker 'A:fixZ16b (integer-bytes?? -2) vector))
+;;@args n
+;;@args
+;;Returns an exact binary fixnum uniform-array prototype with at least
+;;8 bits of precision.
+(define A:fixZ8b (make-prototype-checker 'A:fixZ8b (integer-bytes?? -1) vector))
+
+;;@args k
+;;@args
+;;Returns an exact non-negative binary fixnum uniform-array prototype with at
+;;least 64 bits of precision.
+(define A:fixN64b (make-prototype-checker 'A:fixN64b (integer-bytes?? 8) vector))
+;;@args k
+;;@args
+;;Returns an exact non-negative binary fixnum uniform-array prototype with at
+;;least 32 bits of precision.
+(define A:fixN32b (make-prototype-checker 'A:fixN32b (integer-bytes?? 4) vector))
+;;@args k
+;;@args
+;;Returns an exact non-negative binary fixnum uniform-array prototype with at
+;;least 16 bits of precision.
+(define A:fixN16b (make-prototype-checker 'A:fixN16b (integer-bytes?? 2) vector))
+;;@args k
+;;@args
+;;Returns an exact non-negative binary fixnum uniform-array prototype with at
+;;least 8 bits of precision.
+(define A:fixN8b (make-prototype-checker 'A:fixN8b (integer-bytes?? 1) vector))
+
+;;@args bool
+;;@args
+;;Returns a boolean uniform-array prototype.
+(define A:bool (make-prototype-checker 'A:bool boolean? vector))
+; REFERENCE IMPLEMENTATION FOR SRFI-26 "CUT"
+; ==========================================
+;
+; Sebastian.Egner@philips.com, 5-Jun-2002.
+; adapted from the posting by Al Petrofsky <al@petrofsky.org>
+; placed in the public domain
+;
+; The code to handle the variable argument case was originally
+; proposed by Michael Sperber and has been adapted to the new
+; syntax of the macro using an explicit rest-slot symbol. The
+; code to evaluate the non-slots for cute has been proposed by
+; Dale Jordan. The code to allow a slot for the procedure position
+; and to process the macro using an internal macro is based on
+; a suggestion by Al Petrofsky. The code found below is, with
+; exception of this header and some changes in variable names,
+; entirely written by Al Petrofsky.
+;
+; compliance:
+; Scheme R5RS (including macros).
+;
+; loading this file into Scheme 48 0.57:
+; ,load cut.scm
+;
+; history of this file:
+; SE, 6-Feb-2002: initial version as 'curry' with ". <>" notation
+; SE, 14-Feb-2002: revised for <___>
+; SE, 27-Feb-2002: revised for 'cut'
+; SE, 03-Jun-2002: revised for proc-slot, cute
+; SE, 04-Jun-2002: rewritten with internal transformer (no "loop" pattern)
+; SE, 05-Jun-2002: replace my code by Al's; substituted "constant" etc.
+; to match the convention in the SRFI-document
+
+; (srfi-26-internal-cut slot-names combination . se)
+; transformer used internally
+; slot-names : the internal names of the slots
+; combination : procedure being specialized, followed by its arguments
+; se : slots-or-exprs, the qualifiers of the macro
+
+(define-syntax srfi-26-internal-cut
+ (syntax-rules (<> <___>)
+
+ ;; construct fixed- or variable-arity procedure:
+ ;; (begin proc) throws an error if proc is not an <expression>
+ ((srfi-26-internal-cut (slot-name ...) (proc arg ...))
+ (lambda (slot-name ...) ((begin proc) arg ...)))
+ ((srfi-26-internal-cut (slot-name ...) (proc arg ...) <___>)
+ (lambda (slot-name ... . rest-slot) (apply proc arg ... rest-slot)))
+
+ ;; process one slot-or-expr
+ ((srfi-26-internal-cut (slot-name ...) (position ...) <> . se)
+ (srfi-26-internal-cut (slot-name ... x) (position ... x) . se))
+ ((srfi-26-internal-cut (slot-name ...) (position ...) nse . se)
+ (srfi-26-internal-cut (slot-name ...) (position ... nse) . se))))
+
+; (srfi-26-internal-cute slot-names nse-bindings combination . se)
+; transformer used internally
+; slot-names : the internal names of the slots
+; nse-bindings : let-style bindings for the non-slot expressions.
+; combination : procedure being specialized, followed by its arguments
+; se : slots-or-exprs, the qualifiers of the macro
+
+(define-syntax srfi-26-internal-cute
+ (syntax-rules (<> <___>)
+
+ ;; If there are no slot-or-exprs to process, then:
+ ;; construct a fixed-arity procedure,
+ ((srfi-26-internal-cute
+ (slot-name ...) nse-bindings (proc arg ...))
+ (let nse-bindings (lambda (slot-name ...) (proc arg ...))))
+ ;; or a variable-arity procedure
+ ((srfi-26-internal-cute
+ (slot-name ...) nse-bindings (proc arg ...) <___>)
+ (let nse-bindings (lambda (slot-name ... . x) (apply proc arg ... x))))
+
+ ;; otherwise, process one slot:
+ ((srfi-26-internal-cute
+ (slot-name ...) nse-bindings (position ...) <> . se)
+ (srfi-26-internal-cute
+ (slot-name ... x) nse-bindings (position ... x) . se))
+ ;; or one non-slot expression
+ ((srfi-26-internal-cute
+ slot-names nse-bindings (position ...) nse . se)
+ (srfi-26-internal-cute
+ slot-names ((x nse) . nse-bindings) (position ... x) . se))))
+
+; exported syntax
+
+(define-syntax cut
+ (syntax-rules ()
+ ((cut . slots-or-exprs)
+ (srfi-26-internal-cut () () . slots-or-exprs))))
+
+(define-syntax cute
+ (syntax-rules ()
+ ((cute . slots-or-exprs)
+ (srfi-26-internal-cute () () () . slots-or-exprs))))
+;;;; "logical.scm", bit access and operations for integers for Scheme
+;;; Copyright (C) 1991, 1993, 2001, 2003, 2005 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.
+
+(define logical:boole-xor
+ '#(#(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)
+ #(1 0 3 2 5 4 7 6 9 8 11 10 13 12 15 14)
+ #(2 3 0 1 6 7 4 5 10 11 8 9 14 15 12 13)
+ #(3 2 1 0 7 6 5 4 11 10 9 8 15 14 13 12)
+ #(4 5 6 7 0 1 2 3 12 13 14 15 8 9 10 11)
+ #(5 4 7 6 1 0 3 2 13 12 15 14 9 8 11 10)
+ #(6 7 4 5 2 3 0 1 14 15 12 13 10 11 8 9)
+ #(7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8)
+ #(8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7)
+ #(9 8 11 10 13 12 15 14 1 0 3 2 5 4 7 6)
+ #(10 11 8 9 14 15 12 13 2 3 0 1 6 7 4 5)
+ #(11 10 9 8 15 14 13 12 3 2 1 0 7 6 5 4)
+ #(12 13 14 15 8 9 10 11 4 5 6 7 0 1 2 3)
+ #(13 12 15 14 9 8 11 10 5 4 7 6 1 0 3 2)
+ #(14 15 12 13 10 11 8 9 6 7 4 5 2 3 0 1)
+ #(15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0)))
+
+(define logical:boole-and
+ '#(#(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
+ #(0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1)
+ #(0 0 2 2 0 0 2 2 0 0 2 2 0 0 2 2)
+ #(0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3)
+ #(0 0 0 0 4 4 4 4 0 0 0 0 4 4 4 4)
+ #(0 1 0 1 4 5 4 5 0 1 0 1 4 5 4 5)
+ #(0 0 2 2 4 4 6 6 0 0 2 2 4 4 6 6)
+ #(0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7)
+ #(0 0 0 0 0 0 0 0 8 8 8 8 8 8 8 8)
+ #(0 1 0 1 0 1 0 1 8 9 8 9 8 9 8 9)
+ #(0 0 2 2 0 0 2 2 8 8 10 10 8 8 10 10)
+ #(0 1 2 3 0 1 2 3 8 9 10 11 8 9 10 11)
+ #(0 0 0 0 4 4 4 4 8 8 8 8 12 12 12 12)
+ #(0 1 0 1 4 5 4 5 8 9 8 9 12 13 12 13)
+ #(0 0 2 2 4 4 6 6 8 8 10 10 12 12 14 14)
+ #(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)))
+
+(define (logical:ash-4 x)
+ (if (negative? x)
+ (+ -1 (quotient (+ 1 x) 16))
+ (quotient x 16)))
+
+(define (logical:reduce op4 ident)
+ (lambda args
+ (do ((res ident (op4 res (car rgs) 1 0))
+ (rgs args (cdr rgs)))
+ ((null? rgs) res))))
+
+;@
+(define logand
+ (letrec
+ ((lgand
+ (lambda (n2 n1 scl acc)
+ (cond ((= n1 n2) (+ acc (* scl n1)))
+ ((zero? n2) acc)
+ ((zero? n1) acc)
+ (else (lgand (logical:ash-4 n2)
+ (logical:ash-4 n1)
+ (* 16 scl)
+ (+ (* (vector-ref (vector-ref logical:boole-and
+ (modulo n1 16))
+ (modulo n2 16))
+ scl)
+ acc)))))))
+ (logical:reduce lgand -1)))
+;@
+(define logior
+ (letrec
+ ((lgior
+ (lambda (n2 n1 scl acc)
+ (cond ((= n1 n2) (+ acc (* scl n1)))
+ ((zero? n2) (+ acc (* scl n1)))
+ ((zero? n1) (+ acc (* scl n2)))
+ (else (lgior (logical:ash-4 n2)
+ (logical:ash-4 n1)
+ (* 16 scl)
+ (+ (* (- 15 (vector-ref
+ (vector-ref logical:boole-and
+ (- 15 (modulo n1 16)))
+ (- 15 (modulo n2 16))))
+ scl)
+ acc)))))))
+ (logical:reduce lgior 0)))
+;@
+(define logxor
+ (letrec
+ ((lgxor
+ (lambda (n2 n1 scl acc)
+ (cond ((= n1 n2) acc)
+ ((zero? n2) (+ acc (* scl n1)))
+ ((zero? n1) (+ acc (* scl n2)))
+ (else (lgxor (logical:ash-4 n2)
+ (logical:ash-4 n1)
+ (* 16 scl)
+ (+ (* (vector-ref (vector-ref logical:boole-xor
+ (modulo n1 16))
+ (modulo n2 16))
+ scl)
+ acc)))))))
+ (logical:reduce lgxor 0)))
+;@
+(define (lognot n) (- -1 n))
+;@
+(define (logtest n1 n2)
+ (not (zero? (logand n1 n2))))
+;@
+(define (logbit? index n)
+ (logtest (expt 2 index) n))
+;@
+(define (copy-bit index to bool)
+ (if bool
+ (logior to (arithmetic-shift 1 index))
+ (logand to (lognot (arithmetic-shift 1 index)))))
+;@
+(define (bitwise-if mask n0 n1)
+ (logior (logand mask n0)
+ (logand (lognot mask) n1)))
+;@
+(define (bit-field n start end)
+ (logand (lognot (ash -1 (- end start)))
+ (arithmetic-shift n (- start))))
+;@
+(define (copy-bit-field to from start end)
+ (bitwise-if (arithmetic-shift (lognot (ash -1 (- end start))) start)
+ (arithmetic-shift from start)
+ to))
+;@
+(define (rotate-bit-field n count start end)
+ (define width (- end start))
+ (set! count (modulo count width))
+ (let ((mask (lognot (ash -1 width))))
+ (define zn (logand mask (arithmetic-shift n (- start))))
+ (logior (arithmetic-shift
+ (logior (logand mask (arithmetic-shift zn count))
+ (arithmetic-shift zn (- count width)))
+ start)
+ (logand (lognot (ash mask start)) n))))
+;@
+(define (arithmetic-shift n count)
+ (if (negative? count)
+ (let ((k (expt 2 (- count))))
+ (if (negative? n)
+ (+ -1 (quotient (+ 1 n) k))
+ (quotient n k)))
+ (* (expt 2 count) n)))
+;@
+(define integer-length
+ (letrec ((intlen (lambda (n tot)
+ (case n
+ ((0 -1) (+ 0 tot))
+ ((1 -2) (+ 1 tot))
+ ((2 3 -3 -4) (+ 2 tot))
+ ((4 5 6 7 -5 -6 -7 -8) (+ 3 tot))
+ (else (intlen (logical:ash-4 n) (+ 4 tot)))))))
+ (lambda (n) (intlen n 0))))
+;@
+(define logcount
+ (letrec ((logcnt (lambda (n tot)
+ (if (zero? n)
+ tot
+ (logcnt (quotient n 16)
+ (+ (vector-ref
+ '#(0 1 1 2 1 2 2 3 1 2 2 3 2 3 3 4)
+ (modulo n 16))
+ tot))))))
+ (lambda (n)
+ (cond ((negative? n) (logcnt (lognot n) 0))
+ ((positive? n) (logcnt n 0))
+ (else 0)))))
+;@
+(define (log2-binary-factors n)
+ (+ -1 (integer-length (logand n (- n)))))
+
+(define (bit-reverse k n)
+ (do ((m (if (negative? n) (lognot n) n) (arithmetic-shift m -1))
+ (k (+ -1 k) (+ -1 k))
+ (rvs 0 (logior (arithmetic-shift rvs 1) (logand 1 m))))
+ ((negative? k) (if (negative? n) (lognot rvs) rvs))))
+;@
+(define (reverse-bit-field n start end)
+ (define width (- end start))
+ (let ((mask (lognot (ash -1 width))))
+ (define zn (logand mask (arithmetic-shift n (- start))))
+ (logior (arithmetic-shift (bit-reverse width zn) start)
+ (logand (lognot (ash mask start)) n))))
+;@
+(define (integer->list k . len)
+ (if (null? len)
+ (do ((k k (arithmetic-shift k -1))
+ (lst '() (cons (odd? k) lst)))
+ ((<= k 0) lst))
+ (do ((idx (+ -1 (car len)) (+ -1 idx))
+ (k k (arithmetic-shift k -1))
+ (lst '() (cons (odd? k) lst)))
+ ((negative? idx) lst))))
+;@
+(define (list->integer bools)
+ (do ((bs bools (cdr bs))
+ (acc 0 (+ acc acc (if (car bs) 1 0))))
+ ((null? bs) acc)))
+(define (booleans->integer . bools)
+ (list->integer bools))
+
+;;;;@ SRFI-60 aliases
+(define ash arithmetic-shift)
+(define bitwise-ior logior)
+(define bitwise-xor logxor)
+(define bitwise-and logand)
+(define bitwise-not lognot)
+(define bit-count logcount)
+(define bit-set? logbit?)
+(define any-bits-set? logtest)
+(define first-set-bit log2-binary-factors)
+(define bitwise-merge bitwise-if)
+
+;;; Legacy
+;;(define (logical:rotate k count len) (rotate-bit-field k count 0 len))
+;;(define (logical:ones deg) (lognot (ash -1 deg)))
+;;(define integer-expt expt) ; legacy name
+; 54-BIT INTEGER IMPLEMENTATION OF THE "MRG32K3A"-GENERATOR
+; =========================================================
+;
+; Sebastian.Egner@philips.com, Mar-2002.
+;
+; This file is an implementation of Pierre L'Ecuyer's MRG32k3a
+; pseudo random number generator. Please refer to 'mrg32k3a.scm'
+; for more information.
+;
+; compliance:
+; Scheme R5RS with integers covering at least {-2^53..2^53-1}.
+;
+; history of this file:
+; SE, 18-Mar-2002: initial version
+; SE, 22-Mar-2002: comments adjusted, range added
+; SE, 25-Mar-2002: pack/unpack just return their argument
+
+; the actual generator
+
+(define (mrg32k3a-random-m1 state)
+ (let ((x11 (vector-ref state 0))
+ (x12 (vector-ref state 1))
+ (x13 (vector-ref state 2))
+ (x21 (vector-ref state 3))
+ (x22 (vector-ref state 4))
+ (x23 (vector-ref state 5)))
+ (let ((x10 (modulo (- (* 1403580 x12) (* 810728 x13)) 4294967087))
+ (x20 (modulo (- (* 527612 x21) (* 1370589 x23)) 4294944443)))
+ (vector-set! state 0 x10)
+ (vector-set! state 1 x11)
+ (vector-set! state 2 x12)
+ (vector-set! state 3 x20)
+ (vector-set! state 4 x21)
+ (vector-set! state 5 x22)
+ (modulo (- x10 x20) 4294967087))))
+
+; interface to the generic parts of the generator
+
+(define (mrg32k3a-pack-state unpacked-state)
+ unpacked-state)
+
+(define (mrg32k3a-unpack-state state)
+ state)
+
+(define (mrg32k3a-random-range) ; m1
+ 4294967087)
+
+(define (mrg32k3a-random-integer state range) ; rejection method
+ (let* ((q (quotient 4294967087 range))
+ (qn (* q range)))
+ (do ((x (mrg32k3a-random-m1 state) (mrg32k3a-random-m1 state)))
+ ((< x qn) (quotient x q)))))
+
+(define (mrg32k3a-random-real state) ; normalization is 1/(m1+1)
+ (* 0.0000000002328306549295728 (+ 1.0 (mrg32k3a-random-m1 state))))
+
+;;; Copyright (C) 2004 Taylor Campbell. All rights reserved.
+
+;;; Made an R7RS library by Taylan Ulrich Bayırlı/Kammer, Copyright (C) 2014.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+(define-library (srfi 61)
+ (export cond)
+ (import (except (scheme base) cond))
+ (begin
+
+ (define-syntax cond
+ (syntax-rules (=> else)
+
+ ((cond (else else1 else2 ...))
+ ;; The (if #t (begin ...)) wrapper ensures that there may be no
+ ;; internal definitions in the body of the clause. R5RS mandates
+ ;; this in text (by referring to each subform of the clauses as
+ ;; <expression>) but not in its reference implementation of `cond',
+ ;; which just expands to (begin ...) with no (if #t ...) wrapper.
+ (if #t (begin else1 else2 ...)))
+
+ ((cond (test => receiver) more-clause ...)
+ (let ((t test))
+ (cond/maybe-more t
+ (receiver t)
+ more-clause ...)))
+
+ ((cond (generator guard => receiver) more-clause ...)
+ (call-with-values (lambda () generator)
+ (lambda t
+ (cond/maybe-more (apply guard t)
+ (apply receiver t)
+ more-clause ...))))
+
+ ((cond (test) more-clause ...)
+ (let ((t test))
+ (cond/maybe-more t t more-clause ...)))
+
+ ((cond (test body1 body2 ...) more-clause ...)
+ (cond/maybe-more test
+ (begin body1 body2 ...)
+ more-clause ...))))
+
+ (define-syntax cond/maybe-more
+ (syntax-rules ()
+ ((cond/maybe-more test consequent)
+ (if test
+ consequent))
+ ((cond/maybe-more test consequent clause ...)
+ (if test
+ consequent
+ (cond clause ...)))))
+
+ ))
+; GENERIC PART OF MRG32k3a-GENERATOR FOR SRFI-27
+; ==============================================
+;
+; Sebastian.Egner@philips.com, 2002.
+;
+; This is the generic R5RS-part of the implementation of the MRG32k3a
+; generator to be used in SRFI-27. It is based on a separate implementation
+; of the core generator (presumably in native code) and on code to
+; provide essential functionality not available in R5RS (see below).
+;
+; compliance:
+; Scheme R5RS with integer covering at least {-2^53..2^53-1}.
+; In addition,
+; SRFI-23: error
+;
+; history of this file:
+; SE, 22-Mar-2002: refactored from earlier versions
+; SE, 25-Mar-2002: pack/unpack need not allocate
+; SE, 27-Mar-2002: changed interface to core generator
+; SE, 10-Apr-2002: updated spec of mrg32k3a-random-integer
+
+; Generator
+; =========
+;
+; Pierre L'Ecuyer's MRG32k3a generator is a Combined Multiple Recursive
+; Generator. It produces the sequence {(x[1,n] - x[2,n]) mod m1 : n}
+; defined by the two recursive generators
+;
+; x[1,n] = ( a12 x[1,n-2] + a13 x[1,n-3]) mod m1,
+; x[2,n] = (a21 x[2,n-1] + a23 x[2,n-3]) mod m2,
+;
+; where the constants are
+; m1 = 4294967087 = 2^32 - 209 modulus of 1st component
+; m2 = 4294944443 = 2^32 - 22853 modulus of 2nd component
+; a12 = 1403580 recursion coefficients
+; a13 = -810728
+; a21 = 527612
+; a23 = -1370589
+;
+; The generator passes all tests of G. Marsaglia's Diehard testsuite.
+; Its period is (m1^3 - 1)(m2^3 - 1)/2 which is nearly 2^191.
+; L'Ecuyer reports: "This generator is well-behaved in all dimensions
+; up to at least 45: ..." [with respect to the spectral test, SE].
+;
+; The period is maximal for all values of the seed as long as the
+; state of both recursive generators is not entirely zero.
+;
+; As the successor state is a linear combination of previous
+; states, it is possible to advance the generator by more than one
+; iteration by applying a linear transformation. The following
+; publication provides detailed information on how to do that:
+;
+; [1] P. L'Ecuyer, R. Simard, E. J. Chen, W. D. Kelton:
+; An Object-Oriented Random-Number Package With Many Long
+; Streams and Substreams. 2001.
+; To appear in Operations Research.
+;
+; Arithmetics
+; ===========
+;
+; The MRG32k3a generator produces values in {0..2^32-209-1}. All
+; subexpressions of the actual generator fit into {-2^53..2^53-1}.
+; The code below assumes that Scheme's "integer" covers this range.
+; In addition, it is assumed that floating point literals can be
+; read and there is some arithmetics with inexact numbers.
+;
+; However, for advancing the state of the generator by more than
+; one step at a time, the full range {0..2^32-209-1} is needed.
+
+
+; Required: Backbone Generator
+; ============================
+;
+; At this point in the code, the following procedures are assumed
+; to be defined to execute the core generator:
+;
+; (mrg32k3a-pack-state unpacked-state) -> packed-state
+; (mrg32k3a-unpack-state packed-state) -> unpacked-state
+; pack/unpack a state of the generator. The core generator works
+; on packed states, passed as an explicit argument, only. This
+; allows native code implementations to store their state in a
+; suitable form. Unpacked states are #(x10 x11 x12 x20 x21 x22)
+; with integer x_ij. Pack/unpack need not allocate new objects
+; in case packed and unpacked states are identical.
+;
+; (mrg32k3a-random-range) -> m-max
+; (mrg32k3a-random-integer packed-state range) -> x in {0..range-1}
+; advance the state of the generator and return the next random
+; range-limited integer.
+; Note that the state is not necessarily advanced by just one
+; step because we use the rejection method to avoid any problems
+; with distribution anomalies.
+; The range argument must be an exact integer in {1..m-max}.
+; It can be assumed that range is a fixnum if the Scheme system
+; has such a number representation.
+;
+; (mrg32k3a-random-real packed-state) -> x in (0,1)
+; advance the state of the generator and return the next random
+; real number between zero and one (both excluded). The type of
+; the result should be a flonum if possible.
+
+; Required: Record Data Type
+; ==========================
+;
+; At this point in the code, the following procedures are assumed
+; to be defined to create and access a new record data type:
+;
+; (\:random-source-make a0 a1 a2 a3 a4 a5) -> s
+; constructs a new random source object s consisting of the
+; objects a0 .. a5 in this order.
+;
+; (\:random-source? obj) -> bool
+; tests if a Scheme object is a :random-source.
+;
+; (\:random-source-state-ref s) -> a0
+; (\:random-source-state-set! s) -> a1
+; (\:random-source-randomize! s) -> a2
+; (\:random-source-pseudo-randomize! s) -> a3
+; (\:random-source-make-integers s) -> a4
+; (\:random-source-make-reals s) -> a5
+; retrieve the values in the fields of the object s.
+
+; Required: Current Time as an Integer
+; ====================================
+;
+; At this point in the code, the following procedure is assumed
+; to be defined to obtain a value that is likely to be different
+; for each invokation of the Scheme system:
+;
+; (\:random-source-current-time) -> x
+; an integer that depends on the system clock. It is desired
+; that the integer changes as fast as possible.
+
+
+; Accessing the State
+; ===================
+
+(define (mrg32k3a-state-ref packed-state)
+ (cons 'lecuyer-mrg32k3a
+ (vector->list (mrg32k3a-unpack-state packed-state))))
+
+(define (mrg32k3a-state-set external-state)
+
+ (define (check-value x m)
+ (if (and (integer? x)
+ (exact? x)
+ (<= 0 x (- m 1)))
+ #t
+ (error "illegal value" x)))
+
+ (if (and (list? external-state)
+ (= (length external-state) 7)
+ (eq? (car external-state) 'lecuyer-mrg32k3a))
+ (let ((s (cdr external-state)))
+ (check-value (list-ref s 0) mrg32k3a-m1)
+ (check-value (list-ref s 1) mrg32k3a-m1)
+ (check-value (list-ref s 2) mrg32k3a-m1)
+ (check-value (list-ref s 3) mrg32k3a-m2)
+ (check-value (list-ref s 4) mrg32k3a-m2)
+ (check-value (list-ref s 5) mrg32k3a-m2)
+ (if (or (zero? (+ (list-ref s 0) (list-ref s 1) (list-ref s 2)))
+ (zero? (+ (list-ref s 3) (list-ref s 4) (list-ref s 5))))
+ (error "illegal degenerate state" external-state))
+ (mrg32k3a-pack-state (list->vector s)))
+ (error "malformed state" external-state)))
+
+
+; Pseudo-Randomization
+; ====================
+;
+; Reference [1] above shows how to obtain many long streams and
+; substream from the backbone generator.
+;
+; The idea is that the generator is a linear operation on the state.
+; Hence, we can express this operation as a 3x3-matrix acting on the
+; three most recent states. Raising the matrix to the k-th power, we
+; obtain the operation to advance the state by k steps at once. The
+; virtual streams and substreams are now simply parts of the entire
+; periodic sequence (which has period around 2^191).
+;
+; For the implementation it is necessary to compute with matrices in
+; the ring (Z/(m1*m1)*Z)^(3x3). By the Chinese-Remainder Theorem, this
+; is isomorphic to ((Z/m1*Z) x (Z/m2*Z))^(3x3). We represent such a pair
+; of matrices
+; [ [[x00 x01 x02],
+; [x10 x11 x12],
+; [x20 x21 x22]], mod m1
+; [[y00 y01 y02],
+; [y10 y11 y12],
+; [y20 y21 y22]] mod m2]
+; as a vector of length 18 of the integers as writen above:
+; #(x00 x01 x02 x10 x11 x12 x20 x21 x22
+; y00 y01 y02 y10 y11 y12 y20 y21 y22)
+;
+; As the implementation should only use the range {-2^53..2^53-1}, the
+; fundamental operation (x*y) mod m, where x, y, m are nearly 2^32,
+; is computed by breaking up x and y as x = x1*w + x0 and y = y1*w + y0
+; where w = 2^16. In this case, all operations fit the range because
+; w^2 mod m is a small number. If proper multiprecision integers are
+; available this is not necessary, but pseudo-randomize! is an expected
+; to be called only occasionally so we do not provide this implementation.
+
+(define mrg32k3a-m1 4294967087) ; modulus of component 1
+(define mrg32k3a-m2 4294944443) ; modulus of component 2
+
+(define mrg32k3a-initial-state ; 0 3 6 9 12 15 of A^16, see below
+ '#( 1062452522
+ 2961816100
+ 342112271
+ 2854655037
+ 3321940838
+ 3542344109))
+
+(define mrg32k3a-generators #f) ; computed when needed
+
+(define (mrg32k3a-pseudo-randomize-state i j)
+
+ (define (product A B) ; A*B in ((Z/m1*Z) x (Z/m2*Z))^(3x3)
+
+ (define w 65536) ; wordsize to split {0..2^32-1}
+ (define w-sqr1 209) ; w^2 mod m1
+ (define w-sqr2 22853) ; w^2 mod m2
+
+ (define (lc i0 i1 i2 j0 j1 j2 m w-sqr) ; linear combination
+ (let ((a0h (quotient (vector-ref A i0) w))
+ (a0l (modulo (vector-ref A i0) w))
+ (a1h (quotient (vector-ref A i1) w))
+ (a1l (modulo (vector-ref A i1) w))
+ (a2h (quotient (vector-ref A i2) w))
+ (a2l (modulo (vector-ref A i2) w))
+ (b0h (quotient (vector-ref B j0) w))
+ (b0l (modulo (vector-ref B j0) w))
+ (b1h (quotient (vector-ref B j1) w))
+ (b1l (modulo (vector-ref B j1) w))
+ (b2h (quotient (vector-ref B j2) w))
+ (b2l (modulo (vector-ref B j2) w)))
+ (modulo
+ (+ (* (+ (* a0h b0h)
+ (* a1h b1h)
+ (* a2h b2h))
+ w-sqr)
+ (* (+ (* a0h b0l)
+ (* a0l b0h)
+ (* a1h b1l)
+ (* a1l b1h)
+ (* a2h b2l)
+ (* a2l b2h))
+ w)
+ (* a0l b0l)
+ (* a1l b1l)
+ (* a2l b2l))
+ m)))
+
+ (vector
+ (lc 0 1 2 0 3 6 mrg32k3a-m1 w-sqr1) ; (A*B)_00 mod m1
+ (lc 0 1 2 1 4 7 mrg32k3a-m1 w-sqr1) ; (A*B)_01
+ (lc 0 1 2 2 5 8 mrg32k3a-m1 w-sqr1)
+ (lc 3 4 5 0 3 6 mrg32k3a-m1 w-sqr1) ; (A*B)_10
+ (lc 3 4 5 1 4 7 mrg32k3a-m1 w-sqr1)
+ (lc 3 4 5 2 5 8 mrg32k3a-m1 w-sqr1)
+ (lc 6 7 8 0 3 6 mrg32k3a-m1 w-sqr1)
+ (lc 6 7 8 1 4 7 mrg32k3a-m1 w-sqr1)
+ (lc 6 7 8 2 5 8 mrg32k3a-m1 w-sqr1)
+ (lc 9 10 11 9 12 15 mrg32k3a-m2 w-sqr2) ; (A*B)_00 mod m2
+ (lc 9 10 11 10 13 16 mrg32k3a-m2 w-sqr2)
+ (lc 9 10 11 11 14 17 mrg32k3a-m2 w-sqr2)
+ (lc 12 13 14 9 12 15 mrg32k3a-m2 w-sqr2)
+ (lc 12 13 14 10 13 16 mrg32k3a-m2 w-sqr2)
+ (lc 12 13 14 11 14 17 mrg32k3a-m2 w-sqr2)
+ (lc 15 16 17 9 12 15 mrg32k3a-m2 w-sqr2)
+ (lc 15 16 17 10 13 16 mrg32k3a-m2 w-sqr2)
+ (lc 15 16 17 11 14 17 mrg32k3a-m2 w-sqr2)))
+
+ (define (power A e) ; A^e
+ (cond
+ ((zero? e)
+ '#(1 0 0 0 1 0 0 0 1 1 0 0 0 1 0 0 0 1))
+ ((= e 1)
+ A)
+ ((even? e)
+ (power (product A A) (quotient e 2)))
+ (else
+ (product (power A (- e 1)) A))))
+
+ (define (power-power A b) ; A^(2^b)
+ (if (zero? b)
+ A
+ (power-power (product A A) (- b 1))))
+
+ (define A ; the MRG32k3a recursion
+ '#( 0 1403580 4294156359
+ 1 0 0
+ 0 1 0
+ 527612 0 4293573854
+ 1 0 0
+ 0 1 0))
+
+ ; check arguments
+ (if (not (and (integer? i)
+ (exact? i)
+ (integer? j)
+ (exact? j)))
+ (error "i j must be exact integer" i j))
+
+ ; precompute A^(2^127) and A^(2^76) only once
+
+ (if (not mrg32k3a-generators)
+ (set! mrg32k3a-generators
+ (list (power-power A 127)
+ (power-power A 76)
+ (power A 16))))
+
+ ; compute M = A^(16 + i*2^127 + j*2^76)
+ (let ((M (product
+ (list-ref mrg32k3a-generators 2)
+ (product
+ (power (list-ref mrg32k3a-generators 0)
+ (modulo i (expt 2 28)))
+ (power (list-ref mrg32k3a-generators 1)
+ (modulo j (expt 2 28)))))))
+ (mrg32k3a-pack-state
+ (vector
+ (vector-ref M 0)
+ (vector-ref M 3)
+ (vector-ref M 6)
+ (vector-ref M 9)
+ (vector-ref M 12)
+ (vector-ref M 15)))))
+
+; True Randomization
+; ==================
+;
+; The value obtained from the system time is feed into a very
+; simple pseudo random number generator. This in turn is used
+; to obtain numbers to randomize the state of the MRG32k3a
+; generator, avoiding period degeneration.
+
+(define (mrg32k3a-randomize-state state)
+ ;; G. Marsaglia's simple 16-bit generator with carry
+ (let* ((m 65536)
+ (x (modulo (random-source-current-time) m)))
+ (define (random-m)
+ (let ((y (modulo x m)))
+ (set! x (+ (* 30903 y) (quotient x m)))
+ y))
+ (define (random n) ; m < n < m^2
+ (modulo (+ (* (random-m) m) (random-m)) n))
+
+ ; modify the state
+ (let ((m1 mrg32k3a-m1)
+ (m2 mrg32k3a-m2)
+ (s (mrg32k3a-unpack-state state)))
+ (mrg32k3a-pack-state
+ (vector
+ (+ 1 (modulo (+ (vector-ref s 0) (random (- m1 1))) (- m1 1)))
+ (modulo (+ (vector-ref s 1) (random m1)) m1)
+ (modulo (+ (vector-ref s 2) (random m1)) m1)
+ (+ 1 (modulo (+ (vector-ref s 3) (random (- m2 1))) (- m2 1)))
+ (modulo (+ (vector-ref s 4) (random m2)) m2)
+ (modulo (+ (vector-ref s 5) (random m2)) m2))))))
+
+
+; Large Integers
+; ==============
+;
+; To produce large integer random deviates, for n > m-max, we first
+; construct large random numbers in the range {0..m-max^k-1} for some
+; k such that m-max^k >= n and then use the rejection method to choose
+; uniformly from the range {0..n-1}.
+
+(define mrg32k3a-m-max
+ (mrg32k3a-random-range))
+
+(define (mrg32k3a-random-power state k) ; n = m-max^k, k >= 1
+ (if (= k 1)
+ (mrg32k3a-random-integer state mrg32k3a-m-max)
+ (+ (* (mrg32k3a-random-power state (- k 1)) mrg32k3a-m-max)
+ (mrg32k3a-random-integer state mrg32k3a-m-max))))
+
+(define (mrg32k3a-random-large state n) ; n > m-max
+ (do ((k 2 (+ k 1))
+ (mk (* mrg32k3a-m-max mrg32k3a-m-max) (* mk mrg32k3a-m-max)))
+ ((>= mk n)
+ (let* ((mk-by-n (quotient mk n))
+ (a (* mk-by-n n)))
+ (do ((x (mrg32k3a-random-power state k)
+ (mrg32k3a-random-power state k)))
+ ((< x a) (quotient x mk-by-n)))))))
+
+
+; Multiple Precision Reals
+; ========================
+;
+; To produce multiple precision reals we produce a large integer value
+; and convert it into a real value. This value is then normalized.
+; The precision goal is unit <= 1/(m^k + 1), or 1/unit - 1 <= m^k.
+; If you know more about the floating point number types of the
+; Scheme system, this can be improved.
+
+(define (mrg32k3a-random-real-mp state unit)
+ (do ((k 1 (+ k 1))
+ (u (- (/ 1 unit) 1) (/ u mrg32k3a-m1)))
+ ((<= u 1)
+ (/ (exact->inexact (+ (mrg32k3a-random-power state k) 1))
+ (exact->inexact (+ (expt mrg32k3a-m-max k) 1))))))
+
+
+; Provide the Interface as Specified in the SRFI
+; ==============================================
+;
+; An object of type random-source is a record containing the procedures
+; as components. The actual state of the generator is stored in the
+; binding-time environment of make-random-source.
+
+(define (make-random-source)
+ (let ((state (mrg32k3a-pack-state ; make a new copy
+ (list->vector (vector->list mrg32k3a-initial-state)))))
+ (\:random-source-make
+ (lambda ()
+ (mrg32k3a-state-ref state))
+ (lambda (new-state)
+ (set! state (mrg32k3a-state-set new-state)))
+ (lambda ()
+ (set! state (mrg32k3a-randomize-state state)))
+ (lambda (i j)
+ (set! state (mrg32k3a-pseudo-randomize-state i j)))
+ (lambda ()
+ (lambda (n)
+ (cond
+ ((not (and (integer? n) (exact? n) (positive? n)))
+ (error "range must be exact positive integer" n))
+ ((<= n mrg32k3a-m-max)
+ (mrg32k3a-random-integer state n))
+ (else
+ (mrg32k3a-random-large state n)))))
+ (lambda args
+ (cond
+ ((null? args)
+ (lambda ()
+ (mrg32k3a-random-real state)))
+ ((null? (cdr args))
+ (let ((unit (car args)))
+ (cond
+ ((not (and (real? unit) (< 0 unit 1)))
+ (error "unit must be real in (0,1)" unit))
+ ((<= (- (/ 1 unit) 1) mrg32k3a-m1)
+ (lambda ()
+ (mrg32k3a-random-real state)))
+ (else
+ (lambda ()
+ (mrg32k3a-random-real-mp state unit))))))
+ (else
+ (error "illegal arguments" args)))))))
+
+(define random-source?
+ \:random-source?)
+
+(define (random-source-state-ref s)
+ ((\:random-source-state-ref s)))
+
+(define (random-source-state-set! s state)
+ ((\:random-source-state-set! s) state))
+
+(define (random-source-randomize! s)
+ ((\:random-source-randomize! s)))
+
+(define (random-source-pseudo-randomize! s i j)
+ ((\:random-source-pseudo-randomize! s) i j))
+
+; ---
+
+(define (random-source-make-integers s)
+ ((\:random-source-make-integers s)))
+
+(define (random-source-make-reals s . unit)
+ (apply (\:random-source-make-reals s) unit))
+
+; ---
+
+(define default-random-source
+ (make-random-source))
+
+(define random-integer
+ (random-source-make-integers default-random-source))
+
+(define random-real
+ (random-source-make-reals default-random-source))
+(define-library (srfi 27)
+ (export
+ random-integer
+ random-real
+ default-random-source
+ make-random-source
+ random-source?
+ random-source-state-ref
+ random-source-state-set!
+ random-source-randomize!
+ random-source-pseudo-randomize!
+ random-source-make-integers
+ random-source-make-reals
+ )
+ (import
+ (scheme base)
+ (scheme time))
+ (begin
+
+ (define-record-type \:random-source
+ (\\:random-source-make
+ state-ref
+ state-set!
+ randomize!
+ pseudo-randomize!
+ make-integers
+ make-reals)
+ \:random-source?
+ (state-ref \:random-source-state-ref)
+ (state-set! \:random-source-state-set!)
+ (randomize! \:random-source-randomize!)
+ (pseudo-randomize! \:random-source-pseudo-randomize!)
+ (make-integers \:random-source-make-integers)
+ (make-reals \:random-source-make-reals))
+
+ (define (\\:random-source-current-time)
+ (current-jiffy))
+
+ (define exact->inexact inexact)
+
+ )
+ (include "27.mrg32k3a-a.upstream.scm")
+ (include "27.mrg32k3a.upstream.scm"))
+;; Copyright (C) Scott G. Miller (2002). All Rights Reserved.
+
+;; Made an R7RS library by Taylan Ulrich Bayırlı/Kammer, Copyright (C) 2014.
+
+;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;; of this software and associated documentation files (the "Software"), to deal
+;; in the Software without restriction, including without limitation the rights
+;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+;; copies of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be included in
+;; all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+(define-library (srfi 28)
+ (export format)
+ (import
+ (scheme base)
+ (scheme write))
+ (begin
+ (define format
+ (lambda (format-string . objects)
+ (let ((buffer (open-output-string)))
+ (let loop ((format-list (string->list format-string))
+ (objects objects))
+ (cond ((null? format-list) (get-output-string buffer))
+ ((char=? (car format-list) #\~)
+ (if (null? (cdr format-list))
+ (error 'format "Incomplete escape sequence")
+ (case (cadr format-list)
+ ((#\a)
+ (if (null? objects)
+ (error 'format "No value for escape sequence")
+ (begin
+ (display (car objects) buffer)
+ (loop (cddr format-list) (cdr objects)))))
+ ((#\s)
+ (if (null? objects)
+ (error 'format "No value for escape sequence")
+ (begin
+ (write (car objects) buffer)
+ (loop (cddr format-list) (cdr objects)))))
+ ((#\%)
+ (newline buffer)
+ (loop (cddr format-list) objects))
+ ((#\~)
+ (write-char #\~ buffer)
+ (loop (cddr format-list) objects))
+ (else
+ (error 'format "Unrecognized escape sequence")))))
+ (else (write-char (car format-list) buffer)
+ (loop (cdr format-list) objects)))))))))
+;; Copyright (C) Taylan Ulrich Bayırlı/Kammer (2015). All Rights Reserved.
+
+;; Permission is hereby granted, free of charge, to any person obtaining
+;; a copy of this software and associated documentation files (the
+;; "Software"), to deal in the Software without restriction, including
+;; without limitation the rights to use, copy, modify, merge, publish,
+;; distribute, sublicense, and/or sell copies of the Software, and to
+;; permit persons to whom the Software is furnished to do so, subject to
+;; the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+(define-library (srfi 31)
+ (export rec)
+ (import (scheme base))
+ (begin
+ (define-syntax rec
+ (syntax-rules ()
+ ((rec (name . args) body ...)
+ (letrec ((name (lambda args body ...)))
+ name))
+ ((rec name expr)
+ (letrec ((name expr))
+ name))))))
+;; Copyright (C) Richard Kelsey, Michael Sperber (2002). All Rights Reserved.
+;;
+;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;; of this software and associated documentation files (the "Software"), to deal
+;; in the Software without restriction, including without limitation the rights
+;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+;; copies of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be included in
+;; all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+(define-record-type <condition-type>
+ (really-make-condition-type name supertype fields all-fields)
+ condition-type?
+ (name condition-type-name)
+ (supertype condition-type-supertype)
+ (fields condition-type-fields)
+ (all-fields condition-type-all-fields))
+
+(define (make-condition-type name supertype fields)
+ (if (not (symbol? name))
+ (error "make-condition-type: name is not a symbol"
+ name))
+ (if (not (condition-type? supertype))
+ (error "make-condition-type: supertype is not a condition type"
+ supertype))
+ (if (not
+ (null? (lset-intersection eq?
+ (condition-type-all-fields supertype)
+ fields)))
+ (error "duplicate field name" ))
+ (really-make-condition-type name
+ supertype
+ fields
+ (append (condition-type-all-fields supertype)
+ fields)))
+
+(define-syntax define-condition-type
+ (syntax-rules ()
+ ((define-condition-type ?name ?supertype ?predicate
+ (?field1 ?accessor1) ...)
+ (begin
+ (define ?name
+ (make-condition-type '?name
+ ?supertype
+ '(?field1 ...)))
+ (define (?predicate thing)
+ (and (condition? thing)
+ (condition-has-type? thing ?name)))
+ (define (?accessor1 condition)
+ (condition-ref (extract-condition condition ?name)
+ '?field1))
+ ...))))
+
+(define (condition-subtype? subtype supertype)
+ (let recur ((subtype subtype))
+ (cond ((not subtype) #f)
+ ((eq? subtype supertype) #t)
+ (else
+ (recur (condition-type-supertype subtype))))))
+
+(define (condition-type-field-supertype condition-type field)
+ (let loop ((condition-type condition-type))
+ (cond ((not condition-type) #f)
+ ((memq field (condition-type-fields condition-type))
+ condition-type)
+ (else
+ (loop (condition-type-supertype condition-type))))))
+
+; The type-field-alist is of the form
+; ((<type> (<field-name> . <value>) ...) ...)
+(define-record-type <condition>
+ (really-make-condition type-field-alist)
+ condition?
+ (type-field-alist condition-type-field-alist))
+
+(define (make-condition type . field-plist)
+ (let ((alist (let label ((plist field-plist))
+ (if (null? plist)
+ '()
+ (cons (cons (car plist)
+ (cadr plist))
+ (label (cddr plist)))))))
+ (if (not (lset= eq?
+ (condition-type-all-fields type)
+ (map car alist)))
+ (error "condition fields don't match condition type"))
+ (really-make-condition (list (cons type alist)))))
+
+(define (condition-has-type? condition type)
+ (any (lambda (has-type)
+ (condition-subtype? has-type type))
+ (condition-types condition)))
+
+(define (condition-ref condition field)
+ (type-field-alist-ref (condition-type-field-alist condition)
+ field))
+
+(define (type-field-alist-ref type-field-alist field)
+ (let loop ((type-field-alist type-field-alist))
+ (cond ((null? type-field-alist)
+ (error "type-field-alist-ref: field not found"
+ type-field-alist field))
+ ((assq field (cdr (car type-field-alist)))
+ => cdr)
+ (else
+ (loop (cdr type-field-alist))))))
+
+(define (make-compound-condition condition-1 . conditions)
+ (really-make-condition
+ (apply append (map condition-type-field-alist
+ (cons condition-1 conditions)))))
+
+(define (extract-condition condition type)
+ (let ((entry (find (lambda (entry)
+ (condition-subtype? (car entry) type))
+ (condition-type-field-alist condition))))
+ (if (not entry)
+ (error "extract-condition: invalid condition type"
+ condition type))
+ (really-make-condition
+ (list (cons type
+ (map (lambda (field)
+ (assq field (cdr entry)))
+ (condition-type-all-fields type)))))))
+
+(define-syntax condition
+ (syntax-rules ()
+ ((condition (?type1 (?field1 ?value1) ...) ...)
+ (type-field-alist->condition
+ (list
+ (cons ?type1
+ (list (cons '?field1 ?value1) ...))
+ ...)))))
+
+(define (type-field-alist->condition type-field-alist)
+ (really-make-condition
+ (map (lambda (entry)
+ (cons (car entry)
+ (map (lambda (field)
+ (or (assq field (cdr entry))
+ (cons field
+ (type-field-alist-ref type-field-alist field))))
+ (condition-type-all-fields (car entry)))))
+ type-field-alist)))
+
+(define (condition-types condition)
+ (map car (condition-type-field-alist condition)))
+
+(define (check-condition-type-field-alist the-type-field-alist)
+ (let loop ((type-field-alist the-type-field-alist))
+ (if (not (null? type-field-alist))
+ (let* ((entry (car type-field-alist))
+ (type (car entry))
+ (field-alist (cdr entry))
+ (fields (map car field-alist))
+ (all-fields (condition-type-all-fields type)))
+ (for-each (lambda (missing-field)
+ (let ((supertype
+ (condition-type-field-supertype type missing-field)))
+ (if (not
+ (any (lambda (entry)
+ (let ((type (car entry)))
+ (condition-subtype? type supertype)))
+ the-type-field-alist))
+ (error "missing field in condition construction"
+ type
+ missing-field))))
+ (lset-difference eq? all-fields fields))
+ (loop (cdr type-field-alist))))))
+
+(define &condition (really-make-condition-type '&condition
+ #f
+ '()
+ '()))
+
+(define-condition-type &message &condition
+ message-condition?
+ (message condition-message))
+
+(define-condition-type &serious &condition
+ serious-condition?)
+
+(define-condition-type &error &serious
+ error?)
+(define-library (srfi 35)
+ (export
+ make-condition-type
+ condition-type?
+ make-condition
+ condition?
+ condition-has-type?
+ condition-ref
+ make-compound-condition
+ extract-condition
+ define-condition-type
+ condition
+ &condition
+ &message
+ &serious
+ &error
+ )
+ (import
+ (scheme base)
+ (srfi 1))
+ (include "35.body.scm"))
+(define-library (srfi 64)
+ (import
+ (srfi 64 test-runner)
+ (srfi 64 test-runner-simple)
+ (srfi 64 execution))
+ (export
+ ;; Execution
+ test-begin test-end test-group test-group-with-cleanup
+
+ test-skip test-expect-fail
+ test-match-name test-match-nth
+ test-match-all test-match-any
+
+ test-assert test-eqv test-eq test-equal test-approximate
+ test-error test-read-eval-string
+
+ test-apply test-with-runner
+
+ test-exit
+
+ ;; Test runner
+ test-runner-null test-runner? test-runner-reset
+
+ test-result-alist test-result-alist!
+ test-result-ref test-result-set!
+ test-result-remove test-result-clear
+
+ test-runner-pass-count
+ test-runner-fail-count
+ test-runner-xpass-count
+ test-runner-xfail-count
+ test-runner-skip-count
+
+ test-runner-test-name
+
+ test-runner-group-path
+ test-runner-group-stack
+
+ test-runner-aux-value test-runner-aux-value!
+
+ test-result-kind test-passed?
+
+ test-runner-on-test-begin test-runner-on-test-begin!
+ test-runner-on-test-end test-runner-on-test-end!
+ test-runner-on-group-begin test-runner-on-group-begin!
+ test-runner-on-group-end test-runner-on-group-end!
+ test-runner-on-final test-runner-on-final!
+ test-runner-on-bad-count test-runner-on-bad-count!
+ test-runner-on-bad-end-name test-runner-on-bad-end-name!
+
+ test-runner-factory test-runner-create
+ test-runner-current test-runner-get
+
+ ;; Simple test runner
+ test-runner-simple
+ test-on-group-begin-simple test-on-group-end-simple test-on-final-simple
+ test-on-test-begin-simple test-on-test-end-simple
+ test-on-bad-count-simple test-on-bad-end-name-simple
+ ))
+;; Copyright (C) Richard Kelsey, Michael Sperber (2002). All Rights Reserved.
+;;
+;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;; of this software and associated documentation files (the "Software"), to deal
+;; in the Software without restriction, including without limitation the rights
+;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+;; copies of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be included in
+;; all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+(define-record-type condition-type
+ (really-make-condition-type name supertype fields all-fields)
+ condition-type?
+ (name condition-type-name)
+ (supertype condition-type-supertype)
+ (fields condition-type-fields)
+ (all-fields condition-type-all-fields))
+
+(define (make-condition-type name supertype fields)
+ (if (not (symbol? name))
+ (error "make-condition-type: name is not a symbol"
+ name))
+ (if (not (condition-type? supertype))
+ (error "make-condition-type: supertype is not a condition type"
+ supertype))
+ (if (not
+ (null? (lset-intersection eq?
+ (condition-type-all-fields supertype)
+ fields)))
+ (error "duplicate field name" ))
+ (really-make-condition-type name
+ supertype
+ fields
+ (append (condition-type-all-fields supertype)
+ fields)))
+
+(define-syntax define-condition-type
+ (syntax-rules ()
+ ((define-condition-type ?name ?supertype ?predicate
+ (?field1 ?accessor1) ...)
+ (begin
+ (define ?name
+ (make-condition-type '?name
+ ?supertype
+ '(?field1 ...)))
+ (define (?predicate thing)
+ (and (condition? thing)
+ (condition-has-type? thing ?name)))
+ (define (?accessor1 condition)
+ (condition-ref (extract-condition condition ?name)
+ '?field1))
+ ...))))
+
+(define (condition-subtype? subtype supertype)
+ (let recur ((subtype subtype))
+ (cond ((not subtype) #f)
+ ((eq? subtype supertype) #t)
+ (else
+ (recur (condition-type-supertype subtype))))))
+
+(define (condition-type-field-supertype condition-type field)
+ (let loop ((condition-type condition-type))
+ (cond ((not condition-type) #f)
+ ((memq field (condition-type-fields condition-type))
+ condition-type)
+ (else
+ (loop (condition-type-supertype condition-type))))))
+
+; The type-field-alist is of the form
+; ((<type> (<field-name> . <value>) ...) ...)
+(define-record-type condition
+ (really-make-condition type-field-alist)
+ condition?
+ (type-field-alist condition-type-field-alist))
+
+(define (make-condition type . field-plist)
+ (let ((alist (let label ((plist field-plist))
+ (if (null? plist)
+ '()
+ (cons (cons (car plist)
+ (cadr plist))
+ (label (cddr plist)))))))
+ (if (not (lset= eq?
+ (condition-type-all-fields type)
+ (map car alist)))
+ (error "condition fields don't match condition type"))
+ (really-make-condition (list (cons type alist)))))
+
+(define (condition-has-type? condition type)
+ (any (lambda (has-type)
+ (condition-subtype? has-type type))
+ (condition-types condition)))
+
+(define (condition-ref condition field)
+ (type-field-alist-ref (condition-type-field-alist condition)
+ field))
+
+(define (type-field-alist-ref type-field-alist field)
+ (let loop ((type-field-alist type-field-alist))
+ (cond ((null? type-field-alist)
+ (error "type-field-alist-ref: field not found"
+ type-field-alist field))
+ ((assq field (cdr (car type-field-alist)))
+ => cdr)
+ (else
+ (loop (cdr type-field-alist))))))
+
+(define (make-compound-condition condition-1 . conditions)
+ (really-make-condition
+ (apply append (map condition-type-field-alist
+ (cons condition-1 conditions)))))
+
+(define (extract-condition condition type)
+ (let ((entry (find (lambda (entry)
+ (condition-subtype? (car entry) type))
+ (condition-type-field-alist condition))))
+ (if (not entry)
+ (error "extract-condition: invalid condition type"
+ condition type))
+ (really-make-condition
+ (list (cons type
+ (map (lambda (field)
+ (assq field (cdr entry)))
+ (condition-type-all-fields type)))))))
+
+(define-syntax condition
+ (syntax-rules ()
+ ((condition (?type1 (?field1 ?value1) ...) ...)
+ (type-field-alist->condition
+ (list
+ (cons ?type1
+ (list (cons '?field1 ?value1) ...))
+ ...)))))
+
+(define (type-field-alist->condition type-field-alist)
+ (really-make-condition
+ (map (lambda (entry)
+ (cons (car entry)
+ (map (lambda (field)
+ (or (assq field (cdr entry))
+ (cons field
+ (type-field-alist-ref type-field-alist field))))
+ (condition-type-all-fields (car entry)))))
+ type-field-alist)))
+
+(define (condition-types condition)
+ (map car (condition-type-field-alist condition)))
+
+(define (check-condition-type-field-alist the-type-field-alist)
+ (let loop ((type-field-alist the-type-field-alist))
+ (if (not (null? type-field-alist))
+ (let* ((entry (car type-field-alist))
+ (type (car entry))
+ (field-alist (cdr entry))
+ (fields (map car field-alist))
+ (all-fields (condition-type-all-fields type)))
+ (for-each (lambda (missing-field)
+ (let ((supertype
+ (condition-type-field-supertype type missing-field)))
+ (if (not
+ (any (lambda (entry)
+ (let ((type (car entry)))
+ (condition-subtype? type supertype)))
+ the-type-field-alist))
+ (error "missing field in condition construction"
+ type
+ missing-field))))
+ (lset-difference eq? all-fields fields))
+ (loop (cdr type-field-alist))))))
+
+(define &condition (really-make-condition-type '&condition
+ #f
+ '()
+ '()))
+
+(define-condition-type &message &condition
+ message-condition?
+ (message condition-message))
+
+(define-condition-type &serious &condition
+ serious-condition?)
+
+(define-condition-type &error &serious
+ error?)
+;;; args-fold.scm - a program argument processor
+;;;
+;;; Copyright (c) 2002 Anthony Carrico
+;;; Copyright (c) 2014 Taylan Ulrich Bayırlı/Kammer
+;;;
+;;; All rights reserved.
+;;;
+;;; Redistribution and use in source and binary forms, with or without
+;;; modification, are permitted provided that the following conditions
+;;; are met:
+;;; 1. Redistributions of source code must retain the above copyright
+;;; notice, this list of conditions and the following disclaimer.
+;;; 2. Redistributions in binary form must reproduce the above copyright
+;;; notice, this list of conditions and the following disclaimer in the
+;;; documentation and/or other materials provided with the distribution.
+;;; 3. The name of the authors may not be used to endorse or promote products
+;;; derived from this software without specific prior written permission.
+;;;
+;;; THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+;;; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+;;; OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+;;; IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+;;; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+;;; NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+;;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+;;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+;;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+;;; THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+(define-record-type <option>
+ (option names required-arg? optional-arg? processor)
+ option?
+ (names option-names)
+ (required-arg? option-required-arg?)
+ (optional-arg? option-optional-arg?)
+ (processor option-processor))
+
+(define (args-fold args options unrecognized-option-proc operand-proc . seeds)
+
+ (define (find-option name)
+ ;; ISSUE: This is a brute force search. Could use a table.
+ (find (lambda (option)
+ (find (lambda (test-name)
+ (equal? name test-name))
+ (option-names option)))
+ options))
+
+ (define (scan-short-options index shorts args seeds)
+ (if (= index (string-length shorts))
+ (scan-args args seeds)
+ (let* ((name (string-ref shorts index))
+ (option (or (find-option name)
+ (option (list name)
+ #f
+ #f
+ unrecognized-option-proc))))
+ (cond
+ ((and (< (+ index 1) (string-length shorts))
+ (or (option-required-arg? option)
+ (option-optional-arg? option)))
+ (let-values
+ ((seeds (apply (option-processor option)
+ option
+ name
+ (substring
+ shorts
+ (+ index 1)
+ (string-length shorts))
+ seeds)))
+ (scan-args args seeds)))
+ ((and (option-required-arg? option)
+ (pair? args))
+ (let-values
+ ((seeds (apply (option-processor option)
+ option
+ name
+ (car args)
+ seeds)))
+ (scan-args (cdr args) seeds)))
+ (else
+ (let-values
+ ((seeds (apply (option-processor option)
+ option
+ name
+ #f
+ seeds)))
+ (scan-short-options
+ (+ index 1)
+ shorts
+ args
+ seeds)))))))
+
+ (define (scan-operands operands seeds)
+ (if (null? operands)
+ (apply values seeds)
+ (let-values ((seeds (apply operand-proc
+ (car operands)
+ seeds)))
+ (scan-operands (cdr operands) seeds))))
+
+ (define (scan-args args seeds)
+ (if (null? args)
+ (apply values seeds)
+ (let ((arg (car args))
+ (args (cdr args)))
+ ;; NOTE: This string matching code would be simpler
+ ;; using a regular expression matcher.
+ (cond
+ ((string=? "--" arg)
+ ;; End option scanning:
+ (scan-operands args seeds))
+ ((and (> (string-length arg) 4)
+ (char=? #\- (string-ref arg 0))
+ (char=? #\- (string-ref arg 1))
+ (not (char=? #\= (string-ref arg 2)))
+ (let loop ((index 3))
+ (cond ((= index (string-length arg))
+ #f)
+ ((char=? #\= (string-ref arg index))
+ index)
+ (else
+ (loop (+ 1 index))))))
+ ;; Found long option with arg:
+ => (lambda (=-index)
+ (let*-values
+ (((name)
+ (substring arg 2 =-index))
+ ((option-arg)
+ (substring arg
+ (+ =-index 1)
+ (string-length arg)))
+ ((option)
+ (or (find-option name)
+ (option (list name)
+ #t
+ #f
+ unrecognized-option-proc)))
+ (seeds
+ (apply (option-processor option)
+ option
+ name
+ option-arg
+ seeds)))
+ (scan-args args seeds))))
+ ((and (> (string-length arg) 3)
+ (char=? #\- (string-ref arg 0))
+ (char=? #\- (string-ref arg 1)))
+ ;; Found long option:
+ (let* ((name (substring arg 2 (string-length arg)))
+ (option (or (find-option name)
+ (option
+ (list name)
+ #f
+ #f
+ unrecognized-option-proc))))
+ (if (and (option-required-arg? option)
+ (pair? args))
+ (let-values
+ ((seeds (apply (option-processor option)
+ option
+ name
+ (car args)
+ seeds)))
+ (scan-args (cdr args) seeds))
+ (let-values
+ ((seeds (apply (option-processor option)
+ option
+ name
+ #f
+ seeds)))
+ (scan-args args seeds)))))
+ ((and (> (string-length arg) 1)
+ (char=? #\- (string-ref arg 0)))
+ ;; Found short options
+ (let ((shorts (substring arg 1 (string-length arg))))
+ (scan-short-options 0 shorts args seeds)))
+ (else
+ (let-values ((seeds (apply operand-proc arg seeds)))
+ (scan-args args seeds)))))))
+
+ (scan-args args seeds))
+(define-library (srfi 37)
+ (export
+ args-fold
+ option
+ option-names
+ option-required-arg?
+ option-optional-arg?
+ option-processor
+ )
+ (import
+ (scheme base)
+ (srfi 1))
+ (include "37.body.scm"))
+;;; Copyright (C) 2006 Chongkai Zhu. All Rights Reserved.
+
+;;; Made an R7RS library by Taylan Ulrich Bayırlı/Kammer, Copyright (C) 2014.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+(define-library (srfi 87)
+ (export case)
+ (import (except (scheme base) case))
+ (begin
+ (define-syntax case
+ (syntax-rules (else =>)
+ ((case (key ...)
+ clauses ...)
+ (let ((atom-key (key ...)))
+ (case atom-key clauses ...)))
+ ((case key
+ (else => result))
+ (result key))
+ ((case key
+ ((atoms ...) => result))
+ (if (memv key '(atoms ...))
+ (result key)))
+ ((case key
+ ((atoms ...) => result)
+ clause clauses ...)
+ (if (memv key '(atoms ...))
+ (result key)
+ (case key clause clauses ...)))
+ ((case key
+ (else result1 result2 ...))
+ (begin result1 result2 ...))
+ ((case key
+ ((atoms ...) result1 result2 ...))
+ (if (memv key '(atoms ...))
+ (begin result1 result2 ...)))
+ ((case key
+ ((atoms ...) result1 result2 ...)
+ clause clauses ...)
+ (if (memv key '(atoms ...))
+ (begin result1 result2 ...)
+ (case key clause clauses ...)))))))
+;;; args-fold.scm - a program argument processor
+;;;
+;;; Copyright (c) 2002 Anthony Carrico
+;;;
+;;; All rights reserved.
+;;;
+;;; Redistribution and use in source and binary forms, with or without
+;;; modification, are permitted provided that the following conditions
+;;; are met:
+;;; 1. Redistributions of source code must retain the above copyright
+;;; notice, this list of conditions and the following disclaimer.
+;;; 2. Redistributions in binary form must reproduce the above copyright
+;;; notice, this list of conditions and the following disclaimer in the
+;;; documentation and/or other materials provided with the distribution.
+;;; 3. The name of the authors may not be used to endorse or promote products
+;;; derived from this software without specific prior written permission.
+;;;
+;;; THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+;;; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+;;; OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+;;; IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+;;; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+;;; NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+;;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+;;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+;;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+;;; THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+;;; NOTE: This implementation uses the following SRFIs:
+;;; "SRFI 9: Defining Record Types"
+;;; "SRFI 11: Syntax for receiving multiple values"
+;;;
+;;; NOTE: The scsh-utils and Chicken implementations use regular
+;;; expressions. These might be easier to read and understand.
+
+(define option #f)
+(define option-names #f)
+(define option-required-arg? #f)
+(define option-optional-arg? #f)
+(define option-processor #f)
+(define option? #f)
+
+(let ()
+ (define-record-type option-type
+ ($option names required-arg? optional-arg? processor)
+ $option?
+ (names $option-names)
+ (required-arg? $option-required-arg?)
+ (optional-arg? $option-optional-arg?)
+ (processor $option-processor))
+ (set! option $option)
+ (set! option-names $option-names)
+ (set! option-required-arg? $option-required-arg?)
+ (set! option-optional-arg? $option-optional-arg?)
+ (set! option-processor $option-processor)
+ (set! option? $option?))
+
+(define args-fold
+ (lambda (args
+ options
+ unrecognized-option-proc
+ operand-proc
+ . seeds)
+ (letrec
+ ((find
+ (lambda (l ?)
+ (cond ((null? l) #f)
+ ((? (car l)) (car l))
+ (else (find (cdr l) ?)))))
+ (find-option
+ ;; ISSUE: This is a brute force search. Could use a table.
+ (lambda (name)
+ (find
+ options
+ (lambda (option)
+ (find
+ (option-names option)
+ (lambda (test-name)
+ (equal? name test-name)))))))
+ (scan-short-options
+ (lambda (index shorts args seeds)
+ (if (= index (string-length shorts))
+ (scan-args args seeds)
+ (let* ((name (string-ref shorts index))
+ (option (or (find-option name)
+ (option (list name)
+ #f
+ #f
+ unrecognized-option-proc))))
+ (cond ((and (< (+ index 1) (string-length shorts))
+ (or (option-required-arg? option)
+ (option-optional-arg? option)))
+ (let-values
+ ((seeds (apply (option-processor option)
+ option
+ name
+ (substring
+ shorts
+ (+ index 1)
+ (string-length shorts))
+ seeds)))
+ (scan-args args seeds)))
+ ((and (option-required-arg? option)
+ (pair? args))
+ (let-values
+ ((seeds (apply (option-processor option)
+ option
+ name
+ (car args)
+ seeds)))
+ (scan-args (cdr args) seeds)))
+ (else
+ (let-values
+ ((seeds (apply (option-processor option)
+ option
+ name
+ #f
+ seeds)))
+ (scan-short-options
+ (+ index 1)
+ shorts
+ args
+ seeds))))))))
+ (scan-operands
+ (lambda (operands seeds)
+ (if (null? operands)
+ (apply values seeds)
+ (let-values ((seeds (apply operand-proc
+ (car operands)
+ seeds)))
+ (scan-operands (cdr operands) seeds)))))
+ (scan-args
+ (lambda (args seeds)
+ (if (null? args)
+ (apply values seeds)
+ (let ((arg (car args))
+ (args (cdr args)))
+ ;; NOTE: This string matching code would be simpler
+ ;; using a regular expression matcher.
+ (cond
+ (;; (rx bos "--" eos)
+ (string=? "--" arg)
+ ;; End option scanning:
+ (scan-operands args seeds))
+ (;;(rx bos
+ ;; "--"
+ ;; (submatch (+ (~ "=")))
+ ;; "="
+ ;; (submatch (* any)))
+ (and (> (string-length arg) 4)
+ (char=? #\- (string-ref arg 0))
+ (char=? #\- (string-ref arg 1))
+ (not (char=? #\= (string-ref arg 2)))
+ (let loop ((index 3))
+ (cond ((= index (string-length arg))
+ #f)
+ ((char=? #\= (string-ref arg index))
+ index)
+ (else
+ (loop (+ 1 index))))))
+ ;; Found long option with arg:
+ => (lambda (=-index)
+ (let*-values
+ (((name)
+ (substring arg 2 =-index))
+ ((option-arg)
+ (substring arg
+ (+ =-index 1)
+ (string-length arg)))
+ ((option)
+ (or (find-option name)
+ (option (list name)
+ #t
+ #f
+ unrecognized-option-proc)))
+ (seeds
+ (apply (option-processor option)
+ option
+ name
+ option-arg
+ seeds)))
+ (scan-args args seeds))))
+ (;;(rx bos "--" (submatch (+ any)))
+ (and (> (string-length arg) 3)
+ (char=? #\- (string-ref arg 0))
+ (char=? #\- (string-ref arg 1)))
+ ;; Found long option:
+ (let* ((name (substring arg 2 (string-length arg)))
+ (option (or (find-option name)
+ (option
+ (list name)
+ #f
+ #f
+ unrecognized-option-proc))))
+ (if (and (option-required-arg? option)
+ (pair? args))
+ (let-values
+ ((seeds (apply (option-processor option)
+ option
+ name
+ (car args)
+ seeds)))
+ (scan-args (cdr args) seeds))
+ (let-values
+ ((seeds (apply (option-processor option)
+ option
+ name
+ #f
+ seeds)))
+ (scan-args args seeds)))))
+ (;; (rx bos "-" (submatch (+ any)))
+ (and (> (string-length arg) 1)
+ (char=? #\- (string-ref arg 0)))
+ ;; Found short options
+ (let ((shorts (substring arg 1 (string-length arg))))
+ (scan-short-options 0 shorts args seeds)))
+ (else
+ (let-values ((seeds (apply operand-proc arg seeds)))
+ (scan-args args seeds)))))))))
+ (scan-args args seeds))))
+(define-library (srfi 41)
+ (export
+ stream-null stream-cons stream? stream-null? stream-pair? stream-car
+ stream-cdr stream-lambda define-stream list->stream port->stream stream
+ stream->list stream-append stream-concat stream-constant stream-drop
+ stream-drop-while stream-filter stream-fold stream-for-each stream-from
+ stream-iterate stream-length stream-let stream-map stream-match _
+ stream-of stream-range stream-ref stream-reverse stream-scan stream-take
+ stream-take-while stream-unfold stream-unfolds stream-zip
+ )
+ (import
+ (srfi 41 primitive)
+ (srfi 41 derived)))
+; <PLAINTEXT>
+; Eager Comprehensions in [outer..inner|expr]-Convention
+; ======================================================
+;
+; sebastian.egner@philips.com, Eindhoven, The Netherlands, 26-Dec-2007
+; Scheme R5RS (incl. macros), SRFI-23 (error).
+;
+; Loading the implementation into Scheme48 0.57:
+; ,open srfi-23
+; ,load ec.scm
+;
+; Loading the implementation into PLT/DrScheme 317:
+; ; File > Open ... "ec.scm", click Execute
+;
+; Loading the implementation into SCM 5d7:
+; (require 'macro) (require 'record)
+; (load "ec.scm")
+;
+; Implementation comments:
+; * All local (not exported) identifiers are named ec-<something>.
+; * This implementation focuses on portability, performance,
+; readability, and simplicity roughly in this order. Design
+; decisions related to performance are taken for Scheme48.
+; * Alternative implementations, Comments and Warnings are
+; mentioned after the definition with a heading.
+
+
+; ==========================================================================
+; The fundamental comprehension do-ec
+; ==========================================================================
+;
+; All eager comprehensions are reduced into do-ec and
+; all generators are reduced to :do.
+;
+; We use the following short names for syntactic variables
+; q - qualifier
+; cc - current continuation, thing to call at the end;
+; the CPS is (m (cc ...) arg ...) -> (cc ... expr ...)
+; cmd - an expression being evaluated for its side-effects
+; expr - an expression
+; gen - a generator of an eager comprehension
+; ob - outer binding
+; oc - outer command
+; lb - loop binding
+; ne1? - not-end1? (before the payload)
+; ib - inner binding
+; ic - inner command
+; ne2? - not-end2? (after the payload)
+; ls - loop step
+; etc - more arguments of mixed type
+
+
+; (do-ec q ... cmd)
+; handles nested, if/not/and/or, begin, :let, and calls generator
+; macros in CPS to transform them into fully decorated :do.
+; The code generation for a :do is delegated to do-ec:do.
+
+(define-syntax do-ec
+ (syntax-rules (nested if not and or begin do let)
+
+ ; explicit nesting -> implicit nesting
+ ((do-ec (nested q ...) etc ...)
+ (do-ec q ... etc ...) )
+
+ ; implicit nesting -> fold do-ec
+ ((do-ec q1 q2 etc1 etc ...)
+ (do-ec q1 (do-ec q2 etc1 etc ...)) )
+
+ ; no qualifiers at all -> evaluate cmd once
+ ((do-ec cmd)
+ (begin cmd (if #f #f)) )
+
+; now (do-ec q cmd) remains
+
+ ; filter -> make conditional
+ ((do-ec (if test) cmd)
+ (if test (do-ec cmd)) )
+ ((do-ec (not test) cmd)
+ (if (not test) (do-ec cmd)) )
+ ((do-ec (and test ...) cmd)
+ (if (and test ...) (do-ec cmd)) )
+ ((do-ec (or test ...) cmd)
+ (if (or test ...) (do-ec cmd)) )
+
+ ; begin -> make a sequence
+ ((do-ec (begin etc ...) cmd)
+ (begin etc ... (do-ec cmd)) )
+
+ ; fully decorated :do-generator -> delegate to do-ec:do
+ ((do-ec (#\:do olet lbs ne1? ilet ne2? lss) cmd)
+ (do-ec:do cmd (#\:do olet lbs ne1? ilet ne2? lss)) )
+
+; anything else -> call generator-macro in CPS; reentry at (*)
+
+ ((do-ec (g arg1 arg ...) cmd)
+ (g (do-ec:do cmd) arg1 arg ...) )))
+
+
+; (do-ec:do cmd (#\:do olet lbs ne1? ilet ne2? lss))
+; generates code for a single fully decorated :do-generator
+; with cmd as payload, taking care of special cases.
+
+(define-syntax do-ec:do
+ (syntax-rules (#\:do let)
+
+ ; reentry point (*) -> generate code
+ ((do-ec:do cmd
+ (#\:do (let obs oc ...)
+ lbs
+ ne1?
+ (let ibs ic ...)
+ ne2?
+ (ls ...) ))
+ (ec-simplify
+ (let obs
+ oc ...
+ (let loop lbs
+ (ec-simplify
+ (if ne1?
+ (ec-simplify
+ (let ibs
+ ic ...
+ cmd
+ (ec-simplify
+ (if ne2?
+ (loop ls ...) )))))))))) ))
+
+
+; (ec-simplify <expression>)
+; generates potentially more efficient code for <expression>.
+; The macro handles if, (begin <command>*), and (let () <command>*)
+; and takes care of special cases.
+
+(define-syntax ec-simplify
+ (syntax-rules (if not let begin)
+
+; one- and two-sided if
+
+ ; literal <test>
+ ((ec-simplify (if #t consequent))
+ consequent )
+ ((ec-simplify (if #f consequent))
+ (if #f #f) )
+ ((ec-simplify (if #t consequent alternate))
+ consequent )
+ ((ec-simplify (if #f consequent alternate))
+ alternate )
+
+ ; (not (not <test>))
+ ((ec-simplify (if (not (not test)) consequent))
+ (ec-simplify (if test consequent)) )
+ ((ec-simplify (if (not (not test)) consequent alternate))
+ (ec-simplify (if test consequent alternate)) )
+
+; (let () <command>*)
+
+ ; empty <binding spec>*
+ ((ec-simplify (let () command ...))
+ (ec-simplify (begin command ...)) )
+
+; begin
+
+ ; flatten use helper (ec-simplify 1 done to-do)
+ ((ec-simplify (begin command ...))
+ (ec-simplify 1 () (command ...)) )
+ ((ec-simplify 1 done ((begin to-do1 ...) to-do2 ...))
+ (ec-simplify 1 done (to-do1 ... to-do2 ...)) )
+ ((ec-simplify 1 (done ...) (to-do1 to-do ...))
+ (ec-simplify 1 (done ... to-do1) (to-do ...)) )
+
+ ; exit helper
+ ((ec-simplify 1 () ())
+ (if #f #f) )
+ ((ec-simplify 1 (command) ())
+ command )
+ ((ec-simplify 1 (command1 command ...) ())
+ (begin command1 command ...) )
+
+; anything else
+
+ ((ec-simplify expression)
+ expression )))
+
+
+; ==========================================================================
+; The special generators :do, :let, :parallel, :while, and :until
+; ==========================================================================
+
+(define-syntax \:do
+ (syntax-rules ()
+
+ ; full decorated -> continue with cc, reentry at (*)
+ ((#\:do (cc ...) olet lbs ne1? ilet ne2? lss)
+ (cc ... (#\:do olet lbs ne1? ilet ne2? lss)) )
+
+ ; short form -> fill in default values
+ ((#\:do cc lbs ne1? lss)
+ (#\:do cc (let ()) lbs ne1? (let ()) #t lss) )))
+
+
+(define-syntax \:let
+ (syntax-rules (index)
+ ((\:let cc var (index i) expression)
+ (#\:do cc (let ((var expression) (i 0))) () #t (let ()) #f ()) )
+ ((\:let cc var expression)
+ (#\:do cc (let ((var expression))) () #t (let ()) #f ()) )))
+
+
+(define-syntax \:parallel
+ (syntax-rules (#\:do)
+ ((\:parallel cc)
+ cc )
+ ((\:parallel cc (g arg1 arg ...) gen ...)
+ (g (\:parallel-1 cc (gen ...)) arg1 arg ...) )))
+
+; (\:parallel-1 cc (to-do ...) result [ next ] )
+; iterates over to-do by converting the first generator into
+; the :do-generator next and merging next into result.
+
+(define-syntax \:parallel-1 ; used as
+ (syntax-rules (#\:do let)
+
+ ; process next element of to-do, reentry at (**)
+ ((\:parallel-1 cc ((g arg1 arg ...) gen ...) result)
+ (g (\:parallel-1 cc (gen ...) result) arg1 arg ...) )
+
+ ; reentry point (**) -> merge next into result
+ ((\:parallel-1
+ cc
+ gens
+ (#\:do (let (ob1 ...) oc1 ...)
+ (lb1 ...)
+ ne1?1
+ (let (ib1 ...) ic1 ...)
+ ne2?1
+ (ls1 ...) )
+ (#\:do (let (ob2 ...) oc2 ...)
+ (lb2 ...)
+ ne1?2
+ (let (ib2 ...) ic2 ...)
+ ne2?2
+ (ls2 ...) ))
+ (\:parallel-1
+ cc
+ gens
+ (#\:do (let (ob1 ... ob2 ...) oc1 ... oc2 ...)
+ (lb1 ... lb2 ...)
+ (and ne1?1 ne1?2)
+ (let (ib1 ... ib2 ...) ic1 ... ic2 ...)
+ (and ne2?1 ne2?2)
+ (ls1 ... ls2 ...) )))
+
+ ; no more gens -> continue with cc, reentry at (*)
+ ((\:parallel-1 (cc ...) () result)
+ (cc ... result) )))
+
+(define-syntax \:while
+ (syntax-rules ()
+ ((\:while cc (g arg1 arg ...) test)
+ (g (\:while-1 cc test) arg1 arg ...) )))
+
+; (\:while-1 cc test (#\:do ...))
+; modifies the fully decorated :do-generator such that it
+; runs while test is a true value.
+; The original implementation just replaced ne1? by
+; (and ne1? test) as follows:
+;
+; (define-syntax \:while-1
+; (syntax-rules (#\:do)
+; ((\:while-1 cc test (#\:do olet lbs ne1? ilet ne2? lss))
+; (#\:do cc olet lbs (and ne1? test) ilet ne2? lss) )))
+;
+; Bug #1:
+; Unfortunately, this code is wrong because ne1? may depend
+; in the inner bindings introduced in ilet, but ne1? is evaluated
+; outside of the inner bindings. (Refer to the specification of
+; :do to see the structure.)
+; The problem manifests itself (as sunnan@handgranat.org
+; observed, 25-Apr-2005) when the :list-generator is modified:
+;
+; (do-ec (\:while (\:list x '(1 2)) (= x 1)) (display x)).
+;
+; In order to generate proper code, we introduce temporary
+; variables saving the values of the inner bindings. The inner
+; bindings are executed in a new ne1?, which also evaluates ne1?
+; outside the scope of the inner bindings, then the inner commands
+; are executed (possibly changing the variables), and then the
+; values of the inner bindings are saved and (and ne1? test) is
+; returned. In the new ilet, the inner variables are bound and
+; initialized and their values are restored. So we construct:
+;
+; (let (ob .. (ib-tmp #f) ...)
+; oc ...
+; (let loop (lb ...)
+; (if (let (ne1?-value ne1?)
+; (let ((ib-var ib-rhs) ...)
+; ic ...
+; (set! ib-tmp ib-var) ...)
+; (and ne1?-value test))
+; (let ((ib-var ib-tmp) ...)
+; /payload/
+; (if ne2?
+; (loop ls ...) )))))
+;
+; Bug #2:
+; Unfortunately, the above expansion is still incorrect (as Jens-Axel
+; Soegaard pointed out, 4-Jun-2007) because ib-rhs are evaluated even
+; if ne1?-value is #f, indicating that the loop has ended.
+; The problem manifests itself in the following example:
+;
+; (do-ec (\:while (\:list x '(1)) #t) (display x))
+;
+; Which iterates :list beyond exhausting the list '(1).
+;
+; For the fix, we follow Jens-Axel's approach of guarding the evaluation
+; of ib-rhs with a check on ne1?-value.
+
+(define-syntax \:while-1
+ (syntax-rules (#\:do let)
+ ((\:while-1 cc test (#\:do olet lbs ne1? ilet ne2? lss))
+ (\:while-2 cc test () () () (#\:do olet lbs ne1? ilet ne2? lss)))))
+
+(define-syntax \:while-2
+ (syntax-rules (#\:do let)
+ ((\:while-2 cc
+ test
+ (ib-let ...)
+ (ib-save ...)
+ (ib-restore ...)
+ (#\:do olet
+ lbs
+ ne1?
+ (let ((ib-var ib-rhs) ib ...) ic ...)
+ ne2?
+ lss))
+ (\:while-2 cc
+ test
+ (ib-let ... (ib-tmp #f))
+ (ib-save ... (ib-var ib-rhs))
+ (ib-restore ... (ib-var ib-tmp))
+ (#\:do olet
+ lbs
+ ne1?
+ (let (ib ...) ic ... (set! ib-tmp ib-var))
+ ne2?
+ lss)))
+ ((\:while-2 cc
+ test
+ (ib-let ...)
+ (ib-save ...)
+ (ib-restore ...)
+ (#\:do (let (ob ...) oc ...) lbs ne1? (let () ic ...) ne2? lss))
+ (#\:do cc
+ (let (ob ... ib-let ...) oc ...)
+ lbs
+ (let ((ne1?-value ne1?))
+ (and ne1?-value
+ (let (ib-save ...)
+ ic ...
+ test)))
+ (let (ib-restore ...))
+ ne2?
+ lss))))
+
+
+(define-syntax \:until
+ (syntax-rules ()
+ ((\:until cc (g arg1 arg ...) test)
+ (g (\:until-1 cc test) arg1 arg ...) )))
+
+(define-syntax \:until-1
+ (syntax-rules (#\:do)
+ ((\:until-1 cc test (#\:do olet lbs ne1? ilet ne2? lss))
+ (#\:do cc olet lbs ne1? ilet (and ne2? (not test)) lss) )))
+
+
+; ==========================================================================
+; The typed generators :list :string :vector etc.
+; ==========================================================================
+
+(define-syntax \:list
+ (syntax-rules (index)
+ ((\:list cc var (index i) arg ...)
+ (\:parallel cc (\:list var arg ...) (\:integers i)) )
+ ((\:list cc var arg1 arg2 arg ...)
+ (\:list cc var (append arg1 arg2 arg ...)) )
+ ((\:list cc var arg)
+ (#\:do cc
+ (let ())
+ ((t arg))
+ (not (null? t))
+ (let ((var (car t))))
+ #t
+ ((cdr t)) ))))
+
+
+(define-syntax \:string
+ (syntax-rules (index)
+ ((\:string cc var (index i) arg)
+ (#\:do cc
+ (let ((str arg) (len 0))
+ (set! len (string-length str)))
+ ((i 0))
+ (< i len)
+ (let ((var (string-ref str i))))
+ #t
+ ((+ i 1)) ))
+ ((\:string cc var (index i) arg1 arg2 arg ...)
+ (\:string cc var (index i) (string-append arg1 arg2 arg ...)) )
+ ((\:string cc var arg1 arg ...)
+ (\:string cc var (index i) arg1 arg ...) )))
+
+; Alternative: An implementation in the style of :vector can also
+; be used for :string. However, it is less interesting as the
+; overhead of string-append is much less than for 'vector-append'.
+
+
+(define-syntax \:vector
+ (syntax-rules (index)
+ ((\:vector cc var arg)
+ (\:vector cc var (index i) arg) )
+ ((\:vector cc var (index i) arg)
+ (#\:do cc
+ (let ((vec arg) (len 0))
+ (set! len (vector-length vec)))
+ ((i 0))
+ (< i len)
+ (let ((var (vector-ref vec i))))
+ #t
+ ((+ i 1)) ))
+
+ ((\:vector cc var (index i) arg1 arg2 arg ...)
+ (\:parallel cc (\:vector cc var arg1 arg2 arg ...) (\:integers i)) )
+ ((\:vector cc var arg1 arg2 arg ...)
+ (#\:do cc
+ (let ((vec #f)
+ (len 0)
+ (vecs (ec-:vector-filter (list arg1 arg2 arg ...))) ))
+ ((k 0))
+ (if (< k len)
+ #t
+ (if (null? vecs)
+ #f
+ (begin (set! vec (car vecs))
+ (set! vecs (cdr vecs))
+ (set! len (vector-length vec))
+ (set! k 0)
+ #t )))
+ (let ((var (vector-ref vec k))))
+ #t
+ ((+ k 1)) ))))
+
+(define (ec-:vector-filter vecs)
+ (if (null? vecs)
+ '()
+ (if (zero? (vector-length (car vecs)))
+ (ec-:vector-filter (cdr vecs))
+ (cons (car vecs) (ec-:vector-filter (cdr vecs))) )))
+
+; Alternative: A simpler implementation for :vector uses vector->list
+; append and :list in the multi-argument case. Please refer to the
+; 'design.scm' for more details.
+
+
+(define-syntax \:integers
+ (syntax-rules (index)
+ ((\:integers cc var (index i))
+ (#\:do cc ((var 0) (i 0)) #t ((+ var 1) (+ i 1))) )
+ ((\:integers cc var)
+ (#\:do cc ((var 0)) #t ((+ var 1))) )))
+
+
+(define-syntax \:range
+ (syntax-rules (index)
+
+ ; handle index variable and add optional args
+ ((\:range cc var (index i) arg1 arg ...)
+ (\:parallel cc (\:range var arg1 arg ...) (\:integers i)) )
+ ((\:range cc var arg1)
+ (\:range cc var 0 arg1 1) )
+ ((\:range cc var arg1 arg2)
+ (\:range cc var arg1 arg2 1) )
+
+; special cases (partially evaluated by hand from general case)
+
+ ((\:range cc var 0 arg2 1)
+ (#\:do cc
+ (let ((b arg2))
+ (if (not (and (integer? b) (exact? b)))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" 0 b 1 )))
+ ((var 0))
+ (< var b)
+ (let ())
+ #t
+ ((+ var 1)) ))
+
+ ((\:range cc var 0 arg2 -1)
+ (#\:do cc
+ (let ((b arg2))
+ (if (not (and (integer? b) (exact? b)))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" 0 b 1 )))
+ ((var 0))
+ (> var b)
+ (let ())
+ #t
+ ((- var 1)) ))
+
+ ((\:range cc var arg1 arg2 1)
+ (#\:do cc
+ (let ((a arg1) (b arg2))
+ (if (not (and (integer? a) (exact? a)
+ (integer? b) (exact? b) ))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" a b 1 )) )
+ ((var a))
+ (< var b)
+ (let ())
+ #t
+ ((+ var 1)) ))
+
+ ((\:range cc var arg1 arg2 -1)
+ (#\:do cc
+ (let ((a arg1) (b arg2) (s -1) (stop 0))
+ (if (not (and (integer? a) (exact? a)
+ (integer? b) (exact? b) ))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" a b -1 )) )
+ ((var a))
+ (> var b)
+ (let ())
+ #t
+ ((- var 1)) ))
+
+; the general case
+
+ ((\:range cc var arg1 arg2 arg3)
+ (#\:do cc
+ (let ((a arg1) (b arg2) (s arg3) (stop 0))
+ (if (not (and (integer? a) (exact? a)
+ (integer? b) (exact? b)
+ (integer? s) (exact? s) ))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" a b s ))
+ (if (zero? s)
+ (error "step size must not be zero in :range") )
+ (set! stop (+ a (* (max 0 (ceiling (/ (- b a) s))) s))) )
+ ((var a))
+ (not (= var stop))
+ (let ())
+ #t
+ ((+ var s)) ))))
+
+; Comment: The macro :range inserts some code to make sure the values
+; are exact integers. This overhead has proven very helpful for
+; saving users from themselves.
+
+
+(define-syntax \:real-range
+ (syntax-rules (index)
+
+ ; add optional args and index variable
+ ((\:real-range cc var arg1)
+ (\:real-range cc var (index i) 0 arg1 1) )
+ ((\:real-range cc var (index i) arg1)
+ (\:real-range cc var (index i) 0 arg1 1) )
+ ((\:real-range cc var arg1 arg2)
+ (\:real-range cc var (index i) arg1 arg2 1) )
+ ((\:real-range cc var (index i) arg1 arg2)
+ (\:real-range cc var (index i) arg1 arg2 1) )
+ ((\:real-range cc var arg1 arg2 arg3)
+ (\:real-range cc var (index i) arg1 arg2 arg3) )
+
+ ; the fully qualified case
+ ((\:real-range cc var (index i) arg1 arg2 arg3)
+ (#\:do cc
+ (let ((a arg1) (b arg2) (s arg3) (istop 0))
+ (if (not (and (real? a) (real? b) (real? s)))
+ (error "arguments of :real-range are not real" a b s) )
+ (if (and (exact? a) (or (not (exact? b)) (not (exact? s))))
+ (set! a (inexact a)) )
+ (set! istop (/ (- b a) s)) )
+ ((i 0))
+ (< i istop)
+ (let ((var (+ a (* s i)))))
+ #t
+ ((+ i 1)) ))))
+
+; Comment: The macro :real-range adapts the exactness of the start
+; value in case any of the other values is inexact. This is a
+; precaution to avoid (list-ec (\: x 0 3.0) x) => '(0 1.0 2.0).
+
+
+(define-syntax \:char-range
+ (syntax-rules (index)
+ ((\:char-range cc var (index i) arg1 arg2)
+ (\:parallel cc (\:char-range var arg1 arg2) (\:integers i)) )
+ ((\:char-range cc var arg1 arg2)
+ (#\:do cc
+ (let ((imax (char->integer arg2))))
+ ((i (char->integer arg1)))
+ (<= i imax)
+ (let ((var (integer->char i))))
+ #t
+ ((+ i 1)) ))))
+
+; Warning: There is no R5RS-way to implement the :char-range generator
+; because the integers obtained by char->integer are not necessarily
+; consecutive. We simply assume this anyhow for illustration.
+
+
+(define-syntax \:port
+ (syntax-rules (index)
+ ((\:port cc var (index i) arg1 arg ...)
+ (\:parallel cc (\:port var arg1 arg ...) (\:integers i)) )
+ ((\:port cc var arg)
+ (\:port cc var arg read) )
+ ((\:port cc var arg1 arg2)
+ (#\:do cc
+ (let ((port arg1) (read-proc arg2)))
+ ((var (read-proc port)))
+ (not (eof-object? var))
+ (let ())
+ #t
+ ((read-proc port)) ))))
+
+
+; ==========================================================================
+; The typed generator :dispatched and utilities for constructing dispatchers
+; ==========================================================================
+
+(define-syntax \:dispatched
+ (syntax-rules (index)
+ ((\:dispatched cc var (index i) dispatch arg1 arg ...)
+ (\:parallel cc
+ (\:integers i)
+ (\:dispatched var dispatch arg1 arg ...) ))
+ ((\:dispatched cc var dispatch arg1 arg ...)
+ (#\:do cc
+ (let ((d dispatch)
+ (args (list arg1 arg ...))
+ (g #f)
+ (empty (list #f)) )
+ (set! g (d args))
+ (if (not (procedure? g))
+ (error "unrecognized arguments in dispatching"
+ args
+ (d '()) )))
+ ((var (g empty)))
+ (not (eq? var empty))
+ (let ())
+ #t
+ ((g empty)) ))))
+
+; Comment: The unique object empty is created as a newly allocated
+; non-empty list. It is compared using eq? which distinguishes
+; the object from any other object, according to R5RS 6.1.
+
+
+(define-syntax \:generator-proc
+ (syntax-rules (#\:do let)
+
+ ; call g with a variable, reentry at (**)
+ ((\:generator-proc (g arg ...))
+ (g (\:generator-proc var) var arg ...) )
+
+ ; reentry point (**) -> make the code from a single :do
+ ((\:generator-proc
+ var
+ (#\:do (let obs oc ...)
+ ((lv li) ...)
+ ne1?
+ (let ((i v) ...) ic ...)
+ ne2?
+ (ls ...)) )
+ (ec-simplify
+ (let obs
+ oc ...
+ (let ((lv li) ... (ne2 #t))
+ (ec-simplify
+ (let ((i #f) ...) ; v not yet valid
+ (lambda (empty)
+ (if (and ne1? ne2)
+ (ec-simplify
+ (begin
+ (set! i v) ...
+ ic ...
+ (let ((value var))
+ (ec-simplify
+ (if ne2?
+ (ec-simplify
+ (begin (set! lv ls) ...) )
+ (set! ne2 #f) ))
+ value )))
+ empty ))))))))
+
+ ; silence warnings of some macro expanders
+ ((\:generator-proc var)
+ (error "illegal macro call") )))
+
+
+(define (dispatch-union d1 d2)
+ (lambda (args)
+ (let ((g1 (d1 args)) (g2 (d2 args)))
+ (if g1
+ (if g2
+ (if (null? args)
+ (append (if (list? g1) g1 (list g1))
+ (if (list? g2) g2 (list g2)) )
+ (error "dispatching conflict" args (d1 '()) (d2 '())) )
+ g1 )
+ (if g2 g2 #f) ))))
+
+
+; ==========================================================================
+; The dispatching generator :
+; ==========================================================================
+
+(define (make-initial-:-dispatch)
+ (lambda (args)
+ (case (length args)
+ ((0) 'SRFI42)
+ ((1) (let ((a1 (car args)))
+ (cond
+ ((list? a1)
+ (\:generator-proc (\:list a1)) )
+ ((string? a1)
+ (\:generator-proc (\:string a1)) )
+ ((vector? a1)
+ (\:generator-proc (\:vector a1)) )
+ ((and (integer? a1) (exact? a1))
+ (\:generator-proc (\:range a1)) )
+ ((real? a1)
+ (\:generator-proc (\:real-range a1)) )
+ ((input-port? a1)
+ (\:generator-proc (\:port a1)) )
+ (else
+ #f ))))
+ ((2) (let ((a1 (car args)) (a2 (cadr args)))
+ (cond
+ ((and (list? a1) (list? a2))
+ (\:generator-proc (\:list a1 a2)) )
+ ((and (string? a1) (string? a1))
+ (\:generator-proc (\:string a1 a2)) )
+ ((and (vector? a1) (vector? a2))
+ (\:generator-proc (\:vector a1 a2)) )
+ ((and (integer? a1) (exact? a1) (integer? a2) (exact? a2))
+ (\:generator-proc (\:range a1 a2)) )
+ ((and (real? a1) (real? a2))
+ (\:generator-proc (\:real-range a1 a2)) )
+ ((and (char? a1) (char? a2))
+ (\:generator-proc (\:char-range a1 a2)) )
+ ((and (input-port? a1) (procedure? a2))
+ (\:generator-proc (\:port a1 a2)) )
+ (else
+ #f ))))
+ ((3) (let ((a1 (car args)) (a2 (cadr args)) (a3 (caddr args)))
+ (cond
+ ((and (list? a1) (list? a2) (list? a3))
+ (\:generator-proc (\:list a1 a2 a3)) )
+ ((and (string? a1) (string? a1) (string? a3))
+ (\:generator-proc (\:string a1 a2 a3)) )
+ ((and (vector? a1) (vector? a2) (vector? a3))
+ (\:generator-proc (\:vector a1 a2 a3)) )
+ ((and (integer? a1) (exact? a1)
+ (integer? a2) (exact? a2)
+ (integer? a3) (exact? a3))
+ (\:generator-proc (\:range a1 a2 a3)) )
+ ((and (real? a1) (real? a2) (real? a3))
+ (\:generator-proc (\:real-range a1 a2 a3)) )
+ (else
+ #f ))))
+ (else
+ (letrec ((every?
+ (lambda (pred args)
+ (if (null? args)
+ #t
+ (and (pred (car args))
+ (every? pred (cdr args)) )))))
+ (cond
+ ((every? list? args)
+ (\:generator-proc (\:list (apply append args))) )
+ ((every? string? args)
+ (\:generator-proc (\:string (apply string-append args))) )
+ ((every? vector? args)
+ (\:generator-proc (\:list (apply append (map vector->list args)))) )
+ (else
+ #f )))))))
+
+(define \:-dispatch
+ (make-initial-:-dispatch) )
+
+(define (\:-dispatch-ref)
+ \:-dispatch )
+
+(define (\:-dispatch-set! dispatch)
+ (if (not (procedure? dispatch))
+ (error "not a procedure" dispatch) )
+ (set! \:-dispatch dispatch) )
+
+(define-syntax \:
+ (syntax-rules (index)
+ ((\: cc var (index i) arg1 arg ...)
+ (\:dispatched cc var (index i) \:-dispatch arg1 arg ...) )
+ ((\: cc var arg1 arg ...)
+ (\:dispatched cc var \:-dispatch arg1 arg ...) )))
+
+
+; ==========================================================================
+; The utility comprehensions fold-ec, fold3-ec
+; ==========================================================================
+
+(define-syntax fold3-ec
+ (syntax-rules (nested)
+ ((fold3-ec x0 (nested q1 ...) q etc1 etc2 etc3 etc ...)
+ (fold3-ec x0 (nested q1 ... q) etc1 etc2 etc3 etc ...) )
+ ((fold3-ec x0 q1 q2 etc1 etc2 etc3 etc ...)
+ (fold3-ec x0 (nested q1 q2) etc1 etc2 etc3 etc ...) )
+ ((fold3-ec x0 expression f1 f2)
+ (fold3-ec x0 (nested) expression f1 f2) )
+
+ ((fold3-ec x0 qualifier expression f1 f2)
+ (let ((result #f) (empty #t))
+ (do-ec qualifier
+ (let ((value expression)) ; don't duplicate
+ (if empty
+ (begin (set! result (f1 value))
+ (set! empty #f) )
+ (set! result (f2 value result)) )))
+ (if empty x0 result) ))))
+
+
+(define-syntax fold-ec
+ (syntax-rules (nested)
+ ((fold-ec x0 (nested q1 ...) q etc1 etc2 etc ...)
+ (fold-ec x0 (nested q1 ... q) etc1 etc2 etc ...) )
+ ((fold-ec x0 q1 q2 etc1 etc2 etc ...)
+ (fold-ec x0 (nested q1 q2) etc1 etc2 etc ...) )
+ ((fold-ec x0 expression f2)
+ (fold-ec x0 (nested) expression f2) )
+
+ ((fold-ec x0 qualifier expression f2)
+ (let ((result x0))
+ (do-ec qualifier (set! result (f2 expression result)))
+ result ))))
+
+
+; ==========================================================================
+; The comprehensions list-ec string-ec vector-ec etc.
+; ==========================================================================
+
+(define-syntax list-ec
+ (syntax-rules ()
+ ((list-ec etc1 etc ...)
+ (reverse (fold-ec '() etc1 etc ... cons)) )))
+
+; Alternative: Reverse can safely be replaced by reverse! if you have it.
+;
+; Alternative: It is possible to construct the result in the correct order
+; using set-cdr! to add at the tail. This removes the overhead of copying
+; at the end, at the cost of more book-keeping.
+
+
+(define-syntax append-ec
+ (syntax-rules ()
+ ((append-ec etc1 etc ...)
+ (apply append (list-ec etc1 etc ...)) )))
+
+(define-syntax string-ec
+ (syntax-rules ()
+ ((string-ec etc1 etc ...)
+ (list->string (list-ec etc1 etc ...)) )))
+
+; Alternative: For very long strings, the intermediate list may be a
+; problem. A more space-aware implementation collect the characters
+; in an intermediate list and when this list becomes too large it is
+; converted into an intermediate string. At the end, the intermediate
+; strings are concatenated with string-append.
+
+
+(define-syntax string-append-ec
+ (syntax-rules ()
+ ((string-append-ec etc1 etc ...)
+ (apply string-append (list-ec etc1 etc ...)) )))
+
+(define-syntax vector-ec
+ (syntax-rules ()
+ ((vector-ec etc1 etc ...)
+ (list->vector (list-ec etc1 etc ...)) )))
+
+; Comment: A similar approach as for string-ec can be used for vector-ec.
+; However, the space overhead for the intermediate list is much lower
+; than for string-ec and as there is no vector-append, the intermediate
+; vectors must be copied explicitly.
+
+(define-syntax vector-of-length-ec
+ (syntax-rules (nested)
+ ((vector-of-length-ec k (nested q1 ...) q etc1 etc ...)
+ (vector-of-length-ec k (nested q1 ... q) etc1 etc ...) )
+ ((vector-of-length-ec k q1 q2 etc1 etc ...)
+ (vector-of-length-ec k (nested q1 q2) etc1 etc ...) )
+ ((vector-of-length-ec k expression)
+ (vector-of-length-ec k (nested) expression) )
+
+ ((vector-of-length-ec k qualifier expression)
+ (let ((len k))
+ (let ((vec (make-vector len))
+ (i 0) )
+ (do-ec qualifier
+ (if (< i len)
+ (begin (vector-set! vec i expression)
+ (set! i (+ i 1)) )
+ (error "vector is too short for the comprehension") ))
+ (if (= i len)
+ vec
+ (error "vector is too long for the comprehension") ))))))
+
+
+(define-syntax sum-ec
+ (syntax-rules ()
+ ((sum-ec etc1 etc ...)
+ (fold-ec (+) etc1 etc ... +) )))
+
+(define-syntax product-ec
+ (syntax-rules ()
+ ((product-ec etc1 etc ...)
+ (fold-ec (*) etc1 etc ... *) )))
+
+(define-syntax min-ec
+ (syntax-rules ()
+ ((min-ec etc1 etc ...)
+ (fold3-ec (min) etc1 etc ... min min) )))
+
+(define-syntax max-ec
+ (syntax-rules ()
+ ((max-ec etc1 etc ...)
+ (fold3-ec (max) etc1 etc ... max max) )))
+
+(define-syntax last-ec
+ (syntax-rules (nested)
+ ((last-ec default (nested q1 ...) q etc1 etc ...)
+ (last-ec default (nested q1 ... q) etc1 etc ...) )
+ ((last-ec default q1 q2 etc1 etc ...)
+ (last-ec default (nested q1 q2) etc1 etc ...) )
+ ((last-ec default expression)
+ (last-ec default (nested) expression) )
+
+ ((last-ec default qualifier expression)
+ (let ((result default))
+ (do-ec qualifier (set! result expression))
+ result ))))
+
+
+; ==========================================================================
+; The fundamental early-stopping comprehension first-ec
+; ==========================================================================
+
+(define-syntax first-ec
+ (syntax-rules (nested)
+ ((first-ec default (nested q1 ...) q etc1 etc ...)
+ (first-ec default (nested q1 ... q) etc1 etc ...) )
+ ((first-ec default q1 q2 etc1 etc ...)
+ (first-ec default (nested q1 q2) etc1 etc ...) )
+ ((first-ec default expression)
+ (first-ec default (nested) expression) )
+
+ ((first-ec default qualifier expression)
+ (let ((result default) (stop #f))
+ (ec-guarded-do-ec
+ stop
+ (nested qualifier)
+ (begin (set! result expression)
+ (set! stop #t) ))
+ result ))))
+
+; (ec-guarded-do-ec stop (nested q ...) cmd)
+; constructs (do-ec q ... cmd) where the generators gen in q ... are
+; replaced by (\:until gen stop).
+
+(define-syntax ec-guarded-do-ec
+ (syntax-rules (nested if not and or begin)
+
+ ((ec-guarded-do-ec stop (nested (nested q1 ...) q2 ...) cmd)
+ (ec-guarded-do-ec stop (nested q1 ... q2 ...) cmd) )
+
+ ((ec-guarded-do-ec stop (nested (if test) q ...) cmd)
+ (if test (ec-guarded-do-ec stop (nested q ...) cmd)) )
+ ((ec-guarded-do-ec stop (nested (not test) q ...) cmd)
+ (if (not test) (ec-guarded-do-ec stop (nested q ...) cmd)) )
+ ((ec-guarded-do-ec stop (nested (and test ...) q ...) cmd)
+ (if (and test ...) (ec-guarded-do-ec stop (nested q ...) cmd)) )
+ ((ec-guarded-do-ec stop (nested (or test ...) q ...) cmd)
+ (if (or test ...) (ec-guarded-do-ec stop (nested q ...) cmd)) )
+
+ ((ec-guarded-do-ec stop (nested (begin etc ...) q ...) cmd)
+ (begin etc ... (ec-guarded-do-ec stop (nested q ...) cmd)) )
+
+ ((ec-guarded-do-ec stop (nested gen q ...) cmd)
+ (do-ec
+ (\:until gen stop)
+ (ec-guarded-do-ec stop (nested q ...) cmd) ))
+
+ ((ec-guarded-do-ec stop (nested) cmd)
+ (do-ec cmd) )))
+
+; Alternative: Instead of modifying the generator with :until, it is
+; possible to use call-with-current-continuation:
+;
+; (define-synatx first-ec
+; ...same as above...
+; ((first-ec default qualifier expression)
+; (call-with-current-continuation
+; (lambda (cc)
+; (do-ec qualifier (cc expression))
+; default ))) ))
+;
+; This is much simpler but not necessarily as efficient.
+
+
+; ==========================================================================
+; The early-stopping comprehensions any?-ec every?-ec
+; ==========================================================================
+
+(define-syntax any?-ec
+ (syntax-rules (nested)
+ ((any?-ec (nested q1 ...) q etc1 etc ...)
+ (any?-ec (nested q1 ... q) etc1 etc ...) )
+ ((any?-ec q1 q2 etc1 etc ...)
+ (any?-ec (nested q1 q2) etc1 etc ...) )
+ ((any?-ec expression)
+ (any?-ec (nested) expression) )
+
+ ((any?-ec qualifier expression)
+ (first-ec #f qualifier (if expression) #t) )))
+
+(define-syntax every?-ec
+ (syntax-rules (nested)
+ ((every?-ec (nested q1 ...) q etc1 etc ...)
+ (every?-ec (nested q1 ... q) etc1 etc ...) )
+ ((every?-ec q1 q2 etc1 etc ...)
+ (every?-ec (nested q1 q2) etc1 etc ...) )
+ ((every?-ec expression)
+ (every?-ec (nested) expression) )
+
+ ((every?-ec qualifier expression)
+ (first-ec #t qualifier (if (not expression)) #f) )))
+
+(define-library (srfi 42)
+ (export
+ \:
+ \:-dispatch-ref
+ \:-dispatch-set!
+ \:char-range
+ \:dispatched
+ \:do
+ \:generator-proc
+ \:integers
+ \:let
+ \:list
+ \:parallel
+ \:port
+ \:range
+ \:real-range
+ \:string
+ \:until
+ \:vector
+ \:while
+ any?-ec
+ append-ec
+ dispatch-union
+ do-ec
+ every?-ec
+ first-ec
+ fold-ec
+ fold3-ec
+ last-ec
+ list-ec
+ make-initial-\:-dispatch
+ max-ec
+ min-ec
+ product-ec
+ string-append-ec
+ string-ec
+ sum-ec
+ vector-ec
+ vector-of-length-ec
+ )
+ (import
+ (scheme base)
+ (scheme cxr)
+ (scheme read))
+ (include "42.body.scm"))
+;;; "sort.scm" Defines: sorted?, merge, merge!, sort, sort!
+;;; Author : Richard A. O'Keefe (based on Prolog code by D.H.D.Warren)
+;;;
+;;; Made an R7RS library by Taylan Ulrich Bayırlı/Kammer, Copyright (C) 2014.
+
+;;; Copyright (C) Aubrey Jaffer 2006. All Rights Reserved.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+;;; Updated: 11 June 1991
+;;; Modified for scheme library: Aubrey Jaffer 19 Sept. 1991
+;;; Updated: 19 June 1995
+;;; (sort, sort!, sorted?): Generalized to strings by jaffer: 2003-09-09
+;;; (sort, sort!, sorted?): Generalized to arrays by jaffer: 2003-10-04
+;;; jaffer: 2006-10-08:
+;;; (sort, sort!, sorted?, merge, merge!): Added optional KEY argument.
+;;; jaffer: 2006-11-05:
+;;; (sorted?, merge, merge!, sort, sort!): Call KEY arg at most once
+;;; per element.
+
+;;; (sorted? sequence less?)
+;;; is true when sequence is a list (x0 x1 ... xm) or a vector #(x0 ... xm)
+;;; such that for all 1 <= i <= m,
+;;; (not (less? (list-ref list i) (list-ref list (- i 1)))).
+;@
+(define (sorted? seq less? . opt-key)
+ (define key (if (null? opt-key) values (car opt-key)))
+ (cond ((null? seq) #t)
+ ((array? seq)
+ (let ((dimax (+ -1 (car (array-dimensions seq)))))
+ (or (<= dimax 1)
+ (let loop ((idx (+ -1 dimax))
+ (last (key (array-ref seq dimax))))
+ (or (negative? idx)
+ (let ((nxt (key (array-ref seq idx))))
+ (and (less? nxt last)
+ (loop (+ -1 idx) nxt))))))))
+ ((null? (cdr seq)) #t)
+ (else
+ (let loop ((last (key (car seq)))
+ (next (cdr seq)))
+ (or (null? next)
+ (let ((nxt (key (car next))))
+ (and (not (less? nxt last))
+ (loop nxt (cdr next)))))))))
+
+;;; (merge a b less?)
+;;; takes two lists a and b such that (sorted? a less?) and (sorted? b less?)
+;;; and returns a new list in which the elements of a and b have been stably
+;;; interleaved so that (sorted? (merge a b less?) less?).
+;;; Note: this does _not_ accept arrays. See below.
+;@
+(define (merge a b less? . opt-key)
+ (define key (if (null? opt-key) values (car opt-key)))
+ (cond ((null? a) b)
+ ((null? b) a)
+ (else
+ (let loop ((x (car a)) (kx (key (car a))) (a (cdr a))
+ (y (car b)) (ky (key (car b))) (b (cdr b)))
+ ;; The loop handles the merging of non-empty lists. It has
+ ;; been written this way to save testing and car/cdring.
+ (if (less? ky kx)
+ (if (null? b)
+ (cons y (cons x a))
+ (cons y (loop x kx a (car b) (key (car b)) (cdr b))))
+ ;; x <= y
+ (if (null? a)
+ (cons x (cons y b))
+ (cons x (loop (car a) (key (car a)) (cdr a) y ky b))))))))
+
+(define (sort:merge! a b less? key)
+ (define (loop r a kcara b kcarb)
+ (cond ((less? kcarb kcara)
+ (set-cdr! r b)
+ (if (null? (cdr b))
+ (set-cdr! b a)
+ (loop b a kcara (cdr b) (key (cadr b)))))
+ (else ; (car a) <= (car b)
+ (set-cdr! r a)
+ (if (null? (cdr a))
+ (set-cdr! a b)
+ (loop a (cdr a) (key (cadr a)) b kcarb)))))
+ (cond ((null? a) b)
+ ((null? b) a)
+ (else
+ (let ((kcara (key (car a)))
+ (kcarb (key (car b))))
+ (cond
+ ((less? kcarb kcara)
+ (if (null? (cdr b))
+ (set-cdr! b a)
+ (loop b a kcara (cdr b) (key (cadr b))))
+ b)
+ (else ; (car a) <= (car b)
+ (if (null? (cdr a))
+ (set-cdr! a b)
+ (loop a (cdr a) (key (cadr a)) b kcarb))
+ a))))))
+
+;;; takes two sorted lists a and b and smashes their cdr fields to form a
+;;; single sorted list including the elements of both.
+;;; Note: this does _not_ accept arrays.
+;@
+(define (merge! a b less? . opt-key)
+ (sort:merge! a b less? (if (null? opt-key) values (car opt-key))))
+
+(define (sort:sort-list! seq less? key)
+ (define keyer (if key car values))
+ (define (step n)
+ (cond ((> n 2) (let* ((j (quotient n 2))
+ (a (step j))
+ (k (- n j))
+ (b (step k)))
+ (sort:merge! a b less? keyer)))
+ ((= n 2) (let ((x (car seq))
+ (y (cadr seq))
+ (p seq))
+ (set! seq (cddr seq))
+ (cond ((less? (keyer y) (keyer x))
+ (set-car! p y)
+ (set-car! (cdr p) x)))
+ (set-cdr! (cdr p) '())
+ p))
+ ((= n 1) (let ((p seq))
+ (set! seq (cdr seq))
+ (set-cdr! p '())
+ p))
+ (else '())))
+ (define (key-wrap! lst)
+ (cond ((null? lst))
+ (else (set-car! lst (cons (key (car lst)) (car lst)))
+ (key-wrap! (cdr lst)))))
+ (define (key-unwrap! lst)
+ (cond ((null? lst))
+ (else (set-car! lst (cdar lst))
+ (key-unwrap! (cdr lst)))))
+ (cond (key
+ (key-wrap! seq)
+ (set! seq (step (length seq)))
+ (key-unwrap! seq)
+ seq)
+ (else
+ (step (length seq)))))
+
+(define (rank-1-array->list array)
+ (define dimensions (array-dimensions array))
+ (do ((idx (+ -1 (car dimensions)) (+ -1 idx))
+ (lst '() (cons (array-ref array idx) lst)))
+ ((< idx 0) lst)))
+
+;;; (sort! sequence less?)
+;;; sorts the list, array, or string sequence destructively. It uses
+;;; a version of merge-sort invented, to the best of my knowledge, by
+;;; David H. D. Warren, and first used in the DEC-10 Prolog system.
+;;; R. A. O'Keefe adapted it to work destructively in Scheme.
+;;; A. Jaffer modified to always return the original list.
+;@
+(define (sort! seq less? . opt-key)
+ (define key (if (null? opt-key) #f (car opt-key)))
+ (cond ((array? seq)
+ (let ((dims (array-dimensions seq)))
+ (do ((sorted (sort:sort-list! (rank-1-array->list seq) less? key)
+ (cdr sorted))
+ (i 0 (+ i 1)))
+ ((null? sorted) seq)
+ (array-set! seq (car sorted) i))))
+ (else ; otherwise, assume it is a list
+ (let ((ret (sort:sort-list! seq less? key)))
+ (if (not (eq? ret seq))
+ (do ((crt ret (cdr crt)))
+ ((eq? (cdr crt) seq)
+ (set-cdr! crt ret)
+ (let ((scar (car seq)) (scdr (cdr seq)))
+ (set-car! seq (car ret)) (set-cdr! seq (cdr ret))
+ (set-car! ret scar) (set-cdr! ret scdr)))))
+ seq))))
+
+;;; (sort sequence less?)
+;;; sorts a array, string, or list non-destructively. It does this
+;;; by sorting a copy of the sequence. My understanding is that the
+;;; Standard says that the result of append is always "newly
+;;; allocated" except for sharing structure with "the last argument",
+;;; so (append x '()) ought to be a standard way of copying a list x.
+;@
+(define (sort seq less? . opt-key)
+ (define key (if (null? opt-key) #f (car opt-key)))
+ (cond ((array? seq)
+ (let ((dims (array-dimensions seq)))
+ (define newra (apply make-array seq dims))
+ (do ((sorted (sort:sort-list! (rank-1-array->list seq) less? key)
+ (cdr sorted))
+ (i 0 (+ i 1)))
+ ((null? sorted) newra)
+ (array-set! newra (car sorted) i))))
+ (else (sort:sort-list! (append seq '()) less? key))))
+; <PLAINTEXT>
+; Eager Comprehensions in [outer..inner|expr]-Convention
+; ======================================================
+;
+; sebastian.egner@philips.com, Eindhoven, The Netherlands, 26-Dec-2007
+; Scheme R5RS (incl. macros), SRFI-23 (error).
+;
+; Loading the implementation into Scheme48 0.57:
+; ,open srfi-23
+; ,load ec.scm
+;
+; Loading the implementation into PLT/DrScheme 317:
+; ; File > Open ... "ec.scm", click Execute
+;
+; Loading the implementation into SCM 5d7:
+; (require 'macro) (require 'record)
+; (load "ec.scm")
+;
+; Implementation comments:
+; * All local (not exported) identifiers are named ec-<something>.
+; * This implementation focuses on portability, performance,
+; readability, and simplicity roughly in this order. Design
+; decisions related to performance are taken for Scheme48.
+; * Alternative implementations, Comments and Warnings are
+; mentioned after the definition with a heading.
+
+
+; ==========================================================================
+; The fundamental comprehension do-ec
+; ==========================================================================
+;
+; All eager comprehensions are reduced into do-ec and
+; all generators are reduced to :do.
+;
+; We use the following short names for syntactic variables
+; q - qualifier
+; cc - current continuation, thing to call at the end;
+; the CPS is (m (cc ...) arg ...) -> (cc ... expr ...)
+; cmd - an expression being evaluated for its side-effects
+; expr - an expression
+; gen - a generator of an eager comprehension
+; ob - outer binding
+; oc - outer command
+; lb - loop binding
+; ne1? - not-end1? (before the payload)
+; ib - inner binding
+; ic - inner command
+; ne2? - not-end2? (after the payload)
+; ls - loop step
+; etc - more arguments of mixed type
+
+
+; (do-ec q ... cmd)
+; handles nested, if/not/and/or, begin, :let, and calls generator
+; macros in CPS to transform them into fully decorated :do.
+; The code generation for a :do is delegated to do-ec:do.
+
+(define-syntax do-ec
+ (syntax-rules (nested if not and or begin \:do let)
+
+ ; explicit nesting -> implicit nesting
+ ((do-ec (nested q ...) etc ...)
+ (do-ec q ... etc ...) )
+
+ ; implicit nesting -> fold do-ec
+ ((do-ec q1 q2 etc1 etc ...)
+ (do-ec q1 (do-ec q2 etc1 etc ...)) )
+
+ ; no qualifiers at all -> evaluate cmd once
+ ((do-ec cmd)
+ (begin cmd (if #f #f)) )
+
+; now (do-ec q cmd) remains
+
+ ; filter -> make conditional
+ ((do-ec (if test) cmd)
+ (if test (do-ec cmd)) )
+ ((do-ec (not test) cmd)
+ (if (not test) (do-ec cmd)) )
+ ((do-ec (and test ...) cmd)
+ (if (and test ...) (do-ec cmd)) )
+ ((do-ec (or test ...) cmd)
+ (if (or test ...) (do-ec cmd)) )
+
+ ; begin -> make a sequence
+ ((do-ec (begin etc ...) cmd)
+ (begin etc ... (do-ec cmd)) )
+
+ ; fully decorated :do-generator -> delegate to do-ec:do
+ ((do-ec (#\:do olet lbs ne1? ilet ne2? lss) cmd)
+ (do-ec:do cmd (#\:do olet lbs ne1? ilet ne2? lss)) )
+
+; anything else -> call generator-macro in CPS; reentry at (*)
+
+ ((do-ec (g arg1 arg ...) cmd)
+ (g (do-ec:do cmd) arg1 arg ...) )))
+
+
+; (do-ec:do cmd (#\:do olet lbs ne1? ilet ne2? lss))
+; generates code for a single fully decorated :do-generator
+; with cmd as payload, taking care of special cases.
+
+(define-syntax do-ec:do
+ (syntax-rules (#\:do let)
+
+ ; reentry point (*) -> generate code
+ ((do-ec:do cmd
+ (#\:do (let obs oc ...)
+ lbs
+ ne1?
+ (let ibs ic ...)
+ ne2?
+ (ls ...) ))
+ (ec-simplify
+ (let obs
+ oc ...
+ (let loop lbs
+ (ec-simplify
+ (if ne1?
+ (ec-simplify
+ (let ibs
+ ic ...
+ cmd
+ (ec-simplify
+ (if ne2?
+ (loop ls ...) )))))))))) ))
+
+
+; (ec-simplify <expression>)
+; generates potentially more efficient code for <expression>.
+; The macro handles if, (begin <command>*), and (let () <command>*)
+; and takes care of special cases.
+
+(define-syntax ec-simplify
+ (syntax-rules (if not let begin)
+
+; one- and two-sided if
+
+ ; literal <test>
+ ((ec-simplify (if #t consequent))
+ consequent )
+ ((ec-simplify (if #f consequent))
+ (if #f #f) )
+ ((ec-simplify (if #t consequent alternate))
+ consequent )
+ ((ec-simplify (if #f consequent alternate))
+ alternate )
+
+ ; (not (not <test>))
+ ((ec-simplify (if (not (not test)) consequent))
+ (ec-simplify (if test consequent)) )
+ ((ec-simplify (if (not (not test)) consequent alternate))
+ (ec-simplify (if test consequent alternate)) )
+
+; (let () <command>*)
+
+ ; empty <binding spec>*
+ ((ec-simplify (let () command ...))
+ (ec-simplify (begin command ...)) )
+
+; begin
+
+ ; flatten use helper (ec-simplify 1 done to-do)
+ ((ec-simplify (begin command ...))
+ (ec-simplify 1 () (command ...)) )
+ ((ec-simplify 1 done ((begin to-do1 ...) to-do2 ...))
+ (ec-simplify 1 done (to-do1 ... to-do2 ...)) )
+ ((ec-simplify 1 (done ...) (to-do1 to-do ...))
+ (ec-simplify 1 (done ... to-do1) (to-do ...)) )
+
+ ; exit helper
+ ((ec-simplify 1 () ())
+ (if #f #f) )
+ ((ec-simplify 1 (command) ())
+ command )
+ ((ec-simplify 1 (command1 command ...) ())
+ (begin command1 command ...) )
+
+; anything else
+
+ ((ec-simplify expression)
+ expression )))
+
+
+; ==========================================================================
+; The special generators :do, :let, :parallel, :while, and :until
+; ==========================================================================
+
+(define-syntax \:do
+ (syntax-rules ()
+
+ ; full decorated -> continue with cc, reentry at (*)
+ ((#\:do (cc ...) olet lbs ne1? ilet ne2? lss)
+ (cc ... (#\:do olet lbs ne1? ilet ne2? lss)) )
+
+ ; short form -> fill in default values
+ ((#\:do cc lbs ne1? lss)
+ (#\:do cc (let ()) lbs ne1? (let ()) #t lss) )))
+
+
+(define-syntax \:let
+ (syntax-rules (index)
+ ((\:let cc var (index i) expression)
+ (#\:do cc (let ((var expression) (i 0))) () #t (let ()) #f ()) )
+ ((\:let cc var expression)
+ (#\:do cc (let ((var expression))) () #t (let ()) #f ()) )))
+
+
+(define-syntax \:parallel
+ (syntax-rules (#\:do)
+ ((\:parallel cc)
+ cc )
+ ((\:parallel cc (g arg1 arg ...) gen ...)
+ (g (\:parallel-1 cc (gen ...)) arg1 arg ...) )))
+
+; (\:parallel-1 cc (to-do ...) result [ next ] )
+; iterates over to-do by converting the first generator into
+; the :do-generator next and merging next into result.
+
+(define-syntax \:parallel-1 ; used as
+ (syntax-rules (#\:do let)
+
+ ; process next element of to-do, reentry at (**)
+ ((\:parallel-1 cc ((g arg1 arg ...) gen ...) result)
+ (g (\:parallel-1 cc (gen ...) result) arg1 arg ...) )
+
+ ; reentry point (**) -> merge next into result
+ ((\:parallel-1
+ cc
+ gens
+ (#\:do (let (ob1 ...) oc1 ...)
+ (lb1 ...)
+ ne1?1
+ (let (ib1 ...) ic1 ...)
+ ne2?1
+ (ls1 ...) )
+ (#\:do (let (ob2 ...) oc2 ...)
+ (lb2 ...)
+ ne1?2
+ (let (ib2 ...) ic2 ...)
+ ne2?2
+ (ls2 ...) ))
+ (\:parallel-1
+ cc
+ gens
+ (#\:do (let (ob1 ... ob2 ...) oc1 ... oc2 ...)
+ (lb1 ... lb2 ...)
+ (and ne1?1 ne1?2)
+ (let (ib1 ... ib2 ...) ic1 ... ic2 ...)
+ (and ne2?1 ne2?2)
+ (ls1 ... ls2 ...) )))
+
+ ; no more gens -> continue with cc, reentry at (*)
+ ((\:parallel-1 (cc ...) () result)
+ (cc ... result) )))
+
+(define-syntax \:while
+ (syntax-rules ()
+ ((\:while cc (g arg1 arg ...) test)
+ (g (\:while-1 cc test) arg1 arg ...) )))
+
+; (\:while-1 cc test (#\:do ...))
+; modifies the fully decorated :do-generator such that it
+; runs while test is a true value.
+; The original implementation just replaced ne1? by
+; (and ne1? test) as follows:
+;
+; (define-syntax \:while-1
+; (syntax-rules (#\:do)
+; ((\:while-1 cc test (#\:do olet lbs ne1? ilet ne2? lss))
+; (#\:do cc olet lbs (and ne1? test) ilet ne2? lss) )))
+;
+; Bug #1:
+; Unfortunately, this code is wrong because ne1? may depend
+; in the inner bindings introduced in ilet, but ne1? is evaluated
+; outside of the inner bindings. (Refer to the specification of
+; :do to see the structure.)
+; The problem manifests itself (as sunnan@handgranat.org
+; observed, 25-Apr-2005) when the :list-generator is modified:
+;
+; (do-ec (\:while (\:list x '(1 2)) (= x 1)) (display x)).
+;
+; In order to generate proper code, we introduce temporary
+; variables saving the values of the inner bindings. The inner
+; bindings are executed in a new ne1?, which also evaluates ne1?
+; outside the scope of the inner bindings, then the inner commands
+; are executed (possibly changing the variables), and then the
+; values of the inner bindings are saved and (and ne1? test) is
+; returned. In the new ilet, the inner variables are bound and
+; initialized and their values are restored. So we construct:
+;
+; (let (ob .. (ib-tmp #f) ...)
+; oc ...
+; (let loop (lb ...)
+; (if (let (ne1?-value ne1?)
+; (let ((ib-var ib-rhs) ...)
+; ic ...
+; (set! ib-tmp ib-var) ...)
+; (and ne1?-value test))
+; (let ((ib-var ib-tmp) ...)
+; /payload/
+; (if ne2?
+; (loop ls ...) )))))
+;
+; Bug #2:
+; Unfortunately, the above expansion is still incorrect (as Jens-Axel
+; Soegaard pointed out, 4-Jun-2007) because ib-rhs are evaluated even
+; if ne1?-value is #f, indicating that the loop has ended.
+; The problem manifests itself in the following example:
+;
+; (do-ec (\:while (\:list x '(1)) #t) (display x))
+;
+; Which iterates :list beyond exhausting the list '(1).
+;
+; For the fix, we follow Jens-Axel's approach of guarding the evaluation
+; of ib-rhs with a check on ne1?-value.
+
+(define-syntax \:while-1
+ (syntax-rules (#\:do let)
+ ((\:while-1 cc test (#\:do olet lbs ne1? ilet ne2? lss))
+ (\:while-2 cc test () () () (#\:do olet lbs ne1? ilet ne2? lss)))))
+
+(define-syntax \:while-2
+ (syntax-rules (#\:do let)
+ ((\:while-2 cc
+ test
+ (ib-let ...)
+ (ib-save ...)
+ (ib-restore ...)
+ (#\:do olet
+ lbs
+ ne1?
+ (let ((ib-var ib-rhs) ib ...) ic ...)
+ ne2?
+ lss))
+ (\:while-2 cc
+ test
+ (ib-let ... (ib-tmp #f))
+ (ib-save ... (ib-var ib-rhs))
+ (ib-restore ... (ib-var ib-tmp))
+ (#\:do olet
+ lbs
+ ne1?
+ (let (ib ...) ic ... (set! ib-tmp ib-var))
+ ne2?
+ lss)))
+ ((\:while-2 cc
+ test
+ (ib-let ...)
+ (ib-save ...)
+ (ib-restore ...)
+ (#\:do (let (ob ...) oc ...) lbs ne1? (let () ic ...) ne2? lss))
+ (#\:do cc
+ (let (ob ... ib-let ...) oc ...)
+ lbs
+ (let ((ne1?-value ne1?))
+ (and ne1?-value
+ (let (ib-save ...)
+ ic ...
+ test)))
+ (let (ib-restore ...))
+ ne2?
+ lss))))
+
+
+(define-syntax \:until
+ (syntax-rules ()
+ ((\:until cc (g arg1 arg ...) test)
+ (g (\:until-1 cc test) arg1 arg ...) )))
+
+(define-syntax \:until-1
+ (syntax-rules (#\:do)
+ ((\:until-1 cc test (#\:do olet lbs ne1? ilet ne2? lss))
+ (#\:do cc olet lbs ne1? ilet (and ne2? (not test)) lss) )))
+
+
+; ==========================================================================
+; The typed generators :list :string :vector etc.
+; ==========================================================================
+
+(define-syntax \:list
+ (syntax-rules (index)
+ ((\:list cc var (index i) arg ...)
+ (\:parallel cc (\:list var arg ...) (\:integers i)) )
+ ((\:list cc var arg1 arg2 arg ...)
+ (\:list cc var (append arg1 arg2 arg ...)) )
+ ((\:list cc var arg)
+ (#\:do cc
+ (let ())
+ ((t arg))
+ (not (null? t))
+ (let ((var (car t))))
+ #t
+ ((cdr t)) ))))
+
+
+(define-syntax \:string
+ (syntax-rules (index)
+ ((\:string cc var (index i) arg)
+ (#\:do cc
+ (let ((str arg) (len 0))
+ (set! len (string-length str)))
+ ((i 0))
+ (< i len)
+ (let ((var (string-ref str i))))
+ #t
+ ((+ i 1)) ))
+ ((\:string cc var (index i) arg1 arg2 arg ...)
+ (\:string cc var (index i) (string-append arg1 arg2 arg ...)) )
+ ((\:string cc var arg1 arg ...)
+ (\:string cc var (index i) arg1 arg ...) )))
+
+; Alternative: An implementation in the style of :vector can also
+; be used for :string. However, it is less interesting as the
+; overhead of string-append is much less than for 'vector-append'.
+
+
+(define-syntax \:vector
+ (syntax-rules (index)
+ ((\:vector cc var arg)
+ (\:vector cc var (index i) arg) )
+ ((\:vector cc var (index i) arg)
+ (#\:do cc
+ (let ((vec arg) (len 0))
+ (set! len (vector-length vec)))
+ ((i 0))
+ (< i len)
+ (let ((var (vector-ref vec i))))
+ #t
+ ((+ i 1)) ))
+
+ ((\:vector cc var (index i) arg1 arg2 arg ...)
+ (\:parallel cc (\:vector cc var arg1 arg2 arg ...) (\:integers i)) )
+ ((\:vector cc var arg1 arg2 arg ...)
+ (#\:do cc
+ (let ((vec #f)
+ (len 0)
+ (vecs (ec-:vector-filter (list arg1 arg2 arg ...))) ))
+ ((k 0))
+ (if (< k len)
+ #t
+ (if (null? vecs)
+ #f
+ (begin (set! vec (car vecs))
+ (set! vecs (cdr vecs))
+ (set! len (vector-length vec))
+ (set! k 0)
+ #t )))
+ (let ((var (vector-ref vec k))))
+ #t
+ ((+ k 1)) ))))
+
+(define (ec-:vector-filter vecs)
+ (if (null? vecs)
+ '()
+ (if (zero? (vector-length (car vecs)))
+ (ec-:vector-filter (cdr vecs))
+ (cons (car vecs) (ec-:vector-filter (cdr vecs))) )))
+
+; Alternative: A simpler implementation for :vector uses vector->list
+; append and :list in the multi-argument case. Please refer to the
+; 'design.scm' for more details.
+
+
+(define-syntax \:integers
+ (syntax-rules (index)
+ ((\:integers cc var (index i))
+ (#\:do cc ((var 0) (i 0)) #t ((+ var 1) (+ i 1))) )
+ ((\:integers cc var)
+ (#\:do cc ((var 0)) #t ((+ var 1))) )))
+
+
+(define-syntax \:range
+ (syntax-rules (index)
+
+ ; handle index variable and add optional args
+ ((\:range cc var (index i) arg1 arg ...)
+ (\:parallel cc (\:range var arg1 arg ...) (\:integers i)) )
+ ((\:range cc var arg1)
+ (\:range cc var 0 arg1 1) )
+ ((\:range cc var arg1 arg2)
+ (\:range cc var arg1 arg2 1) )
+
+; special cases (partially evaluated by hand from general case)
+
+ ((\:range cc var 0 arg2 1)
+ (#\:do cc
+ (let ((b arg2))
+ (if (not (and (integer? b) (exact? b)))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" 0 b 1 )))
+ ((var 0))
+ (< var b)
+ (let ())
+ #t
+ ((+ var 1)) ))
+
+ ((\:range cc var 0 arg2 -1)
+ (#\:do cc
+ (let ((b arg2))
+ (if (not (and (integer? b) (exact? b)))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" 0 b 1 )))
+ ((var 0))
+ (> var b)
+ (let ())
+ #t
+ ((- var 1)) ))
+
+ ((\:range cc var arg1 arg2 1)
+ (#\:do cc
+ (let ((a arg1) (b arg2))
+ (if (not (and (integer? a) (exact? a)
+ (integer? b) (exact? b) ))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" a b 1 )) )
+ ((var a))
+ (< var b)
+ (let ())
+ #t
+ ((+ var 1)) ))
+
+ ((\:range cc var arg1 arg2 -1)
+ (#\:do cc
+ (let ((a arg1) (b arg2) (s -1) (stop 0))
+ (if (not (and (integer? a) (exact? a)
+ (integer? b) (exact? b) ))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" a b -1 )) )
+ ((var a))
+ (> var b)
+ (let ())
+ #t
+ ((- var 1)) ))
+
+; the general case
+
+ ((\:range cc var arg1 arg2 arg3)
+ (#\:do cc
+ (let ((a arg1) (b arg2) (s arg3) (stop 0))
+ (if (not (and (integer? a) (exact? a)
+ (integer? b) (exact? b)
+ (integer? s) (exact? s) ))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" a b s ))
+ (if (zero? s)
+ (error "step size must not be zero in :range") )
+ (set! stop (+ a (* (max 0 (ceiling (/ (- b a) s))) s))) )
+ ((var a))
+ (not (= var stop))
+ (let ())
+ #t
+ ((+ var s)) ))))
+
+; Comment: The macro :range inserts some code to make sure the values
+; are exact integers. This overhead has proven very helpful for
+; saving users from themselves.
+
+
+(define-syntax \:real-range
+ (syntax-rules (index)
+
+ ; add optional args and index variable
+ ((\:real-range cc var arg1)
+ (\:real-range cc var (index i) 0 arg1 1) )
+ ((\:real-range cc var (index i) arg1)
+ (\:real-range cc var (index i) 0 arg1 1) )
+ ((\:real-range cc var arg1 arg2)
+ (\:real-range cc var (index i) arg1 arg2 1) )
+ ((\:real-range cc var (index i) arg1 arg2)
+ (\:real-range cc var (index i) arg1 arg2 1) )
+ ((\:real-range cc var arg1 arg2 arg3)
+ (\:real-range cc var (index i) arg1 arg2 arg3) )
+
+ ; the fully qualified case
+ ((\:real-range cc var (index i) arg1 arg2 arg3)
+ (#\:do cc
+ (let ((a arg1) (b arg2) (s arg3) (istop 0))
+ (if (not (and (real? a) (real? b) (real? s)))
+ (error "arguments of :real-range are not real" a b s) )
+ (if (and (exact? a) (or (not (exact? b)) (not (exact? s))))
+ (set! a (exact->inexact a)) )
+ (set! istop (/ (- b a) s)) )
+ ((i 0))
+ (< i istop)
+ (let ((var (+ a (* s i)))))
+ #t
+ ((+ i 1)) ))))
+
+; Comment: The macro :real-range adapts the exactness of the start
+; value in case any of the other values is inexact. This is a
+; precaution to avoid (list-ec (\: x 0 3.0) x) => '(0 1.0 2.0).
+
+
+(define-syntax \:char-range
+ (syntax-rules (index)
+ ((\:char-range cc var (index i) arg1 arg2)
+ (\:parallel cc (\:char-range var arg1 arg2) (\:integers i)) )
+ ((\:char-range cc var arg1 arg2)
+ (#\:do cc
+ (let ((imax (char->integer arg2))))
+ ((i (char->integer arg1)))
+ (<= i imax)
+ (let ((var (integer->char i))))
+ #t
+ ((+ i 1)) ))))
+
+; Warning: There is no R5RS-way to implement the :char-range generator
+; because the integers obtained by char->integer are not necessarily
+; consecutive. We simply assume this anyhow for illustration.
+
+
+(define-syntax \:port
+ (syntax-rules (index)
+ ((\:port cc var (index i) arg1 arg ...)
+ (\:parallel cc (\:port var arg1 arg ...) (\:integers i)) )
+ ((\:port cc var arg)
+ (\:port cc var arg read) )
+ ((\:port cc var arg1 arg2)
+ (#\:do cc
+ (let ((port arg1) (read-proc arg2)))
+ ((var (read-proc port)))
+ (not (eof-object? var))
+ (let ())
+ #t
+ ((read-proc port)) ))))
+
+
+; ==========================================================================
+; The typed generator :dispatched and utilities for constructing dispatchers
+; ==========================================================================
+
+(define-syntax \:dispatched
+ (syntax-rules (index)
+ ((\:dispatched cc var (index i) dispatch arg1 arg ...)
+ (\:parallel cc
+ (\:integers i)
+ (\:dispatched var dispatch arg1 arg ...) ))
+ ((\:dispatched cc var dispatch arg1 arg ...)
+ (#\:do cc
+ (let ((d dispatch)
+ (args (list arg1 arg ...))
+ (g #f)
+ (empty (list #f)) )
+ (set! g (d args))
+ (if (not (procedure? g))
+ (error "unrecognized arguments in dispatching"
+ args
+ (d '()) )))
+ ((var (g empty)))
+ (not (eq? var empty))
+ (let ())
+ #t
+ ((g empty)) ))))
+
+; Comment: The unique object empty is created as a newly allocated
+; non-empty list. It is compared using eq? which distinguishes
+; the object from any other object, according to R5RS 6.1.
+
+
+(define-syntax \:generator-proc
+ (syntax-rules (#\:do let)
+
+ ; call g with a variable, reentry at (**)
+ ((\:generator-proc (g arg ...))
+ (g (\:generator-proc var) var arg ...) )
+
+ ; reentry point (**) -> make the code from a single :do
+ ((\:generator-proc
+ var
+ (#\:do (let obs oc ...)
+ ((lv li) ...)
+ ne1?
+ (let ((i v) ...) ic ...)
+ ne2?
+ (ls ...)) )
+ (ec-simplify
+ (let obs
+ oc ...
+ (let ((lv li) ... (ne2 #t))
+ (ec-simplify
+ (let ((i #f) ...) ; v not yet valid
+ (lambda (empty)
+ (if (and ne1? ne2)
+ (ec-simplify
+ (begin
+ (set! i v) ...
+ ic ...
+ (let ((value var))
+ (ec-simplify
+ (if ne2?
+ (ec-simplify
+ (begin (set! lv ls) ...) )
+ (set! ne2 #f) ))
+ value )))
+ empty ))))))))
+
+ ; silence warnings of some macro expanders
+ ((\:generator-proc var)
+ (error "illegal macro call") )))
+
+
+(define (dispatch-union d1 d2)
+ (lambda (args)
+ (let ((g1 (d1 args)) (g2 (d2 args)))
+ (if g1
+ (if g2
+ (if (null? args)
+ (append (if (list? g1) g1 (list g1))
+ (if (list? g2) g2 (list g2)) )
+ (error "dispatching conflict" args (d1 '()) (d2 '())) )
+ g1 )
+ (if g2 g2 #f) ))))
+
+
+; ==========================================================================
+; The dispatching generator :
+; ==========================================================================
+
+(define (make-initial-\:-dispatch)
+ (lambda (args)
+ (case (length args)
+ ((0) 'SRFI42)
+ ((1) (let ((a1 (car args)))
+ (cond
+ ((list? a1)
+ (\:generator-proc (\:list a1)) )
+ ((string? a1)
+ (\:generator-proc (\:string a1)) )
+ ((vector? a1)
+ (\:generator-proc (\:vector a1)) )
+ ((and (integer? a1) (exact? a1))
+ (\:generator-proc (\:range a1)) )
+ ((real? a1)
+ (\:generator-proc (\:real-range a1)) )
+ ((input-port? a1)
+ (\:generator-proc (\:port a1)) )
+ (else
+ #f ))))
+ ((2) (let ((a1 (car args)) (a2 (cadr args)))
+ (cond
+ ((and (list? a1) (list? a2))
+ (\:generator-proc (\:list a1 a2)) )
+ ((and (string? a1) (string? a1))
+ (\:generator-proc (\:string a1 a2)) )
+ ((and (vector? a1) (vector? a2))
+ (\:generator-proc (\:vector a1 a2)) )
+ ((and (integer? a1) (exact? a1) (integer? a2) (exact? a2))
+ (\:generator-proc (\:range a1 a2)) )
+ ((and (real? a1) (real? a2))
+ (\:generator-proc (\:real-range a1 a2)) )
+ ((and (char? a1) (char? a2))
+ (\:generator-proc (\:char-range a1 a2)) )
+ ((and (input-port? a1) (procedure? a2))
+ (\:generator-proc (\:port a1 a2)) )
+ (else
+ #f ))))
+ ((3) (let ((a1 (car args)) (a2 (cadr args)) (a3 (caddr args)))
+ (cond
+ ((and (list? a1) (list? a2) (list? a3))
+ (\:generator-proc (\:list a1 a2 a3)) )
+ ((and (string? a1) (string? a1) (string? a3))
+ (\:generator-proc (\:string a1 a2 a3)) )
+ ((and (vector? a1) (vector? a2) (vector? a3))
+ (\:generator-proc (\:vector a1 a2 a3)) )
+ ((and (integer? a1) (exact? a1)
+ (integer? a2) (exact? a2)
+ (integer? a3) (exact? a3))
+ (\:generator-proc (\:range a1 a2 a3)) )
+ ((and (real? a1) (real? a2) (real? a3))
+ (\:generator-proc (\:real-range a1 a2 a3)) )
+ (else
+ #f ))))
+ (else
+ (letrec ((every?
+ (lambda (pred args)
+ (if (null? args)
+ #t
+ (and (pred (car args))
+ (every? pred (cdr args)) )))))
+ (cond
+ ((every? list? args)
+ (\:generator-proc (\:list (apply append args))) )
+ ((every? string? args)
+ (\:generator-proc (\:string (apply string-append args))) )
+ ((every? vector? args)
+ (\:generator-proc (\:list (apply append (map vector->list args)))) )
+ (else
+ #f )))))))
+
+(define \\:-dispatch
+ (make-initial-\:-dispatch) )
+
+(define (\\:-dispatch-ref)
+ \:-dispatch )
+
+(define (\\:-dispatch-set! dispatch)
+ (if (not (procedure? dispatch))
+ (error "not a procedure" dispatch) )
+ (set! \:-dispatch dispatch) )
+
+(define-syntax \:
+ (syntax-rules (index)
+ ((\: cc var (index i) arg1 arg ...)
+ (\:dispatched cc var (index i) \:-dispatch arg1 arg ...) )
+ ((\: cc var arg1 arg ...)
+ (\:dispatched cc var \:-dispatch arg1 arg ...) )))
+
+
+; ==========================================================================
+; The utility comprehensions fold-ec, fold3-ec
+; ==========================================================================
+
+(define-syntax fold3-ec
+ (syntax-rules (nested)
+ ((fold3-ec x0 (nested q1 ...) q etc1 etc2 etc3 etc ...)
+ (fold3-ec x0 (nested q1 ... q) etc1 etc2 etc3 etc ...) )
+ ((fold3-ec x0 q1 q2 etc1 etc2 etc3 etc ...)
+ (fold3-ec x0 (nested q1 q2) etc1 etc2 etc3 etc ...) )
+ ((fold3-ec x0 expression f1 f2)
+ (fold3-ec x0 (nested) expression f1 f2) )
+
+ ((fold3-ec x0 qualifier expression f1 f2)
+ (let ((result #f) (empty #t))
+ (do-ec qualifier
+ (let ((value expression)) ; don't duplicate
+ (if empty
+ (begin (set! result (f1 value))
+ (set! empty #f) )
+ (set! result (f2 value result)) )))
+ (if empty x0 result) ))))
+
+
+(define-syntax fold-ec
+ (syntax-rules (nested)
+ ((fold-ec x0 (nested q1 ...) q etc1 etc2 etc ...)
+ (fold-ec x0 (nested q1 ... q) etc1 etc2 etc ...) )
+ ((fold-ec x0 q1 q2 etc1 etc2 etc ...)
+ (fold-ec x0 (nested q1 q2) etc1 etc2 etc ...) )
+ ((fold-ec x0 expression f2)
+ (fold-ec x0 (nested) expression f2) )
+
+ ((fold-ec x0 qualifier expression f2)
+ (let ((result x0))
+ (do-ec qualifier (set! result (f2 expression result)))
+ result ))))
+
+
+; ==========================================================================
+; The comprehensions list-ec string-ec vector-ec etc.
+; ==========================================================================
+
+(define-syntax list-ec
+ (syntax-rules ()
+ ((list-ec etc1 etc ...)
+ (reverse (fold-ec '() etc1 etc ... cons)) )))
+
+; Alternative: Reverse can safely be replaced by reverse! if you have it.
+;
+; Alternative: It is possible to construct the result in the correct order
+; using set-cdr! to add at the tail. This removes the overhead of copying
+; at the end, at the cost of more book-keeping.
+
+
+(define-syntax append-ec
+ (syntax-rules ()
+ ((append-ec etc1 etc ...)
+ (apply append (list-ec etc1 etc ...)) )))
+
+(define-syntax string-ec
+ (syntax-rules ()
+ ((string-ec etc1 etc ...)
+ (list->string (list-ec etc1 etc ...)) )))
+
+; Alternative: For very long strings, the intermediate list may be a
+; problem. A more space-aware implementation collect the characters
+; in an intermediate list and when this list becomes too large it is
+; converted into an intermediate string. At the end, the intermediate
+; strings are concatenated with string-append.
+
+
+(define-syntax string-append-ec
+ (syntax-rules ()
+ ((string-append-ec etc1 etc ...)
+ (apply string-append (list-ec etc1 etc ...)) )))
+
+(define-syntax vector-ec
+ (syntax-rules ()
+ ((vector-ec etc1 etc ...)
+ (list->vector (list-ec etc1 etc ...)) )))
+
+; Comment: A similar approach as for string-ec can be used for vector-ec.
+; However, the space overhead for the intermediate list is much lower
+; than for string-ec and as there is no vector-append, the intermediate
+; vectors must be copied explicitly.
+
+(define-syntax vector-of-length-ec
+ (syntax-rules (nested)
+ ((vector-of-length-ec k (nested q1 ...) q etc1 etc ...)
+ (vector-of-length-ec k (nested q1 ... q) etc1 etc ...) )
+ ((vector-of-length-ec k q1 q2 etc1 etc ...)
+ (vector-of-length-ec k (nested q1 q2) etc1 etc ...) )
+ ((vector-of-length-ec k expression)
+ (vector-of-length-ec k (nested) expression) )
+
+ ((vector-of-length-ec k qualifier expression)
+ (let ((len k))
+ (let ((vec (make-vector len))
+ (i 0) )
+ (do-ec qualifier
+ (if (< i len)
+ (begin (vector-set! vec i expression)
+ (set! i (+ i 1)) )
+ (error "vector is too short for the comprehension") ))
+ (if (= i len)
+ vec
+ (error "vector is too long for the comprehension") ))))))
+
+
+(define-syntax sum-ec
+ (syntax-rules ()
+ ((sum-ec etc1 etc ...)
+ (fold-ec (+) etc1 etc ... +) )))
+
+(define-syntax product-ec
+ (syntax-rules ()
+ ((product-ec etc1 etc ...)
+ (fold-ec (*) etc1 etc ... *) )))
+
+(define-syntax min-ec
+ (syntax-rules ()
+ ((min-ec etc1 etc ...)
+ (fold3-ec (min) etc1 etc ... min min) )))
+
+(define-syntax max-ec
+ (syntax-rules ()
+ ((max-ec etc1 etc ...)
+ (fold3-ec (max) etc1 etc ... max max) )))
+
+(define-syntax last-ec
+ (syntax-rules (nested)
+ ((last-ec default (nested q1 ...) q etc1 etc ...)
+ (last-ec default (nested q1 ... q) etc1 etc ...) )
+ ((last-ec default q1 q2 etc1 etc ...)
+ (last-ec default (nested q1 q2) etc1 etc ...) )
+ ((last-ec default expression)
+ (last-ec default (nested) expression) )
+
+ ((last-ec default qualifier expression)
+ (let ((result default))
+ (do-ec qualifier (set! result expression))
+ result ))))
+
+
+; ==========================================================================
+; The fundamental early-stopping comprehension first-ec
+; ==========================================================================
+
+(define-syntax first-ec
+ (syntax-rules (nested)
+ ((first-ec default (nested q1 ...) q etc1 etc ...)
+ (first-ec default (nested q1 ... q) etc1 etc ...) )
+ ((first-ec default q1 q2 etc1 etc ...)
+ (first-ec default (nested q1 q2) etc1 etc ...) )
+ ((first-ec default expression)
+ (first-ec default (nested) expression) )
+
+ ((first-ec default qualifier expression)
+ (let ((result default) (stop #f))
+ (ec-guarded-do-ec
+ stop
+ (nested qualifier)
+ (begin (set! result expression)
+ (set! stop #t) ))
+ result ))))
+
+; (ec-guarded-do-ec stop (nested q ...) cmd)
+; constructs (do-ec q ... cmd) where the generators gen in q ... are
+; replaced by (\:until gen stop).
+
+(define-syntax ec-guarded-do-ec
+ (syntax-rules (nested if not and or begin)
+
+ ((ec-guarded-do-ec stop (nested (nested q1 ...) q2 ...) cmd)
+ (ec-guarded-do-ec stop (nested q1 ... q2 ...) cmd) )
+
+ ((ec-guarded-do-ec stop (nested (if test) q ...) cmd)
+ (if test (ec-guarded-do-ec stop (nested q ...) cmd)) )
+ ((ec-guarded-do-ec stop (nested (not test) q ...) cmd)
+ (if (not test) (ec-guarded-do-ec stop (nested q ...) cmd)) )
+ ((ec-guarded-do-ec stop (nested (and test ...) q ...) cmd)
+ (if (and test ...) (ec-guarded-do-ec stop (nested q ...) cmd)) )
+ ((ec-guarded-do-ec stop (nested (or test ...) q ...) cmd)
+ (if (or test ...) (ec-guarded-do-ec stop (nested q ...) cmd)) )
+
+ ((ec-guarded-do-ec stop (nested (begin etc ...) q ...) cmd)
+ (begin etc ... (ec-guarded-do-ec stop (nested q ...) cmd)) )
+
+ ((ec-guarded-do-ec stop (nested gen q ...) cmd)
+ (do-ec
+ (\:until gen stop)
+ (ec-guarded-do-ec stop (nested q ...) cmd) ))
+
+ ((ec-guarded-do-ec stop (nested) cmd)
+ (do-ec cmd) )))
+
+; Alternative: Instead of modifying the generator with :until, it is
+; possible to use call-with-current-continuation:
+;
+; (define-synatx first-ec
+; ...same as above...
+; ((first-ec default qualifier expression)
+; (call-with-current-continuation
+; (lambda (cc)
+; (do-ec qualifier (cc expression))
+; default ))) ))
+;
+; This is much simpler but not necessarily as efficient.
+
+
+; ==========================================================================
+; The early-stopping comprehensions any?-ec every?-ec
+; ==========================================================================
+
+(define-syntax any?-ec
+ (syntax-rules (nested)
+ ((any?-ec (nested q1 ...) q etc1 etc ...)
+ (any?-ec (nested q1 ... q) etc1 etc ...) )
+ ((any?-ec q1 q2 etc1 etc ...)
+ (any?-ec (nested q1 q2) etc1 etc ...) )
+ ((any?-ec expression)
+ (any?-ec (nested) expression) )
+
+ ((any?-ec qualifier expression)
+ (first-ec #f qualifier (if expression) #t) )))
+
+(define-syntax every?-ec
+ (syntax-rules (nested)
+ ((every?-ec (nested q1 ...) q etc1 etc ...)
+ (every?-ec (nested q1 ... q) etc1 etc ...) )
+ ((every?-ec q1 q2 etc1 etc ...)
+ (every?-ec (nested q1 q2) etc1 etc ...) )
+ ((every?-ec expression)
+ (every?-ec (nested) expression) )
+
+ ((every?-ec qualifier expression)
+ (first-ec #t qualifier (if (not expression)) #f) )))
+
+;;;;;; SRFI 43: Vector library -*- Scheme -*-
+;;;
+;;; $Id: vector-lib.scm,v 1.7 2009/03/29 09:46:03 sperber Exp $
+;;;
+;;; Taylor Campbell wrote this code; he places it in the public domain.
+;;; Will Clinger [wdc] made some corrections, also in the public domain.
+
+;;; Copyright (C) Taylor Campbell (2003). All rights reserved.
+
+;;; Made an R7RS library by Taylan Ulrich Bayırlı/Kammer, Copyright (C) 2014.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+;;; --------------------
+;;; Exported procedure index
+;;;
+;;; * Constructors
+;;; make-vector vector
+;;; vector-unfold vector-unfold-right
+;;; vector-copy vector-reverse-copy
+;;; vector-append vector-concatenate
+;;;
+;;; * Predicates
+;;; vector?
+;;; vector-empty?
+;;; vector=
+;;;
+;;; * Selectors
+;;; vector-ref
+;;; vector-length
+;;;
+;;; * Iteration
+;;; vector-fold vector-fold-right
+;;; vector-map vector-map!
+;;; vector-for-each
+;;; vector-count
+;;;
+;;; * Searching
+;;; vector-index vector-skip
+;;; vector-index-right vector-skip-right
+;;; vector-binary-search
+;;; vector-any vector-every
+;;;
+;;; * Mutators
+;;; vector-set!
+;;; vector-swap!
+;;; vector-fill!
+;;; vector-reverse!
+;;; vector-copy! vector-reverse-copy!
+;;; vector-reverse!
+;;;
+;;; * Conversion
+;;; vector->list reverse-vector->list
+;;; list->vector reverse-list->vector
+
+
+
+;;; --------------------
+;;; Commentary on efficiency of the code
+
+;;; This code is somewhat tuned for efficiency. There are several
+;;; internal routines that can be optimized greatly to greatly improve
+;;; the performance of much of the library. These internal procedures
+;;; are already carefully tuned for performance, and lambda-lifted by
+;;; hand. Some other routines are lambda-lifted by hand, but only the
+;;; loops are lambda-lifted, and only if some routine has two possible
+;;; loops -- a fast path and an n-ary case --, whereas _all_ of the
+;;; internal routines' loops are lambda-lifted so as to never cons a
+;;; closure in their body (VECTOR-PARSE-START+END doesn't have a loop),
+;;; even in Scheme systems that perform no loop optimization (which is
+;;; most of them, unfortunately).
+;;;
+;;; Fast paths are provided for common cases in most of the loops in
+;;; this library.
+;;;
+;;; All calls to primitive vector operations are protected by a prior
+;;; type check; they can be safely converted to use unsafe equivalents
+;;; of the operations, if available. Ideally, the compiler should be
+;;; able to determine this, but the state of Scheme compilers today is
+;;; not a happy one.
+;;;
+;;; Efficiency of the actual algorithms is a rather mundane point to
+;;; mention; vector operations are rarely beyond being straightforward.
+
+
+
+;;; --------------------
+;;; Utilities
+
+(define (nonneg-int? x)
+ (and (integer? x)
+ (not (negative? x))))
+
+(define (between? x y z)
+ (and (< x y)
+ (<= y z)))
+
+(define (unspecified-value) (if #f #f))
+
+;++ This should be implemented more efficiently. It shouldn't cons a
+;++ closure, and the cons cells used in the loops when using this could
+;++ be reused.
+(define (vectors-ref vectors i)
+ (map (lambda (v) (vector-ref v i)) vectors))
+
+
+
+;;; --------------------
+;;; Internal routines
+
+;;; These should all be integrated, native, or otherwise optimized --
+;;; they're used a _lot_ --. All of the loops and LETs inside loops
+;;; are lambda-lifted by hand, just so as not to cons closures in the
+;;; loops. (If your compiler can do better than that if they're not
+;;; lambda-lifted, then lambda-drop (?) them.)
+
+;;; (VECTOR-PARSE-START+END <vector> <arguments>
+;;; <start-name> <end-name>
+;;; <callee>)
+;;; -> [start end]
+;;; Return two values, composing a valid range within VECTOR, as
+;;; extracted from ARGUMENTS or defaulted from VECTOR -- 0 for START
+;;; and the length of VECTOR for END --; START-NAME and END-NAME are
+;;; purely for error checking.
+(define (vector-parse-start+end vec args start-name end-name callee)
+ (let ((len (vector-length vec)))
+ (cond ((null? args)
+ (values 0 len))
+ ((null? (cdr args))
+ (check-indices vec
+ (car args) start-name
+ len end-name
+ callee))
+ ((null? (cddr args))
+ (check-indices vec
+ (car args) start-name
+ (cadr args) end-name
+ callee))
+ (else
+ (error "too many arguments"
+ `(extra args were ,(cddr args))
+ `(while calling ,callee))))))
+
+(define-syntax let-vector-start+end
+ (syntax-rules ()
+ ((let-vector-start+end ?callee ?vec ?args (?start ?end)
+ ?body1 ?body2 ...)
+ (let ((?vec (check-type vector? ?vec ?callee)))
+ (receive (?start ?end)
+ (vector-parse-start+end ?vec ?args '?start '?end
+ ?callee)
+ ?body1 ?body2 ...)))))
+
+;;; (%SMALLEST-LENGTH <vector-list> <default-length> <callee>)
+;;; -> exact, nonnegative integer
+;;; Compute the smallest length of VECTOR-LIST. DEFAULT-LENGTH is
+;;; the length that is returned if VECTOR-LIST is empty. Common use
+;;; of this is in n-ary vector routines:
+;;; (define (f vec . vectors)
+;;; (let ((vec (check-type vector? vec f)))
+;;; ...(%smallest-length vectors (vector-length vec) f)...))
+;;; %SMALLEST-LENGTH takes care of the type checking -- which is what
+;;; the CALLEE argument is for --; thus, the design is tuned for
+;;; avoiding redundant type checks.
+(define %smallest-length
+ (letrec ((loop (lambda (vector-list length callee)
+ (if (null? vector-list)
+ length
+ (loop (cdr vector-list)
+ (min (vector-length
+ (check-type vector?
+ (car vector-list)
+ callee))
+ length)
+ callee)))))
+ loop))
+
+;;; (%VECTOR-REVERSE-COPY! <target> <tstart> <source> <sstart> <send>)
+;;; Copy elements from SSTART to SEND from SOURCE to TARGET, in the
+;;; reverse order.
+(define %vector-reverse-copy!
+ (letrec ((loop (lambda (target source sstart i j)
+ (cond ((>= i sstart)
+ (vector-set! target j (vector-ref source i))
+ (loop target source sstart
+ (- i 1)
+ (+ j 1)))))))
+ (lambda (target tstart source sstart send)
+ (loop target source sstart
+ (- send 1)
+ tstart))))
+
+;;; (%VECTOR-REVERSE! <vector>)
+(define %vector-reverse!
+ (letrec ((loop (lambda (vec i j)
+ (cond ((<= i j)
+ (let ((v (vector-ref vec i)))
+ (vector-set! vec i (vector-ref vec j))
+ (vector-set! vec j v)
+ (loop vec (+ i 1) (- j 1))))))))
+ (lambda (vec start end)
+ (loop vec start (- end 1)))))
+
+;;; (%VECTOR-FOLD1 <kons> <knil> <vector>) -> knil'
+;;; (KONS <index> <knil> <elt>) -> knil'
+(define %vector-fold1
+ (letrec ((loop (lambda (kons knil len vec i)
+ (if (= i len)
+ knil
+ (loop kons
+ (kons i knil (vector-ref vec i))
+ len vec (+ i 1))))))
+ (lambda (kons knil len vec)
+ (loop kons knil len vec 0))))
+
+;;; (%VECTOR-FOLD2+ <kons> <knil> <vector> ...) -> knil'
+;;; (KONS <index> <knil> <elt> ...) -> knil'
+(define %vector-fold2+
+ (letrec ((loop (lambda (kons knil len vectors i)
+ (if (= i len)
+ knil
+ (loop kons
+ (apply kons i knil
+ (vectors-ref vectors i))
+ len vectors (+ i 1))))))
+ (lambda (kons knil len vectors)
+ (loop kons knil len vectors 0))))
+
+;;; (%VECTOR-MAP! <f> <target> <length> <vector>) -> target
+;;; (F <index> <elt>) -> elt'
+(define %vector-map1!
+ (letrec ((loop (lambda (f target vec i)
+ (if (zero? i)
+ target
+ (let ((j (- i 1)))
+ (vector-set! target j
+ (f j (vector-ref vec j)))
+ (loop f target vec j))))))
+ (lambda (f target vec len)
+ (loop f target vec len))))
+
+;;; (%VECTOR-MAP2+! <f> <target> <vectors> <len>) -> target
+;;; (F <index> <elt> ...) -> elt'
+(define %vector-map2+!
+ (letrec ((loop (lambda (f target vectors i)
+ (if (zero? i)
+ target
+ (let ((j (- i 1)))
+ (vector-set! target j
+ (apply f j (vectors-ref vectors j)))
+ (loop f target vectors j))))))
+ (lambda (f target vectors len)
+ (loop f target vectors len))))
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;; ***** vector-lib ***** ;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; --------------------
+;;; Constructors
+
+;;; (VECTOR-UNFOLD <f> <length> <initial-seed> ...) -> vector
+;;; (F <index> <seed> ...) -> [elt seed' ...]
+;;; The fundamental vector constructor. Creates a vector whose
+;;; length is LENGTH and iterates across each index K between 0 and
+;;; LENGTH, applying F at each iteration to the current index and the
+;;; current seeds to receive N+1 values: first, the element to put in
+;;; the Kth slot and then N new seeds for the next iteration.
+(define vector-unfold
+ (letrec ((tabulate! ; Special zero-seed case.
+ (lambda (f vec i len)
+ (cond ((< i len)
+ (vector-set! vec i (f i))
+ (tabulate! f vec (+ i 1) len)))))
+ (unfold1! ; Fast path for one seed.
+ (lambda (f vec i len seed)
+ (if (< i len)
+ (receive (elt new-seed)
+ (f i seed)
+ (vector-set! vec i elt)
+ (unfold1! f vec (+ i 1) len new-seed)))))
+ (unfold2+! ; Slower variant for N seeds.
+ (lambda (f vec i len seeds)
+ (if (< i len)
+ (receive (elt . new-seeds)
+ (apply f i seeds)
+ (vector-set! vec i elt)
+ (unfold2+! f vec (+ i 1) len new-seeds))))))
+ (lambda (f len . initial-seeds)
+ (let ((f (check-type procedure? f vector-unfold))
+ (len (check-type nonneg-int? len vector-unfold)))
+ (let ((vec (make-vector len)))
+ (cond ((null? initial-seeds)
+ (tabulate! f vec 0 len))
+ ((null? (cdr initial-seeds))
+ (unfold1! f vec 0 len (car initial-seeds)))
+ (else
+ (unfold2+! f vec 0 len initial-seeds)))
+ vec)))))
+
+;;; (VECTOR-UNFOLD-RIGHT <f> <length> <initial-seed> ...) -> vector
+;;; (F <seed> ...) -> [seed' ...]
+;;; Like VECTOR-UNFOLD, but it generates elements from LENGTH to 0
+;;; (still exclusive with LENGTH and inclusive with 0), not 0 to
+;;; LENGTH as with VECTOR-UNFOLD.
+(define vector-unfold-right
+ (letrec ((tabulate!
+ (lambda (f vec i)
+ (cond ((>= i 0)
+ (vector-set! vec i (f i))
+ (tabulate! f vec (- i 1))))))
+ (unfold1!
+ (lambda (f vec i seed)
+ (if (>= i 0)
+ (receive (elt new-seed)
+ (f i seed)
+ (vector-set! vec i elt)
+ (unfold1! f vec (- i 1) new-seed)))))
+ (unfold2+!
+ (lambda (f vec i seeds)
+ (if (>= i 0)
+ (receive (elt . new-seeds)
+ (apply f i seeds)
+ (vector-set! vec i elt)
+ (unfold2+! f vec (- i 1) new-seeds))))))
+ (lambda (f len . initial-seeds)
+ (let ((f (check-type procedure? f vector-unfold-right))
+ (len (check-type nonneg-int? len vector-unfold-right)))
+ (let ((vec (make-vector len))
+ (i (- len 1)))
+ (cond ((null? initial-seeds)
+ (tabulate! f vec i))
+ ((null? (cdr initial-seeds))
+ (unfold1! f vec i (car initial-seeds)))
+ (else
+ (unfold2+! f vec i initial-seeds)))
+ vec)))))
+
+;;; (VECTOR-REVERSE-COPY <vector> [<start> <end>]) -> vector
+;;; Create a newly allocated vector whose elements are the reversed
+;;; sequence of elements between START and END in VECTOR. START's
+;;; default is 0; END's default is the length of VECTOR.
+(define (vector-reverse-copy vec . maybe-start+end)
+ (let-vector-start+end vector-reverse-copy vec maybe-start+end
+ (start end)
+ (let ((new (make-vector (- end start))))
+ (%vector-reverse-copy! new 0 vec start end)
+ new)))
+
+;;; (VECTOR-CONCATENATE <vector-list>) -> vector
+;;; Concatenate the vectors in VECTOR-LIST. This is equivalent to
+;;; (apply vector-append VECTOR-LIST)
+;;; but VECTOR-APPEND tends to be implemented in terms of
+;;; VECTOR-CONCATENATE, and some Schemes bork when the list to apply
+;;; a function to is too long.
+;;;
+;;; Actually, they're both implemented in terms of an internal routine.
+(define (vector-concatenate vector-list)
+ (vector-concatenate:aux vector-list vector-concatenate))
+
+;;; Auxiliary for VECTOR-APPEND and VECTOR-CONCATENATE
+(define vector-concatenate:aux
+ (letrec ((compute-length
+ (lambda (vectors len callee)
+ (if (null? vectors)
+ len
+ (let ((vec (check-type vector? (car vectors)
+ callee)))
+ (compute-length (cdr vectors)
+ (+ (vector-length vec) len)
+ callee)))))
+ (concatenate!
+ (lambda (vectors target to)
+ (if (null? vectors)
+ target
+ (let* ((vec1 (car vectors))
+ (len (vector-length vec1)))
+ (vector-copy! target to vec1 0 len)
+ (concatenate! (cdr vectors) target
+ (+ to len)))))))
+ (lambda (vectors callee)
+ (cond ((null? vectors) ;+++
+ (make-vector 0))
+ ((null? (cdr vectors)) ;+++
+ ;; Blech, we still have to allocate a new one.
+ (let* ((vec (check-type vector? (car vectors) callee))
+ (len (vector-length vec))
+ (new (make-vector len)))
+ (vector-copy! new 0 vec 0 len)
+ new))
+ (else
+ (let ((new-vector
+ (make-vector (compute-length vectors 0 callee))))
+ (concatenate! vectors new-vector 0)
+ new-vector))))))
+
+
+
+;;; --------------------
+;;; Predicates
+
+;;; (VECTOR-EMPTY? <vector>) -> boolean
+;;; Return #T if VECTOR has zero elements in it, i.e. VECTOR's length
+;;; is 0, and #F if not.
+(define (vector-empty? vec)
+ (let ((vec (check-type vector? vec vector-empty?)))
+ (zero? (vector-length vec))))
+
+;;; (VECTOR= <elt=?> <vector> ...) -> boolean
+;;; (ELT=? <value> <value>) -> boolean
+;;; Determine vector equality generalized across element comparators.
+;;; Vectors A and B are equal iff their lengths are the same and for
+;;; each respective elements E_a and E_b (element=? E_a E_b) returns
+;;; a true value. ELT=? is always applied to two arguments. Element
+;;; comparison must be consistent wtih EQ?; that is, if (eq? E_a E_b)
+;;; results in a true value, then (ELEMENT=? E_a E_b) must result in a
+;;; true value. This may be exploited to avoid multiple unnecessary
+;;; element comparisons. (This implementation does, but does not deal
+;;; with the situation that ELEMENT=? is EQ? to avoid more unnecessary
+;;; comparisons, but I believe this optimization is probably fairly
+;;; insignificant.)
+;;;
+;;; If the number of vector arguments is zero or one, then #T is
+;;; automatically returned. If there are N vector arguments,
+;;; VECTOR_1 VECTOR_2 ... VECTOR_N, then VECTOR_1 & VECTOR_2 are
+;;; compared; if they are equal, the vectors VECTOR_2 ... VECTOR_N
+;;; are compared. The precise order in which ELT=? is applied is not
+;;; specified.
+(define (vector= elt=? . vectors)
+ (let ((elt=? (check-type procedure? elt=? vector=)))
+ (cond ((null? vectors)
+ #t)
+ ((null? (cdr vectors))
+ (check-type vector? (car vectors) vector=)
+ #t)
+ (else
+ (let loop ((vecs vectors))
+ (let ((vec1 (check-type vector? (car vecs) vector=))
+ (vec2+ (cdr vecs)))
+ (or (null? vec2+)
+ (and (binary-vector= elt=? vec1 (car vec2+))
+ (loop vec2+)))))))))
+(define (binary-vector= elt=? vector-a vector-b)
+ (or (eq? vector-a vector-b) ;+++
+ (let ((length-a (vector-length vector-a))
+ (length-b (vector-length vector-b)))
+ (letrec ((loop (lambda (i)
+ (or (= i length-a)
+ (and (< i length-b)
+ (test (vector-ref vector-a i)
+ (vector-ref vector-b i)
+ i)))))
+ (test (lambda (elt-a elt-b i)
+ (and (or (eq? elt-a elt-b) ;+++
+ (elt=? elt-a elt-b))
+ (loop (+ i 1))))))
+ (and (= length-a length-b)
+ (loop 0))))))
+
+
+
+;;; --------------------
+;;; Iteration
+
+;;; (VECTOR-FOLD <kons> <initial-knil> <vector> ...) -> knil
+;;; (KONS <knil> <elt> ...) -> knil' ; N vectors -> N+1 args
+;;; The fundamental vector iterator. KONS is iterated over each
+;;; index in all of the vectors in parallel, stopping at the end of
+;;; the shortest; KONS is applied to an argument list of (list I
+;;; STATE (vector-ref VEC I) ...), where STATE is the current state
+;;; value -- the state value begins with KNIL and becomes whatever
+;;; KONS returned at the respective iteration --, and I is the
+;;; current index in the iteration. The iteration is strictly left-
+;;; to-right.
+;;; (vector-fold KONS KNIL (vector E_1 E_2 ... E_N))
+;;; <=>
+;;; (KONS (... (KONS (KONS KNIL E_1) E_2) ... E_N-1) E_N)
+(define (vector-fold kons knil vec . vectors)
+ (let ((kons (check-type procedure? kons vector-fold))
+ (vec (check-type vector? vec vector-fold)))
+ (if (null? vectors)
+ (%vector-fold1 kons knil (vector-length vec) vec)
+ (%vector-fold2+ kons knil
+ (%smallest-length vectors
+ (vector-length vec)
+ vector-fold)
+ (cons vec vectors)))))
+
+;;; (VECTOR-FOLD-RIGHT <kons> <initial-knil> <vector> ...) -> knil
+;;; (KONS <knil> <elt> ...) -> knil' ; N vectors => N+1 args
+;;; The fundamental vector recursor. Iterates in parallel across
+;;; VECTOR ... right to left, applying KONS to the elements and the
+;;; current state value; the state value becomes what KONS returns
+;;; at each next iteration. KNIL is the initial state value.
+;;; (vector-fold-right KONS KNIL (vector E_1 E_2 ... E_N))
+;;; <=>
+;;; (KONS (... (KONS (KONS KNIL E_N) E_N-1) ... E_2) E_1)
+;;;
+;;; Not implemented in terms of a more primitive operations that might
+;;; called %VECTOR-FOLD-RIGHT due to the fact that it wouldn't be very
+;;; useful elsewhere.
+(define vector-fold-right
+ (letrec ((loop1 (lambda (kons knil vec i)
+ (if (negative? i)
+ knil
+ (loop1 kons (kons i knil (vector-ref vec i))
+ vec
+ (- i 1)))))
+ (loop2+ (lambda (kons knil vectors i)
+ (if (negative? i)
+ knil
+ (loop2+ kons
+ (apply kons i knil
+ (vectors-ref vectors i))
+ vectors
+ (- i 1))))))
+ (lambda (kons knil vec . vectors)
+ (let ((kons (check-type procedure? kons vector-fold-right))
+ (vec (check-type vector? vec vector-fold-right)))
+ (if (null? vectors)
+ (loop1 kons knil vec (- (vector-length vec) 1))
+ (loop2+ kons knil (cons vec vectors)
+ (- (%smallest-length vectors
+ (vector-length vec)
+ vector-fold-right)
+ 1)))))))
+
+;;; (VECTOR-MAP <f> <vector> ...) -> vector
+;;; (F <elt> ...) -> value ; N vectors -> N args
+;;; Constructs a new vector of the shortest length of the vector
+;;; arguments. Each element at index I of the new vector is mapped
+;;; from the old vectors by (F I (vector-ref VECTOR I) ...). The
+;;; dynamic order of application of F is unspecified.
+(define (vector-map f vec . vectors)
+ (let ((f (check-type procedure? f vector-map))
+ (vec (check-type vector? vec vector-map)))
+ (if (null? vectors)
+ (let ((len (vector-length vec)))
+ (%vector-map1! f (make-vector len) vec len))
+ (let ((len (%smallest-length vectors
+ (vector-length vec)
+ vector-map)))
+ (%vector-map2+! f (make-vector len) (cons vec vectors)
+ len)))))
+
+;;; (VECTOR-MAP! <f> <vector> ...) -> unspecified
+;;; (F <elt> ...) -> element' ; N vectors -> N args
+;;; Similar to VECTOR-MAP, but rather than mapping the new elements
+;;; into a new vector, the new mapped elements are destructively
+;;; inserted into the first vector. Again, the dynamic order of
+;;; application of F is unspecified, so it is dangerous for F to
+;;; manipulate the first VECTOR.
+(define (vector-map! f vec . vectors)
+ (let ((f (check-type procedure? f vector-map!))
+ (vec (check-type vector? vec vector-map!)))
+ (if (null? vectors)
+ (%vector-map1! f vec vec (vector-length vec))
+ (%vector-map2+! f vec (cons vec vectors)
+ (%smallest-length vectors
+ (vector-length vec)
+ vector-map!)))
+ (unspecified-value)))
+
+;;; (VECTOR-FOR-EACH <f> <vector> ...) -> unspecified
+;;; (F <elt> ...) ; N vectors -> N args
+;;; Simple vector iterator: applies F to each index in the range [0,
+;;; LENGTH), where LENGTH is the length of the smallest vector
+;;; argument passed, and the respective element at that index. In
+;;; contrast with VECTOR-MAP, F is reliably applied to each
+;;; subsequent elements, starting at index 0 from left to right, in
+;;; the vectors.
+(define vector-for-each
+ (letrec ((for-each1
+ (lambda (f vec i len)
+ (cond ((< i len)
+ (f i (vector-ref vec i))
+ (for-each1 f vec (+ i 1) len)))))
+ (for-each2+
+ (lambda (f vecs i len)
+ (cond ((< i len)
+ (apply f i (vectors-ref vecs i))
+ (for-each2+ f vecs (+ i 1) len))))))
+ (lambda (f vec . vectors)
+ (let ((f (check-type procedure? f vector-for-each))
+ (vec (check-type vector? vec vector-for-each)))
+ (if (null? vectors)
+ (for-each1 f vec 0 (vector-length vec))
+ (for-each2+ f (cons vec vectors) 0
+ (%smallest-length vectors
+ (vector-length vec)
+ vector-for-each)))))))
+
+;;; (VECTOR-COUNT <predicate?> <vector> ...)
+;;; -> exact, nonnegative integer
+;;; (PREDICATE? <index> <value> ...) ; N vectors -> N+1 args
+;;; PREDICATE? is applied element-wise to the elements of VECTOR ...,
+;;; and a count is tallied of the number of elements for which a
+;;; true value is produced by PREDICATE?. This count is returned.
+(define (vector-count pred? vec . vectors)
+ (let ((pred? (check-type procedure? pred? vector-count))
+ (vec (check-type vector? vec vector-count)))
+ (if (null? vectors)
+ (%vector-fold1 (lambda (index count elt)
+ (if (pred? index elt)
+ (+ count 1)
+ count))
+ 0
+ (vector-length vec)
+ vec)
+ (%vector-fold2+ (lambda (index count . elts)
+ (if (apply pred? index elts)
+ (+ count 1)
+ count))
+ 0
+ (%smallest-length vectors
+ (vector-length vec)
+ vector-count)
+ (cons vec vectors)))))
+
+
+
+;;; --------------------
+;;; Searching
+
+;;; (VECTOR-INDEX <predicate?> <vector> ...)
+;;; -> exact, nonnegative integer or #F
+;;; (PREDICATE? <elt> ...) -> boolean ; N vectors -> N args
+;;; Search left-to-right across VECTOR ... in parallel, returning the
+;;; index of the first set of values VALUE ... such that (PREDICATE?
+;;; VALUE ...) returns a true value; if no such set of elements is
+;;; reached, return #F.
+(define (vector-index pred? vec . vectors)
+ (vector-index/skip pred? vec vectors vector-index))
+
+;;; (VECTOR-SKIP <predicate?> <vector> ...)
+;;; -> exact, nonnegative integer or #F
+;;; (PREDICATE? <elt> ...) -> boolean ; N vectors -> N args
+;;; (vector-index (lambda elts (not (apply PREDICATE? elts)))
+;;; VECTOR ...)
+;;; Like VECTOR-INDEX, but find the index of the first set of values
+;;; that do _not_ satisfy PREDICATE?.
+(define (vector-skip pred? vec . vectors)
+ (vector-index/skip (lambda elts (not (apply pred? elts)))
+ vec vectors
+ vector-skip))
+
+;;; Auxiliary for VECTOR-INDEX & VECTOR-SKIP
+(define vector-index/skip
+ (letrec ((loop1 (lambda (pred? vec len i)
+ (cond ((= i len) #f)
+ ((pred? (vector-ref vec i)) i)
+ (else (loop1 pred? vec len (+ i 1))))))
+ (loop2+ (lambda (pred? vectors len i)
+ (cond ((= i len) #f)
+ ((apply pred? (vectors-ref vectors i)) i)
+ (else (loop2+ pred? vectors len
+ (+ i 1)))))))
+ (lambda (pred? vec vectors callee)
+ (let ((pred? (check-type procedure? pred? callee))
+ (vec (check-type vector? vec callee)))
+ (if (null? vectors)
+ (loop1 pred? vec (vector-length vec) 0)
+ (loop2+ pred? (cons vec vectors)
+ (%smallest-length vectors
+ (vector-length vec)
+ callee)
+ 0))))))
+
+;;; (VECTOR-INDEX-RIGHT <predicate?> <vector> ...)
+;;; -> exact, nonnegative integer or #F
+;;; (PREDICATE? <elt> ...) -> boolean ; N vectors -> N args
+;;; Right-to-left variant of VECTOR-INDEX.
+(define (vector-index-right pred? vec . vectors)
+ (vector-index/skip-right pred? vec vectors vector-index-right))
+
+;;; (VECTOR-SKIP-RIGHT <predicate?> <vector> ...)
+;;; -> exact, nonnegative integer or #F
+;;; (PREDICATE? <elt> ...) -> boolean ; N vectors -> N args
+;;; Right-to-left variant of VECTOR-SKIP.
+(define (vector-skip-right pred? vec . vectors)
+ (vector-index/skip-right (lambda elts (not (apply pred? elts)))
+ vec vectors
+ vector-index-right))
+
+(define vector-index/skip-right
+ (letrec ((loop1 (lambda (pred? vec i)
+ (cond ((negative? i) #f)
+ ((pred? (vector-ref vec i)) i)
+ (else (loop1 pred? vec (- i 1))))))
+ (loop2+ (lambda (pred? vectors i)
+ (cond ((negative? i) #f)
+ ((apply pred? (vectors-ref vectors i)) i)
+ (else (loop2+ pred? vectors (- i 1)))))))
+ (lambda (pred? vec vectors callee)
+ (let ((pred? (check-type procedure? pred? callee))
+ (vec (check-type vector? vec callee)))
+ (if (null? vectors)
+ (loop1 pred? vec (- (vector-length vec) 1))
+ (loop2+ pred? (cons vec vectors)
+ (- (%smallest-length vectors
+ (vector-length vec)
+ callee)
+ 1)))))))
+
+;;; (VECTOR-BINARY-SEARCH <vector> <value> <cmp> [<start> <end>])
+;;; -> exact, nonnegative integer or #F
+;;; (CMP <value1> <value2>) -> integer
+;;; positive -> VALUE1 > VALUE2
+;;; zero -> VALUE1 = VALUE2
+;;; negative -> VALUE1 < VALUE2
+;;; Perform a binary search through VECTOR for VALUE, comparing each
+;;; element to VALUE with CMP.
+(define (vector-binary-search vec value cmp . maybe-start+end)
+ (let ((cmp (check-type procedure? cmp vector-binary-search)))
+ (let-vector-start+end vector-binary-search vec maybe-start+end
+ (start end)
+ (let loop ((start start) (end end) (j #f))
+ (let ((i (quotient (+ start end) 2)))
+ (if (or (= start end) (and j (= i j)))
+ #f
+ (let ((comparison
+ (check-type integer?
+ (cmp (vector-ref vec i) value)
+ `(,cmp for ,vector-binary-search))))
+ (cond ((zero? comparison) i)
+ ((positive? comparison) (loop start i i))
+ (else (loop i end i))))))))))
+
+;;; (VECTOR-ANY <pred?> <vector> ...) -> value
+;;; Apply PRED? to each parallel element in each VECTOR ...; if PRED?
+;;; should ever return a true value, immediately stop and return that
+;;; value; otherwise, when the shortest vector runs out, return #F.
+;;; The iteration and order of application of PRED? across elements
+;;; is of the vectors is strictly left-to-right.
+(define vector-any
+ (letrec ((loop1 (lambda (pred? vec i len len-1)
+ (and (not (= i len))
+ (if (= i len-1)
+ (pred? (vector-ref vec i))
+ (or (pred? (vector-ref vec i))
+ (loop1 pred? vec (+ i 1)
+ len len-1))))))
+ (loop2+ (lambda (pred? vectors i len len-1)
+ (and (not (= i len))
+ (if (= i len-1)
+ (apply pred? (vectors-ref vectors i))
+ (or (apply pred? (vectors-ref vectors i))
+ (loop2+ pred? vectors (+ i 1)
+ len len-1)))))))
+ (lambda (pred? vec . vectors)
+ (let ((pred? (check-type procedure? pred? vector-any))
+ (vec (check-type vector? vec vector-any)))
+ (if (null? vectors)
+ (let ((len (vector-length vec)))
+ (loop1 pred? vec 0 len (- len 1)))
+ (let ((len (%smallest-length vectors
+ (vector-length vec)
+ vector-any)))
+ (loop2+ pred? (cons vec vectors) 0 len (- len 1))))))))
+
+;;; (VECTOR-EVERY <pred?> <vector> ...) -> value
+;;; Apply PRED? to each parallel value in each VECTOR ...; if PRED?
+;;; should ever return #F, immediately stop and return #F; otherwise,
+;;; if PRED? should return a true value for each element, stopping at
+;;; the end of the shortest vector, return the last value that PRED?
+;;; returned. In the case that there is an empty vector, return #T.
+;;; The iteration and order of application of PRED? across elements
+;;; is of the vectors is strictly left-to-right.
+(define vector-every
+ (letrec ((loop1 (lambda (pred? vec i len len-1)
+ (or (= i len)
+ (if (= i len-1)
+ (pred? (vector-ref vec i))
+ (and (pred? (vector-ref vec i))
+ (loop1 pred? vec (+ i 1)
+ len len-1))))))
+ (loop2+ (lambda (pred? vectors i len len-1)
+ (or (= i len)
+ (if (= i len-1)
+ (apply pred? (vectors-ref vectors i))
+ (and (apply pred? (vectors-ref vectors i))
+ (loop2+ pred? vectors (+ i 1)
+ len len-1)))))))
+ (lambda (pred? vec . vectors)
+ (let ((pred? (check-type procedure? pred? vector-every))
+ (vec (check-type vector? vec vector-every)))
+ (if (null? vectors)
+ (let ((len (vector-length vec)))
+ (loop1 pred? vec 0 len (- len 1)))
+ (let ((len (%smallest-length vectors
+ (vector-length vec)
+ vector-every)))
+ (loop2+ pred? (cons vec vectors) 0 len (- len 1))))))))
+
+
+
+;;; --------------------
+;;; Mutators
+
+;;; (VECTOR-SWAP! <vector> <index1> <index2>) -> unspecified
+;;; Swap the values in the locations at INDEX1 and INDEX2.
+(define (vector-swap! vec i j)
+ (let ((vec (check-type vector? vec vector-swap!)))
+ (let ((i (check-index vec i vector-swap!))
+ (j (check-index vec j vector-swap!)))
+ (let ((x (vector-ref vec i)))
+ (vector-set! vec i (vector-ref vec j))
+ (vector-set! vec j x)))))
+
+;;; (VECTOR-REVERSE-COPY! <target> <tstart> <source> [<sstart> <send>])
+;;; [wdc] Corrected to allow 0 <= sstart <= send <= (vector-length source).
+(define (vector-reverse-copy! target tstart source . maybe-sstart+send)
+ (define (doit! sstart send source-length)
+ (let ((tstart (check-type nonneg-int? tstart vector-reverse-copy!))
+ (sstart (check-type nonneg-int? sstart vector-reverse-copy!))
+ (send (check-type nonneg-int? send vector-reverse-copy!)))
+ (cond ((and (eq? target source)
+ (or (between? sstart tstart send)
+ (between? tstart sstart
+ (+ tstart (- send sstart)))))
+ (error "vector range for self-copying overlaps"
+ vector-reverse-copy!
+ `(vector was ,target)
+ `(tstart was ,tstart)
+ `(sstart was ,sstart)
+ `(send was ,send)))
+ ((and (<= 0 sstart send source-length)
+ (<= (+ tstart (- send sstart)) (vector-length target)))
+ (%vector-reverse-copy! target tstart source sstart send))
+ (else
+ (error "illegal arguments"
+ `(while calling ,vector-reverse-copy!)
+ `(target was ,target)
+ `(target-length was ,(vector-length target))
+ `(tstart was ,tstart)
+ `(source was ,source)
+ `(source-length was ,source-length)
+ `(sstart was ,sstart)
+ `(send was ,send))))))
+ (let ((n (vector-length source)))
+ (cond ((null? maybe-sstart+send)
+ (doit! 0 n n))
+ ((null? (cdr maybe-sstart+send))
+ (doit! (car maybe-sstart+send) n n))
+ ((null? (cddr maybe-sstart+send))
+ (doit! (car maybe-sstart+send) (cadr maybe-sstart+send) n))
+ (else
+ (error "too many arguments"
+ vector-reverse-copy!
+ (cddr maybe-sstart+send))))))
+
+;;; (VECTOR-REVERSE! <vector> [<start> <end>]) -> unspecified
+;;; Destructively reverse the contents of the sequence of locations
+;;; in VECTOR between START, whose default is 0, and END, whose
+;;; default is the length of VECTOR.
+(define (vector-reverse! vec . start+end)
+ (let-vector-start+end vector-reverse! vec start+end
+ (start end)
+ (%vector-reverse! vec start end)))
+
+
+
+;;; --------------------
+;;; Conversion
+
+;;; (REVERSE-VECTOR->LIST <vector> [<start> <end>]) -> list
+;;; Produce a list containing the elements in the locations between
+;;; START, whose default is 0, and END, whose default is the length
+;;; of VECTOR, from VECTOR, in reverse order.
+(define (reverse-vector->list vec . maybe-start+end)
+ (let-vector-start+end reverse-vector->list vec maybe-start+end
+ (start end)
+ ;(unfold (lambda (i) (= i end)) ; No SRFI 1.
+ ; (lambda (i) (vector-ref vec i))
+ ; (lambda (i) (+ i 1))
+ ; start)
+ (do ((i start (+ i 1))
+ (result '() (cons (vector-ref vec i) result)))
+ ((= i end) result))))
+
+;;; (LIST->VECTOR <list> [<start> <end>]) -> vector
+;;; [R5RS+] Produce a vector containing the elements in LIST, which
+;;; must be a proper list, between START, whose default is 0, & END,
+;;; whose default is the length of LIST. It is suggested that if the
+;;; length of LIST is known in advance, the START and END arguments
+;;; be passed, so that LIST->VECTOR need not call LENGTH to determine
+;;; the the length.
+;;;
+;;; This implementation diverges on circular lists, unless LENGTH fails
+;;; and causes - to fail as well. Given a LENGTH* that computes the
+;;; length of a list's cycle, this wouldn't diverge, and would work
+;;; great for circular lists.
+(define list->vector
+ (case-lambda
+ ((lst) (%list->vector lst))
+ ((lst start) (list->vector lst start (length lst)))
+ ((lst start end)
+ (let ((start (check-type nonneg-int? start list->vector))
+ (end (check-type nonneg-int? end list->vector)))
+ ((lambda (f)
+ (vector-unfold f (- end start) (list-tail lst start)))
+ (lambda (index l)
+ (cond ((null? l)
+ (error "list was too short"
+ `(list was ,lst)
+ `(attempted end was ,end)
+ `(while calling ,list->vector)))
+ ((pair? l)
+ (values (car l) (cdr l)))
+ (else
+ ;; Make this look as much like what CHECK-TYPE
+ ;; would report as possible.
+ (error "erroneous value"
+ ;; We want SRFI 1's PROPER-LIST?, but it
+ ;; would be a waste to link all of SRFI
+ ;; 1 to this module for only the single
+ ;; function PROPER-LIST?.
+ (list list? lst)
+ `(while calling
+ ,list->vector))))))))))
+
+;;; (REVERSE-LIST->VECTOR <list> [<start> <end>]) -> vector
+;;; Produce a vector containing the elements in LIST, which must be a
+;;; proper list, between START, whose default is 0, and END, whose
+;;; default is the length of LIST, in reverse order. It is suggested
+;;; that if the length of LIST is known in advance, the START and END
+;;; arguments be passed, so that REVERSE-LIST->VECTOR need not call
+;;; LENGTH to determine the the length.
+;;;
+;;; This also diverges on circular lists unless, again, LENGTH returns
+;;; something that makes - bork.
+(define reverse-list->vector
+ (case-lambda
+ ((lst) (reverse-list->vector lst 0 (length lst)))
+ ((lst start) (reverse-list->vector start (length lst)))
+ ((lst start end)
+ (let ((start (check-type nonneg-int? start reverse-list->vector))
+ (end (check-type nonneg-int? end reverse-list->vector)))
+ ((lambda (f)
+ (vector-unfold-right f (- end start) (list-tail lst start)))
+ (lambda (index l)
+ (cond ((null? l)
+ (error "list too short"
+ `(list was ,lst)
+ `(attempted end was ,end)
+ `(while calling ,reverse-list->vector)))
+ ((pair? l)
+ (values (car l) (cdr l)))
+ (else
+ (error "erroneous value"
+ (list list? lst)
+ `(while calling ,reverse-list->vector))))))))))
+(define-library (srfi 43)
+ (export
+
+ ;; Constructors
+ vector-unfold vector-unfold-right
+ vector-reverse-copy
+ vector-concatenate
+
+ ;; Predicates
+ vector-empty?
+ vector=
+
+ ;; Iteration
+ vector-fold vector-fold-right
+ vector-map vector-map!
+ vector-for-each
+ vector-count
+
+ ;; Searching
+ vector-index vector-index-right
+ vector-skip vector-skip-right
+ vector-binary-search
+ vector-any vector-every
+
+ ;; Mutators
+ vector-swap!
+ vector-reverse!
+ vector-reverse-copy!
+
+ ;; Conversion
+ reverse-vector->list
+ list->vector
+ reverse-list->vector
+
+ )
+ (import
+ (rename (scheme base) (list->vector %list->vector))
+ (scheme case-lambda)
+ (scheme cxr)
+ (srfi 8)
+ (srfi aux))
+ (begin
+
+ (define-aux-forms check-type let-optionals* #\:optional)
+
+ ;; (CHECK-INDEX <vector> <index> <callee>) -> index
+ ;; Ensure that INDEX is a valid index into VECTOR; if not, signal an
+ ;; error stating that it is not and that this happened in a call to
+ ;; CALLEE. Return INDEX when it is valid. (Note that this does NOT
+ ;; check that VECTOR is indeed a vector.)
+ (define check-index
+ (if (debug-mode)
+ (lambda (vec index callee)
+ (let ((index (check-type integer? index callee)))
+ (cond ((< index 0)
+ (check-index vec
+ (error "vector index too low"
+ index
+ `(into vector ,vec)
+ `(while calling ,callee))
+ callee))
+ ((>= index (vector-length vec))
+ (check-index vec
+ (error "vector index too high"
+ index
+ `(into vector ,vec)
+ `(while calling ,callee))
+ callee))
+ (else index))))
+ (lambda (vec index callee)
+ index)))
+
+ ;; (CHECK-INDICES <vector>
+ ;; <start> <start-name>
+ ;; <end> <end-name>
+ ;; <caller>) -> [start end]
+ ;; Ensure that START and END are valid bounds of a range within
+ ;; VECTOR; if not, signal an error stating that they are not, with
+ ;; the message being informative about what the argument names were
+ ;; called -- by using START-NAME & END-NAME --, and that it occurred
+ ;; while calling CALLEE. Also ensure that VEC is in fact a vector.
+ ;; Returns no useful value.
+ (define check-indices
+ (if (debug-mode)
+ (lambda (vec start start-name end end-name callee)
+ (let ((lose (lambda things
+ (apply error "vector range out of bounds"
+ (append things
+ `(vector was ,vec)
+ `(,start-name was ,start)
+ `(,end-name was ,end)
+ `(while calling ,callee)))))
+ (start (check-type integer? start callee))
+ (end (check-type integer? end callee)))
+ (cond ((> start end)
+ ;; I'm not sure how well this will work. The intent is that
+ ;; the programmer tells the debugger to proceed with both a
+ ;; new START & a new END by returning multiple values
+ ;; somewhere.
+ (receive (new-start new-end)
+ (lose `(,end-name < ,start-name))
+ (check-indices vec
+ new-start start-name
+ new-end end-name
+ callee)))
+ ((< start 0)
+ (check-indices vec
+ (lose `(,start-name < 0))
+ start-name
+ end end-name
+ callee))
+ ((>= start (vector-length vec))
+ (check-indices vec
+ (lose `(,start-name > len)
+ `(len was ,(vector-length vec)))
+ start-name
+ end end-name
+ callee))
+ ((> end (vector-length vec))
+ (check-indices vec
+ start start-name
+ (lose `(,end-name > len)
+ `(len was ,(vector-length vec)))
+ end-name
+ callee))
+ (else
+ (values start end)))))
+ (lambda (vec start start-name end end-name callee)
+ (values start end))))
+
+ )
+ (include "43.body.scm"))
+(define-library (srfi 95)
+ (export sorted? merge merge! sort sort!)
+ (import
+ (except (scheme base) equal?)
+ (srfi 63))
+ (include "95.body.scm"))
+;;;;;; SRFI 43: Vector library -*- Scheme -*-
+;;;
+;;; $Id: vector-lib.scm,v 1.7 2009/03/29 09:46:03 sperber Exp $
+;;;
+;;; Taylor Campbell wrote this code; he places it in the public domain.
+;;; Will Clinger [wdc] made some corrections, also in the public domain.
+
+;;; Copyright (C) Taylor Campbell (2003). All rights reserved.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+;;; --------------------
+;;; Exported procedure index
+;;;
+;;; * Constructors
+;;; make-vector vector
+;;; vector-unfold vector-unfold-right
+;;; vector-copy vector-reverse-copy
+;;; vector-append vector-concatenate
+;;;
+;;; * Predicates
+;;; vector?
+;;; vector-empty?
+;;; vector=
+;;;
+;;; * Selectors
+;;; vector-ref
+;;; vector-length
+;;;
+;;; * Iteration
+;;; vector-fold vector-fold-right
+;;; vector-map vector-map!
+;;; vector-for-each
+;;; vector-count
+;;;
+;;; * Searching
+;;; vector-index vector-skip
+;;; vector-index-right vector-skip-right
+;;; vector-binary-search
+;;; vector-any vector-every
+;;;
+;;; * Mutators
+;;; vector-set!
+;;; vector-swap!
+;;; vector-fill!
+;;; vector-reverse!
+;;; vector-copy! vector-reverse-copy!
+;;; vector-reverse!
+;;;
+;;; * Conversion
+;;; vector->list reverse-vector->list
+;;; list->vector reverse-list->vector
+
+
+
+;;; --------------------
+;;; Commentary on efficiency of the code
+
+;;; This code is somewhat tuned for efficiency. There are several
+;;; internal routines that can be optimized greatly to greatly improve
+;;; the performance of much of the library. These internal procedures
+;;; are already carefully tuned for performance, and lambda-lifted by
+;;; hand. Some other routines are lambda-lifted by hand, but only the
+;;; loops are lambda-lifted, and only if some routine has two possible
+;;; loops -- a fast path and an n-ary case --, whereas _all_ of the
+;;; internal routines' loops are lambda-lifted so as to never cons a
+;;; closure in their body (VECTOR-PARSE-START+END doesn't have a loop),
+;;; even in Scheme systems that perform no loop optimization (which is
+;;; most of them, unfortunately).
+;;;
+;;; Fast paths are provided for common cases in most of the loops in
+;;; this library.
+;;;
+;;; All calls to primitive vector operations are protected by a prior
+;;; type check; they can be safely converted to use unsafe equivalents
+;;; of the operations, if available. Ideally, the compiler should be
+;;; able to determine this, but the state of Scheme compilers today is
+;;; not a happy one.
+;;;
+;;; Efficiency of the actual algorithms is a rather mundane point to
+;;; mention; vector operations are rarely beyond being straightforward.
+
+
+
+;;; --------------------
+;;; Utilities
+
+;;; SRFI 8, too trivial to put in the dependencies list.
+(define-syntax receive
+ (syntax-rules ()
+ ((receive ?formals ?producer ?body1 ?body2 ...)
+ (call-with-values (lambda () ?producer)
+ (lambda ?formals ?body1 ?body2 ...)))))
+
+;;; Not the best LET*-OPTIONALS, but not the worst, either. Use Olin's
+;;; if it's available to you.
+(define-syntax let*-optionals
+ (syntax-rules ()
+ ((let*-optionals (?x ...) ((?var ?default) ...) ?body1 ?body2 ...)
+ (let ((args (?x ...)))
+ (let*-optionals args ((?var ?default) ...) ?body1 ?body2 ...)))
+ ((let*-optionals ?args ((?var ?default) ...) ?body1 ?body2 ...)
+ (let*-optionals:aux ?args ?args ((?var ?default) ...)
+ ?body1 ?body2 ...))))
+
+(define-syntax let*-optionals:aux
+ (syntax-rules ()
+ ((aux ?orig-args-var ?args-var () ?body1 ?body2 ...)
+ (if (null? ?args-var)
+ (let () ?body1 ?body2 ...)
+ (error "too many arguments" (length ?orig-args-var)
+ ?orig-args-var)))
+ ((aux ?orig-args-var ?args-var
+ ((?var ?default) ?more ...)
+ ?body1 ?body2 ...)
+ (if (null? ?args-var)
+ (let* ((?var ?default) ?more ...) ?body1 ?body2 ...)
+ (let ((?var (car ?args-var))
+ (new-args (cdr ?args-var)))
+ (let*-optionals:aux ?orig-args-var new-args
+ (?more ...)
+ ?body1 ?body2 ...))))))
+
+(define (nonneg-int? x)
+ (and (integer? x)
+ (not (negative? x))))
+
+(define (between? x y z)
+ (and (< x y)
+ (<= y z)))
+
+(define (unspecified-value) (if #f #f))
+
+;++ This should be implemented more efficiently. It shouldn't cons a
+;++ closure, and the cons cells used in the loops when using this could
+;++ be reused.
+(define (vectors-ref vectors i)
+ (map (lambda (v) (vector-ref v i)) vectors))
+
+
+
+;;; --------------------
+;;; Error checking
+
+;;; Error signalling (not checking) is done in a way that tries to be
+;;; as helpful to the person who gets the debugging prompt as possible.
+;;; That said, error _checking_ tries to be as unredundant as possible.
+
+;;; I don't use any sort of general condition mechanism; I use simply
+;;; SRFI 23's ERROR, even in cases where it might be better to use such
+;;; a general condition mechanism. Fix that when porting this to a
+;;; Scheme implementation that has its own condition system.
+
+;;; In argument checks, upon receiving an invalid argument, the checker
+;;; procedure recursively calls itself, but in one of the arguments to
+;;; itself is a call to ERROR; this mechanism is used in the hopes that
+;;; the user may be thrown into a debugger prompt, proceed with another
+;;; value, and let it be checked again.
+
+;;; Type checking is pretty basic, but easily factored out and replaced
+;;; with whatever your implementation's preferred type checking method
+;;; is. I doubt there will be many other methods of index checking,
+;;; though the index checkers might be better implemented natively.
+
+;;; (CHECK-TYPE <type-predicate?> <value> <callee>) -> value
+;;; Ensure that VALUE satisfies TYPE-PREDICATE?; if not, signal an
+;;; error stating that VALUE did not satisfy TYPE-PREDICATE?, showing
+;;; that this happened while calling CALLEE. Return VALUE if no
+;;; error was signalled.
+(define (check-type pred? value callee)
+ (if (pred? value)
+ value
+ ;; Recur: when (or if) the user gets a debugger prompt, he can
+ ;; proceed where the call to ERROR was with the correct value.
+ (check-type pred?
+ (error "erroneous value"
+ (list pred? value)
+ `(while calling ,callee))
+ callee)))
+
+;;; (CHECK-INDEX <vector> <index> <callee>) -> index
+;;; Ensure that INDEX is a valid index into VECTOR; if not, signal an
+;;; error stating that it is not and that this happened in a call to
+;;; CALLEE. Return INDEX when it is valid. (Note that this does NOT
+;;; check that VECTOR is indeed a vector.)
+(define (check-index vec index callee)
+ (let ((index (check-type integer? index callee)))
+ (cond ((< index 0)
+ (check-index vec
+ (error "vector index too low"
+ index
+ `(into vector ,vec)
+ `(while calling ,callee))
+ callee))
+ ((>= index (vector-length vec))
+ (check-index vec
+ (error "vector index too high"
+ index
+ `(into vector ,vec)
+ `(while calling ,callee))
+ callee))
+ (else index))))
+
+;;; (CHECK-INDICES <vector>
+;;; <start> <start-name>
+;;; <end> <end-name>
+;;; <caller>) -> [start end]
+;;; Ensure that START and END are valid bounds of a range within
+;;; VECTOR; if not, signal an error stating that they are not, with
+;;; the message being informative about what the argument names were
+;;; called -- by using START-NAME & END-NAME --, and that it occurred
+;;; while calling CALLEE. Also ensure that VEC is in fact a vector.
+;;; Returns no useful value.
+(define (check-indices vec start start-name end end-name callee)
+ (let ((lose (lambda things
+ (apply error "vector range out of bounds"
+ (append things
+ `(vector was ,vec)
+ `(,start-name was ,start)
+ `(,end-name was ,end)
+ `(while calling ,callee)))))
+ (start (check-type integer? start callee))
+ (end (check-type integer? end callee)))
+ (cond ((> start end)
+ ;; I'm not sure how well this will work. The intent is that
+ ;; the programmer tells the debugger to proceed with both a
+ ;; new START & a new END by returning multiple values
+ ;; somewhere.
+ (receive (new-start new-end)
+ (lose `(,end-name < ,start-name))
+ (check-indices vec
+ new-start start-name
+ new-end end-name
+ callee)))
+ ((< start 0)
+ (check-indices vec
+ (lose `(,start-name < 0))
+ start-name
+ end end-name
+ callee))
+ ((>= start (vector-length vec))
+ (check-indices vec
+ (lose `(,start-name > len)
+ `(len was ,(vector-length vec)))
+ start-name
+ end end-name
+ callee))
+ ((> end (vector-length vec))
+ (check-indices vec
+ start start-name
+ (lose `(,end-name > len)
+ `(len was ,(vector-length vec)))
+ end-name
+ callee))
+ (else
+ (values start end)))))
+
+
+
+;;; --------------------
+;;; Internal routines
+
+;;; These should all be integrated, native, or otherwise optimized --
+;;; they're used a _lot_ --. All of the loops and LETs inside loops
+;;; are lambda-lifted by hand, just so as not to cons closures in the
+;;; loops. (If your compiler can do better than that if they're not
+;;; lambda-lifted, then lambda-drop (?) them.)
+
+;;; (VECTOR-PARSE-START+END <vector> <arguments>
+;;; <start-name> <end-name>
+;;; <callee>)
+;;; -> [start end]
+;;; Return two values, composing a valid range within VECTOR, as
+;;; extracted from ARGUMENTS or defaulted from VECTOR -- 0 for START
+;;; and the length of VECTOR for END --; START-NAME and END-NAME are
+;;; purely for error checking.
+(define (vector-parse-start+end vec args start-name end-name callee)
+ (let ((len (vector-length vec)))
+ (cond ((null? args)
+ (values 0 len))
+ ((null? (cdr args))
+ (check-indices vec
+ (car args) start-name
+ len end-name
+ callee))
+ ((null? (cddr args))
+ (check-indices vec
+ (car args) start-name
+ (cadr args) end-name
+ callee))
+ (else
+ (error "too many arguments"
+ `(extra args were ,(cddr args))
+ `(while calling ,callee))))))
+
+(define-syntax let-vector-start+end
+ (syntax-rules ()
+ ((let-vector-start+end ?callee ?vec ?args (?start ?end)
+ ?body1 ?body2 ...)
+ (let ((?vec (check-type vector? ?vec ?callee)))
+ (receive (?start ?end)
+ (vector-parse-start+end ?vec ?args '?start '?end
+ ?callee)
+ ?body1 ?body2 ...)))))
+
+;;; (%SMALLEST-LENGTH <vector-list> <default-length> <callee>)
+;;; -> exact, nonnegative integer
+;;; Compute the smallest length of VECTOR-LIST. DEFAULT-LENGTH is
+;;; the length that is returned if VECTOR-LIST is empty. Common use
+;;; of this is in n-ary vector routines:
+;;; (define (f vec . vectors)
+;;; (let ((vec (check-type vector? vec f)))
+;;; ...(%smallest-length vectors (vector-length vec) f)...))
+;;; %SMALLEST-LENGTH takes care of the type checking -- which is what
+;;; the CALLEE argument is for --; thus, the design is tuned for
+;;; avoiding redundant type checks.
+(define %smallest-length
+ (letrec ((loop (lambda (vector-list length callee)
+ (if (null? vector-list)
+ length
+ (loop (cdr vector-list)
+ (min (vector-length
+ (check-type vector?
+ (car vector-list)
+ callee))
+ length)
+ callee)))))
+ loop))
+
+;;; (%VECTOR-COPY! <target> <tstart> <source> <sstart> <send>)
+;;; Copy elements at locations SSTART to SEND from SOURCE to TARGET,
+;;; starting at TSTART in TARGET.
+;;;
+;;; Optimize this! Probably with some combination of:
+;;; - Force it to be integrated.
+;;; - Let it use unsafe vector element dereferencing routines: bounds
+;;; checking already happens outside of it. (Or use a compiler
+;;; that figures this out, but Olin Shivers' PhD thesis seems to
+;;; have been largely ignored in actual implementations...)
+;;; - Implement it natively as a VM primitive: the VM can undoubtedly
+;;; perform much faster than it can make Scheme perform, even with
+;;; bounds checking.
+;;; - Implement it in assembly: you _want_ the fine control that
+;;; assembly can give you for this.
+;;; I already lambda-lift it by hand, but you should be able to make it
+;;; even better than that.
+(define %vector-copy!
+ (letrec ((loop/l->r (lambda (target source send i j)
+ (cond ((< i send)
+ (vector-set! target j
+ (vector-ref source i))
+ (loop/l->r target source send
+ (+ i 1) (+ j 1))))))
+ (loop/r->l (lambda (target source sstart i j)
+ (cond ((>= i sstart)
+ (vector-set! target j
+ (vector-ref source i))
+ (loop/r->l target source sstart
+ (- i 1) (- j 1)))))))
+ (lambda (target tstart source sstart send)
+ (if (> sstart tstart) ; Make sure we don't copy over
+ ; ourselves.
+ (loop/l->r target source send sstart tstart)
+ (loop/r->l target source sstart (- send 1)
+ (+ -1 tstart send (- sstart)))))))
+
+;;; (%VECTOR-REVERSE-COPY! <target> <tstart> <source> <sstart> <send>)
+;;; Copy elements from SSTART to SEND from SOURCE to TARGET, in the
+;;; reverse order.
+(define %vector-reverse-copy!
+ (letrec ((loop (lambda (target source sstart i j)
+ (cond ((>= i sstart)
+ (vector-set! target j (vector-ref source i))
+ (loop target source sstart
+ (- i 1)
+ (+ j 1)))))))
+ (lambda (target tstart source sstart send)
+ (loop target source sstart
+ (- send 1)
+ tstart))))
+
+;;; (%VECTOR-REVERSE! <vector>)
+(define %vector-reverse!
+ (letrec ((loop (lambda (vec i j)
+ (cond ((<= i j)
+ (let ((v (vector-ref vec i)))
+ (vector-set! vec i (vector-ref vec j))
+ (vector-set! vec j v)
+ (loop vec (+ i 1) (- j 1))))))))
+ (lambda (vec start end)
+ (loop vec start (- end 1)))))
+
+;;; (%VECTOR-FOLD1 <kons> <knil> <vector>) -> knil'
+;;; (KONS <index> <knil> <elt>) -> knil'
+(define %vector-fold1
+ (letrec ((loop (lambda (kons knil len vec i)
+ (if (= i len)
+ knil
+ (loop kons
+ (kons i knil (vector-ref vec i))
+ len vec (+ i 1))))))
+ (lambda (kons knil len vec)
+ (loop kons knil len vec 0))))
+
+;;; (%VECTOR-FOLD2+ <kons> <knil> <vector> ...) -> knil'
+;;; (KONS <index> <knil> <elt> ...) -> knil'
+(define %vector-fold2+
+ (letrec ((loop (lambda (kons knil len vectors i)
+ (if (= i len)
+ knil
+ (loop kons
+ (apply kons i knil
+ (vectors-ref vectors i))
+ len vectors (+ i 1))))))
+ (lambda (kons knil len vectors)
+ (loop kons knil len vectors 0))))
+
+;;; (%VECTOR-MAP! <f> <target> <length> <vector>) -> target
+;;; (F <index> <elt>) -> elt'
+(define %vector-map1!
+ (letrec ((loop (lambda (f target vec i)
+ (if (zero? i)
+ target
+ (let ((j (- i 1)))
+ (vector-set! target j
+ (f j (vector-ref vec j)))
+ (loop f target vec j))))))
+ (lambda (f target vec len)
+ (loop f target vec len))))
+
+;;; (%VECTOR-MAP2+! <f> <target> <vectors> <len>) -> target
+;;; (F <index> <elt> ...) -> elt'
+(define %vector-map2+!
+ (letrec ((loop (lambda (f target vectors i)
+ (if (zero? i)
+ target
+ (let ((j (- i 1)))
+ (vector-set! target j
+ (apply f j (vectors-ref vectors j)))
+ (loop f target vectors j))))))
+ (lambda (f target vectors len)
+ (loop f target vectors len))))
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;; ***** vector-lib ***** ;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; --------------------
+;;; Constructors
+
+;;; (MAKE-VECTOR <size> [<fill>]) -> vector
+;;; [R5RS] Create a vector of length LENGTH. If FILL is present,
+;;; initialize each slot in the vector with it; if not, the vector's
+;;; initial contents are unspecified.
+(define make-vector make-vector)
+
+;;; (VECTOR <elt> ...) -> vector
+;;; [R5RS] Create a vector containing ELEMENT ..., in order.
+(define vector vector)
+
+;;; This ought to be able to be implemented much more efficiently -- if
+;;; we have the number of arguments available to us, we can create the
+;;; vector without using LENGTH to determine the number of elements it
+;;; should have.
+;(define (vector . elements) (list->vector elements))
+
+;;; (VECTOR-UNFOLD <f> <length> <initial-seed> ...) -> vector
+;;; (F <index> <seed> ...) -> [elt seed' ...]
+;;; The fundamental vector constructor. Creates a vector whose
+;;; length is LENGTH and iterates across each index K between 0 and
+;;; LENGTH, applying F at each iteration to the current index and the
+;;; current seeds to receive N+1 values: first, the element to put in
+;;; the Kth slot and then N new seeds for the next iteration.
+(define vector-unfold
+ (letrec ((tabulate! ; Special zero-seed case.
+ (lambda (f vec i len)
+ (cond ((< i len)
+ (vector-set! vec i (f i))
+ (tabulate! f vec (+ i 1) len)))))
+ (unfold1! ; Fast path for one seed.
+ (lambda (f vec i len seed)
+ (if (< i len)
+ (receive (elt new-seed)
+ (f i seed)
+ (vector-set! vec i elt)
+ (unfold1! f vec (+ i 1) len new-seed)))))
+ (unfold2+! ; Slower variant for N seeds.
+ (lambda (f vec i len seeds)
+ (if (< i len)
+ (receive (elt . new-seeds)
+ (apply f i seeds)
+ (vector-set! vec i elt)
+ (unfold2+! f vec (+ i 1) len new-seeds))))))
+ (lambda (f len . initial-seeds)
+ (let ((f (check-type procedure? f vector-unfold))
+ (len (check-type nonneg-int? len vector-unfold)))
+ (let ((vec (make-vector len)))
+ (cond ((null? initial-seeds)
+ (tabulate! f vec 0 len))
+ ((null? (cdr initial-seeds))
+ (unfold1! f vec 0 len (car initial-seeds)))
+ (else
+ (unfold2+! f vec 0 len initial-seeds)))
+ vec)))))
+
+;;; (VECTOR-UNFOLD-RIGHT <f> <length> <initial-seed> ...) -> vector
+;;; (F <seed> ...) -> [seed' ...]
+;;; Like VECTOR-UNFOLD, but it generates elements from LENGTH to 0
+;;; (still exclusive with LENGTH and inclusive with 0), not 0 to
+;;; LENGTH as with VECTOR-UNFOLD.
+(define vector-unfold-right
+ (letrec ((tabulate!
+ (lambda (f vec i)
+ (cond ((>= i 0)
+ (vector-set! vec i (f i))
+ (tabulate! f vec (- i 1))))))
+ (unfold1!
+ (lambda (f vec i seed)
+ (if (>= i 0)
+ (receive (elt new-seed)
+ (f i seed)
+ (vector-set! vec i elt)
+ (unfold1! f vec (- i 1) new-seed)))))
+ (unfold2+!
+ (lambda (f vec i seeds)
+ (if (>= i 0)
+ (receive (elt . new-seeds)
+ (apply f i seeds)
+ (vector-set! vec i elt)
+ (unfold2+! f vec (- i 1) new-seeds))))))
+ (lambda (f len . initial-seeds)
+ (let ((f (check-type procedure? f vector-unfold-right))
+ (len (check-type nonneg-int? len vector-unfold-right)))
+ (let ((vec (make-vector len))
+ (i (- len 1)))
+ (cond ((null? initial-seeds)
+ (tabulate! f vec i))
+ ((null? (cdr initial-seeds))
+ (unfold1! f vec i (car initial-seeds)))
+ (else
+ (unfold2+! f vec i initial-seeds)))
+ vec)))))
+
+;;; (VECTOR-COPY <vector> [<start> <end> <fill>]) -> vector
+;;; Create a newly allocated vector containing the elements from the
+;;; range [START,END) in VECTOR. START defaults to 0; END defaults
+;;; to the length of VECTOR. END may be greater than the length of
+;;; VECTOR, in which case the vector is enlarged; if FILL is passed,
+;;; the new locations from which there is no respective element in
+;;; VECTOR are filled with FILL.
+(define (vector-copy vec . args)
+ (let ((vec (check-type vector? vec vector-copy)))
+ ;; We can't use LET-VECTOR-START+END, because we have one more
+ ;; argument, and we want finer control, too.
+ ;;
+ ;; Olin's implementation of LET*-OPTIONALS would prove useful here:
+ ;; the built-in argument-checks-as-you-go-along produces almost
+ ;; _exactly_ the same code as VECTOR-COPY:PARSE-ARGS.
+ (receive (start end fill)
+ (vector-copy:parse-args vec args)
+ (let ((new-vector (make-vector (- end start) fill)))
+ (%vector-copy! new-vector 0
+ vec start
+ (if (> end (vector-length vec))
+ (vector-length vec)
+ end))
+ new-vector))))
+
+;;; Auxiliary for VECTOR-COPY.
+;;; [wdc] Corrected to allow 0 <= start <= (vector-length vec).
+(define (vector-copy:parse-args vec args)
+ (define (parse-args start end n fill)
+ (let ((start (check-type nonneg-int? start vector-copy))
+ (end (check-type nonneg-int? end vector-copy)))
+ (cond ((and (<= 0 start end)
+ (<= start n))
+ (values start end fill))
+ (else
+ (error "illegal arguments"
+ `(while calling ,vector-copy)
+ `(start was ,start)
+ `(end was ,end)
+ `(vector was ,vec))))))
+ (let ((n (vector-length vec)))
+ (cond ((null? args)
+ (parse-args 0 n n (unspecified-value)))
+ ((null? (cdr args))
+ (parse-args (car args) n n (unspecified-value)))
+ ((null? (cddr args))
+ (parse-args (car args) (cadr args) n (unspecified-value)))
+ ((null? (cdddr args))
+ (parse-args (car args) (cadr args) n (caddr args)))
+ (else
+ (error "too many arguments"
+ vector-copy
+ (cdddr args))))))
+
+;;; (VECTOR-REVERSE-COPY <vector> [<start> <end>]) -> vector
+;;; Create a newly allocated vector whose elements are the reversed
+;;; sequence of elements between START and END in VECTOR. START's
+;;; default is 0; END's default is the length of VECTOR.
+(define (vector-reverse-copy vec . maybe-start+end)
+ (let-vector-start+end vector-reverse-copy vec maybe-start+end
+ (start end)
+ (let ((new (make-vector (- end start))))
+ (%vector-reverse-copy! new 0 vec start end)
+ new)))
+
+;;; (VECTOR-APPEND <vector> ...) -> vector
+;;; Append VECTOR ... into a newly allocated vector and return that
+;;; new vector.
+(define (vector-append . vectors)
+ (vector-concatenate:aux vectors vector-append))
+
+;;; (VECTOR-CONCATENATE <vector-list>) -> vector
+;;; Concatenate the vectors in VECTOR-LIST. This is equivalent to
+;;; (apply vector-append VECTOR-LIST)
+;;; but VECTOR-APPEND tends to be implemented in terms of
+;;; VECTOR-CONCATENATE, and some Schemes bork when the list to apply
+;;; a function to is too long.
+;;;
+;;; Actually, they're both implemented in terms of an internal routine.
+(define (vector-concatenate vector-list)
+ (vector-concatenate:aux vector-list vector-concatenate))
+
+;;; Auxiliary for VECTOR-APPEND and VECTOR-CONCATENATE
+(define vector-concatenate:aux
+ (letrec ((compute-length
+ (lambda (vectors len callee)
+ (if (null? vectors)
+ len
+ (let ((vec (check-type vector? (car vectors)
+ callee)))
+ (compute-length (cdr vectors)
+ (+ (vector-length vec) len)
+ callee)))))
+ (concatenate!
+ (lambda (vectors target to)
+ (if (null? vectors)
+ target
+ (let* ((vec1 (car vectors))
+ (len (vector-length vec1)))
+ (%vector-copy! target to vec1 0 len)
+ (concatenate! (cdr vectors) target
+ (+ to len)))))))
+ (lambda (vectors callee)
+ (cond ((null? vectors) ;+++
+ (make-vector 0))
+ ((null? (cdr vectors)) ;+++
+ ;; Blech, we still have to allocate a new one.
+ (let* ((vec (check-type vector? (car vectors) callee))
+ (len (vector-length vec))
+ (new (make-vector len)))
+ (%vector-copy! new 0 vec 0 len)
+ new))
+ (else
+ (let ((new-vector
+ (make-vector (compute-length vectors 0 callee))))
+ (concatenate! vectors new-vector 0)
+ new-vector))))))
+
+
+
+;;; --------------------
+;;; Predicates
+
+;;; (VECTOR? <value>) -> boolean
+;;; [R5RS] Return #T if VALUE is a vector and #F if not.
+(define vector? vector?)
+
+;;; (VECTOR-EMPTY? <vector>) -> boolean
+;;; Return #T if VECTOR has zero elements in it, i.e. VECTOR's length
+;;; is 0, and #F if not.
+(define (vector-empty? vec)
+ (let ((vec (check-type vector? vec vector-empty?)))
+ (zero? (vector-length vec))))
+
+;;; (VECTOR= <elt=?> <vector> ...) -> boolean
+;;; (ELT=? <value> <value>) -> boolean
+;;; Determine vector equality generalized across element comparators.
+;;; Vectors A and B are equal iff their lengths are the same and for
+;;; each respective elements E_a and E_b (element=? E_a E_b) returns
+;;; a true value. ELT=? is always applied to two arguments. Element
+;;; comparison must be consistent wtih EQ?; that is, if (eq? E_a E_b)
+;;; results in a true value, then (ELEMENT=? E_a E_b) must result in a
+;;; true value. This may be exploited to avoid multiple unnecessary
+;;; element comparisons. (This implementation does, but does not deal
+;;; with the situation that ELEMENT=? is EQ? to avoid more unnecessary
+;;; comparisons, but I believe this optimization is probably fairly
+;;; insignificant.)
+;;;
+;;; If the number of vector arguments is zero or one, then #T is
+;;; automatically returned. If there are N vector arguments,
+;;; VECTOR_1 VECTOR_2 ... VECTOR_N, then VECTOR_1 & VECTOR_2 are
+;;; compared; if they are equal, the vectors VECTOR_2 ... VECTOR_N
+;;; are compared. The precise order in which ELT=? is applied is not
+;;; specified.
+(define (vector= elt=? . vectors)
+ (let ((elt=? (check-type procedure? elt=? vector=)))
+ (cond ((null? vectors)
+ #t)
+ ((null? (cdr vectors))
+ (check-type vector? (car vectors) vector=)
+ #t)
+ (else
+ (let loop ((vecs vectors))
+ (let ((vec1 (check-type vector? (car vecs) vector=))
+ (vec2+ (cdr vecs)))
+ (or (null? vec2+)
+ (and (binary-vector= elt=? vec1 (car vec2+))
+ (loop vec2+)))))))))
+(define (binary-vector= elt=? vector-a vector-b)
+ (or (eq? vector-a vector-b) ;+++
+ (let ((length-a (vector-length vector-a))
+ (length-b (vector-length vector-b)))
+ (letrec ((loop (lambda (i)
+ (or (= i length-a)
+ (and (< i length-b)
+ (test (vector-ref vector-a i)
+ (vector-ref vector-b i)
+ i)))))
+ (test (lambda (elt-a elt-b i)
+ (and (or (eq? elt-a elt-b) ;+++
+ (elt=? elt-a elt-b))
+ (loop (+ i 1))))))
+ (and (= length-a length-b)
+ (loop 0))))))
+
+
+
+;;; --------------------
+;;; Selectors
+
+;;; (VECTOR-REF <vector> <index>) -> value
+;;; [R5RS] Return the value that the location in VECTOR at INDEX is
+;;; mapped to in the store.
+(define vector-ref vector-ref)
+
+;;; (VECTOR-LENGTH <vector>) -> exact, nonnegative integer
+;;; [R5RS] Return the length of VECTOR.
+(define vector-length vector-length)
+
+
+
+;;; --------------------
+;;; Iteration
+
+;;; (VECTOR-FOLD <kons> <initial-knil> <vector> ...) -> knil
+;;; (KONS <knil> <elt> ...) -> knil' ; N vectors -> N+1 args
+;;; The fundamental vector iterator. KONS is iterated over each
+;;; index in all of the vectors in parallel, stopping at the end of
+;;; the shortest; KONS is applied to an argument list of (list I
+;;; STATE (vector-ref VEC I) ...), where STATE is the current state
+;;; value -- the state value begins with KNIL and becomes whatever
+;;; KONS returned at the respective iteration --, and I is the
+;;; current index in the iteration. The iteration is strictly left-
+;;; to-right.
+;;; (vector-fold KONS KNIL (vector E_1 E_2 ... E_N))
+;;; <=>
+;;; (KONS (... (KONS (KONS KNIL E_1) E_2) ... E_N-1) E_N)
+(define (vector-fold kons knil vec . vectors)
+ (let ((kons (check-type procedure? kons vector-fold))
+ (vec (check-type vector? vec vector-fold)))
+ (if (null? vectors)
+ (%vector-fold1 kons knil (vector-length vec) vec)
+ (%vector-fold2+ kons knil
+ (%smallest-length vectors
+ (vector-length vec)
+ vector-fold)
+ (cons vec vectors)))))
+
+;;; (VECTOR-FOLD-RIGHT <kons> <initial-knil> <vector> ...) -> knil
+;;; (KONS <knil> <elt> ...) -> knil' ; N vectors => N+1 args
+;;; The fundamental vector recursor. Iterates in parallel across
+;;; VECTOR ... right to left, applying KONS to the elements and the
+;;; current state value; the state value becomes what KONS returns
+;;; at each next iteration. KNIL is the initial state value.
+;;; (vector-fold-right KONS KNIL (vector E_1 E_2 ... E_N))
+;;; <=>
+;;; (KONS (... (KONS (KONS KNIL E_N) E_N-1) ... E_2) E_1)
+;;;
+;;; Not implemented in terms of a more primitive operations that might
+;;; called %VECTOR-FOLD-RIGHT due to the fact that it wouldn't be very
+;;; useful elsewhere.
+(define vector-fold-right
+ (letrec ((loop1 (lambda (kons knil vec i)
+ (if (negative? i)
+ knil
+ (loop1 kons (kons i knil (vector-ref vec i))
+ vec
+ (- i 1)))))
+ (loop2+ (lambda (kons knil vectors i)
+ (if (negative? i)
+ knil
+ (loop2+ kons
+ (apply kons i knil
+ (vectors-ref vectors i))
+ vectors
+ (- i 1))))))
+ (lambda (kons knil vec . vectors)
+ (let ((kons (check-type procedure? kons vector-fold-right))
+ (vec (check-type vector? vec vector-fold-right)))
+ (if (null? vectors)
+ (loop1 kons knil vec (- (vector-length vec) 1))
+ (loop2+ kons knil (cons vec vectors)
+ (- (%smallest-length vectors
+ (vector-length vec)
+ vector-fold-right)
+ 1)))))))
+
+;;; (VECTOR-MAP <f> <vector> ...) -> vector
+;;; (F <elt> ...) -> value ; N vectors -> N args
+;;; Constructs a new vector of the shortest length of the vector
+;;; arguments. Each element at index I of the new vector is mapped
+;;; from the old vectors by (F I (vector-ref VECTOR I) ...). The
+;;; dynamic order of application of F is unspecified.
+(define (vector-map f vec . vectors)
+ (let ((f (check-type procedure? f vector-map))
+ (vec (check-type vector? vec vector-map)))
+ (if (null? vectors)
+ (let ((len (vector-length vec)))
+ (%vector-map1! f (make-vector len) vec len))
+ (let ((len (%smallest-length vectors
+ (vector-length vec)
+ vector-map)))
+ (%vector-map2+! f (make-vector len) (cons vec vectors)
+ len)))))
+
+;;; (VECTOR-MAP! <f> <vector> ...) -> unspecified
+;;; (F <elt> ...) -> element' ; N vectors -> N args
+;;; Similar to VECTOR-MAP, but rather than mapping the new elements
+;;; into a new vector, the new mapped elements are destructively
+;;; inserted into the first vector. Again, the dynamic order of
+;;; application of F is unspecified, so it is dangerous for F to
+;;; manipulate the first VECTOR.
+(define (vector-map! f vec . vectors)
+ (let ((f (check-type procedure? f vector-map!))
+ (vec (check-type vector? vec vector-map!)))
+ (if (null? vectors)
+ (%vector-map1! f vec vec (vector-length vec))
+ (%vector-map2+! f vec (cons vec vectors)
+ (%smallest-length vectors
+ (vector-length vec)
+ vector-map!)))
+ (unspecified-value)))
+
+;;; (VECTOR-FOR-EACH <f> <vector> ...) -> unspecified
+;;; (F <elt> ...) ; N vectors -> N args
+;;; Simple vector iterator: applies F to each index in the range [0,
+;;; LENGTH), where LENGTH is the length of the smallest vector
+;;; argument passed, and the respective element at that index. In
+;;; contrast with VECTOR-MAP, F is reliably applied to each
+;;; subsequent elements, starting at index 0 from left to right, in
+;;; the vectors.
+(define vector-for-each
+ (letrec ((for-each1
+ (lambda (f vec i len)
+ (cond ((< i len)
+ (f i (vector-ref vec i))
+ (for-each1 f vec (+ i 1) len)))))
+ (for-each2+
+ (lambda (f vecs i len)
+ (cond ((< i len)
+ (apply f i (vectors-ref vecs i))
+ (for-each2+ f vecs (+ i 1) len))))))
+ (lambda (f vec . vectors)
+ (let ((f (check-type procedure? f vector-for-each))
+ (vec (check-type vector? vec vector-for-each)))
+ (if (null? vectors)
+ (for-each1 f vec 0 (vector-length vec))
+ (for-each2+ f (cons vec vectors) 0
+ (%smallest-length vectors
+ (vector-length vec)
+ vector-for-each)))))))
+
+;;; (VECTOR-COUNT <predicate?> <vector> ...)
+;;; -> exact, nonnegative integer
+;;; (PREDICATE? <index> <value> ...) ; N vectors -> N+1 args
+;;; PREDICATE? is applied element-wise to the elements of VECTOR ...,
+;;; and a count is tallied of the number of elements for which a
+;;; true value is produced by PREDICATE?. This count is returned.
+(define (vector-count pred? vec . vectors)
+ (let ((pred? (check-type procedure? pred? vector-count))
+ (vec (check-type vector? vec vector-count)))
+ (if (null? vectors)
+ (%vector-fold1 (lambda (index count elt)
+ (if (pred? index elt)
+ (+ count 1)
+ count))
+ 0
+ (vector-length vec)
+ vec)
+ (%vector-fold2+ (lambda (index count . elts)
+ (if (apply pred? index elts)
+ (+ count 1)
+ count))
+ 0
+ (%smallest-length vectors
+ (vector-length vec)
+ vector-count)
+ (cons vec vectors)))))
+
+
+
+;;; --------------------
+;;; Searching
+
+;;; (VECTOR-INDEX <predicate?> <vector> ...)
+;;; -> exact, nonnegative integer or #F
+;;; (PREDICATE? <elt> ...) -> boolean ; N vectors -> N args
+;;; Search left-to-right across VECTOR ... in parallel, returning the
+;;; index of the first set of values VALUE ... such that (PREDICATE?
+;;; VALUE ...) returns a true value; if no such set of elements is
+;;; reached, return #F.
+(define (vector-index pred? vec . vectors)
+ (vector-index/skip pred? vec vectors vector-index))
+
+;;; (VECTOR-SKIP <predicate?> <vector> ...)
+;;; -> exact, nonnegative integer or #F
+;;; (PREDICATE? <elt> ...) -> boolean ; N vectors -> N args
+;;; (vector-index (lambda elts (not (apply PREDICATE? elts)))
+;;; VECTOR ...)
+;;; Like VECTOR-INDEX, but find the index of the first set of values
+;;; that do _not_ satisfy PREDICATE?.
+(define (vector-skip pred? vec . vectors)
+ (vector-index/skip (lambda elts (not (apply pred? elts)))
+ vec vectors
+ vector-skip))
+
+;;; Auxiliary for VECTOR-INDEX & VECTOR-SKIP
+(define vector-index/skip
+ (letrec ((loop1 (lambda (pred? vec len i)
+ (cond ((= i len) #f)
+ ((pred? (vector-ref vec i)) i)
+ (else (loop1 pred? vec len (+ i 1))))))
+ (loop2+ (lambda (pred? vectors len i)
+ (cond ((= i len) #f)
+ ((apply pred? (vectors-ref vectors i)) i)
+ (else (loop2+ pred? vectors len
+ (+ i 1)))))))
+ (lambda (pred? vec vectors callee)
+ (let ((pred? (check-type procedure? pred? callee))
+ (vec (check-type vector? vec callee)))
+ (if (null? vectors)
+ (loop1 pred? vec (vector-length vec) 0)
+ (loop2+ pred? (cons vec vectors)
+ (%smallest-length vectors
+ (vector-length vec)
+ callee)
+ 0))))))
+
+;;; (VECTOR-INDEX-RIGHT <predicate?> <vector> ...)
+;;; -> exact, nonnegative integer or #F
+;;; (PREDICATE? <elt> ...) -> boolean ; N vectors -> N args
+;;; Right-to-left variant of VECTOR-INDEX.
+(define (vector-index-right pred? vec . vectors)
+ (vector-index/skip-right pred? vec vectors vector-index-right))
+
+;;; (VECTOR-SKIP-RIGHT <predicate?> <vector> ...)
+;;; -> exact, nonnegative integer or #F
+;;; (PREDICATE? <elt> ...) -> boolean ; N vectors -> N args
+;;; Right-to-left variant of VECTOR-SKIP.
+(define (vector-skip-right pred? vec . vectors)
+ (vector-index/skip-right (lambda elts (not (apply pred? elts)))
+ vec vectors
+ vector-index-right))
+
+(define vector-index/skip-right
+ (letrec ((loop1 (lambda (pred? vec i)
+ (cond ((negative? i) #f)
+ ((pred? (vector-ref vec i)) i)
+ (else (loop1 pred? vec (- i 1))))))
+ (loop2+ (lambda (pred? vectors i)
+ (cond ((negative? i) #f)
+ ((apply pred? (vectors-ref vectors i)) i)
+ (else (loop2+ pred? vectors (- i 1)))))))
+ (lambda (pred? vec vectors callee)
+ (let ((pred? (check-type procedure? pred? callee))
+ (vec (check-type vector? vec callee)))
+ (if (null? vectors)
+ (loop1 pred? vec (- (vector-length vec) 1))
+ (loop2+ pred? (cons vec vectors)
+ (- (%smallest-length vectors
+ (vector-length vec)
+ callee)
+ 1)))))))
+
+;;; (VECTOR-BINARY-SEARCH <vector> <value> <cmp> [<start> <end>])
+;;; -> exact, nonnegative integer or #F
+;;; (CMP <value1> <value2>) -> integer
+;;; positive -> VALUE1 > VALUE2
+;;; zero -> VALUE1 = VALUE2
+;;; negative -> VALUE1 < VALUE2
+;;; Perform a binary search through VECTOR for VALUE, comparing each
+;;; element to VALUE with CMP.
+(define (vector-binary-search vec value cmp . maybe-start+end)
+ (let ((cmp (check-type procedure? cmp vector-binary-search)))
+ (let-vector-start+end vector-binary-search vec maybe-start+end
+ (start end)
+ (let loop ((start start) (end end) (j #f))
+ (let ((i (quotient (+ start end) 2)))
+ (if (or (= start end) (and j (= i j)))
+ #f
+ (let ((comparison
+ (check-type integer?
+ (cmp (vector-ref vec i) value)
+ `(,cmp for ,vector-binary-search))))
+ (cond ((zero? comparison) i)
+ ((positive? comparison) (loop start i i))
+ (else (loop i end i))))))))))
+
+;;; (VECTOR-ANY <pred?> <vector> ...) -> value
+;;; Apply PRED? to each parallel element in each VECTOR ...; if PRED?
+;;; should ever return a true value, immediately stop and return that
+;;; value; otherwise, when the shortest vector runs out, return #F.
+;;; The iteration and order of application of PRED? across elements
+;;; is of the vectors is strictly left-to-right.
+(define vector-any
+ (letrec ((loop1 (lambda (pred? vec i len len-1)
+ (and (not (= i len))
+ (if (= i len-1)
+ (pred? (vector-ref vec i))
+ (or (pred? (vector-ref vec i))
+ (loop1 pred? vec (+ i 1)
+ len len-1))))))
+ (loop2+ (lambda (pred? vectors i len len-1)
+ (and (not (= i len))
+ (if (= i len-1)
+ (apply pred? (vectors-ref vectors i))
+ (or (apply pred? (vectors-ref vectors i))
+ (loop2+ pred? vectors (+ i 1)
+ len len-1)))))))
+ (lambda (pred? vec . vectors)
+ (let ((pred? (check-type procedure? pred? vector-any))
+ (vec (check-type vector? vec vector-any)))
+ (if (null? vectors)
+ (let ((len (vector-length vec)))
+ (loop1 pred? vec 0 len (- len 1)))
+ (let ((len (%smallest-length vectors
+ (vector-length vec)
+ vector-any)))
+ (loop2+ pred? (cons vec vectors) 0 len (- len 1))))))))
+
+;;; (VECTOR-EVERY <pred?> <vector> ...) -> value
+;;; Apply PRED? to each parallel value in each VECTOR ...; if PRED?
+;;; should ever return #F, immediately stop and return #F; otherwise,
+;;; if PRED? should return a true value for each element, stopping at
+;;; the end of the shortest vector, return the last value that PRED?
+;;; returned. In the case that there is an empty vector, return #T.
+;;; The iteration and order of application of PRED? across elements
+;;; is of the vectors is strictly left-to-right.
+(define vector-every
+ (letrec ((loop1 (lambda (pred? vec i len len-1)
+ (or (= i len)
+ (if (= i len-1)
+ (pred? (vector-ref vec i))
+ (and (pred? (vector-ref vec i))
+ (loop1 pred? vec (+ i 1)
+ len len-1))))))
+ (loop2+ (lambda (pred? vectors i len len-1)
+ (or (= i len)
+ (if (= i len-1)
+ (apply pred? (vectors-ref vectors i))
+ (and (apply pred? (vectors-ref vectors i))
+ (loop2+ pred? vectors (+ i 1)
+ len len-1)))))))
+ (lambda (pred? vec . vectors)
+ (let ((pred? (check-type procedure? pred? vector-every))
+ (vec (check-type vector? vec vector-every)))
+ (if (null? vectors)
+ (let ((len (vector-length vec)))
+ (loop1 pred? vec 0 len (- len 1)))
+ (let ((len (%smallest-length vectors
+ (vector-length vec)
+ vector-every)))
+ (loop2+ pred? (cons vec vectors) 0 len (- len 1))))))))
+
+
+
+;;; --------------------
+;;; Mutators
+
+;;; (VECTOR-SET! <vector> <index> <value>) -> unspecified
+;;; [R5RS] Assign the location at INDEX in VECTOR to VALUE.
+(define vector-set! vector-set!)
+
+;;; (VECTOR-SWAP! <vector> <index1> <index2>) -> unspecified
+;;; Swap the values in the locations at INDEX1 and INDEX2.
+(define (vector-swap! vec i j)
+ (let ((vec (check-type vector? vec vector-swap!)))
+ (let ((i (check-index vec i vector-swap!))
+ (j (check-index vec j vector-swap!)))
+ (let ((x (vector-ref vec i)))
+ (vector-set! vec i (vector-ref vec j))
+ (vector-set! vec j x)))))
+
+;;; (VECTOR-FILL! <vector> <value> [<start> <end>]) -> unspecified
+;;; [R5RS+] Fill the locations in VECTOR between START, whose default
+;;; is 0, and END, whose default is the length of VECTOR, with VALUE.
+;;;
+;;; This one can probably be made really fast natively.
+(define vector-fill!
+ (let ((%vector-fill! vector-fill!)) ; Take the native one, under
+ ; the assumption that it's
+ ; faster, so we can use it if
+ ; there are no optional
+ ; arguments.
+ (lambda (vec value . maybe-start+end)
+ (if (null? maybe-start+end)
+ (%vector-fill! vec value) ;+++
+ (let-vector-start+end vector-fill! vec maybe-start+end
+ (start end)
+ (do ((i start (+ i 1)))
+ ((= i end))
+ (vector-set! vec i value)))))))
+
+;;; (VECTOR-COPY! <target> <tstart> <source> [<sstart> <send>])
+;;; -> unspecified
+;;; Copy the values in the locations in [SSTART,SEND) from SOURCE to
+;;; to TARGET, starting at TSTART in TARGET.
+;;; [wdc] Corrected to allow 0 <= sstart <= send <= (vector-length source).
+(define (vector-copy! target tstart source . maybe-sstart+send)
+ (define (doit! sstart send source-length)
+ (let ((tstart (check-type nonneg-int? tstart vector-copy!))
+ (sstart (check-type nonneg-int? sstart vector-copy!))
+ (send (check-type nonneg-int? send vector-copy!)))
+ (cond ((and (<= 0 sstart send source-length)
+ (<= (+ tstart (- send sstart)) (vector-length target)))
+ (%vector-copy! target tstart source sstart send))
+ (else
+ (error "illegal arguments"
+ `(while calling ,vector-copy!)
+ `(target was ,target)
+ `(target-length was ,(vector-length target))
+ `(tstart was ,tstart)
+ `(source was ,source)
+ `(source-length was ,source-length)
+ `(sstart was ,sstart)
+ `(send was ,send))))))
+ (let ((n (vector-length source)))
+ (cond ((null? maybe-sstart+send)
+ (doit! 0 n n))
+ ((null? (cdr maybe-sstart+send))
+ (doit! (car maybe-sstart+send) n n))
+ ((null? (cddr maybe-sstart+send))
+ (doit! (car maybe-sstart+send) (cadr maybe-sstart+send) n))
+ (else
+ (error "too many arguments"
+ vector-copy!
+ (cddr maybe-sstart+send))))))
+
+;;; (VECTOR-REVERSE-COPY! <target> <tstart> <source> [<sstart> <send>])
+;;; [wdc] Corrected to allow 0 <= sstart <= send <= (vector-length source).
+(define (vector-reverse-copy! target tstart source . maybe-sstart+send)
+ (define (doit! sstart send source-length)
+ (let ((tstart (check-type nonneg-int? tstart vector-reverse-copy!))
+ (sstart (check-type nonneg-int? sstart vector-reverse-copy!))
+ (send (check-type nonneg-int? send vector-reverse-copy!)))
+ (cond ((and (eq? target source)
+ (or (between? sstart tstart send)
+ (between? tstart sstart
+ (+ tstart (- send sstart)))))
+ (error "vector range for self-copying overlaps"
+ vector-reverse-copy!
+ `(vector was ,target)
+ `(tstart was ,tstart)
+ `(sstart was ,sstart)
+ `(send was ,send)))
+ ((and (<= 0 sstart send source-length)
+ (<= (+ tstart (- send sstart)) (vector-length target)))
+ (%vector-reverse-copy! target tstart source sstart send))
+ (else
+ (error "illegal arguments"
+ `(while calling ,vector-reverse-copy!)
+ `(target was ,target)
+ `(target-length was ,(vector-length target))
+ `(tstart was ,tstart)
+ `(source was ,source)
+ `(source-length was ,source-length)
+ `(sstart was ,sstart)
+ `(send was ,send))))))
+ (let ((n (vector-length source)))
+ (cond ((null? maybe-sstart+send)
+ (doit! 0 n n))
+ ((null? (cdr maybe-sstart+send))
+ (doit! (car maybe-sstart+send) n n))
+ ((null? (cddr maybe-sstart+send))
+ (doit! (car maybe-sstart+send) (cadr maybe-sstart+send) n))
+ (else
+ (error "too many arguments"
+ vector-reverse-copy!
+ (cddr maybe-sstart+send))))))
+
+;;; (VECTOR-REVERSE! <vector> [<start> <end>]) -> unspecified
+;;; Destructively reverse the contents of the sequence of locations
+;;; in VECTOR between START, whose default is 0, and END, whose
+;;; default is the length of VECTOR.
+(define (vector-reverse! vec . start+end)
+ (let-vector-start+end vector-reverse! vec start+end
+ (start end)
+ (%vector-reverse! vec start end)))
+
+
+
+;;; --------------------
+;;; Conversion
+
+;;; (VECTOR->LIST <vector> [<start> <end>]) -> list
+;;; [R5RS+] Produce a list containing the elements in the locations
+;;; between START, whose default is 0, and END, whose default is the
+;;; length of VECTOR, from VECTOR.
+(define vector->list
+ (let ((%vector->list vector->list))
+ (lambda (vec . maybe-start+end)
+ (if (null? maybe-start+end) ; Oughta use CASE-LAMBDA.
+ (%vector->list vec) ;+++
+ (let-vector-start+end vector->list vec maybe-start+end
+ (start end)
+ ;(unfold (lambda (i) ; No SRFI 1.
+ ; (< i start))
+ ; (lambda (i) (vector-ref vec i))
+ ; (lambda (i) (- i 1))
+ ; (- end 1))
+ (do ((i (- end 1) (- i 1))
+ (result '() (cons (vector-ref vec i) result)))
+ ((< i start) result)))))))
+
+;;; (REVERSE-VECTOR->LIST <vector> [<start> <end>]) -> list
+;;; Produce a list containing the elements in the locations between
+;;; START, whose default is 0, and END, whose default is the length
+;;; of VECTOR, from VECTOR, in reverse order.
+(define (reverse-vector->list vec . maybe-start+end)
+ (let-vector-start+end reverse-vector->list vec maybe-start+end
+ (start end)
+ ;(unfold (lambda (i) (= i end)) ; No SRFI 1.
+ ; (lambda (i) (vector-ref vec i))
+ ; (lambda (i) (+ i 1))
+ ; start)
+ (do ((i start (+ i 1))
+ (result '() (cons (vector-ref vec i) result)))
+ ((= i end) result))))
+
+;;; (LIST->VECTOR <list> [<start> <end>]) -> vector
+;;; [R5RS+] Produce a vector containing the elements in LIST, which
+;;; must be a proper list, between START, whose default is 0, & END,
+;;; whose default is the length of LIST. It is suggested that if the
+;;; length of LIST is known in advance, the START and END arguments
+;;; be passed, so that LIST->VECTOR need not call LENGTH to determine
+;;; the the length.
+;;;
+;;; This implementation diverges on circular lists, unless LENGTH fails
+;;; and causes - to fail as well. Given a LENGTH* that computes the
+;;; length of a list's cycle, this wouldn't diverge, and would work
+;;; great for circular lists.
+(define list->vector
+ (let ((%list->vector list->vector))
+ (lambda (lst . maybe-start+end)
+ ;; Checking the type of a proper list is expensive, so we do it
+ ;; amortizedly, or let %LIST->VECTOR or LIST-TAIL do it.
+ (if (null? maybe-start+end) ; Oughta use CASE-LAMBDA.
+ (%list->vector lst) ;+++
+ ;; We can't use LET-VECTOR-START+END, because we're using the
+ ;; bounds of a _list_, not a vector.
+ (let*-optionals maybe-start+end
+ ((start 0)
+ (end (length lst))) ; Ugh -- LENGTH
+ (let ((start (check-type nonneg-int? start list->vector))
+ (end (check-type nonneg-int? end list->vector)))
+ ((lambda (f)
+ (vector-unfold f (- end start) (list-tail lst start)))
+ (lambda (index l)
+ (cond ((null? l)
+ (error "list was too short"
+ `(list was ,lst)
+ `(attempted end was ,end)
+ `(while calling ,list->vector)))
+ ((pair? l)
+ (values (car l) (cdr l)))
+ (else
+ ;; Make this look as much like what CHECK-TYPE
+ ;; would report as possible.
+ (error "erroneous value"
+ ;; We want SRFI 1's PROPER-LIST?, but it
+ ;; would be a waste to link all of SRFI
+ ;; 1 to this module for only the single
+ ;; function PROPER-LIST?.
+ (list list? lst)
+ `(while calling
+ ,list->vector))))))))))))
+
+;;; (REVERSE-LIST->VECTOR <list> [<start> <end>]) -> vector
+;;; Produce a vector containing the elements in LIST, which must be a
+;;; proper list, between START, whose default is 0, and END, whose
+;;; default is the length of LIST, in reverse order. It is suggested
+;;; that if the length of LIST is known in advance, the START and END
+;;; arguments be passed, so that REVERSE-LIST->VECTOR need not call
+;;; LENGTH to determine the the length.
+;;;
+;;; This also diverges on circular lists unless, again, LENGTH returns
+;;; something that makes - bork.
+(define (reverse-list->vector lst . maybe-start+end)
+ (let*-optionals maybe-start+end
+ ((start 0)
+ (end (length lst))) ; Ugh -- LENGTH
+ (let ((start (check-type nonneg-int? start reverse-list->vector))
+ (end (check-type nonneg-int? end reverse-list->vector)))
+ ((lambda (f)
+ (vector-unfold-right f (- end start) (list-tail lst start)))
+ (lambda (index l)
+ (cond ((null? l)
+ (error "list too short"
+ `(list was ,lst)
+ `(attempted end was ,end)
+ `(while calling ,reverse-list->vector)))
+ ((pair? l)
+ (values (car l) (cdr l)))
+ (else
+ (error "erroneous value"
+ (list list? lst)
+ `(while calling ,reverse-list->vector)))))))))
+;;; SPDX-FileCopyrightText: 2014 Taylan Kammer <taylan.kammer@gmail.com>
+;;;
+;;; SPDX-License-Identifier: MIT
+
+(define-library (srfi 48)
+ (export format)
+ (import (rename (scheme base)
+ (exact inexact->exact)
+ (inexact exact->inexact))
+ (scheme char)
+ (scheme complex)
+ (rename (scheme write)
+ (write-shared write-with-shared-structure)))
+ (include "48.upstream.scm"))
+;;; "sort.scm" Defines: sorted?, merge, merge!, sort, sort!
+;;; Author : Richard A. O'Keefe (based on Prolog code by D.H.D.Warren)
+
+;;; Copyright (C) Aubrey Jaffer 2006. All Rights Reserved.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+;;; Updated: 11 June 1991
+;;; Modified for scheme library: Aubrey Jaffer 19 Sept. 1991
+;;; Updated: 19 June 1995
+;;; (sort, sort!, sorted?): Generalized to strings by jaffer: 2003-09-09
+;;; (sort, sort!, sorted?): Generalized to arrays by jaffer: 2003-10-04
+;;; jaffer: 2006-10-08:
+;;; (sort, sort!, sorted?, merge, merge!): Added optional KEY argument.
+;;; jaffer: 2006-11-05:
+;;; (sorted?, merge, merge!, sort, sort!): Call KEY arg at most once
+;;; per element.
+
+(require 'array)
+
+;;; (sorted? sequence less?)
+;;; is true when sequence is a list (x0 x1 ... xm) or a vector #(x0 ... xm)
+;;; such that for all 1 <= i <= m,
+;;; (not (less? (list-ref list i) (list-ref list (- i 1)))).
+;@
+(define (sorted? seq less? . opt-key)
+ (define key (if (null? opt-key) identity (car opt-key)))
+ (cond ((null? seq) #t)
+ ((array? seq)
+ (let ((dimax (+ -1 (car (array-dimensions seq)))))
+ (or (<= dimax 1)
+ (let loop ((idx (+ -1 dimax))
+ (last (key (array-ref seq dimax))))
+ (or (negative? idx)
+ (let ((nxt (key (array-ref seq idx))))
+ (and (less? nxt last)
+ (loop (+ -1 idx) nxt))))))))
+ ((null? (cdr seq)) #t)
+ (else
+ (let loop ((last (key (car seq)))
+ (next (cdr seq)))
+ (or (null? next)
+ (let ((nxt (key (car next))))
+ (and (not (less? nxt last))
+ (loop nxt (cdr next)))))))))
+
+;;; (merge a b less?)
+;;; takes two lists a and b such that (sorted? a less?) and (sorted? b less?)
+;;; and returns a new list in which the elements of a and b have been stably
+;;; interleaved so that (sorted? (merge a b less?) less?).
+;;; Note: this does _not_ accept arrays. See below.
+;@
+(define (merge a b less? . opt-key)
+ (define key (if (null? opt-key) identity (car opt-key)))
+ (cond ((null? a) b)
+ ((null? b) a)
+ (else
+ (let loop ((x (car a)) (kx (key (car a))) (a (cdr a))
+ (y (car b)) (ky (key (car b))) (b (cdr b)))
+ ;; The loop handles the merging of non-empty lists. It has
+ ;; been written this way to save testing and car/cdring.
+ (if (less? ky kx)
+ (if (null? b)
+ (cons y (cons x a))
+ (cons y (loop x kx a (car b) (key (car b)) (cdr b))))
+ ;; x <= y
+ (if (null? a)
+ (cons x (cons y b))
+ (cons x (loop (car a) (key (car a)) (cdr a) y ky b))))))))
+
+(define (sort:merge! a b less? key)
+ (define (loop r a kcara b kcarb)
+ (cond ((less? kcarb kcara)
+ (set-cdr! r b)
+ (if (null? (cdr b))
+ (set-cdr! b a)
+ (loop b a kcara (cdr b) (key (cadr b)))))
+ (else ; (car a) <= (car b)
+ (set-cdr! r a)
+ (if (null? (cdr a))
+ (set-cdr! a b)
+ (loop a (cdr a) (key (cadr a)) b kcarb)))))
+ (cond ((null? a) b)
+ ((null? b) a)
+ (else
+ (let ((kcara (key (car a)))
+ (kcarb (key (car b))))
+ (cond
+ ((less? kcarb kcara)
+ (if (null? (cdr b))
+ (set-cdr! b a)
+ (loop b a kcara (cdr b) (key (cadr b))))
+ b)
+ (else ; (car a) <= (car b)
+ (if (null? (cdr a))
+ (set-cdr! a b)
+ (loop a (cdr a) (key (cadr a)) b kcarb))
+ a))))))
+
+;;; takes two sorted lists a and b and smashes their cdr fields to form a
+;;; single sorted list including the elements of both.
+;;; Note: this does _not_ accept arrays.
+;@
+(define (merge! a b less? . opt-key)
+ (sort:merge! a b less? (if (null? opt-key) identity (car opt-key))))
+
+(define (sort:sort-list! seq less? key)
+ (define keyer (if key car identity))
+ (define (step n)
+ (cond ((> n 2) (let* ((j (quotient n 2))
+ (a (step j))
+ (k (- n j))
+ (b (step k)))
+ (sort:merge! a b less? keyer)))
+ ((= n 2) (let ((x (car seq))
+ (y (cadr seq))
+ (p seq))
+ (set! seq (cddr seq))
+ (cond ((less? (keyer y) (keyer x))
+ (set-car! p y)
+ (set-car! (cdr p) x)))
+ (set-cdr! (cdr p) '())
+ p))
+ ((= n 1) (let ((p seq))
+ (set! seq (cdr seq))
+ (set-cdr! p '())
+ p))
+ (else '())))
+ (define (key-wrap! lst)
+ (cond ((null? lst))
+ (else (set-car! lst (cons (key (car lst)) (car lst)))
+ (key-wrap! (cdr lst)))))
+ (define (key-unwrap! lst)
+ (cond ((null? lst))
+ (else (set-car! lst (cdar lst))
+ (key-unwrap! (cdr lst)))))
+ (cond (key
+ (key-wrap! seq)
+ (set! seq (step (length seq)))
+ (key-unwrap! seq)
+ seq)
+ (else
+ (step (length seq)))))
+
+(define (rank-1-array->list array)
+ (define dimensions (array-dimensions array))
+ (do ((idx (+ -1 (car dimensions)) (+ -1 idx))
+ (lst '() (cons (array-ref array idx) lst)))
+ ((< idx 0) lst)))
+
+;;; (sort! sequence less?)
+;;; sorts the list, array, or string sequence destructively. It uses
+;;; a version of merge-sort invented, to the best of my knowledge, by
+;;; David H. D. Warren, and first used in the DEC-10 Prolog system.
+;;; R. A. O'Keefe adapted it to work destructively in Scheme.
+;;; A. Jaffer modified to always return the original list.
+;@
+(define (sort! seq less? . opt-key)
+ (define key (if (null? opt-key) #f (car opt-key)))
+ (cond ((array? seq)
+ (let ((dims (array-dimensions seq)))
+ (do ((sorted (sort:sort-list! (rank-1-array->list seq) less? key)
+ (cdr sorted))
+ (i 0 (+ i 1)))
+ ((null? sorted) seq)
+ (array-set! seq (car sorted) i))))
+ (else ; otherwise, assume it is a list
+ (let ((ret (sort:sort-list! seq less? key)))
+ (if (not (eq? ret seq))
+ (do ((crt ret (cdr crt)))
+ ((eq? (cdr crt) seq)
+ (set-cdr! crt ret)
+ (let ((scar (car seq)) (scdr (cdr seq)))
+ (set-car! seq (car ret)) (set-cdr! seq (cdr ret))
+ (set-car! ret scar) (set-cdr! ret scdr)))))
+ seq))))
+
+;;; (sort sequence less?)
+;;; sorts a array, string, or list non-destructively. It does this
+;;; by sorting a copy of the sequence. My understanding is that the
+;;; Standard says that the result of append is always "newly
+;;; allocated" except for sharing structure with "the last argument",
+;;; so (append x '()) ought to be a standard way of copying a list x.
+;@
+(define (sort seq less? . opt-key)
+ (define key (if (null? opt-key) #f (car opt-key)))
+ (cond ((array? seq)
+ (let ((dims (array-dimensions seq)))
+ (define newra (apply make-array seq dims))
+ (do ((sorted (sort:sort-list! (rank-1-array->list seq) less? key)
+ (cdr sorted))
+ (i 0 (+ i 1)))
+ ((null? sorted) newra)
+ (array-set! newra (car sorted) i))))
+ (else (sort:sort-list! (append seq '()) less? key))))
+;;; SPDX-FileCopyrightText: 2003 Kenneth A Dickey <ken.dickey@allvantage.com>
+;;; SPDX-FileCopyrightText: 2017 Hamayama <hamay1010@gmail.com>
+;;;
+;;; SPDX-License-Identifier: MIT
+
+;; IMPLEMENTATION DEPENDENT options
+
+(define ascii-tab (integer->char 9)) ;; NB: assumes ASCII encoding
+(define dont-print (if (eq? #t #f) 1))
+;;(define DONT-PRINT (string->symbol ""))
+;;(define DONT-PRINT (void))
+;;(define DONT-PRINT #!void)
+(define pretty-print write) ; ugly but permitted
+;; (require 'srfi-38) ;; write-with-shared-structure
+
+;; Following three procedures are used by format ~F .
+;; 'inexact-number->string' determines whether output is fixed-point
+;; notation or exponential notation. In the current definition,
+;; the notation depends on the implementation of 'number->string'.
+;; 'exact-number->string' is expected to output only numeric characters
+;; (not including such as '#', 'e', '.', '/') if the input is an positive
+;; integer or zero.
+;; 'real-number->string' is used when the digits of ~F is not specified.
+(define (inexact-number->string x) (number->string (exact->inexact x)))
+(define (exact-number->string x) (number->string (inexact->exact x)))
+(define (real-number->string x) (number->string x))
+
+;; FORMAT
+(define (format . args)
+ (cond
+ ((null? args)
+ (error "FORMAT: required format-string argument is missing")
+ )
+ ((string? (car args))
+ (apply format (cons #f args)))
+ ((< (length args) 2)
+ (error (format #f "FORMAT: too few arguments ~s" (cons 'format args)))
+ )
+ (else
+ (let ( (output-port (car args))
+ (format-string (cadr args))
+ (args (cddr args))
+ )
+ (letrec ( (port
+ (cond ((output-port? output-port) output-port)
+ ((eq? output-port #t) (current-output-port))
+ ((eq? output-port #f) (open-output-string))
+ (else (error
+ (format #f "FORMAT: bad output-port argument: ~s"
+ output-port)))
+ ) )
+ (return-value
+ (if (eq? output-port #f) ;; if format into a string
+ (lambda () (get-output-string port)) ;; then return the string
+ (lambda () dont-print)) ;; else do something harmless
+ )
+ )
+
+ (define (string-index str c)
+ (let ( (len (string-length str)) )
+ (let loop ( (i 0) )
+ (cond ((= i len) #f)
+ ((eqv? c (string-ref str i)) i)
+ (else (loop (+ i 1)))))))
+
+ (define (string-grow str len char)
+ (let ( (off (- len (string-length str))) )
+ (if (positive? off)
+ (string-append (make-string off char) str)
+ str)))
+
+ (define (compose-with-digits digits pre-str frac-str exp-str)
+ (let ( (frac-len (string-length frac-str)) )
+ (cond
+ ((< frac-len digits) ;; grow frac part, pad with zeros
+ (string-append pre-str "."
+ frac-str (make-string (- digits frac-len) #\0)
+ exp-str)
+ )
+ ((= frac-len digits) ;; frac-part is exactly the right size
+ (string-append pre-str "."
+ frac-str
+ exp-str)
+ )
+ (else ;; must round to shrink it
+ (let* ( (minus-flag (and (> (string-length pre-str) 0)
+ (char=? (string-ref pre-str 0) #\-)))
+ (pre-str* (if minus-flag
+ (substring pre-str 1 (string-length pre-str))
+ pre-str))
+ (first-part (substring frac-str 0 digits))
+ (last-part (substring frac-str digits frac-len))
+ (temp-str
+ (string-grow
+ (exact-number->string
+ (round (string->number
+ (string-append pre-str* first-part "." last-part))))
+ digits
+ #\0))
+ (temp-len (string-length temp-str))
+ (new-pre (substring temp-str 0 (- temp-len digits)))
+ (new-frac (substring temp-str (- temp-len digits) temp-len))
+ )
+ (string-append
+ (if minus-flag "-" "")
+ (if (string=? new-pre "")
+ ;; check if the system displays integer part of numbers
+ ;; whose absolute value is 0 < x < 1.
+ (if (and (string=? pre-str* "")
+ (> digits 0)
+ (not (= (string->number new-frac) 0)))
+ "" "0")
+ new-pre)
+ "."
+ new-frac
+ exp-str)))
+ ) ) )
+
+ (define (format-fixed number-or-string width digits) ; returns a string
+ (cond
+ ((string? number-or-string)
+ (string-grow number-or-string width #\space)
+ )
+ ((number? number-or-string)
+ (let ( (real (real-part number-or-string))
+ (imag (imag-part number-or-string))
+ )
+ (cond
+ ((not (zero? imag))
+ (string-grow
+ (string-append (format-fixed real 0 digits)
+ (if (negative? imag) "" "+")
+ (format-fixed imag 0 digits)
+ "i")
+ width
+ #\space)
+ )
+ (digits
+ (let* ( (num-str (inexact-number->string real))
+ (dot-index (string-index num-str #\.))
+ (exp-index (string-index num-str #\e))
+ (length (string-length num-str))
+ (pre-string
+ (if dot-index
+ (substring num-str 0 dot-index)
+ (if exp-index
+ (substring num-str 0 exp-index)
+ num-str))
+ )
+ (exp-string
+ (if exp-index
+ (substring num-str exp-index length)
+ "")
+ )
+ (frac-string
+ (if dot-index
+ (if exp-index
+ (substring num-str (+ dot-index 1) exp-index)
+ (substring num-str (+ dot-index 1) length))
+ "")
+ )
+ )
+ ;; check +inf.0, -inf.0, +nan.0, -nan.0
+ (if (string-index num-str #\n)
+ (string-grow num-str width #\space)
+ (string-grow
+ (compose-with-digits digits
+ pre-string
+ frac-string
+ exp-string)
+ width
+ #\space))
+ ))
+ (else ;; no digits
+ (string-grow (real-number->string real) width #\space)))
+ ))
+ (else
+ (error
+ (format "FORMAT: ~F requires a number or a string, got ~s" number-or-string)))
+ ))
+
+ (define documentation-string
+"(format [<port>] <format-string> [<arg>...]) -- <port> is #t, #f or an output-port
+OPTION [MNEMONIC] DESCRIPTION -- Implementation Assumes ASCII Text Encoding
+~H [Help] output this text
+~A [Any] (display arg) for humans
+~S [Slashified] (write arg) for parsers
+~W [WriteCircular] like ~s but outputs circular and recursive data structures
+~~ [tilde] output a tilde
+~T [Tab] output a tab character
+~% [Newline] output a newline character
+~& [Freshline] output a newline character if the previous output was not a newline
+~D [Decimal] the arg is a number which is output in decimal radix
+~X [heXadecimal] the arg is a number which is output in hexdecimal radix
+~O [Octal] the arg is a number which is output in octal radix
+~B [Binary] the arg is a number which is output in binary radix
+~w,dF [Fixed] the arg is a string or number which has width w and d digits after the decimal
+~C [Character] charater arg is output by write-char
+~_ [Space] a single space character is output
+~Y [Yuppify] the list arg is pretty-printed to the output
+~? [Indirection] recursive format: next 2 args are format-string and list of arguments
+~K [Indirection] same as ~?
+"
+ )
+
+ (define (require-an-arg args)
+ (if (null? args)
+ (error "FORMAT: too few arguments" ))
+ )
+
+ (define (format-help format-strg arglist)
+
+ (letrec (
+ (length-of-format-string (string-length format-strg))
+
+ (anychar-dispatch
+ (lambda (pos arglist last-was-newline)
+ (if (>= pos length-of-format-string)
+ arglist ; return unused args
+ (let ( (char (string-ref format-strg pos)) )
+ (cond
+ ((eqv? char #\~)
+ (tilde-dispatch (+ pos 1) arglist last-was-newline))
+ (else
+ (write-char char port)
+ (anychar-dispatch (+ pos 1) arglist #f)
+ ))
+ ))
+ )) ; end anychar-dispatch
+
+ (has-newline?
+ (lambda (whatever last-was-newline)
+ (or (eqv? whatever #\newline)
+ (and (string? whatever)
+ (let ( (len (string-length whatever)) )
+ (if (zero? len)
+ last-was-newline
+ (eqv? #\newline (string-ref whatever (- len 1)))))))
+ )) ; end has-newline?
+
+ (tilde-dispatch
+ (lambda (pos arglist last-was-newline)
+ (cond
+ ((>= pos length-of-format-string)
+ (write-char #\~ port) ; tilde at end of string is just output
+ arglist ; return unused args
+ )
+ (else
+ (case (char-upcase (string-ref format-strg pos))
+ ((#\A) ; Any -- for humans
+ (require-an-arg arglist)
+ (let ( (whatever (car arglist)) )
+ (display whatever port)
+ (anychar-dispatch (+ pos 1)
+ (cdr arglist)
+ (has-newline? whatever last-was-newline))
+ ))
+ ((#\S) ; Slashified -- for parsers
+ (require-an-arg arglist)
+ (let ( (whatever (car arglist)) )
+ (write whatever port)
+ (anychar-dispatch (+ pos 1)
+ (cdr arglist)
+ (has-newline? whatever last-was-newline))
+ ))
+ ((#\W)
+ (require-an-arg arglist)
+ (let ( (whatever (car arglist)) )
+ (write-with-shared-structure whatever port) ;; srfi-38
+ (anychar-dispatch (+ pos 1)
+ (cdr arglist)
+ (has-newline? whatever last-was-newline))
+ ))
+ ((#\D) ; Decimal
+ (require-an-arg arglist)
+ (display (number->string (car arglist) 10) port)
+ (anychar-dispatch (+ pos 1) (cdr arglist) #f)
+ )
+ ((#\X) ; HeXadecimal
+ (require-an-arg arglist)
+ (display (number->string (car arglist) 16) port)
+ (anychar-dispatch (+ pos 1) (cdr arglist) #f)
+ )
+ ((#\O) ; Octal
+ (require-an-arg arglist)
+ (display (number->string (car arglist) 8) port)
+ (anychar-dispatch (+ pos 1) (cdr arglist) #f)
+ )
+ ((#\B) ; Binary
+ (require-an-arg arglist)
+ (display (number->string (car arglist) 2) port)
+ (anychar-dispatch (+ pos 1) (cdr arglist) #f)
+ )
+ ((#\C) ; Character
+ (require-an-arg arglist)
+ (write-char (car arglist) port)
+ (anychar-dispatch (+ pos 1) (cdr arglist) (eqv? (car arglist) #\newline))
+ )
+ ((#\~) ; Tilde
+ (write-char #\~ port)
+ (anychar-dispatch (+ pos 1) arglist #f)
+ )
+ ((#\%) ; Newline
+ (newline port)
+ (anychar-dispatch (+ pos 1) arglist #t)
+ )
+ ((#\&) ; Freshline
+ (if (not last-was-newline) ;; (unless last-was-newline ..
+ (newline port))
+ (anychar-dispatch (+ pos 1) arglist #t)
+ )
+ ((#\_) ; Space
+ (write-char #\space port)
+ (anychar-dispatch (+ pos 1) arglist #f)
+ )
+ ((#\T) ; Tab -- IMPLEMENTATION DEPENDENT ENCODING
+ (write-char ascii-tab port)
+ (anychar-dispatch (+ pos 1) arglist #f)
+ )
+ ((#\Y) ; Pretty-print
+ (pretty-print (car arglist) port) ;; IMPLEMENTATION DEPENDENT
+ (anychar-dispatch (+ pos 1) (cdr arglist) #f)
+ )
+ ((#\F)
+ (require-an-arg arglist)
+ (display (format-fixed (car arglist) 0 #f) port)
+ (anychar-dispatch (+ pos 1) (cdr arglist) #f)
+ )
+ ((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9) ;; gather "~w[,d]F" w and d digits
+ (let loop ( (index (+ pos 1))
+ (w-digits (list (string-ref format-strg pos)))
+ (d-digits '())
+ (in-width? #t)
+ )
+ (if (>= index length-of-format-string)
+ (error
+ (format "FORMAT: improper numeric format directive in ~s" format-strg))
+ (let ( (next-char (string-ref format-strg index)) )
+ (cond
+ ((char-numeric? next-char)
+ (if in-width?
+ (loop (+ index 1)
+ (cons next-char w-digits)
+ d-digits
+ in-width?)
+ (loop (+ index 1)
+ w-digits
+ (cons next-char d-digits)
+ in-width?))
+ )
+ ((char=? (char-upcase next-char) #\F)
+ (let ( (width (string->number (list->string (reverse w-digits))))
+ (digits (if (zero? (length d-digits))
+ #f
+ (string->number (list->string (reverse d-digits)))))
+ )
+ (display (format-fixed (car arglist) width digits) port)
+ (anychar-dispatch (+ index 1) (cdr arglist) #f))
+ )
+ ((char=? next-char #\,)
+ (if in-width?
+ (loop (+ index 1)
+ w-digits
+ d-digits
+ #f)
+ (error
+ (format "FORMAT: too many commas in directive ~s" format-strg)))
+ )
+ (else
+ (error (format "FORMAT: ~~w.dF directive ill-formed in ~s" format-strg))))))
+ ))
+ ((#\? #\K) ; indirection -- take next arg as format string
+ (cond ; and following arg as list of format args
+ ((< (length arglist) 2)
+ (error
+ (format "FORMAT: less arguments than specified for ~~?: ~s" arglist))
+ )
+ ((not (string? (car arglist)))
+ (error
+ (format "FORMAT: ~~? requires a string: ~s" (car arglist)))
+ )
+ (else
+ (format-help (car arglist) (cadr arglist))
+ (anychar-dispatch (+ pos 1) (cddr arglist) #f)
+ )))
+ ((#\H) ; Help
+ (display documentation-string port)
+ (anychar-dispatch (+ pos 1) arglist #t)
+ )
+ (else
+ (error (format "FORMAT: unknown tilde escape: ~s"
+ (string-ref format-strg pos))))
+ )))
+ )) ; end tilde-dispatch
+ ) ; end letrec
+
+ ; format-help main
+ (anychar-dispatch 0 arglist #f)
+ )) ; end format-help
+
+ ; format main
+ (let ( (unused-args (format-help format-string args)) )
+ (if (not (null? unused-args))
+ (error
+ (format "FORMAT: unused arguments ~s" unused-args)))
+ (return-value))
+
+ )) ; end letrec, if
+))) ; end format
+;; Copyright (C) Taylan Ulrich Bayırlı/Kammer (2015). All Rights Reserved.
+
+;; Permission is hereby granted, free of charge, to any person obtaining
+;; a copy of this software and associated documentation files (the
+;; "Software"), to deal in the Software without restriction, including
+;; without limitation the rights to use, copy, modify, merge, publish,
+;; distribute, sublicense, and/or sell copies of the Software, and to
+;; permit persons to whom the Software is furnished to do so, subject to
+;; the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+(define-library (srfi 5)
+ (export (rename let+ let))
+ (import (scheme base))
+ (begin
+
+ (define-syntax let+
+ (syntax-rules ()
+ ;; Unnamed, no rest args.
+ ((_ ((var val) ...) body ...)
+ (let ((var val) ...) body ...))
+ ;; Unnamed, with rest args.
+ ((_ ((var val) spec ...) body ...)
+ (rest ((var val) spec ...) () () body ...))
+ ;; Signature style, no rest args.
+ ((_ (name (var val) ...) body ...)
+ (let name ((var val) ...) body ...))
+ ;; Signature style, with rest args.
+ ((_ (name (var val) spec ...) body ...)
+ (rest/named name ((var val) spec ...) () () body ...))
+ ;; Named let, no rest args.
+ ((_ name ((var val) ...) body ...)
+ (let name ((var val) ...) body ...))
+ ;; Named let, with rest args.
+ ((_ name ((var val) spec ...) body ...)
+ (rest/named name ((var val) spec ...) () () body ...))))
+
+ (define-syntax rest
+ (syntax-rules ()
+ ((_ ((var val) spec ...) (var* ...) (val* ...) body ...)
+ (rest name (spec ...) (var var* ...) (val val* ...) body ...))
+ ((_ (rest-var rest-val ...) (var ...) (val ...) body ...)
+ (let ((var val)
+ ...
+ (rest-var (list rest-val ...)))
+ body ...))))
+
+ (define-syntax rest/named
+ (syntax-rules ()
+ ((_ name ((var val) spec ...) (var* ...) (val* ...) body ...)
+ (rest/named name (spec ...) (var var* ...) (val val* ...) body ...))
+ ((_ name (rest-var rest-val ...) (var ...) (val ...) body ...)
+ (letrec ((name (lambda (var ... . rest-var) body ...)))
+ (name val ... rest-val ...)))))
+
+ ))
+(define-library (srfi 51)
+ (export
+ rest-values
+ arg-and
+ arg-ands
+ err-and
+ err-ands
+ arg-or
+ arg-ors
+ err-or
+ err-ors
+ )
+ (import
+ (scheme base)
+ (srfi 1))
+ (include "51.upstream.scm"))
+(define-library (srfi aux)
+ (import
+ (scheme base)
+ (scheme case-lambda)
+ (srfi 31))
+ (export
+ debug-mode
+ define/opt
+ lambda/opt
+ define-check-arg
+ )
+ (begin
+
+ (define debug-mode (make-parameter #f))
+
+ ;; Emacs indentation help:
+ ;; (put 'define/opt 'scheme-indent-function 1)
+ ;; (put 'lambda/opt 'scheme-indent-function 1)
+
+ (define-syntax define/opt
+ (syntax-rules ()
+ ((_ (name . args) . body)
+ (define name (lambda/opt args . body)))))
+
+ (define-syntax lambda/opt
+ (syntax-rules ()
+ ((lambda* args . body)
+ (rec name (opt/split-args name () () args body)))))
+
+ (define-syntax opt/split-args
+ (syntax-rules ()
+ ((_ name non-opts (opts ...) ((opt) . rest) body)
+ (opt/split-args name non-opts (opts ... (opt #f)) rest body))
+ ((_ name non-opts (opts ...) ((opt def) . rest) body)
+ (opt/split-args name non-opts (opts ... (opt def)) rest body))
+ ((_ name (non-opts ...) opts (non-opt . rest) body)
+ (opt/split-args name (non-opts ... non-opt) opts rest body))
+ ;; Rest could be () or a rest-arg here; just propagate it.
+ ((_ name non-opts opts rest body)
+ (opt/make-clauses name () rest non-opts opts body))))
+
+ (define-syntax opt/make-clauses
+ (syntax-rules ()
+ ;; Handle special-case with no optargs.
+ ((_ name () rest (taken ...) () body)
+ (lambda (taken ... . rest)
+ . body))
+ ;; Add clause where no optargs are provided.
+ ((_ name () rest (taken ...) ((opt def) ...) body)
+ (opt/make-clauses
+ name
+ (((taken ...)
+ (name taken ... def ...)))
+ rest
+ (taken ...)
+ ((opt def) ...)
+ body))
+ ;; Add clauses where 1 to n-1 optargs are provided
+ ((_ name (clause ...) rest (taken ...) ((opt def) (opt* def*) ... x) body)
+ (opt/make-clauses
+ name
+ (clause
+ ...
+ ((taken ... opt)
+ (name taken ... opt def* ...)))
+ rest
+ (taken ... opt)
+ ((opt* def*) ... x)
+ body))
+ ;; Add clause where all optargs were given, and possibly more.
+ ((_ name (clause ...) rest (taken ...) ((opt def)) body)
+ (case-lambda
+ clause
+ ...
+ ((taken ... opt . rest)
+ . body)))))
+
+ (define-syntax define-check-arg
+ (syntax-rules ()
+ ((_ check-arg)
+ (define check-arg
+ (if (debug-mode)
+ (lambda (pred val proc)
+ (if (pred val)
+ val
+ (error "Type assertion failed:"
+ `(value ,val)
+ `(expected-type ,pred)
+ `(callee ,proc))))
+ (lambda (pred val proc)
+ val))))))
+
+ ))
+;;; Copyright (C) Joo ChurlSoo (2004). All Rights Reserved.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+(define (rest-values rest . default)
+ (let* ((caller (if (or (null? default)
+ (boolean? (car default))
+ (integer? (car default))
+ (memq (car default) (list + -)))
+ '()
+ (if (string? rest) rest (list rest))))
+ (rest-list (if (null? caller) rest (car default)))
+ (rest-length (if (list? rest-list)
+ (length rest-list)
+ (if (string? caller)
+ (error caller rest-list 'rest-list
+ '(list? rest-list))
+ (apply error "bad rest list" rest-list 'rest-list
+ '(list? rest-list) caller))))
+ (default (if (null? caller) default (cdr default)))
+ (default-list (if (null? default) default (cdr default)))
+ (default-length (length default-list))
+ (number
+ (and (not (null? default))
+ (let ((option (car default)))
+ (or (and (integer? option)
+ (or (and (> rest-length (abs option))
+ (if (string? caller)
+ (error caller rest-list 'rest-list
+ `(<= (length rest-list)
+ ,(abs option)))
+ (apply error "too many arguments"
+ rest-list 'rest-list
+ `(<= (length rest-list)
+ ,(abs option))
+ caller)))
+ (and (> default-length (abs option))
+ (if (string? caller)
+ (error caller default-list
+ 'default-list
+ `(<= (length default-list)
+ ,(abs option)))
+ (apply error "too many defaults"
+ default-list 'default-list
+ `(<= (length default-list)
+ ,(abs option))
+ caller)))
+ option))
+ (eq? option #t)
+ (and (not option) 'false)
+ (and (eq? option +) +)
+ (and (eq? option -) -)
+ (if (string? caller)
+ (error caller option 'option
+ '(or (boolean? option)
+ (integer? option)
+ (memq option (list + -))))
+ (apply error "bad optional argument" option 'option
+ '(or (boolean? option)
+ (integer? option)
+ (memq option (list + -)))
+ caller)))))))
+ (cond
+ ((or (eq? #t number) (eq? 'false number))
+ (and (not (every pair? default-list))
+ (if (string? caller)
+ (error caller default-list 'default-list
+ '(every pair? default-list))
+ (apply error "bad default list" default-list 'default-list
+ '(every pair? default-list) caller)))
+ (let loop ((rest-list rest-list)
+ (default-list default-list)
+ (result '()))
+ (if (null? default-list)
+ (if (null? rest-list)
+ (apply values (reverse result))
+ (if (eq? #t number)
+ (if (string? caller)
+ (error caller rest-list 'rest-list '(null? rest-list))
+ (apply error "bad argument" rest-list 'rest-list
+ '(null? rest-list) caller))
+ (apply values (append-reverse result rest-list))))
+ (if (null? rest-list)
+ (apply values (append-reverse result (map car default-list)))
+ (let ((default (car default-list)))
+ (let lp ((rest rest-list)
+ (head '()))
+ (if (null? rest)
+ (loop (reverse head)
+ (cdr default-list)
+ (cons (car default) result))
+ (if (list? default)
+ (if (member (car rest) default)
+ (loop (append-reverse head (cdr rest))
+ (cdr default-list)
+ (cons (car rest) result))
+ (lp (cdr rest) (cons (car rest) head)))
+ (if ((cdr default) (car rest))
+ (loop (append-reverse head (cdr rest))
+ (cdr default-list)
+ (cons (car rest) result))
+ (lp (cdr rest) (cons (car rest) head)))))))))))
+ ((or (and (integer? number) (> number 0))
+ (eq? number +))
+ (and (not (every pair? default-list))
+ (if (string? caller)
+ (error caller default-list 'default-list
+ '(every pair? default-list))
+ (apply error "bad default list" default-list 'default-list
+ '(every pair? default-list) caller)))
+ (let loop ((rest rest-list)
+ (default default-list))
+ (if (or (null? rest) (null? default))
+ (apply values
+ (if (> default-length rest-length)
+ (append rest-list
+ (map car (list-tail default-list rest-length)))
+ rest-list))
+ (let ((arg (car rest))
+ (par (car default)))
+ (if (list? par)
+ (if (member arg par)
+ (loop (cdr rest) (cdr default))
+ (if (string? caller)
+ (error caller arg 'arg `(member arg ,par))
+ (apply error "unmatched argument"
+ arg 'arg `(member arg ,par) caller)))
+ (if ((cdr par) arg)
+ (loop (cdr rest) (cdr default))
+ (if (string? caller)
+ (error caller arg 'arg `(,(cdr par) arg))
+ (apply error "incorrect argument"
+ arg 'arg `(,(cdr par) arg) caller))))))))
+ (else
+ (apply values (if (> default-length rest-length)
+ (append rest-list (list-tail default-list rest-length))
+ rest-list))))))
+
+(define-syntax arg-and
+ (syntax-rules()
+ ((arg-and arg (a1 a2 ...) ...)
+ (and (or (symbol? 'arg)
+ (error "bad syntax" 'arg '(symbol? 'arg)
+ '(arg-and arg (a1 a2 ...) ...)))
+ (or (a1 a2 ...)
+ (error "incorrect argument" arg 'arg '(a1 a2 ...)))
+ ...))
+ ((arg-and caller arg (a1 a2 ...) ...)
+ (and (or (symbol? 'arg)
+ (error "bad syntax" 'arg '(symbol? 'arg)
+ '(arg-and caller arg (a1 a2 ...) ...)))
+ (or (a1 a2 ...)
+ (if (string? caller)
+ (error caller arg 'arg '(a1 a2 ...))
+ (error "incorrect argument" arg 'arg '(a1 a2 ...) caller)))
+ ...))))
+
+;; accessory macro for arg-ands
+(define-syntax caller-arg-and
+ (syntax-rules()
+ ((caller-arg-and caller arg (a1 a2 ...) ...)
+ (and (or (symbol? 'arg)
+ (error "bad syntax" 'arg '(symbol? 'arg)
+ '(caller-arg-and caller arg (a1 a2 ...) ...)))
+ (or (a1 a2 ...)
+ (if (string? caller)
+ (error caller arg 'arg '(a1 a2 ...))
+ (error "incorrect argument" arg 'arg '(a1 a2 ...) caller)))
+ ...))
+ ((caller-arg-and null caller arg (a1 a2 ...) ...)
+ (and (or (symbol? 'arg)
+ (error "bad syntax" 'arg '(symbol? 'arg)
+ '(caller-arg-and caller arg (a1 a2 ...) ...)))
+ (or (a1 a2 ...)
+ (if (string? caller)
+ (error caller arg 'arg '(a1 a2 ...))
+ (error "incorrect argument" arg 'arg '(a1 a2 ...) caller)))
+ ...))))
+
+(define-syntax arg-ands
+ (syntax-rules (common)
+ ((arg-ands (a1 a2 ...) ...)
+ (and (arg-and a1 a2 ...) ...))
+ ((arg-ands common caller (a1 a2 ...) ...)
+ (and (caller-arg-and caller a1 a2 ...) ...))))
+
+(define-syntax arg-or
+ (syntax-rules()
+ ((arg-or arg (a1 a2 ...) ...)
+ (or (and (not (symbol? 'arg))
+ (error "bad syntax" 'arg '(symbol? 'arg)
+ '(arg-or arg (a1 a2 ...) ...)))
+ (and (a1 a2 ...)
+ (error "incorrect argument" arg 'arg '(a1 a2 ...)))
+ ...))
+ ((arg-or caller arg (a1 a2 ...) ...)
+ (or (and (not (symbol? 'arg))
+ (error "bad syntax" 'arg '(symbol? 'arg)
+ '(arg-or caller arg (a1 a2 ...) ...)))
+ (and (a1 a2 ...)
+ (if (string? caller)
+ (error caller arg 'arg '(a1 a2 ...))
+ (error "incorrect argument" arg 'arg '(a1 a2 ...) caller)))
+ ...))))
+
+;; accessory macro for arg-ors
+(define-syntax caller-arg-or
+ (syntax-rules()
+ ((caller-arg-or caller arg (a1 a2 ...) ...)
+ (or (and (not (symbol? 'arg))
+ (error "bad syntax" 'arg '(symbol? 'arg)
+ '(caller-arg-or caller arg (a1 a2 ...) ...)))
+ (and (a1 a2 ...)
+ (if (string? caller)
+ (error caller arg 'arg '(a1 a2 ...))
+ (error "incorrect argument" arg 'arg '(a1 a2 ...) caller)))
+ ...))
+ ((caller-arg-or null caller arg (a1 a2 ...) ...)
+ (or (and (not (symbol? 'arg))
+ (error "bad syntax" 'arg '(symbol? 'arg)
+ '(caller-arg-or caller arg (a1 a2 ...) ...)))
+ (and (a1 a2 ...)
+ (if (string? caller)
+ (error caller arg 'arg '(a1 a2 ...))
+ (error "incorrect argument" arg 'arg '(a1 a2 ...) caller)))
+ ...))))
+
+(define-syntax arg-ors
+ (syntax-rules (common)
+ ((arg-ors (a1 a2 ...) ...)
+ (or (arg-or a1 a2 ...) ...))
+ ((arg-ors common caller (a1 a2 ...) ...)
+ (or (caller-arg-or caller a1 a2 ...) ...))))
+
+(define-syntax err-and
+ (syntax-rules ()
+ ((err-and err expression ...)
+ (and (or expression
+ (if (string? err)
+ (error err 'expression)
+ (error "false expression" 'expression err)))
+ ...))))
+
+(define-syntax err-ands
+ (syntax-rules ()
+ ((err-ands (err expression ...) ...)
+ (and (err-and err expression ...)
+ ...))))
+
+(define-syntax err-or
+ (syntax-rules ()
+ ((err-or err expression ...)
+ (or (and expression
+ (if (string? err)
+ (error err 'expression)
+ (error "true expression" 'expression err)))
+ ...))))
+
+(define-syntax err-ors
+ (syntax-rules ()
+ ((err-ors (err expression ...) ...)
+ (or (err-or err expression ...)
+ ...))))
+;;; Copyright (C) Joo ChurlSoo (2004). All Rights Reserved.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+(define-syntax alet-cat* ; borrowed from SRFI-86
+ (syntax-rules ()
+ ((alet-cat* z (a . e) bd ...)
+ (let ((y z))
+ (%alet-cat* y (a . e) bd ...)))))
+
+(define-syntax %alet-cat* ; borrowed from SRFI-86
+ (syntax-rules ()
+ ((%alet-cat* z ((n d t ...)) bd ...)
+ (let ((n (if (null? z)
+ d
+ (if (null? (cdr z))
+ (wow-cat-end z n t ...)
+ (error "cat: too many arguments" (cdr z))))))
+ bd ...))
+ ((%alet-cat* z ((n d t ...) . e) bd ...)
+ (let ((n (if (null? z)
+ d
+ (wow-cat! z n d t ...))))
+ (%alet-cat* z e bd ...)))
+ ((%alet-cat* z e bd ...)
+ (let ((e z)) bd ...))))
+
+(define-syntax wow-cat! ; borrowed from SRFI-86
+ (syntax-rules ()
+ ((wow-cat! z n d)
+ (let ((n (car z)))
+ (set! z (cdr z))
+ n))
+ ((wow-cat! z n d t)
+ (let ((n (car z)))
+ (if t
+ (begin (set! z (cdr z)) n)
+ (let lp ((head (list n)) (tail (cdr z)))
+ (if (null? tail)
+ d
+ (let ((n (car tail)))
+ (if t
+ (begin (set! z (append (reverse head) (cdr tail))) n)
+ (lp (cons n head) (cdr tail)))))))))
+ ((wow-cat! z n d t ts)
+ (let ((n (car z)))
+ (if t
+ (begin (set! z (cdr z)) ts)
+ (let lp ((head (list n)) (tail (cdr z)))
+ (if (null? tail)
+ d
+ (let ((n (car tail)))
+ (if t
+ (begin (set! z (append (reverse head) (cdr tail))) ts)
+ (lp (cons n head) (cdr tail)))))))))
+ ((wow-cat! z n d t ts fs)
+ (let ((n (car z)))
+ (if t
+ (begin (set! z (cdr z)) ts)
+ (begin (set! z (cdr z)) fs))))))
+
+(define-syntax wow-cat-end ; borrowed from SRFI-86
+ (syntax-rules ()
+ ((wow-cat-end z n)
+ (car z))
+ ((wow-cat-end z n t)
+ (let ((n (car z)))
+ (if t n (error "cat: too many argument" z))))
+ ((wow-cat-end z n t ts)
+ (let ((n (car z)))
+ (if t ts (error "cat: too many argument" z))))
+ ((wow-cat-end z n t ts fs)
+ (let ((n (car z)))
+ (if t ts fs)))))
+
+(define (str-index str char)
+ (let ((len (string-length str)))
+ (let lp ((n 0))
+ (and (< n len)
+ (if (char=? char (string-ref str n))
+ n
+ (lp (+ n 1)))))))
+
+(define (every? pred ls)
+ (let lp ((ls ls))
+ (or (null? ls)
+ (and (pred (car ls))
+ (lp (cdr ls))))))
+
+(define (part pred ls)
+ (let lp ((ls ls) (true '()) (false '()))
+ (cond
+ ((null? ls) (cons (reverse true) (reverse false)))
+ ((pred (car ls)) (lp (cdr ls) (cons (car ls) true) false))
+ (else (lp (cdr ls) true (cons (car ls) false))))))
+
+(define (e-mold num pre)
+ (let* ((str (number->string (inexact num)))
+ (e-index (str-index str #\e)))
+ (if e-index
+ (string-append (mold (substring str 0 e-index) pre)
+ (substring str e-index (string-length str)))
+ (mold str pre))))
+
+(define (mold str pre)
+ (let ((ind (str-index str #\.)))
+ (if ind
+ (let ((d-len (- (string-length str) (+ ind 1))))
+ (cond
+ ((= d-len pre) str)
+ ((< d-len pre) (string-append str (make-string (- pre d-len) #\0)))
+ ;;((char<? #\4 (string-ref str (+ 1 ind pre)))
+ ;;(let ((com (expt 10 pre)))
+ ;; (number->string (/ (round (* (string->number str) com)) com))))
+ ((or (char<? #\5 (string-ref str (+ 1 ind pre)))
+ (and (char=? #\5 (string-ref str (+ 1 ind pre)))
+ (or (< (+ 1 pre) d-len)
+ (memv (string-ref str (+ ind (if (= 0 pre) -1 pre)))
+ '(#\1 #\3 #\5 #\7 #\9)))))
+ (apply
+ string
+ (let* ((minus (char=? #\- (string-ref str 0)))
+ (str (substring str (if minus 1 0) (+ 1 ind pre)))
+ (char-list
+ (reverse
+ (let lp ((index (- (string-length str) 1))
+ (raise #t))
+ (if (= -1 index)
+ (if raise '(#\1) '())
+ (let ((chr (string-ref str index)))
+ (if (char=? #\. chr)
+ (cons chr (lp (- index 1) raise))
+ (if raise
+ (if (char=? #\9 chr)
+ (cons #\0 (lp (- index 1) raise))
+ (cons (integer->char
+ (+ 1 (char->integer chr)))
+ (lp (- index 1) #f)))
+ (cons chr (lp (- index 1) raise))))))))))
+ (if minus (cons #\- char-list) char-list))))
+ (else
+ (substring str 0 (+ 1 ind pre)))))
+ (string-append str "." (make-string pre #\0)))))
+
+(define (separate str sep num opt)
+ (let* ((len (string-length str))
+ (pos (if opt
+ (let ((pos (remainder (if (eq? opt 'minus) (- len 1) len)
+ num)))
+ (if (= 0 pos) num pos))
+ num)))
+ (apply string-append
+ (let loop ((ini 0)
+ (pos (if (eq? opt 'minus) (+ pos 1) pos)))
+ (if (< pos len)
+ (cons (substring str ini pos)
+ (cons sep (loop pos (+ pos num))))
+ (list (substring str ini len)))))))
+
+(define (cat object . rest)
+ (let* ((str-rest (part string? rest))
+ (str-list (car str-rest))
+ (rest-list (cdr str-rest)))
+ (if (null? rest-list)
+ (apply string-append
+ (cond
+ ((number? object) (number->string object))
+ ((string? object) object)
+ ((char? object) (string object))
+ ((boolean? object) (if object "#t" "#f"))
+ ((symbol? object) (symbol->string object))
+ (else
+ (get-output-string
+ (let ((str-port (open-output-string)))
+ (write object str-port)
+ str-port))))
+ str-list)
+ (alet-cat* rest-list
+ ((width 0 (and (integer? width) (exact? width)))
+ (port #f (or (boolean? port) (output-port? port))
+ (if (eq? port #t) (current-output-port) port))
+ (char #\space (char? char))
+ (converter #f (and (pair? converter)
+ (procedure? (car converter))
+ (procedure? (cdr converter))))
+ (precision #f (and (integer? precision)
+ (inexact? precision)))
+ (sign #f (eq? 'sign sign))
+ (radix 'decimal
+ (memq radix '(decimal octal binary hexadecimal)))
+ (exactness #f (memq exactness '(exact inexact)))
+ (separator #f (and (list? separator)
+ (< 0 (length separator) 3)
+ (char? (car separator))
+ (or (null? (cdr separator))
+ (let ((n (cadr separator)))
+ (and (integer? n) (exact? n)
+ (< 0 n))))))
+ (writer #f (procedure? writer))
+ (pipe #f (and (list? pipe)
+ (not (null? pipe))
+ (every? procedure? pipe)))
+ (take #f (and (list? take)
+ (< 0 (length take) 3)
+ (every? (lambda (x)
+ (and (integer? x) (exact? x)))
+ take))))
+ (let* ((str
+ (cond
+ ((and converter
+ ((car converter) object))
+ (let* ((str ((cdr converter) object))
+ (pad (- (abs width) (string-length str))))
+ (cond
+ ((<= pad 0) str)
+ ((< 0 width) (string-append (make-string pad char) str))
+ (else (string-append str (make-string pad char))))))
+ ((number? object)
+ (and (not (eq? radix 'decimal)) precision
+ (error "cat: non-decimal cannot have a decimal point"))
+ (and precision (< precision 0) (eq? exactness 'exact)
+ (error "cat: exact number cannot have a decimal point without exact sign"))
+ (let* ((exact-sign (and precision
+ (<= 0 precision)
+ (or (eq? exactness 'exact)
+ (and (exact? object)
+ (not (eq? exactness
+ 'inexact))))
+ "#e"))
+ (inexact-sign (and (not (eq? radix 'decimal))
+ (or (and (inexact? object)
+ (not (eq? exactness
+ 'exact)))
+ (eq? exactness 'inexact))
+ "#i"))
+ (radix-sign (cdr (assq radix
+ '((decimal . #f)
+ (octal . "#o")
+ (binary . "#b")
+ (hexadecimal . "#x")))))
+ (plus-sign (and sign (< 0 (real-part object)) "+"))
+ (exactness-sign (or exact-sign inexact-sign))
+ (str
+ (if precision
+ (let ((precision (exact
+ (abs precision)))
+ (imag (imag-part object)))
+ (if (= 0 imag)
+ (e-mold object precision)
+ (string-append
+ (e-mold (real-part object) precision)
+ (if (< 0 imag) "+" "")
+ (e-mold imag precision)
+ "i")))
+ (number->string
+ (cond
+ (inexact-sign (exact object))
+ (exactness
+ (if (eq? exactness 'exact)
+ (exact object)
+ (inexact object)))
+ (else object))
+ (cdr (assq radix '((decimal . 10)
+ (octal . 8)
+ (binary . 2)
+ (hexadecimal . 16)))))))
+ (str
+ (if (and separator
+ (not (or (and (eq? radix 'decimal)
+ (str-index str #\e))
+ (str-index str #\i)
+ (str-index str #\/))))
+ (let ((sep (string (car separator)))
+ (num (if (null? (cdr separator))
+ 3 (cadr separator)))
+ (dot-index (str-index str #\.)))
+ (if dot-index
+ (string-append
+ (separate (substring str 0 dot-index)
+ sep num (if (< object 0)
+ 'minus #t))
+ "."
+ (separate (substring
+ str (+ 1 dot-index)
+ (string-length str))
+ sep num #f))
+ (separate str sep num (if (< object 0)
+ 'minus #t))))
+ str))
+ (pad (- (abs width)
+ (+ (string-length str)
+ (if exactness-sign 2 0)
+ (if radix-sign 2 0)
+ (if plus-sign 1 0))))
+ (pad (if (< 0 pad) pad 0)))
+ (if (< 0 width)
+ (if (char-numeric? char)
+ (if (< (real-part object) 0)
+ (string-append (or exactness-sign "")
+ (or radix-sign "")
+ "-"
+ (make-string pad char)
+ (substring str 1
+ (string-length
+ str)))
+ (string-append (or exactness-sign "")
+ (or radix-sign "")
+ (or plus-sign "")
+ (make-string pad char)
+ str))
+ (string-append (make-string pad char)
+ (or exactness-sign "")
+ (or radix-sign "")
+ (or plus-sign "")
+ str))
+ (string-append (or exactness-sign "")
+ (or radix-sign "")
+ (or plus-sign "")
+ str
+ (make-string pad char)))))
+ (else
+ (let* ((str (cond
+ (writer (get-output-string
+ (let ((str-port
+ (open-output-string)))
+ (writer object str-port)
+ str-port)))
+ ((string? object) object)
+ ((char? object) (string object))
+ ((boolean? object) (if object "#t" "#f"))
+ ((symbol? object) (symbol->string object))
+ (else (get-output-string
+ (let ((str-port (open-output-string)))
+ (write object str-port)
+ str-port)))))
+ (str (if pipe
+ (let loop ((str ((car pipe) str))
+ (fns (cdr pipe)))
+ (if (null? fns)
+ str
+ (loop ((car fns) str)
+ (cdr fns))))
+ str))
+ (str
+ (if take
+ (let ((left (car take))
+ (right (if (null? (cdr take))
+ 0 (cadr take)))
+ (len (string-length str)))
+ (define (substr str beg end)
+ (let ((end (cond
+ ((< end 0) 0)
+ ((< len end) len)
+ (else end)))
+ (beg (cond
+ ((< beg 0) 0)
+ ((< len beg) len)
+ (else beg))))
+ (if (and (= beg 0) (= end len))
+ str
+ (substring str beg end))))
+ (string-append
+ (if (< left 0)
+ (substr str (abs left) len)
+ (substr str 0 left))
+ (if (< right 0)
+ (substr str 0 (+ len right))
+ (substr str (- len right) len))))
+ str))
+ (pad (- (abs width) (string-length str))))
+ (cond
+ ((<= pad 0) str)
+ ((< 0 width) (string-append (make-string pad char) str))
+ (else (string-append str (make-string pad char))))))))
+ (str (apply string-append str str-list)))
+ (and port (display str port))
+ str)))))
+
+;;; eof
+(define-library (srfi 54)
+ (export cat)
+ (import
+ (scheme base)
+ (scheme char)
+ (scheme complex)
+ (scheme write)
+ (srfi 1))
+ (include "54.body.scm"))
+;;; Copyright (C) Joo ChurlSoo (2004). All Rights Reserved.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+(define (cat object . rest)
+ (let* ((str-rest (part string? rest))
+ (str-list (car str-rest))
+ (rest-list (cdr str-rest)))
+ (if (null? rest-list)
+ (apply string-append
+ (cond
+ ((number? object) (number->string object))
+ ((string? object) object)
+ ((char? object) (string object))
+ ((boolean? object) (if object "#t" "#f"))
+ ((symbol? object) (symbol->string object))
+ (else
+ (get-output-string
+ (let ((str-port (open-output-string)))
+ (write object str-port)
+ str-port))))
+ str-list)
+ (alet-cat* rest-list
+ ((width 0 (and (integer? width) (exact? width)))
+ (port #f (or (boolean? port) (output-port? port))
+ (if (eq? port #t) (current-output-port) port))
+ (char #\space (char? char))
+ (converter #f (and (pair? converter)
+ (procedure? (car converter))
+ (procedure? (cdr converter))))
+ (precision #f (and (integer? precision)
+ (inexact? precision)))
+ (sign #f (eq? 'sign sign))
+ (radix 'decimal
+ (memq radix '(decimal octal binary hexadecimal)))
+ (exactness #f (memq exactness '(exact inexact)))
+ (separator #f (and (list? separator)
+ (< 0 (length separator) 3)
+ (char? (car separator))
+ (or (null? (cdr separator))
+ (let ((n (cadr separator)))
+ (and (integer? n) (exact? n)
+ (< 0 n))))))
+ (writer #f (procedure? writer))
+ (pipe #f (and (list? pipe)
+ (not (null? pipe))
+ (every? procedure? pipe)))
+ (take #f (and (list? take)
+ (< 0 (length take) 3)
+ (every? (lambda (x)
+ (and (integer? x) (exact? x)))
+ take))))
+ (let* ((str
+ (cond
+ ((and converter
+ ((car converter) object))
+ (let* ((str ((cdr converter) object))
+ (pad (- (abs width) (string-length str))))
+ (cond
+ ((<= pad 0) str)
+ ((< 0 width) (string-append (make-string pad char) str))
+ (else (string-append str (make-string pad char))))))
+ ((number? object)
+ (and (not (eq? radix 'decimal)) precision
+ (error "cat: non-decimal cannot have a decimal point"))
+ (and precision (< precision 0) (eq? exactness 'exact)
+ (error "cat: exact number cannot have a decimal point without exact sign"))
+ (let* ((exact-sign (and precision
+ (<= 0 precision)
+ (or (eq? exactness 'exact)
+ (and (exact? object)
+ (not (eq? exactness
+ 'inexact))))
+ "#e"))
+ (inexact-sign (and (not (eq? radix 'decimal))
+ (or (and (inexact? object)
+ (not (eq? exactness
+ 'exact)))
+ (eq? exactness 'inexact))
+ "#i"))
+ (radix-sign (cdr (assq radix
+ '((decimal . #f)
+ (octal . "#o")
+ (binary . "#b")
+ (hexadecimal . "#x")))))
+ (plus-sign (and sign (< 0 (real-part object)) "+"))
+ (exactness-sign (or exact-sign inexact-sign))
+ (str
+ (if precision
+ (let ((precision (inexact->exact
+ (abs precision)))
+ (imag (imag-part object)))
+ (if (= 0 imag)
+ (e-mold object precision)
+ (string-append
+ (e-mold (real-part object) precision)
+ (if (< 0 imag) "+" "")
+ (e-mold imag precision)
+ "i")))
+ (number->string
+ (cond
+ (inexact-sign (inexact->exact object))
+ (exactness
+ (if (eq? exactness 'exact)
+ (inexact->exact object)
+ (exact->inexact object)))
+ (else object))
+ (cdr (assq radix '((decimal . 10)
+ (octal . 8)
+ (binary . 2)
+ (hexadecimal . 16)))))))
+ (str
+ (if (and separator
+ (not (or (and (eq? radix 'decimal)
+ (str-index str #\e))
+ (str-index str #\i)
+ (str-index str #\/))))
+ (let ((sep (string (car separator)))
+ (num (if (null? (cdr separator))
+ 3 (cadr separator)))
+ (dot-index (str-index str #\.)))
+ (if dot-index
+ (string-append
+ (separate (substring str 0 dot-index)
+ sep num (if (< object 0)
+ 'minus #t))
+ "."
+ (separate (substring
+ str (+ 1 dot-index)
+ (string-length str))
+ sep num #f))
+ (separate str sep num (if (< object 0)
+ 'minus #t))))
+ str))
+ (pad (- (abs width)
+ (+ (string-length str)
+ (if exactness-sign 2 0)
+ (if radix-sign 2 0)
+ (if plus-sign 1 0))))
+ (pad (if (< 0 pad) pad 0)))
+ (if (< 0 width)
+ (if (char-numeric? char)
+ (if (< (real-part object) 0)
+ (string-append (or exactness-sign "")
+ (or radix-sign "")
+ "-"
+ (make-string pad char)
+ (substring str 1
+ (string-length
+ str)))
+ (string-append (or exactness-sign "")
+ (or radix-sign "")
+ (or plus-sign "")
+ (make-string pad char)
+ str))
+ (string-append (make-string pad char)
+ (or exactness-sign "")
+ (or radix-sign "")
+ (or plus-sign "")
+ str))
+ (string-append (or exactness-sign "")
+ (or radix-sign "")
+ (or plus-sign "")
+ str
+ (make-string pad char)))))
+ (else
+ (let* ((str (cond
+ (writer (get-output-string
+ (let ((str-port
+ (open-output-string)))
+ (writer object str-port)
+ str-port)))
+ ((string? object) object)
+ ((char? object) (string object))
+ ((boolean? object) (if object "#t" "#f"))
+ ((symbol? object) (symbol->string object))
+ (else (get-output-string
+ (let ((str-port (open-output-string)))
+ (write object str-port)
+ str-port)))))
+ (str (if pipe
+ (let loop ((str ((car pipe) str))
+ (fns (cdr pipe)))
+ (if (null? fns)
+ str
+ (loop ((car fns) str)
+ (cdr fns))))
+ str))
+ (str
+ (if take
+ (let ((left (car take))
+ (right (if (null? (cdr take))
+ 0 (cadr take)))
+ (len (string-length str)))
+ (define (substr str beg end)
+ (let ((end (cond
+ ((< end 0) 0)
+ ((< len end) len)
+ (else end)))
+ (beg (cond
+ ((< beg 0) 0)
+ ((< len beg) len)
+ (else beg))))
+ (if (and (= beg 0) (= end len))
+ str
+ (substring str beg end))))
+ (string-append
+ (if (< left 0)
+ (substr str (abs left) len)
+ (substr str 0 left))
+ (if (< right 0)
+ (substr str 0 (+ len right))
+ (substr str (- len right) len))))
+ str))
+ (pad (- (abs width) (string-length str))))
+ (cond
+ ((<= pad 0) str)
+ ((< 0 width) (string-append (make-string pad char) str))
+ (else (string-append str (make-string pad char))))))))
+ (str (apply string-append str str-list)))
+ (and port (display str port))
+ str)))))
+
+(define-syntax alet-cat* ; borrowed from SRFI-86
+ (syntax-rules ()
+ ((alet-cat* z (a . e) bd ...)
+ (let ((y z))
+ (%alet-cat* y (a . e) bd ...)))))
+
+(define-syntax %alet-cat* ; borrowed from SRFI-86
+ (syntax-rules ()
+ ((%alet-cat* z ((n d t ...)) bd ...)
+ (let ((n (if (null? z)
+ d
+ (if (null? (cdr z))
+ (wow-cat-end z n t ...)
+ (error "cat: too many arguments" (cdr z))))))
+ bd ...))
+ ((%alet-cat* z ((n d t ...) . e) bd ...)
+ (let ((n (if (null? z)
+ d
+ (wow-cat! z n d t ...))))
+ (%alet-cat* z e bd ...)))
+ ((%alet-cat* z e bd ...)
+ (let ((e z)) bd ...))))
+
+(define-syntax wow-cat! ; borrowed from SRFI-86
+ (syntax-rules ()
+ ((wow-cat! z n d)
+ (let ((n (car z)))
+ (set! z (cdr z))
+ n))
+ ((wow-cat! z n d t)
+ (let ((n (car z)))
+ (if t
+ (begin (set! z (cdr z)) n)
+ (let lp ((head (list n)) (tail (cdr z)))
+ (if (null? tail)
+ d
+ (let ((n (car tail)))
+ (if t
+ (begin (set! z (append (reverse head) (cdr tail))) n)
+ (lp (cons n head) (cdr tail)))))))))
+ ((wow-cat! z n d t ts)
+ (let ((n (car z)))
+ (if t
+ (begin (set! z (cdr z)) ts)
+ (let lp ((head (list n)) (tail (cdr z)))
+ (if (null? tail)
+ d
+ (let ((n (car tail)))
+ (if t
+ (begin (set! z (append (reverse head) (cdr tail))) ts)
+ (lp (cons n head) (cdr tail)))))))))
+ ((wow-cat! z n d t ts fs)
+ (let ((n (car z)))
+ (if t
+ (begin (set! z (cdr z)) ts)
+ (begin (set! z (cdr z)) fs))))))
+
+(define-syntax wow-cat-end ; borrowed from SRFI-86
+ (syntax-rules ()
+ ((wow-cat-end z n)
+ (car z))
+ ((wow-cat-end z n t)
+ (let ((n (car z)))
+ (if t n (error "cat: too many argument" z))))
+ ((wow-cat-end z n t ts)
+ (let ((n (car z)))
+ (if t ts (error "cat: too many argument" z))))
+ ((wow-cat-end z n t ts fs)
+ (let ((n (car z)))
+ (if t ts fs)))))
+
+(define (str-index str char)
+ (let ((len (string-length str)))
+ (let lp ((n 0))
+ (and (< n len)
+ (if (char=? char (string-ref str n))
+ n
+ (lp (+ n 1)))))))
+
+(define (every? pred ls)
+ (let lp ((ls ls))
+ (or (null? ls)
+ (and (pred (car ls))
+ (lp (cdr ls))))))
+
+(define (part pred ls)
+ (let lp ((ls ls) (true '()) (false '()))
+ (cond
+ ((null? ls) (cons (reverse true) (reverse false)))
+ ((pred (car ls)) (lp (cdr ls) (cons (car ls) true) false))
+ (else (lp (cdr ls) true (cons (car ls) false))))))
+
+(define (e-mold num pre)
+ (let* ((str (number->string (exact->inexact num)))
+ (e-index (str-index str #\e)))
+ (if e-index
+ (string-append (mold (substring str 0 e-index) pre)
+ (substring str e-index (string-length str)))
+ (mold str pre))))
+
+(define (mold str pre)
+ (let ((ind (str-index str #\.)))
+ (if ind
+ (let ((d-len (- (string-length str) (+ ind 1))))
+ (cond
+ ((= d-len pre) str)
+ ((< d-len pre) (string-append str (make-string (- pre d-len) #\0)))
+ ;;((char<? #\4 (string-ref str (+ 1 ind pre)))
+ ;;(let ((com (expt 10 pre)))
+ ;; (number->string (/ (round (* (string->number str) com)) com))))
+ ((or (char<? #\5 (string-ref str (+ 1 ind pre)))
+ (and (char=? #\5 (string-ref str (+ 1 ind pre)))
+ (or (< (+ 1 pre) d-len)
+ (memv (string-ref str (+ ind (if (= 0 pre) -1 pre)))
+ '(#\1 #\3 #\5 #\7 #\9)))))
+ (apply
+ string
+ (let* ((minus (char=? #\- (string-ref str 0)))
+ (str (substring str (if minus 1 0) (+ 1 ind pre)))
+ (char-list
+ (reverse
+ (let lp ((index (- (string-length str) 1))
+ (raise #t))
+ (if (= -1 index)
+ (if raise '(#\1) '())
+ (let ((chr (string-ref str index)))
+ (if (char=? #\. chr)
+ (cons chr (lp (- index 1) raise))
+ (if raise
+ (if (char=? #\9 chr)
+ (cons #\0 (lp (- index 1) raise))
+ (cons (integer->char
+ (+ 1 (char->integer chr)))
+ (lp (- index 1) #f)))
+ (cons chr (lp (- index 1) raise))))))))))
+ (if minus (cons #\- char-list) char-list))))
+ (else
+ (substring str 0 (+ 1 ind pre)))))
+ (string-append str "." (make-string pre #\0)))))
+
+(define (separate str sep num opt)
+ (let* ((len (string-length str))
+ (pos (if opt
+ (let ((pos (remainder (if (eq? opt 'minus) (- len 1) len)
+ num)))
+ (if (= 0 pos) num pos))
+ num)))
+ (apply string-append
+ (let loop ((ini 0)
+ (pos (if (eq? opt 'minus) (+ pos 1) pos)))
+ (if (< pos len)
+ (cons (substring str ini pos)
+ (cons sep (loop pos (+ pos num))))
+ (list (substring str ini len)))))))
+
+;;; eof
+(define-library (srfi 57)
+ (export
+ define-record-type
+ define-record-scheme
+ record-update
+ record-update!
+ record-compose
+ )
+ (import
+ (rename (scheme base) (define-record-type srfi-9:define-record-type))
+ (scheme case-lambda))
+ (include "57.upstream.scm"))
+;; Copyright (c) 2005, 2006, 2007, 2012, 2013 Per Bothner
+;; Added "full" support for Chicken, Gauche, Guile and SISC.
+;; Alex Shinn, Copyright (c) 2005.
+;; Modified for Scheme Spheres by Álvaro Castro-Castilla, Copyright (c) 2012.
+;; Support for Guile 2 by Mark H Weaver <mhw@netris.org>, Copyright (c) 2014.
+;;
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+(cond-expand
+ (chicken
+ (require-extension syntax-case))
+ (guile-2
+ (use-modules (srfi srfi-9)
+ ;; In 2.0.9, srfi-34 and srfi-35 are not well integrated
+ ;; with either Guile's native exceptions or R6RS exceptions.
+ ;;(srfi srfi-34) (srfi srfi-35)
+ (srfi srfi-39)))
+ (guile
+ (use-modules (ice-9 syncase) (srfi srfi-9)
+ ;;(srfi srfi-34) (srfi srfi-35) - not in Guile 1.6.7
+ (srfi srfi-39)))
+ (sisc
+ (require-extension (srfi 9 34 35 39)))
+ (kawa
+ (module-compile-options warn-undefined-variable\: #t
+ warn-invoke-unknown-method\: #t)
+ (provide 'srfi-64)
+ (provide 'testing)
+ (require 'srfi-34)
+ (require 'srfi-35))
+ (else ()
+ ))
+
+(cond-expand
+ (kawa
+ (define-syntax %test-export
+ (syntax-rules ()
+ ((%test-export test-begin . other-names)
+ (module-export %test-begin . other-names)))))
+ (else
+ (define-syntax %test-export
+ (syntax-rules ()
+ ((%test-export . names) (if #f #f))))))
+
+;; List of exported names
+(%test-export
+ test-begin ;; must be listed first, since in Kawa (at least) it is "magic".
+ test-end test-assert test-eqv test-eq test-equal
+ test-approximate test-assert test-error test-apply test-with-runner
+ test-match-nth test-match-all test-match-any test-match-name
+ test-skip test-expect-fail test-read-eval-string
+ test-runner-group-path test-group test-group-with-cleanup
+ test-result-ref test-result-set! test-result-clear test-result-remove
+ test-result-kind test-passed?
+ test-log-to-file
+ ; Misc test-runner functions
+ test-runner? test-runner-reset test-runner-null
+ test-runner-simple test-runner-current test-runner-factory test-runner-get
+ test-runner-create test-runner-test-name
+ ;; test-runner field setter and getter functions - see %test-record-define:
+ test-runner-pass-count test-runner-pass-count!
+ test-runner-fail-count test-runner-fail-count!
+ test-runner-xpass-count test-runner-xpass-count!
+ test-runner-xfail-count test-runner-xfail-count!
+ test-runner-skip-count test-runner-skip-count!
+ test-runner-group-stack test-runner-group-stack!
+ test-runner-on-test-begin test-runner-on-test-begin!
+ test-runner-on-test-end test-runner-on-test-end!
+ test-runner-on-group-begin test-runner-on-group-begin!
+ test-runner-on-group-end test-runner-on-group-end!
+ test-runner-on-final test-runner-on-final!
+ test-runner-on-bad-count test-runner-on-bad-count!
+ test-runner-on-bad-end-name test-runner-on-bad-end-name!
+ test-result-alist test-result-alist!
+ test-runner-aux-value test-runner-aux-value!
+ ;; default/simple call-back functions, used in default test-runner,
+ ;; but can be called to construct more complex ones.
+ test-on-group-begin-simple test-on-group-end-simple
+ test-on-bad-count-simple test-on-bad-end-name-simple
+ test-on-final-simple test-on-test-end-simple
+ test-on-final-simple)
+
+(cond-expand
+ (srfi-9
+ (define-syntax %test-record-define
+ (syntax-rules ()
+ ((%test-record-define alloc runner? (name index setter getter) ...)
+ (define-record-type test-runner
+ (alloc)
+ runner?
+ (name setter getter) ...)))))
+ (else
+ (define %test-runner-cookie (list "test-runner"))
+ (define-syntax %test-record-define
+ (syntax-rules ()
+ ((%test-record-define alloc runner? (name index getter setter) ...)
+ (begin
+ (define (runner? obj)
+ (and (vector? obj)
+ (> (vector-length obj) 1)
+ (eq (vector-ref obj 0) %test-runner-cookie)))
+ (define (alloc)
+ (let ((runner (make-vector 23)))
+ (vector-set! runner 0 %test-runner-cookie)
+ runner))
+ (begin
+ (define (getter runner)
+ (vector-ref runner index)) ...)
+ (begin
+ (define (setter runner value)
+ (vector-set! runner index value)) ...)))))))
+
+(%test-record-define
+ %test-runner-alloc test-runner?
+ ;; Cumulate count of all tests that have passed and were expected to.
+ (pass-count 1 test-runner-pass-count test-runner-pass-count!)
+ (fail-count 2 test-runner-fail-count test-runner-fail-count!)
+ (xpass-count 3 test-runner-xpass-count test-runner-xpass-count!)
+ (xfail-count 4 test-runner-xfail-count test-runner-xfail-count!)
+ (skip-count 5 test-runner-skip-count test-runner-skip-count!)
+ (skip-list 6 %test-runner-skip-list %test-runner-skip-list!)
+ (fail-list 7 %test-runner-fail-list %test-runner-fail-list!)
+ ;; Normally #t, except when in a test-apply.
+ (run-list 8 %test-runner-run-list %test-runner-run-list!)
+ (skip-save 9 %test-runner-skip-save %test-runner-skip-save!)
+ (fail-save 10 %test-runner-fail-save %test-runner-fail-save!)
+ (group-stack 11 test-runner-group-stack test-runner-group-stack!)
+ (on-test-begin 12 test-runner-on-test-begin test-runner-on-test-begin!)
+ (on-test-end 13 test-runner-on-test-end test-runner-on-test-end!)
+ ;; Call-back when entering a group. Takes (runner suite-name count).
+ (on-group-begin 14 test-runner-on-group-begin test-runner-on-group-begin!)
+ ;; Call-back when leaving a group.
+ (on-group-end 15 test-runner-on-group-end test-runner-on-group-end!)
+ ;; Call-back when leaving the outermost group.
+ (on-final 16 test-runner-on-final test-runner-on-final!)
+ ;; Call-back when expected number of tests was wrong.
+ (on-bad-count 17 test-runner-on-bad-count test-runner-on-bad-count!)
+ ;; Call-back when name in test=end doesn't match test-begin.
+ (on-bad-end-name 18 test-runner-on-bad-end-name test-runner-on-bad-end-name!)
+ ;; Cumulate count of all tests that have been done.
+ (total-count 19 %test-runner-total-count %test-runner-total-count!)
+ ;; Stack (list) of (count-at-start . expected-count):
+ (count-list 20 %test-runner-count-list %test-runner-count-list!)
+ (result-alist 21 test-result-alist test-result-alist!)
+ ;; Field can be used by test-runner for any purpose.
+ ;; test-runner-simple uses it for a log file.
+ (aux-value 22 test-runner-aux-value test-runner-aux-value!)
+)
+
+(define (test-runner-reset runner)
+ (test-result-alist! runner '())
+ (test-runner-pass-count! runner 0)
+ (test-runner-fail-count! runner 0)
+ (test-runner-xpass-count! runner 0)
+ (test-runner-xfail-count! runner 0)
+ (test-runner-skip-count! runner 0)
+ (%test-runner-total-count! runner 0)
+ (%test-runner-count-list! runner '())
+ (%test-runner-run-list! runner #t)
+ (%test-runner-skip-list! runner '())
+ (%test-runner-fail-list! runner '())
+ (%test-runner-skip-save! runner '())
+ (%test-runner-fail-save! runner '())
+ (test-runner-group-stack! runner '()))
+
+(define (test-runner-group-path runner)
+ (reverse (test-runner-group-stack runner)))
+
+(define (%test-null-callback runner) #f)
+
+(define (test-runner-null)
+ (let ((runner (%test-runner-alloc)))
+ (test-runner-reset runner)
+ (test-runner-on-group-begin! runner (lambda (runner name count) #f))
+ (test-runner-on-group-end! runner %test-null-callback)
+ (test-runner-on-final! runner %test-null-callback)
+ (test-runner-on-test-begin! runner %test-null-callback)
+ (test-runner-on-test-end! runner %test-null-callback)
+ (test-runner-on-bad-count! runner (lambda (runner count expected) #f))
+ (test-runner-on-bad-end-name! runner (lambda (runner begin end) #f))
+ runner))
+
+;; Not part of the specification. FIXME
+;; Controls whether a log file is generated.
+(define test-log-to-file #t)
+
+(define (test-runner-simple)
+ (let ((runner (%test-runner-alloc)))
+ (test-runner-reset runner)
+ (test-runner-on-group-begin! runner test-on-group-begin-simple)
+ (test-runner-on-group-end! runner test-on-group-end-simple)
+ (test-runner-on-final! runner test-on-final-simple)
+ (test-runner-on-test-begin! runner test-on-test-begin-simple)
+ (test-runner-on-test-end! runner test-on-test-end-simple)
+ (test-runner-on-bad-count! runner test-on-bad-count-simple)
+ (test-runner-on-bad-end-name! runner test-on-bad-end-name-simple)
+ runner))
+
+(cond-expand
+ (srfi-39
+ (define test-runner-current (make-parameter #f))
+ (define test-runner-factory (make-parameter test-runner-simple)))
+ (else
+ (define %test-runner-current #f)
+ (define-syntax test-runner-current
+ (syntax-rules ()
+ ((test-runner-current)
+ %test-runner-current)
+ ((test-runner-current runner)
+ (set! %test-runner-current runner))))
+ (define %test-runner-factory test-runner-simple)
+ (define-syntax test-runner-factory
+ (syntax-rules ()
+ ((test-runner-factory)
+ %test-runner-factory)
+ ((test-runner-factory runner)
+ (set! %test-runner-factory runner))))))
+
+;; A safer wrapper to test-runner-current.
+(define (test-runner-get)
+ (let ((r (test-runner-current)))
+ (if (not r)
+ (cond-expand
+ (srfi-23 (error "test-runner not initialized - test-begin missing?"))
+ (else #t)))
+ r))
+
+(define (%test-specifier-matches spec runner)
+ (spec runner))
+
+(define (test-runner-create)
+ ((test-runner-factory)))
+
+(define (%test-any-specifier-matches list runner)
+ (let ((result #f))
+ (let loop ((l list))
+ (cond ((null? l) result)
+ (else
+ (if (%test-specifier-matches (car l) runner)
+ (set! result #t))
+ (loop (cdr l)))))))
+
+;; Returns #f, #t, or 'xfail.
+(define (%test-should-execute runner)
+ (let ((run (%test-runner-run-list runner)))
+ (cond ((or
+ (not (or (eqv? run #t)
+ (%test-any-specifier-matches run runner)))
+ (%test-any-specifier-matches
+ (%test-runner-skip-list runner)
+ runner))
+ (test-result-set! runner 'result-kind 'skip)
+ #f)
+ ((%test-any-specifier-matches
+ (%test-runner-fail-list runner)
+ runner)
+ (test-result-set! runner 'result-kind 'xfail)
+ 'xfail)
+ (else #t))))
+
+(define (%test-begin suite-name count)
+ (if (not (test-runner-current))
+ (test-runner-current (test-runner-create)))
+ (let ((runner (test-runner-current)))
+ ((test-runner-on-group-begin runner) runner suite-name count)
+ (%test-runner-skip-save! runner
+ (cons (%test-runner-skip-list runner)
+ (%test-runner-skip-save runner)))
+ (%test-runner-fail-save! runner
+ (cons (%test-runner-fail-list runner)
+ (%test-runner-fail-save runner)))
+ (%test-runner-count-list! runner
+ (cons (cons (%test-runner-total-count runner)
+ count)
+ (%test-runner-count-list runner)))
+ (test-runner-group-stack! runner (cons suite-name
+ (test-runner-group-stack runner)))))
+(cond-expand
+ (kawa
+ ;; Kawa has test-begin built in, implemented as:
+ ;; (begin
+ ;; (cond-expand (srfi-64 #!void) (else (require 'srfi-64)))
+ ;; (%test-begin suite-name [count]))
+ ;; This puts test-begin but only test-begin in the default environment.,
+ ;; which makes normal test suites loadable without non-portable commands.
+ )
+ (else
+ (define-syntax test-begin
+ (syntax-rules ()
+ ((test-begin suite-name)
+ (%test-begin suite-name #f))
+ ((test-begin suite-name count)
+ (%test-begin suite-name count))))))
+
+(define (test-on-group-begin-simple runner suite-name count)
+ (if (null? (test-runner-group-stack runner))
+ (begin
+ (display "%%%% Starting test ")
+ (display suite-name)
+ (if test-log-to-file
+ (let* ((log-file-name
+ (if (string? test-log-to-file) test-log-to-file
+ (string-append suite-name ".log")))
+ (log-file
+ (cond-expand (mzscheme
+ (open-output-file log-file-name 'truncate/replace))
+ (else (open-output-file log-file-name)))))
+ (display "%%%% Starting test " log-file)
+ (display suite-name log-file)
+ (newline log-file)
+ (test-runner-aux-value! runner log-file)
+ (display " (Writing full log to \"")
+ (display log-file-name)
+ (display "\")")))
+ (newline)))
+ (let ((log (test-runner-aux-value runner)))
+ (if (output-port? log)
+ (begin
+ (display "Group begin: " log)
+ (display suite-name log)
+ (newline log))))
+ #f)
+
+(define (test-on-group-end-simple runner)
+ (let ((log (test-runner-aux-value runner)))
+ (if (output-port? log)
+ (begin
+ (display "Group end: " log)
+ (display (car (test-runner-group-stack runner)) log)
+ (newline log))))
+ #f)
+
+(define (%test-on-bad-count-write runner count expected-count port)
+ (display "*** Total number of tests was " port)
+ (display count port)
+ (display " but should be " port)
+ (display expected-count port)
+ (display ". ***" port)
+ (newline port)
+ (display "*** Discrepancy indicates testsuite error or exceptions. ***" port)
+ (newline port))
+
+(define (test-on-bad-count-simple runner count expected-count)
+ (%test-on-bad-count-write runner count expected-count (current-output-port))
+ (let ((log (test-runner-aux-value runner)))
+ (if (output-port? log)
+ (%test-on-bad-count-write runner count expected-count log))))
+
+(define (test-on-bad-end-name-simple runner begin-name end-name)
+ (let ((msg (string-append (%test-format-line runner) "test-end " begin-name
+ " does not match test-begin " end-name)))
+ (cond-expand
+ (srfi-23 (error msg))
+ (else (display msg) (newline)))))
+
+
+(define (%test-final-report1 value label port)
+ (if (> value 0)
+ (begin
+ (display label port)
+ (display value port)
+ (newline port))))
+
+(define (%test-final-report-simple runner port)
+ (%test-final-report1 (test-runner-pass-count runner)
+ "# of expected passes " port)
+ (%test-final-report1 (test-runner-xfail-count runner)
+ "# of expected failures " port)
+ (%test-final-report1 (test-runner-xpass-count runner)
+ "# of unexpected successes " port)
+ (%test-final-report1 (test-runner-fail-count runner)
+ "# of unexpected failures " port)
+ (%test-final-report1 (test-runner-skip-count runner)
+ "# of skipped tests " port))
+
+(define (test-on-final-simple runner)
+ (%test-final-report-simple runner (current-output-port))
+ (let ((log (test-runner-aux-value runner)))
+ (if (output-port? log)
+ (%test-final-report-simple runner log))))
+
+(define (%test-format-line runner)
+ (let* ((line-info (test-result-alist runner))
+ (source-file (assq 'source-file line-info))
+ (source-line (assq 'source-line line-info))
+ (file (if source-file (cdr source-file) "")))
+ (if source-line
+ (string-append file ":"
+ (number->string (cdr source-line)) ": ")
+ "")))
+
+(define (%test-end suite-name line-info)
+ (let* ((r (test-runner-get))
+ (groups (test-runner-group-stack r))
+ (line (%test-format-line r)))
+ (test-result-alist! r line-info)
+ (if (null? groups)
+ (let ((msg (string-append line "test-end not in a group")))
+ (cond-expand
+ (srfi-23 (error msg))
+ (else (display msg) (newline)))))
+ (if (and suite-name (not (equal? suite-name (car groups))))
+ ((test-runner-on-bad-end-name r) r suite-name (car groups)))
+ (let* ((count-list (%test-runner-count-list r))
+ (expected-count (cdar count-list))
+ (saved-count (caar count-list))
+ (group-count (- (%test-runner-total-count r) saved-count)))
+ (if (and expected-count
+ (not (= expected-count group-count)))
+ ((test-runner-on-bad-count r) r group-count expected-count))
+ ((test-runner-on-group-end r) r)
+ (test-runner-group-stack! r (cdr (test-runner-group-stack r)))
+ (%test-runner-skip-list! r (car (%test-runner-skip-save r)))
+ (%test-runner-skip-save! r (cdr (%test-runner-skip-save r)))
+ (%test-runner-fail-list! r (car (%test-runner-fail-save r)))
+ (%test-runner-fail-save! r (cdr (%test-runner-fail-save r)))
+ (%test-runner-count-list! r (cdr count-list))
+ (if (null? (test-runner-group-stack r))
+ ((test-runner-on-final r) r)))))
+
+(define-syntax test-group
+ (syntax-rules ()
+ ((test-group suite-name . body)
+ (let ((r (test-runner-current)))
+ ;; Ideally should also set line-number, if available.
+ (test-result-alist! r (list (cons 'test-name suite-name)))
+ (if (%test-should-execute r)
+ (dynamic-wind
+ (lambda () (test-begin suite-name))
+ (lambda () . body)
+ (lambda () (test-end suite-name))))))))
+
+(define-syntax test-group-with-cleanup
+ (syntax-rules ()
+ ((test-group-with-cleanup suite-name form cleanup-form)
+ (test-group suite-name
+ (dynamic-wind
+ (lambda () #f)
+ (lambda () form)
+ (lambda () cleanup-form))))
+ ((test-group-with-cleanup suite-name cleanup-form)
+ (test-group-with-cleanup suite-name #f cleanup-form))
+ ((test-group-with-cleanup suite-name form1 form2 form3 . rest)
+ (test-group-with-cleanup suite-name (begin form1 form2) form3 . rest))))
+
+(define (test-on-test-begin-simple runner)
+ (let ((log (test-runner-aux-value runner)))
+ (if (output-port? log)
+ (let* ((results (test-result-alist runner))
+ (source-file (assq 'source-file results))
+ (source-line (assq 'source-line results))
+ (source-form (assq 'source-form results))
+ (test-name (assq 'test-name results)))
+ (display "Test begin:" log)
+ (newline log)
+ (if test-name (%test-write-result1 test-name log))
+ (if source-file (%test-write-result1 source-file log))
+ (if source-line (%test-write-result1 source-line log))
+ (if source-form (%test-write-result1 source-form log))))))
+
+(define-syntax test-result-ref
+ (syntax-rules ()
+ ((test-result-ref runner pname)
+ (test-result-ref runner pname #f))
+ ((test-result-ref runner pname default)
+ (let ((p (assq pname (test-result-alist runner))))
+ (if p (cdr p) default)))))
+
+(define (test-on-test-end-simple runner)
+ (let ((log (test-runner-aux-value runner))
+ (kind (test-result-ref runner 'result-kind)))
+ (if (memq kind '(fail xpass))
+ (let* ((results (test-result-alist runner))
+ (source-file (assq 'source-file results))
+ (source-line (assq 'source-line results))
+ (test-name (assq 'test-name results)))
+ (if (or source-file source-line)
+ (begin
+ (if source-file (display (cdr source-file)))
+ (display ":")
+ (if source-line (display (cdr source-line)))
+ (display ": ")))
+ (display (if (eq? kind 'xpass) "XPASS" "FAIL"))
+ (if test-name
+ (begin
+ (display " ")
+ (display (cdr test-name))))
+ (newline)))
+ (if (output-port? log)
+ (begin
+ (display "Test end:" log)
+ (newline log)
+ (let loop ((list (test-result-alist runner)))
+ (if (pair? list)
+ (let ((pair (car list)))
+ ;; Write out properties not written out by on-test-begin.
+ (if (not (memq (car pair)
+ '(test-name source-file source-line source-form)))
+ (%test-write-result1 pair log))
+ (loop (cdr list)))))))))
+
+(define (%test-write-result1 pair port)
+ (display " " port)
+ (display (car pair) port)
+ (display ": " port)
+ (write (cdr pair) port)
+ (newline port))
+
+(define (test-result-set! runner pname value)
+ (let* ((alist (test-result-alist runner))
+ (p (assq pname alist)))
+ (if p
+ (set-cdr! p value)
+ (test-result-alist! runner (cons (cons pname value) alist)))))
+
+(define (test-result-clear runner)
+ (test-result-alist! runner '()))
+
+(define (test-result-remove runner pname)
+ (let* ((alist (test-result-alist runner))
+ (p (assq pname alist)))
+ (if p
+ (test-result-alist! runner
+ (let loop ((r alist))
+ (if (eq? r p) (cdr r)
+ (cons (car r) (loop (cdr r)))))))))
+
+(define (test-result-kind . rest)
+ (let ((runner (if (pair? rest) (car rest) (test-runner-current))))
+ (test-result-ref runner 'result-kind)))
+
+(define (test-passed? . rest)
+ (let ((runner (if (pair? rest) (car rest) (test-runner-get))))
+ (memq (test-result-ref runner 'result-kind) '(pass xpass))))
+
+(define (%test-report-result)
+ (let* ((r (test-runner-get))
+ (result-kind (test-result-kind r)))
+ (case result-kind
+ ((pass)
+ (test-runner-pass-count! r (+ 1 (test-runner-pass-count r))))
+ ((fail)
+ (test-runner-fail-count! r (+ 1 (test-runner-fail-count r))))
+ ((xpass)
+ (test-runner-xpass-count! r (+ 1 (test-runner-xpass-count r))))
+ ((xfail)
+ (test-runner-xfail-count! r (+ 1 (test-runner-xfail-count r))))
+ (else
+ (test-runner-skip-count! r (+ 1 (test-runner-skip-count r)))))
+ (%test-runner-total-count! r (+ 1 (%test-runner-total-count r)))
+ ((test-runner-on-test-end r) r)))
+
+(cond-expand
+ (guile
+ (define-syntax %test-evaluate-with-catch
+ (syntax-rules ()
+ ((%test-evaluate-with-catch test-expression)
+ (catch #t
+ (lambda () test-expression)
+ (lambda (key . args)
+ (test-result-set! (test-runner-current) 'actual-error
+ (cons key args))
+ #f))))))
+ (kawa
+ (define-syntax %test-evaluate-with-catch
+ (syntax-rules ()
+ ((%test-evaluate-with-catch test-expression)
+ (try-catch test-expression
+ (ex <java.lang.Throwable>
+ (test-result-set! (test-runner-current) 'actual-error ex)
+ #f))))))
+ (srfi-34
+ (define-syntax %test-evaluate-with-catch
+ (syntax-rules ()
+ ((%test-evaluate-with-catch test-expression)
+ (guard (err (else #f)) test-expression)))))
+ (chicken
+ (define-syntax %test-evaluate-with-catch
+ (syntax-rules ()
+ ((%test-evaluate-with-catch test-expression)
+ (condition-case test-expression (ex () #f))))))
+ (else
+ (define-syntax %test-evaluate-with-catch
+ (syntax-rules ()
+ ((%test-evaluate-with-catch test-expression)
+ test-expression)))))
+
+(cond-expand
+ ((or kawa mzscheme)
+ (cond-expand
+ (mzscheme
+ (define-for-syntax (%test-syntax-file form)
+ (let ((source (syntax-source form)))
+ (cond ((string? source) file)
+ ((path? source) (path->string source))
+ (else #f)))))
+ (kawa
+ (define (%test-syntax-file form)
+ (syntax-source form))))
+ (define (%test-source-line2 form)
+ (let* ((line (syntax-line form))
+ (file (%test-syntax-file form))
+ (line-pair (if line (list (cons 'source-line line)) '())))
+ (cons (cons 'source-form (syntax-object->datum form))
+ (if file (cons (cons 'source-file file) line-pair) line-pair)))))
+ (guile-2
+ (define (%test-source-line2 form)
+ (let* ((src-props (syntax-source form))
+ (file (and src-props (assq-ref src-props 'filename)))
+ (line (and src-props (assq-ref src-props 'line)))
+ (file-alist (if file
+ `((source-file . ,file))
+ '()))
+ (line-alist (if line
+ `((source-line . ,(+ line 1)))
+ '())))
+ (datum->syntax (syntax here)
+ `((source-form . ,(syntax->datum form))
+ ,@file-alist
+ ,@line-alist)))))
+ (else
+ (define (%test-source-line2 form)
+ '())))
+
+(define (%test-on-test-begin r)
+ (%test-should-execute r)
+ ((test-runner-on-test-begin r) r)
+ (not (eq? 'skip (test-result-ref r 'result-kind))))
+
+(define (%test-on-test-end r result)
+ (test-result-set! r 'result-kind
+ (if (eq? (test-result-ref r 'result-kind) 'xfail)
+ (if result 'xpass 'xfail)
+ (if result 'pass 'fail))))
+
+(define (test-runner-test-name runner)
+ (test-result-ref runner 'test-name ""))
+
+(define-syntax %test-comp2body
+ (syntax-rules ()
+ ((%test-comp2body r comp expected expr)
+ (let ()
+ (if (%test-on-test-begin r)
+ (let ((exp expected))
+ (test-result-set! r 'expected-value exp)
+ (let ((res (%test-evaluate-with-catch expr)))
+ (test-result-set! r 'actual-value res)
+ (%test-on-test-end r (comp exp res)))))
+ (%test-report-result)))))
+
+(define (%test-approximate= error)
+ (lambda (value expected)
+ (let ((rval (real-part value))
+ (ival (imag-part value))
+ (rexp (real-part expected))
+ (iexp (imag-part expected)))
+ (and (>= rval (- rexp error))
+ (>= ival (- iexp error))
+ (<= rval (+ rexp error))
+ (<= ival (+ iexp error))))))
+
+(define-syntax %test-comp1body
+ (syntax-rules ()
+ ((%test-comp1body r expr)
+ (let ()
+ (if (%test-on-test-begin r)
+ (let ()
+ (let ((res (%test-evaluate-with-catch expr)))
+ (test-result-set! r 'actual-value res)
+ (%test-on-test-end r res))))
+ (%test-report-result)))))
+
+(cond-expand
+ ((or kawa mzscheme guile-2)
+ ;; Should be made to work for any Scheme with syntax-case
+ ;; However, I haven't gotten the quoting working. FIXME.
+ (define-syntax test-end
+ (lambda (x)
+ (syntax-case (list x (list (syntax quote) (%test-source-line2 x))) ()
+ (((mac suite-name) line)
+ (syntax
+ (%test-end suite-name line)))
+ (((mac) line)
+ (syntax
+ (%test-end #f line))))))
+ (define-syntax test-assert
+ (lambda (x)
+ (syntax-case (list x (list (syntax quote) (%test-source-line2 x))) ()
+ (((mac tname expr) line)
+ (syntax
+ (let* ((r (test-runner-get))
+ (name tname))
+ (test-result-alist! r (cons (cons 'test-name tname) line))
+ (%test-comp1body r expr))))
+ (((mac expr) line)
+ (syntax
+ (let* ((r (test-runner-get)))
+ (test-result-alist! r line)
+ (%test-comp1body r expr)))))))
+ (define (%test-comp2 comp x)
+ (syntax-case (list x (list (syntax quote) (%test-source-line2 x)) comp) ()
+ (((mac tname expected expr) line comp)
+ (syntax
+ (let* ((r (test-runner-get))
+ (name tname))
+ (test-result-alist! r (cons (cons 'test-name tname) line))
+ (%test-comp2body r comp expected expr))))
+ (((mac expected expr) line comp)
+ (syntax
+ (let* ((r (test-runner-get)))
+ (test-result-alist! r line)
+ (%test-comp2body r comp expected expr))))))
+ (define-syntax test-eqv
+ (lambda (x) (%test-comp2 (syntax eqv?) x)))
+ (define-syntax test-eq
+ (lambda (x) (%test-comp2 (syntax eq?) x)))
+ (define-syntax test-equal
+ (lambda (x) (%test-comp2 (syntax equal?) x)))
+ (define-syntax test-approximate ;; FIXME - needed for non-Kawa
+ (lambda (x)
+ (syntax-case (list x (list (syntax quote) (%test-source-line2 x))) ()
+ (((mac tname expected expr error) line)
+ (syntax
+ (let* ((r (test-runner-get))
+ (name tname))
+ (test-result-alist! r (cons (cons 'test-name tname) line))
+ (%test-comp2body r (%test-approximate= error) expected expr))))
+ (((mac expected expr error) line)
+ (syntax
+ (let* ((r (test-runner-get)))
+ (test-result-alist! r line)
+ (%test-comp2body r (%test-approximate= error) expected expr))))))))
+ (else
+ (define-syntax test-end
+ (syntax-rules ()
+ ((test-end)
+ (%test-end #f '()))
+ ((test-end suite-name)
+ (%test-end suite-name '()))))
+ (define-syntax test-assert
+ (syntax-rules ()
+ ((test-assert tname test-expression)
+ (let* ((r (test-runner-get))
+ (name tname))
+ (test-result-alist! r '((test-name . tname)))
+ (%test-comp1body r test-expression)))
+ ((test-assert test-expression)
+ (let* ((r (test-runner-get)))
+ (test-result-alist! r '())
+ (%test-comp1body r test-expression)))))
+ (define-syntax %test-comp2
+ (syntax-rules ()
+ ((%test-comp2 comp tname expected expr)
+ (let* ((r (test-runner-get))
+ (name tname))
+ (test-result-alist! r (list (cons 'test-name tname)))
+ (%test-comp2body r comp expected expr)))
+ ((%test-comp2 comp expected expr)
+ (let* ((r (test-runner-get)))
+ (test-result-alist! r '())
+ (%test-comp2body r comp expected expr)))))
+ (define-syntax test-equal
+ (syntax-rules ()
+ ((test-equal . rest)
+ (%test-comp2 equal? . rest))))
+ (define-syntax test-eqv
+ (syntax-rules ()
+ ((test-eqv . rest)
+ (%test-comp2 eqv? . rest))))
+ (define-syntax test-eq
+ (syntax-rules ()
+ ((test-eq . rest)
+ (%test-comp2 eq? . rest))))
+ (define-syntax test-approximate
+ (syntax-rules ()
+ ((test-approximate tname expected expr error)
+ (%test-comp2 (%test-approximate= error) tname expected expr))
+ ((test-approximate expected expr error)
+ (%test-comp2 (%test-approximate= error) expected expr))))))
+
+(cond-expand
+ (guile
+ (define-syntax %test-error
+ (syntax-rules ()
+ ((%test-error r etype expr)
+ (cond ((%test-on-test-begin r)
+ (let ((et etype))
+ (test-result-set! r 'expected-error et)
+ (%test-on-test-end r
+ (catch #t
+ (lambda ()
+ (test-result-set! r 'actual-value expr)
+ #f)
+ (lambda (key . args)
+ ;; TODO: decide how to specify expected
+ ;; error types for Guile.
+ (test-result-set! r 'actual-error
+ (cons key args))
+ #t)))
+ (%test-report-result))))))))
+ (mzscheme
+ (define-syntax %test-error
+ (syntax-rules ()
+ ((%test-error r etype expr)
+ (%test-comp1body r (with-handlers (((lambda (h) #t) (lambda (h) #t)))
+ (let ()
+ (test-result-set! r 'actual-value expr)
+ #f)))))))
+ (chicken
+ (define-syntax %test-error
+ (syntax-rules ()
+ ((%test-error r etype expr)
+ (%test-comp1body r (condition-case expr (ex () #t)))))))
+ (kawa
+ (define-syntax %test-error
+ (syntax-rules ()
+ ((%test-error r #t expr)
+ (cond ((%test-on-test-begin r)
+ (test-result-set! r 'expected-error #t)
+ (%test-on-test-end r
+ (try-catch
+ (let ()
+ (test-result-set! r 'actual-value expr)
+ #f)
+ (ex <java.lang.Throwable>
+ (test-result-set! r 'actual-error ex)
+ #t)))
+ (%test-report-result))))
+ ((%test-error r etype expr)
+ (if (%test-on-test-begin r)
+ (let ((et etype))
+ (test-result-set! r 'expected-error et)
+ (%test-on-test-end r
+ (try-catch
+ (let ()
+ (test-result-set! r 'actual-value expr)
+ #f)
+ (ex <java.lang.Throwable>
+ (test-result-set! r 'actual-error ex)
+ (cond ((and (instance? et <gnu.bytecode.ClassType>)
+ (gnu.bytecode.ClassType:isSubclass et <java.lang.Throwable>))
+ (instance? ex et))
+ (else #t)))))
+ (%test-report-result)))))))
+ ((and srfi-34 srfi-35)
+ (define-syntax %test-error
+ (syntax-rules ()
+ ((%test-error r etype expr)
+ (%test-comp1body r (guard (ex ((condition-type? etype)
+ (and (condition? ex) (condition-has-type? ex etype)))
+ ((procedure? etype)
+ (etype ex))
+ ((equal? etype #t)
+ #t)
+ (else #t))
+ expr #f))))))
+ (srfi-34
+ (define-syntax %test-error
+ (syntax-rules ()
+ ((%test-error r etype expr)
+ (%test-comp1body r (guard (ex (else #t)) expr #f))))))
+ (else
+ (define-syntax %test-error
+ (syntax-rules ()
+ ((%test-error r etype expr)
+ (begin
+ ((test-runner-on-test-begin r) r)
+ (test-result-set! r 'result-kind 'skip)
+ (%test-report-result)))))))
+
+(cond-expand
+ ((or kawa mzscheme guile-2)
+
+ (define-syntax test-error
+ (lambda (x)
+ (syntax-case (list x (list (syntax quote) (%test-source-line2 x))) ()
+ (((mac tname etype expr) line)
+ (syntax
+ (let* ((r (test-runner-get))
+ (name tname))
+ (test-result-alist! r (cons (cons 'test-name tname) line))
+ (%test-error r etype expr))))
+ (((mac etype expr) line)
+ (syntax
+ (let* ((r (test-runner-get)))
+ (test-result-alist! r line)
+ (%test-error r etype expr))))
+ (((mac expr) line)
+ (syntax
+ (let* ((r (test-runner-get)))
+ (test-result-alist! r line)
+ (%test-error r #t expr))))))))
+ (else
+ (define-syntax test-error
+ (syntax-rules ()
+ ((test-error name etype expr)
+ (let ((r (test-runner-get)))
+ (test-result-alist! r `((test-name . ,name)))
+ (%test-error r etype expr)))
+ ((test-error etype expr)
+ (let ((r (test-runner-get)))
+ (test-result-alist! r '())
+ (%test-error r etype expr)))
+ ((test-error expr)
+ (let ((r (test-runner-get)))
+ (test-result-alist! r '())
+ (%test-error r #t expr)))))))
+
+(define (test-apply first . rest)
+ (if (test-runner? first)
+ (test-with-runner first (apply test-apply rest))
+ (let ((r (test-runner-current)))
+ (if r
+ (let ((run-list (%test-runner-run-list r)))
+ (cond ((null? rest)
+ (%test-runner-run-list! r (reverse run-list))
+ (first)) ;; actually apply procedure thunk
+ (else
+ (%test-runner-run-list!
+ r
+ (if (eq? run-list #t) (list first) (cons first run-list)))
+ (apply test-apply rest)
+ (%test-runner-run-list! r run-list))))
+ (let ((r (test-runner-create)))
+ (test-with-runner r (apply test-apply first rest))
+ ((test-runner-on-final r) r))))))
+
+(define-syntax test-with-runner
+ (syntax-rules ()
+ ((test-with-runner runner form ...)
+ (let ((saved-runner (test-runner-current)))
+ (dynamic-wind
+ (lambda () (test-runner-current runner))
+ (lambda () form ...)
+ (lambda () (test-runner-current saved-runner)))))))
+
+;;; Predicates
+
+(define (%test-match-nth n count)
+ (let ((i 0))
+ (lambda (runner)
+ (set! i (+ i 1))
+ (and (>= i n) (< i (+ n count))))))
+
+(define-syntax test-match-nth
+ (syntax-rules ()
+ ((test-match-nth n)
+ (test-match-nth n 1))
+ ((test-match-nth n count)
+ (%test-match-nth n count))))
+
+(define (%test-match-all . pred-list)
+ (lambda (runner)
+ (let ((result #t))
+ (let loop ((l pred-list))
+ (if (null? l)
+ result
+ (begin
+ (if (not ((car l) runner))
+ (set! result #f))
+ (loop (cdr l))))))))
+
+(define-syntax test-match-all
+ (syntax-rules ()
+ ((test-match-all pred ...)
+ (%test-match-all (%test-as-specifier pred) ...))))
+
+(define (%test-match-any . pred-list)
+ (lambda (runner)
+ (let ((result #f))
+ (let loop ((l pred-list))
+ (if (null? l)
+ result
+ (begin
+ (if ((car l) runner)
+ (set! result #t))
+ (loop (cdr l))))))))
+
+(define-syntax test-match-any
+ (syntax-rules ()
+ ((test-match-any pred ...)
+ (%test-match-any (%test-as-specifier pred) ...))))
+
+;; Coerce to a predicate function:
+(define (%test-as-specifier specifier)
+ (cond ((procedure? specifier) specifier)
+ ((integer? specifier) (test-match-nth 1 specifier))
+ ((string? specifier) (test-match-name specifier))
+ (else
+ (error "not a valid test specifier"))))
+
+(define-syntax test-skip
+ (syntax-rules ()
+ ((test-skip pred ...)
+ (let ((runner (test-runner-get)))
+ (%test-runner-skip-list! runner
+ (cons (test-match-all (%test-as-specifier pred) ...)
+ (%test-runner-skip-list runner)))))))
+
+(define-syntax test-expect-fail
+ (syntax-rules ()
+ ((test-expect-fail pred ...)
+ (let ((runner (test-runner-get)))
+ (%test-runner-fail-list! runner
+ (cons (test-match-all (%test-as-specifier pred) ...)
+ (%test-runner-fail-list runner)))))))
+
+(define (test-match-name name)
+ (lambda (runner)
+ (equal? name (test-runner-test-name runner))))
+
+(define (test-read-eval-string string)
+ (let* ((port (open-input-string string))
+ (form (read port)))
+ (if (eof-object? (read-char port))
+ (cond-expand
+ (guile (eval form (current-module)))
+ (else (eval form)))
+ (cond-expand
+ (srfi-23 (error "(not at eof)"))
+ (else "error")))))
+
+(define-library (srfi 67)
+ (export
+ </<=?
+ </<?
+ <=/<=?
+ <=/<?
+ <=?
+ <?
+ =?
+ >/>=?
+ >/>?
+ >=/>=?
+ >=/>?
+ >=?
+ >?
+ boolean-compare
+ chain<=?
+ chain<?
+ chain=?
+ chain>=?
+ chain>?
+ char-compare
+ char-compare-ci
+ compare-by<
+ compare-by<=
+ compare-by=/<
+ compare-by=/>
+ compare-by>
+ compare-by>=
+ complex-compare
+ cond-compare
+ debug-compare
+ default-compare
+ if-not=?
+ if3
+ if<=?
+ if<?
+ if=?
+ if>=?
+ if>?
+ integer-compare
+ kth-largest
+ list-compare
+ list-compare-as-vector
+ max-compare
+ min-compare
+ not=?
+ number-compare
+ pair-compare
+ pair-compare-car
+ pair-compare-cdr
+ pairwise-not=?
+ rational-compare
+ real-compare
+ refine-compare
+ select-compare
+ symbol-compare
+ vector-compare
+ vector-compare-as-list
+ bytevector-compare
+ bytevector-compare-as-list
+ )
+ (import
+ (scheme base)
+ (scheme case-lambda)
+ (scheme char)
+ (scheme complex)
+ (srfi 27))
+ (include "67.upstream.scm")
+ (begin
+
+ (define (bytevector-compare bv1 bv2)
+ (let ((len1 (bytevector-length bv1))
+ (len2 (bytevector-length bv2)))
+ (cond
+ ((< len1 len2) -1)
+ ((> len1 len2) +1)
+ (else
+ (let lp ((i 0))
+ (if (= i len1)
+ 0
+ (let ((b1 (bytevector-u8-ref bv1 i))
+ (b2 (bytevector-u8-ref bv2 i)))
+ (cond
+ ((< b1 b2) -1)
+ ((> b1 b2) +1)
+ (else
+ (lp (+ 1 i)))))))))))
+
+ (define (bytevector-compare-as-list bv1 bv2)
+ (let ((len1 (bytevector-length bv1))
+ (len2 (bytevector-length bv2)))
+ (let lp ((i 0))
+ (cond
+ ((or (= i len1) (= i len2))
+ (cond ((< len1 len2) -1)
+ ((> len1 len2) +1)
+ (else 0)))
+ (else
+ (let ((b1 (bytevector-u8-ref bv1 i))
+ (b2 (bytevector-u8-ref bv2 i)))
+ (cond
+ ((< b1 b2) -1)
+ ((> b1 b2) +1)
+ (else
+ (lp (+ 1 i))))))))))
+
+ ))
+; Copyright (c) 2005 Sebastian Egner and Jens Axel S{\o}gaard.
+;
+; Permission is hereby granted, free of charge, to any person obtaining
+; a copy of this software and associated documentation files (the
+; ``Software''), to deal in the Software without restriction, including
+; without limitation the rights to use, copy, modify, merge, publish,
+; distribute, sublicense, and/or sell copies of the Software, and to
+; permit persons to whom the Software is furnished to do so, subject to
+; the following conditions:
+;
+; The above copyright notice and this permission notice shall be
+; included in all copies or substantial portions of the Software.
+;
+; THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
+; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+;
+; -----------------------------------------------------------------------
+;
+; Compare procedures SRFI (reference implementation)
+; Sebastian.Egner@philips.com, Jensaxel@soegaard.net
+; history of this file:
+; SE, 14-Oct-2004: first version
+; SE, 18-Oct-2004: 1st redesign: axioms for 'compare function'
+; SE, 29-Oct-2004: 2nd redesign: higher order reverse/map/refine/unite
+; SE, 2-Nov-2004: 3rd redesign: macros cond/refine-compare replace h.o.f's
+; SE, 10-Nov-2004: (im,re) replaced by (re,im) in complex-compare
+; SE, 11-Nov-2004: case-compare by case (not by cond); select-compare added
+; SE, 12-Jan-2005: pair-compare-cdr
+; SE, 15-Feb-2005: stricter typing for compare-<type>; pairwise-not=?
+; SE, 16-Feb-2005: case-compare -> if-compare -> if3; <? </<? chain<? etc.
+; JS, 24-Feb-2005: selection-compare added
+; SE, 25-Feb-2005: selection-compare -> kth-largest modified; if<? etc.
+; JS, 28-Feb-2005: kth-largest modified - is "stable" now
+; SE, 28-Feb-2005: simplified pairwise-not=?/kth-largest; min/max debugged
+; SE, 07-Apr-2005: compare-based type checks made explicit
+; SE, 18-Apr-2005: added (rel? compare) and eq?-test
+; SE, 16-May-2005: naming convention changed; compare-by< etc. optional x y
+
+; =============================================================================
+
+; Reference Implementation
+; ========================
+;
+; in R5RS (including hygienic macros)
+; + SRFI-16 (case-lambda)
+; + SRFI-23 (error)
+; + SRFI-27 (random-integer)
+
+; Implementation remarks:
+; * In general, the emphasis of this implementation is on correctness
+; and portability, not on efficiency.
+; * Variable arity procedures are expressed in terms of case-lambda
+; in the hope that this will produce efficient code for the case
+; where the arity is statically known at the call site.
+; * In procedures that are required to type-check their arguments,
+; we use (compare x x) for executing extra checks. This relies on
+; the assumption that eq? is used to catch this case quickly.
+; * Care has been taken to reference comparison procedures of R5RS
+; only at the time the operations here are being defined. This
+; makes it possible to redefine these operations, if need be.
+; * For the sake of efficiency, some inlining has been done by hand.
+; This is mainly expressed by macros producing defines.
+; * Identifiers of the form compare:<something> are private.
+;
+; Hints for low-level implementation:
+; * The basis of this SRFI are the atomic compare procedures,
+; i.e. boolean-compare, char-compare, etc. and the conditionals
+; if3, if=?, if<? etc., and default-compare. These should make
+; optimal use of the available type information.
+; * For the sake of speed, the reference implementation does not
+; use a LET to save the comparison value c for the ERROR call.
+; This can be fixed in a low-level implementation at no cost.
+; * Type-checks based on (compare x x) are made explicit by the
+; expression (compare:check result compare x ...).
+; * Eq? should can used to speed up built-in compare procedures,
+; but it can only be used after type-checking at least one of
+; the arguments.
+
+(define (compare:checked result compare . args)
+ (for-each (lambda (x) (compare x x)) args)
+ result)
+
+
+; 3-sided conditional
+
+(define-syntax if3
+ (syntax-rules ()
+ ((if3 c less equal greater)
+ (case c
+ ((-1) less)
+ (( 0) equal)
+ (( 1) greater)
+ (else (error "comparison value not in {-1,0,1}"))))))
+
+
+; 2-sided conditionals for comparisons
+
+(define-syntax compare:if-rel?
+ (syntax-rules ()
+ ((compare:if-rel? c-cases a-cases c consequence)
+ (compare:if-rel? c-cases a-cases c consequence (if #f #f)))
+ ((compare:if-rel? c-cases a-cases c consequence alternate)
+ (case c
+ (c-cases consequence)
+ (a-cases alternate)
+ (else (error "comparison value not in {-1,0,1}"))))))
+
+(define-syntax if=?
+ (syntax-rules ()
+ ((if=? arg ...)
+ (compare:if-rel? (0) (-1 1) arg ...))))
+
+(define-syntax if<?
+ (syntax-rules ()
+ ((if<? arg ...)
+ (compare:if-rel? (-1) (0 1) arg ...))))
+
+(define-syntax if>?
+ (syntax-rules ()
+ ((if>? arg ...)
+ (compare:if-rel? (1) (-1 0) arg ...))))
+
+(define-syntax if<=?
+ (syntax-rules ()
+ ((if<=? arg ...)
+ (compare:if-rel? (-1 0) (1) arg ...))))
+
+(define-syntax if>=?
+ (syntax-rules ()
+ ((if>=? arg ...)
+ (compare:if-rel? (0 1) (-1) arg ...))))
+
+(define-syntax if-not=?
+ (syntax-rules ()
+ ((if-not=? arg ...)
+ (compare:if-rel? (-1 1) (0) arg ...))))
+
+
+; predicates from compare procedures
+
+(define-syntax compare:define-rel?
+ (syntax-rules ()
+ ((compare:define-rel? rel? if-rel?)
+ (define rel?
+ (case-lambda
+ (() (lambda (x y) (if-rel? (default-compare x y) #t #f)))
+ ((compare) (lambda (x y) (if-rel? (compare x y) #t #f)))
+ ((x y) (if-rel? (default-compare x y) #t #f))
+ ((compare x y)
+ (if (procedure? compare)
+ (if-rel? (compare x y) #t #f)
+ (error "not a procedure (Did you mean rel/rel??): " compare))))))))
+
+(compare:define-rel? =? if=?)
+(compare:define-rel? <? if<?)
+(compare:define-rel? >? if>?)
+(compare:define-rel? <=? if<=?)
+(compare:define-rel? >=? if>=?)
+(compare:define-rel? not=? if-not=?)
+
+
+; chains of length 3
+
+(define-syntax compare:define-rel1/rel2?
+ (syntax-rules ()
+ ((compare:define-rel1/rel2? rel1/rel2? if-rel1? if-rel2?)
+ (define rel1/rel2?
+ (case-lambda
+ (()
+ (lambda (x y z)
+ (if-rel1? (default-compare x y)
+ (if-rel2? (default-compare y z) #t #f)
+ (compare:checked #f default-compare z))))
+ ((compare)
+ (lambda (x y z)
+ (if-rel1? (compare x y)
+ (if-rel2? (compare y z) #t #f)
+ (compare:checked #f compare z))))
+ ((x y z)
+ (if-rel1? (default-compare x y)
+ (if-rel2? (default-compare y z) #t #f)
+ (compare:checked #f default-compare z)))
+ ((compare x y z)
+ (if-rel1? (compare x y)
+ (if-rel2? (compare y z) #t #f)
+ (compare:checked #f compare z))))))))
+
+(compare:define-rel1/rel2? </<? if<? if<?)
+(compare:define-rel1/rel2? </<=? if<? if<=?)
+(compare:define-rel1/rel2? <=/<? if<=? if<?)
+(compare:define-rel1/rel2? <=/<=? if<=? if<=?)
+(compare:define-rel1/rel2? >/>? if>? if>?)
+(compare:define-rel1/rel2? >/>=? if>? if>=?)
+(compare:define-rel1/rel2? >=/>? if>=? if>?)
+(compare:define-rel1/rel2? >=/>=? if>=? if>=?)
+
+
+; chains of arbitrary length
+
+(define-syntax compare:define-chain-rel?
+ (syntax-rules ()
+ ((compare:define-chain-rel? chain-rel? if-rel?)
+ (define chain-rel?
+ (case-lambda
+ ((compare)
+ #t)
+ ((compare x1)
+ (compare:checked #t compare x1))
+ ((compare x1 x2)
+ (if-rel? (compare x1 x2) #t #f))
+ ((compare x1 x2 x3)
+ (if-rel? (compare x1 x2)
+ (if-rel? (compare x2 x3) #t #f)
+ (compare:checked #f compare x3)))
+ ((compare x1 x2 . x3+)
+ (if-rel? (compare x1 x2)
+ (let chain? ((head x2) (tail x3+))
+ (if (null? tail)
+ #t
+ (if-rel? (compare head (car tail))
+ (chain? (car tail) (cdr tail))
+ (apply compare:checked #f
+ compare (cdr tail)))))
+ (apply compare:checked #f compare x3+))))))))
+
+(compare:define-chain-rel? chain=? if=?)
+(compare:define-chain-rel? chain<? if<?)
+(compare:define-chain-rel? chain>? if>?)
+(compare:define-chain-rel? chain<=? if<=?)
+(compare:define-chain-rel? chain>=? if>=?)
+
+
+; pairwise inequality
+
+(define pairwise-not=?
+ (let ((= =) (<= <=))
+ (case-lambda
+ ((compare)
+ #t)
+ ((compare x1)
+ (compare:checked #t compare x1))
+ ((compare x1 x2)
+ (if-not=? (compare x1 x2) #t #f))
+ ((compare x1 x2 x3)
+ (if-not=? (compare x1 x2)
+ (if-not=? (compare x2 x3)
+ (if-not=? (compare x1 x3) #t #f)
+ #f)
+ (compare:checked #f compare x3)))
+ ((compare . x1+)
+ (let unequal? ((x x1+) (n (length x1+)) (unchecked? #t))
+ (if (< n 2)
+ (if (and unchecked? (= n 1))
+ (compare:checked #t compare (car x))
+ #t)
+ (let* ((i-pivot (random-integer n))
+ (x-pivot (list-ref x i-pivot)))
+ (let split ((i 0) (x x) (x< '()) (x> '()))
+ (if (null? x)
+ (and (unequal? x< (length x<) #f)
+ (unequal? x> (length x>) #f))
+ (if (= i i-pivot)
+ (split (+ i 1) (cdr x) x< x>)
+ (if3 (compare (car x) x-pivot)
+ (split (+ i 1) (cdr x) (cons (car x) x<) x>)
+ (if unchecked?
+ (apply compare:checked #f compare (cdr x))
+ #f)
+ (split (+ i 1) (cdr x) x< (cons (car x) x>)))))))))))))
+
+
+; min/max
+
+(define min-compare
+ (case-lambda
+ ((compare x1)
+ (compare:checked x1 compare x1))
+ ((compare x1 x2)
+ (if<=? (compare x1 x2) x1 x2))
+ ((compare x1 x2 x3)
+ (if<=? (compare x1 x2)
+ (if<=? (compare x1 x3) x1 x3)
+ (if<=? (compare x2 x3) x2 x3)))
+ ((compare x1 x2 x3 x4)
+ (if<=? (compare x1 x2)
+ (if<=? (compare x1 x3)
+ (if<=? (compare x1 x4) x1 x4)
+ (if<=? (compare x3 x4) x3 x4))
+ (if<=? (compare x2 x3)
+ (if<=? (compare x2 x4) x2 x4)
+ (if<=? (compare x3 x4) x3 x4))))
+ ((compare x1 x2 . x3+)
+ (let min ((xmin (if<=? (compare x1 x2) x1 x2)) (xs x3+))
+ (if (null? xs)
+ xmin
+ (min (if<=? (compare xmin (car xs)) xmin (car xs))
+ (cdr xs)))))))
+
+(define max-compare
+ (case-lambda
+ ((compare x1)
+ (compare:checked x1 compare x1))
+ ((compare x1 x2)
+ (if>=? (compare x1 x2) x1 x2))
+ ((compare x1 x2 x3)
+ (if>=? (compare x1 x2)
+ (if>=? (compare x1 x3) x1 x3)
+ (if>=? (compare x2 x3) x2 x3)))
+ ((compare x1 x2 x3 x4)
+ (if>=? (compare x1 x2)
+ (if>=? (compare x1 x3)
+ (if>=? (compare x1 x4) x1 x4)
+ (if>=? (compare x3 x4) x3 x4))
+ (if>=? (compare x2 x3)
+ (if>=? (compare x2 x4) x2 x4)
+ (if>=? (compare x3 x4) x3 x4))))
+ ((compare x1 x2 . x3+)
+ (let max ((xmax (if>=? (compare x1 x2) x1 x2)) (xs x3+))
+ (if (null? xs)
+ xmax
+ (max (if>=? (compare xmax (car xs)) xmax (car xs))
+ (cdr xs)))))))
+
+
+; kth-largest
+
+(define kth-largest
+ (let ((= =) (< <))
+ (case-lambda
+ ((compare k x0)
+ (case (modulo k 1)
+ ((0) (compare:checked x0 compare x0))
+ (else (error "bad index" k))))
+ ((compare k x0 x1)
+ (case (modulo k 2)
+ ((0) (if<=? (compare x0 x1) x0 x1))
+ ((1) (if<=? (compare x0 x1) x1 x0))
+ (else (error "bad index" k))))
+ ((compare k x0 x1 x2)
+ (case (modulo k 3)
+ ((0) (if<=? (compare x0 x1)
+ (if<=? (compare x0 x2) x0 x2)
+ (if<=? (compare x1 x2) x1 x2)))
+ ((1) (if3 (compare x0 x1)
+ (if<=? (compare x1 x2)
+ x1
+ (if<=? (compare x0 x2) x2 x0))
+ (if<=? (compare x0 x2) x1 x0)
+ (if<=? (compare x0 x2)
+ x0
+ (if<=? (compare x1 x2) x2 x1))))
+ ((2) (if<=? (compare x0 x1)
+ (if<=? (compare x1 x2) x2 x1)
+ (if<=? (compare x0 x2) x2 x0)))
+ (else (error "bad index" k))))
+ ((compare k x0 . x1+) ; |x1+| >= 1
+ (if (not (and (integer? k) (exact? k)))
+ (error "bad index" k))
+ (let ((n (+ 1 (length x1+))))
+ (let kth ((k (modulo k n))
+ (n n) ; = |x|
+ (rev #t) ; are x<, x=, x> reversed?
+ (x (cons x0 x1+)))
+ (let ((pivot (list-ref x (random-integer n))))
+ (let split ((x x) (x< '()) (n< 0) (x= '()) (n= 0) (x> '()) (n> 0))
+ (if (null? x)
+ (cond
+ ((< k n<)
+ (kth k n< (not rev) x<))
+ ((< k (+ n< n=))
+ (if rev
+ (list-ref x= (- (- n= 1) (- k n<)))
+ (list-ref x= (- k n<))))
+ (else
+ (kth (- k (+ n< n=)) n> (not rev) x>)))
+ (if3 (compare (car x) pivot)
+ (split (cdr x) (cons (car x) x<) (+ n< 1) x= n= x> n>)
+ (split (cdr x) x< n< (cons (car x) x=) (+ n= 1) x> n>)
+ (split (cdr x) x< n< x= n= (cons (car x) x>) (+ n> 1))))))))))))
+
+
+; compare functions from predicates
+
+(define compare-by<
+ (case-lambda
+ ((lt) (lambda (x y) (if (lt x y) -1 (if (lt y x) 1 0))))
+ ((lt x y) (if (lt x y) -1 (if (lt y x) 1 0)))))
+
+(define compare-by>
+ (case-lambda
+ ((gt) (lambda (x y) (if (gt x y) 1 (if (gt y x) -1 0))))
+ ((gt x y) (if (gt x y) 1 (if (gt y x) -1 0)))))
+
+(define compare-by<=
+ (case-lambda
+ ((le) (lambda (x y) (if (le x y) (if (le y x) 0 -1) 1)))
+ ((le x y) (if (le x y) (if (le y x) 0 -1) 1))))
+
+(define compare-by>=
+ (case-lambda
+ ((ge) (lambda (x y) (if (ge x y) (if (ge y x) 0 1) -1)))
+ ((ge x y) (if (ge x y) (if (ge y x) 0 1) -1))))
+
+(define compare-by=/<
+ (case-lambda
+ ((eq lt) (lambda (x y) (if (eq x y) 0 (if (lt x y) -1 1))))
+ ((eq lt x y) (if (eq x y) 0 (if (lt x y) -1 1)))))
+
+(define compare-by=/>
+ (case-lambda
+ ((eq gt) (lambda (x y) (if (eq x y) 0 (if (gt x y) 1 -1))))
+ ((eq gt x y) (if (eq x y) 0 (if (gt x y) 1 -1)))))
+
+; refine and extend construction
+
+(define-syntax refine-compare
+ (syntax-rules ()
+ ((refine-compare)
+ 0)
+ ((refine-compare c1)
+ c1)
+ ((refine-compare c1 c2 cs ...)
+ (if3 c1 -1 (refine-compare c2 cs ...) 1))))
+
+(define-syntax select-compare
+ (syntax-rules (else)
+ ((select-compare x y clause ...)
+ (let ((x-val x) (y-val y))
+ (select-compare (x-val y-val clause ...))))
+ ; used internally: (select-compare (x y clause ...))
+ ((select-compare (x y))
+ 0)
+ ((select-compare (x y (else c ...)))
+ (refine-compare c ...))
+ ((select-compare (x y (t? c ...) clause ...))
+ (let ((t?-val t?))
+ (let ((tx (t?-val x)) (ty (t?-val y)))
+ (if tx
+ (if ty (refine-compare c ...) -1)
+ (if ty 1 (select-compare (x y clause ...)))))))))
+
+(define-syntax cond-compare
+ (syntax-rules (else)
+ ((cond-compare)
+ 0)
+ ((cond-compare (else cs ...))
+ (refine-compare cs ...))
+ ((cond-compare ((tx ty) cs ...) clause ...)
+ (let ((tx-val tx) (ty-val ty))
+ (if tx-val
+ (if ty-val (refine-compare cs ...) -1)
+ (if ty-val 1 (cond-compare clause ...)))))))
+
+
+; R5RS atomic types
+
+(define-syntax compare:type-check
+ (syntax-rules ()
+ ((compare:type-check type? type-name x)
+ (if (not (type? x))
+ (error (string-append "not " type-name ":") x)))
+ ((compare:type-check type? type-name x y)
+ (begin (compare:type-check type? type-name x)
+ (compare:type-check type? type-name y)))))
+
+(define-syntax compare:define-by=/<
+ (syntax-rules ()
+ ((compare:define-by=/< compare = < type? type-name)
+ (define compare
+ (let ((= =) (< <))
+ (lambda (x y)
+ (if (type? x)
+ (if (eq? x y)
+ 0
+ (if (type? y)
+ (if (= x y) 0 (if (< x y) -1 1))
+ (error (string-append "not " type-name ":") y)))
+ (error (string-append "not " type-name ":") x))))))))
+
+(define (boolean-compare x y)
+ (compare:type-check boolean? "boolean" x y)
+ (if x (if y 0 1) (if y -1 0)))
+
+(compare:define-by=/< char-compare char=? char<? char? "char")
+
+(compare:define-by=/< char-compare-ci char-ci=? char-ci<? char? "char")
+
+(compare:define-by=/< string-compare string=? string<? string? "string")
+
+(compare:define-by=/< string-compare-ci string-ci=? string-ci<? string? "string")
+
+(define (symbol-compare x y)
+ (compare:type-check symbol? "symbol" x y)
+ (string-compare (symbol->string x) (symbol->string y)))
+
+(compare:define-by=/< integer-compare = < integer? "integer")
+
+(compare:define-by=/< rational-compare = < rational? "rational")
+
+(compare:define-by=/< real-compare = < real? "real")
+
+(define (complex-compare x y)
+ (compare:type-check complex? "complex" x y)
+ (if (and (real? x) (real? y))
+ (real-compare x y)
+ (refine-compare (real-compare (real-part x) (real-part y))
+ (real-compare (imag-part x) (imag-part y)))))
+
+(define (number-compare x y)
+ (compare:type-check number? "number" x y)
+ (complex-compare x y))
+
+
+; R5RS compound data structures: dotted pair, list, vector
+
+(define (pair-compare-car compare)
+ (lambda (x y)
+ (compare (car x) (car y))))
+
+(define (pair-compare-cdr compare)
+ (lambda (x y)
+ (compare (cdr x) (cdr y))))
+
+(define pair-compare
+ (case-lambda
+
+ ; dotted pair
+ ((pair-compare-car pair-compare-cdr x y)
+ (refine-compare (pair-compare-car (car x) (car y))
+ (pair-compare-cdr (cdr x) (cdr y))))
+
+ ; possibly improper lists
+ ((compare x y)
+ (cond-compare
+ (((null? x) (null? y)) 0)
+ (((pair? x) (pair? y)) (compare (car x) (car y))
+ (pair-compare compare (cdr x) (cdr y)))
+ (else (compare x y))))
+
+ ; for convenience
+ ((x y)
+ (pair-compare default-compare x y))))
+
+(define list-compare
+ (case-lambda
+ ((compare x y empty? head tail)
+ (cond-compare
+ (((empty? x) (empty? y)) 0)
+ (else (compare (head x) (head y))
+ (list-compare compare (tail x) (tail y) empty? head tail))))
+
+ ; for convenience
+ (( x y empty? head tail)
+ (list-compare default-compare x y empty? head tail))
+ ((compare x y )
+ (list-compare compare x y null? car cdr))
+ (( x y )
+ (list-compare default-compare x y null? car cdr))))
+
+(define list-compare-as-vector
+ (case-lambda
+ ((compare x y empty? head tail)
+ (refine-compare
+ (let compare-length ((x x) (y y))
+ (cond-compare
+ (((empty? x) (empty? y)) 0)
+ (else (compare-length (tail x) (tail y)))))
+ (list-compare compare x y empty? head tail)))
+
+ ; for convenience
+ (( x y empty? head tail)
+ (list-compare-as-vector default-compare x y empty? head tail))
+ ((compare x y )
+ (list-compare-as-vector compare x y null? car cdr))
+ (( x y )
+ (list-compare-as-vector default-compare x y null? car cdr))))
+
+(define vector-compare
+ (let ((= =))
+ (case-lambda
+ ((compare x y size ref)
+ (let ((n (size x)) (m (size y)))
+ (refine-compare
+ (integer-compare n m)
+ (let compare-rest ((i 0)) ; compare x[i..n-1] y[i..n-1]
+ (if (= i n)
+ 0
+ (refine-compare (compare (ref x i) (ref y i))
+ (compare-rest (+ i 1))))))))
+
+ ; for convenience
+ (( x y size ref)
+ (vector-compare default-compare x y size ref))
+ ((compare x y )
+ (vector-compare compare x y vector-length vector-ref))
+ (( x y )
+ (vector-compare default-compare x y vector-length vector-ref)))))
+
+(define vector-compare-as-list
+ (let ((= =))
+ (case-lambda
+ ((compare x y size ref)
+ (let ((nx (size x)) (ny (size y)))
+ (let ((n (min nx ny)))
+ (let compare-rest ((i 0)) ; compare x[i..n-1] y[i..n-1]
+ (if (= i n)
+ (integer-compare nx ny)
+ (refine-compare (compare (ref x i) (ref y i))
+ (compare-rest (+ i 1))))))))
+
+ ; for convenience
+ (( x y size ref)
+ (vector-compare-as-list default-compare x y size ref))
+ ((compare x y )
+ (vector-compare-as-list compare x y vector-length vector-ref))
+ (( x y )
+ (vector-compare-as-list default-compare x y vector-length vector-ref)))))
+
+
+; default compare
+
+(define (default-compare x y)
+ (select-compare
+ x y
+ (null? 0)
+ (pair? (default-compare (car x) (car y))
+ (default-compare (cdr x) (cdr y)))
+ (boolean? (boolean-compare x y))
+ (char? (char-compare x y))
+ (string? (string-compare x y))
+ (symbol? (symbol-compare x y))
+ (number? (number-compare x y))
+ (vector? (vector-compare default-compare x y))
+ (else (error "unrecognized type in default-compare" x y))))
+
+; Note that we pass default-compare to compare-{pair,vector} explictly.
+; This makes sure recursion proceeds with this default-compare, which
+; need not be the one in the lexical scope of compare-{pair,vector}.
+
+
+; debug compare
+
+(define (debug-compare c)
+
+ (define (checked-value c x y)
+ (let ((c-xy (c x y)))
+ (if (or (eqv? c-xy -1) (eqv? c-xy 0) (eqv? c-xy 1))
+ c-xy
+ (error "compare value not in {-1,0,1}" c-xy (list c x y)))))
+
+ (define (random-boolean)
+ (zero? (random-integer 2)))
+
+ (define q ; (u v w) such that u <= v, v <= w, and not u <= w
+ '#(
+ ;x < y x = y x > y [x < z]
+ 0 0 0 ; y < z
+ 0 (z y x) (z y x) ; y = z
+ 0 (z y x) (z y x) ; y > z
+
+ ;x < y x = y x > y [x = z]
+ (y z x) (z x y) 0 ; y < z
+ (y z x) 0 (x z y) ; y = z
+ 0 (y x z) (x z y) ; y > z
+
+ ;x < y x = y x > y [x > z]
+ (x y z) (x y z) 0 ; y < z
+ (x y z) (x y z) 0 ; y = z
+ 0 0 0 ; y > z
+ ))
+
+ (let ((z? #f) (z #f)) ; stored element from previous call
+ (lambda (x y)
+ (let ((c-xx (checked-value c x x))
+ (c-yy (checked-value c y y))
+ (c-xy (checked-value c x y))
+ (c-yx (checked-value c y x)))
+ (if (not (zero? c-xx))
+ (error "compare error: not reflexive" c x))
+ (if (not (zero? c-yy))
+ (error "compare error: not reflexive" c y))
+ (if (not (zero? (+ c-xy c-yx)))
+ (error "compare error: not anti-symmetric" c x y))
+ (if z?
+ (let ((c-xz (checked-value c x z))
+ (c-zx (checked-value c z x))
+ (c-yz (checked-value c y z))
+ (c-zy (checked-value c z y)))
+ (if (not (zero? (+ c-xz c-zx)))
+ (error "compare error: not anti-symmetric" c x z))
+ (if (not (zero? (+ c-yz c-zy)))
+ (error "compare error: not anti-symmetric" c y z))
+ (let ((ijk (vector-ref q (+ c-xy (* 3 c-yz) (* 9 c-xz) 13))))
+ (if (list? ijk)
+ (apply error
+ "compare error: not transitive"
+ c
+ (map (lambda (i) (case i ((x) x) ((y) y) ((z) z)))
+ ijk)))))
+ (set! z? #t))
+ (set! z (if (random-boolean) x y)) ; randomized testing
+ c-xy))))
+;;; Copyright © Panu Kalliokoski (2005). All Rights Reserved.
+
+;;; Made an R7RS library by Taylan Ulrich Bayırlı/Kammer, Copyright © 2014.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+(define default-bound (make-parameter (- (expt 2 29) 3)))
+
+(define (%string-hash s ch-conv bound)
+ (let ((hash 31)
+ (len (string-length s)))
+ (do ((index 0 (+ index 1)))
+ ((>= index len) (modulo hash bound))
+ (set! hash (modulo (+ (* 37 hash)
+ (char->integer (ch-conv (string-ref s index))))
+ (default-bound))))))
+
+(define string-hash
+ (case-lambda
+ ((s) (string-hash s (default-bound)))
+ ((s bound)
+ (%string-hash s (lambda (x) x) bound))))
+
+(define string-ci-hash
+ (case-lambda
+ ((s) (string-ci-hash s (default-bound)))
+ ((s bound)
+ (%string-hash s char-downcase bound))))
+
+(define symbol-hash
+ (case-lambda
+ ((s) (symbol-hash s (default-bound)))
+ ((s bound)
+ (%string-hash (symbol->string s) (lambda (x) x) bound))))
+
+(define hash
+ (case-lambda
+ ((obj) (hash obj (default-bound)))
+ ((obj bound)
+ (cond ((integer? obj) (modulo obj bound))
+ ((string? obj) (string-hash obj bound))
+ ((symbol? obj) (symbol-hash obj bound))
+ ((real? obj) (modulo (+ (numerator obj) (denominator obj)) bound))
+ ((number? obj)
+ (modulo (+ (hash (real-part obj)) (* 3 (hash (imag-part obj))))
+ bound))
+ ((char? obj) (modulo (char->integer obj) bound))
+ ((vector? obj) (vector-hash obj bound))
+ ((pair? obj) (modulo (+ (hash (car obj)) (* 3 (hash (cdr obj))))
+ bound))
+ ((null? obj) 0)
+ ((not obj) 0)
+ ((procedure? obj) (error "hash: procedures cannot be hashed" obj))
+ (else 1)))))
+
+(define hash-by-identity hash)
+
+(define (vector-hash v bound)
+ (let ((hashvalue 571)
+ (len (vector-length v)))
+ (do ((index 0 (+ index 1)))
+ ((>= index len) (modulo hashvalue bound))
+ (set! hashvalue (modulo (+ (* 257 hashvalue) (hash (vector-ref v index)))
+ (default-bound))))))
+
+(define %make-hash-node cons)
+(define %hash-node-set-value! set-cdr!)
+(define %hash-node-key car)
+(define %hash-node-value cdr)
+
+(define-record-type <srfi-hash-table>
+ (%make-hash-table size hash compare associate entries)
+ hash-table?
+ (size hash-table-size hash-table-set-size!)
+ (hash hash-table-hash-function)
+ (compare hash-table-equivalence-function)
+ (associate hash-table-association-function)
+ (entries hash-table-entries hash-table-set-entries!))
+
+(define default-table-size (make-parameter 64))
+
+(define (appropriate-hash-function-for comparison)
+ (or (and (eq? comparison eq?) hash-by-identity)
+ (and (eq? comparison string=?) string-hash)
+ (and (eq? comparison string-ci=?) string-ci-hash)
+ hash))
+
+(define make-hash-table
+ (case-lambda
+ (()
+ (make-hash-table equal?
+ (appropriate-hash-function-for equal?)
+ (default-table-size)))
+ ((comparison)
+ (make-hash-table comparison
+ (appropriate-hash-function-for comparison)
+ (default-table-size)))
+ ((comparison hash)
+ (make-hash-table comparison
+ hash
+ (default-table-size)))
+ ((comparison hash size)
+ (let ((association (or (and (eq? comparison eq?) assq)
+ (and (eq? comparison eqv?) assv)
+ (and (eq? comparison equal?) assoc)
+ (rec (associate val alist)
+ (cond
+ ((null? alist) #f)
+ ((comparison val (caar alist)) (car alist))
+ (else (associate val (cdr alist))))))))
+ (%make-hash-table
+ 0 hash comparison association (make-vector size '()))))))
+
+(define (make-hash-table-maker comp hash)
+ (lambda args (apply make-hash-table (cons comp (cons hash args)))))
+(define make-symbol-hash-table
+ (make-hash-table-maker eq? symbol-hash))
+(define make-string-hash-table
+ (make-hash-table-maker string=? string-hash))
+(define make-string-ci-hash-table
+ (make-hash-table-maker string-ci=? string-ci-hash))
+(define make-integer-hash-table
+ (make-hash-table-maker = modulo))
+
+(define (%hash-table-hash hash-table key)
+ ((hash-table-hash-function hash-table)
+ key (vector-length (hash-table-entries hash-table))))
+
+(define (%hash-table-find entries associate hash key)
+ (associate key (vector-ref entries hash)))
+
+(define (%hash-table-add! entries hash key value)
+ (vector-set! entries hash
+ (cons (%make-hash-node key value)
+ (vector-ref entries hash))))
+
+(define (%hash-table-delete! entries compare hash key)
+ (let ((entrylist (vector-ref entries hash)))
+ (cond ((null? entrylist) #f)
+ ((compare key (caar entrylist))
+ (vector-set! entries hash (cdr entrylist)) #t)
+ (else
+ (let loop ((current (cdr entrylist)) (previous entrylist))
+ (cond ((null? current) #f)
+ ((compare key (caar current))
+ (set-cdr! previous (cdr current)) #t)
+ (else (loop (cdr current) current))))))))
+
+(define (%hash-table-walk proc entries)
+ (do ((index (- (vector-length entries) 1) (- index 1)))
+ ((< index 0)) (for-each proc (vector-ref entries index))))
+
+(define (%hash-table-maybe-resize! hash-table)
+ (let* ((old-entries (hash-table-entries hash-table))
+ (hash-length (vector-length old-entries)))
+ (if (> (hash-table-size hash-table) hash-length)
+ (let* ((new-length (* 2 hash-length))
+ (new-entries (make-vector new-length '()))
+ (hash (hash-table-hash-function hash-table)))
+ (%hash-table-walk
+ (lambda (node)
+ (%hash-table-add! new-entries
+ (hash (%hash-node-key node) new-length)
+ (%hash-node-key node) (%hash-node-value node)))
+ old-entries)
+ (hash-table-set-entries! hash-table new-entries)))))
+
+(define (not-found-error key)
+ (lambda ()
+ (error "No value associated with key:" key)))
+
+(define hash-table-ref
+ (case-lambda
+ ((hash-table key) (hash-table-ref hash-table key (not-found-error key)))
+ ((hash-table key default-thunk)
+ (cond ((%hash-table-find (hash-table-entries hash-table)
+ (hash-table-association-function hash-table)
+ (%hash-table-hash hash-table key) key)
+ => %hash-node-value)
+ (else (default-thunk))))))
+
+(define (hash-table-ref/default hash-table key default)
+ (hash-table-ref hash-table key (lambda () default)))
+
+(define (hash-table-set! hash-table key value)
+ (let ((hash (%hash-table-hash hash-table key))
+ (entries (hash-table-entries hash-table)))
+ (cond ((%hash-table-find entries
+ (hash-table-association-function hash-table)
+ hash key)
+ => (lambda (node) (%hash-node-set-value! node value)))
+ (else (%hash-table-add! entries hash key value)
+ (hash-table-set-size! hash-table
+ (+ 1 (hash-table-size hash-table)))
+ (%hash-table-maybe-resize! hash-table)))))
+
+(define hash-table-update!
+ (case-lambda
+ ((hash-table key function)
+ (hash-table-update! hash-table key function (not-found-error key)))
+ ((hash-table key function default-thunk)
+ (let ((hash (%hash-table-hash hash-table key))
+ (entries (hash-table-entries hash-table)))
+ (cond ((%hash-table-find entries
+ (hash-table-association-function hash-table)
+ hash key)
+ => (lambda (node)
+ (%hash-node-set-value!
+ node (function (%hash-node-value node)))))
+ (else (%hash-table-add! entries hash key
+ (function (default-thunk)))
+ (hash-table-set-size! hash-table
+ (+ 1 (hash-table-size hash-table)))
+ (%hash-table-maybe-resize! hash-table)))))))
+
+(define (hash-table-update!/default hash-table key function default)
+ (hash-table-update! hash-table key function (lambda () default)))
+
+(define (hash-table-delete! hash-table key)
+ (if (%hash-table-delete! (hash-table-entries hash-table)
+ (hash-table-equivalence-function hash-table)
+ (%hash-table-hash hash-table key) key)
+ (hash-table-set-size! hash-table (- (hash-table-size hash-table) 1))))
+
+(define (hash-table-exists? hash-table key)
+ (and (%hash-table-find (hash-table-entries hash-table)
+ (hash-table-association-function hash-table)
+ (%hash-table-hash hash-table key) key) #t))
+
+(define (hash-table-walk hash-table proc)
+ (%hash-table-walk
+ (lambda (node) (proc (%hash-node-key node) (%hash-node-value node)))
+ (hash-table-entries hash-table)))
+
+(define (hash-table-fold hash-table f acc)
+ (hash-table-walk hash-table
+ (lambda (key value) (set! acc (f key value acc))))
+ acc)
+
+(define (appropriate-size-for-alist alist)
+ (max (default-table-size) (* 2 (length alist))))
+
+(define alist->hash-table
+ (case-lambda
+ ((alist)
+ (alist->hash-table alist
+ equal?
+ (appropriate-hash-function-for equal?)
+ (appropriate-size-for-alist alist)))
+ ((alist comparison)
+ (alist->hash-table alist
+ comparison
+ (appropriate-hash-function-for comparison)
+ (appropriate-size-for-alist alist)))
+ ((alist comparison hash)
+ (alist->hash-table alist
+ comparison
+ hash
+ (appropriate-size-for-alist alist)))
+ ((alist comparison hash size)
+ (let ((hash-table (make-hash-table comparison hash size)))
+ (for-each
+ (lambda (elem)
+ (hash-table-update!/default
+ hash-table (car elem) (lambda (x) x) (cdr elem)))
+ alist)
+ hash-table))))
+
+(define (hash-table->alist hash-table)
+ (hash-table-fold hash-table
+ (lambda (key val acc) (cons (cons key val) acc)) '()))
+
+(define (hash-table-copy hash-table)
+ (let ((new (make-hash-table (hash-table-equivalence-function hash-table)
+ (hash-table-hash-function hash-table)
+ (max (default-table-size)
+ (* 2 (hash-table-size hash-table))))))
+ (hash-table-walk hash-table
+ (lambda (key value) (hash-table-set! new key value)))
+ new))
+
+(define (hash-table-merge! hash-table1 hash-table2)
+ (hash-table-walk
+ hash-table2
+ (lambda (key value) (hash-table-set! hash-table1 key value)))
+ hash-table1)
+
+(define (hash-table-keys hash-table)
+ (hash-table-fold hash-table (lambda (key val acc) (cons key acc)) '()))
+
+(define (hash-table-values hash-table)
+ (hash-table-fold hash-table (lambda (key val acc) (cons val acc)) '()))
+(define-library (srfi 69)
+ (export
+ ;; Type constructors and predicate
+ make-hash-table hash-table? alist->hash-table
+ ;; Reflective queries
+ hash-table-equivalence-function hash-table-hash-function
+ ;; Dealing with single elements
+ hash-table-ref hash-table-ref/default hash-table-set! hash-table-delete!
+ hash-table-exists? hash-table-update! hash-table-update!/default
+ ;; Dealing with the whole contents
+ hash-table-size hash-table-keys hash-table-values hash-table-walk
+ hash-table-fold hash-table->alist hash-table-copy hash-table-merge!
+ ;; Hashing
+ hash string-hash string-ci-hash hash-by-identity
+ )
+ (import
+ (scheme base)
+ (scheme case-lambda)
+ (scheme char)
+ (scheme complex)
+ (scheme cxr)
+ (srfi 1)
+ (srfi 31))
+ (include "69.body.scm"))
+;;; Copyright © Panu Kalliokoski (2005). All Rights Reserved.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the "Software"), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+(define *default-bound* (- (expt 2 29) 3))
+
+(define (%string-hash s ch-conv bound)
+ (let ((hash 31)
+ (len (string-length s)))
+ (do ((index 0 (+ index 1)))
+ ((>= index len) (modulo hash bound))
+ (set! hash (modulo (+ (* 37 hash)
+ (char->integer (ch-conv (string-ref s index))))
+ *default-bound*)))))
+
+(define (string-hash s . maybe-bound)
+ (let ((bound (if (null? maybe-bound) *default-bound* (car maybe-bound))))
+ (%string-hash s (lambda (x) x) bound)))
+
+(define (string-ci-hash s . maybe-bound)
+ (let ((bound (if (null? maybe-bound) *default-bound* (car maybe-bound))))
+ (%string-hash s char-downcase bound)))
+
+(define (symbol-hash s . maybe-bound)
+ (let ((bound (if (null? maybe-bound) *default-bound* (car maybe-bound))))
+ (%string-hash (symbol->string s) (lambda (x) x) bound)))
+
+(define (hash obj . maybe-bound)
+ (let ((bound (if (null? maybe-bound) *default-bound* (car maybe-bound))))
+ (cond ((integer? obj) (modulo obj bound))
+ ((string? obj) (string-hash obj bound))
+ ((symbol? obj) (symbol-hash obj bound))
+ ((real? obj) (modulo (+ (numerator obj) (denominator obj)) bound))
+ ((number? obj)
+ (modulo (+ (hash (real-part obj)) (* 3 (hash (imag-part obj))))
+ bound))
+ ((char? obj) (modulo (char->integer obj) bound))
+ ((vector? obj) (vector-hash obj bound))
+ ((pair? obj) (modulo (+ (hash (car obj)) (* 3 (hash (cdr obj))))
+ bound))
+ ((null? obj) 0)
+ ((not obj) 0)
+ ((procedure? obj) (error "hash: procedures cannot be hashed" obj))
+ (else 1))))
+
+(define hash-by-identity hash)
+
+(define (vector-hash v bound)
+ (let ((hashvalue 571)
+ (len (vector-length v)))
+ (do ((index 0 (+ index 1)))
+ ((>= index len) (modulo hashvalue bound))
+ (set! hashvalue (modulo (+ (* 257 hashvalue) (hash (vector-ref v index)))
+ *default-bound*)))))
+
+(define %make-hash-node cons)
+(define %hash-node-set-value! set-cdr!)
+(define %hash-node-key car)
+(define %hash-node-value cdr)
+
+(define-record-type <srfi-hash-table>
+ (%make-hash-table size hash compare associate entries)
+ hash-table?
+ (size hash-table-size hash-table-set-size!)
+ (hash hash-table-hash-function)
+ (compare hash-table-equivalence-function)
+ (associate hash-table-association-function)
+ (entries hash-table-entries hash-table-set-entries!))
+
+(define *default-table-size* 64)
+
+(define (appropriate-hash-function-for comparison)
+ (or (and (eq? comparison eq?) hash-by-identity)
+ (and (eq? comparison string=?) string-hash)
+ (and (eq? comparison string-ci=?) string-ci-hash)
+ hash))
+
+(define (make-hash-table . args)
+ (let* ((comparison (if (null? args) equal? (car args)))
+ (hash
+ (if (or (null? args) (null? (cdr args)))
+ (appropriate-hash-function-for comparison) (cadr args)))
+ (size
+ (if (or (null? args) (null? (cdr args)) (null? (cddr args)))
+ *default-table-size* (caddr args)))
+ (association
+ (or (and (eq? comparison eq?) assq)
+ (and (eq? comparison eqv?) assv)
+ (and (eq? comparison equal?) assoc)
+ (letrec
+ ((associate
+ (lambda (val alist)
+ (cond ((null? alist) #f)
+ ((comparison val (caar alist)) (car alist))
+ (else (associate val (cdr alist)))))))
+ associate))))
+ (%make-hash-table 0 hash comparison association (make-vector size '()))))
+
+(define (make-hash-table-maker comp hash)
+ (lambda args (apply make-hash-table (cons comp (cons hash args)))))
+(define make-symbol-hash-table
+ (make-hash-table-maker eq? symbol-hash))
+(define make-string-hash-table
+ (make-hash-table-maker string=? string-hash))
+(define make-string-ci-hash-table
+ (make-hash-table-maker string-ci=? string-ci-hash))
+(define make-integer-hash-table
+ (make-hash-table-maker = modulo))
+
+(define (%hash-table-hash hash-table key)
+ ((hash-table-hash-function hash-table)
+ key (vector-length (hash-table-entries hash-table))))
+
+(define (%hash-table-find entries associate hash key)
+ (associate key (vector-ref entries hash)))
+
+(define (%hash-table-add! entries hash key value)
+ (vector-set! entries hash
+ (cons (%make-hash-node key value)
+ (vector-ref entries hash))))
+
+(define (%hash-table-delete! entries compare hash key)
+ (let ((entrylist (vector-ref entries hash)))
+ (cond ((null? entrylist) #f)
+ ((compare key (caar entrylist))
+ (vector-set! entries hash (cdr entrylist)) #t)
+ (else
+ (let loop ((current (cdr entrylist)) (previous entrylist))
+ (cond ((null? current) #f)
+ ((compare key (caar current))
+ (set-cdr! previous (cdr current)) #t)
+ (else (loop (cdr current) current))))))))
+
+(define (%hash-table-walk proc entries)
+ (do ((index (- (vector-length entries) 1) (- index 1)))
+ ((< index 0)) (for-each proc (vector-ref entries index))))
+
+(define (%hash-table-maybe-resize! hash-table)
+ (let* ((old-entries (hash-table-entries hash-table))
+ (hash-length (vector-length old-entries)))
+ (if (> (hash-table-size hash-table) hash-length)
+ (let* ((new-length (* 2 hash-length))
+ (new-entries (make-vector new-length '()))
+ (hash (hash-table-hash-function hash-table)))
+ (%hash-table-walk
+ (lambda (node)
+ (%hash-table-add! new-entries
+ (hash (%hash-node-key node) new-length)
+ (%hash-node-key node) (%hash-node-value node)))
+ old-entries)
+ (hash-table-set-entries! hash-table new-entries)))))
+
+(define (hash-table-ref hash-table key . maybe-default)
+ (cond ((%hash-table-find (hash-table-entries hash-table)
+ (hash-table-association-function hash-table)
+ (%hash-table-hash hash-table key) key)
+ => %hash-node-value)
+ ((null? maybe-default)
+ (error "hash-table-ref: no value associated with" key))
+ (else ((car maybe-default)))))
+
+(define (hash-table-ref/default hash-table key default)
+ (hash-table-ref hash-table key (lambda () default)))
+
+(define (hash-table-set! hash-table key value)
+ (let ((hash (%hash-table-hash hash-table key))
+ (entries (hash-table-entries hash-table)))
+ (cond ((%hash-table-find entries
+ (hash-table-association-function hash-table)
+ hash key)
+ => (lambda (node) (%hash-node-set-value! node value)))
+ (else (%hash-table-add! entries hash key value)
+ (hash-table-set-size! hash-table
+ (+ 1 (hash-table-size hash-table)))
+ (%hash-table-maybe-resize! hash-table)))))
+
+(define (hash-table-update! hash-table key function . maybe-default)
+ (let ((hash (%hash-table-hash hash-table key))
+ (entries (hash-table-entries hash-table)))
+ (cond ((%hash-table-find entries
+ (hash-table-association-function hash-table)
+ hash key)
+ => (lambda (node)
+ (%hash-node-set-value!
+ node (function (%hash-node-value node)))))
+ ((null? maybe-default)
+ (error "hash-table-update!: no value exists for key" key))
+ (else (%hash-table-add! entries hash key
+ (function ((car maybe-default))))
+ (hash-table-set-size! hash-table
+ (+ 1 (hash-table-size hash-table)))
+ (%hash-table-maybe-resize! hash-table)))))
+
+(define (hash-table-update!/default hash-table key function default)
+ (hash-table-update! hash-table key function (lambda () default)))
+
+(define (hash-table-delete! hash-table key)
+ (if (%hash-table-delete! (hash-table-entries hash-table)
+ (hash-table-equivalence-function hash-table)
+ (%hash-table-hash hash-table key) key)
+ (hash-table-set-size! hash-table (- (hash-table-size hash-table) 1))))
+
+(define (hash-table-exists? hash-table key)
+ (and (%hash-table-find (hash-table-entries hash-table)
+ (hash-table-association-function hash-table)
+ (%hash-table-hash hash-table key) key) #t))
+
+(define (hash-table-walk hash-table proc)
+ (%hash-table-walk
+ (lambda (node) (proc (%hash-node-key node) (%hash-node-value node)))
+ (hash-table-entries hash-table)))
+
+(define (hash-table-fold hash-table f acc)
+ (hash-table-walk hash-table
+ (lambda (key value) (set! acc (f key value acc))))
+ acc)
+
+(define (alist->hash-table alist . args)
+ (let* ((comparison (if (null? args) equal? (car args)))
+ (hash
+ (if (or (null? args) (null? (cdr args)))
+ (appropriate-hash-function-for comparison) (cadr args)))
+ (size
+ (if (or (null? args) (null? (cdr args)) (null? (cddr args)))
+ (max *default-table-size* (* 2 (length alist))) (caddr args)))
+ (hash-table (make-hash-table comparison hash size)))
+ (for-each
+ (lambda (elem)
+ (hash-table-update!/default
+ hash-table (car elem) (lambda (x) x) (cdr elem)))
+ alist)
+ hash-table))
+
+(define (hash-table->alist hash-table)
+ (hash-table-fold hash-table
+ (lambda (key val acc) (cons (cons key val) acc)) '()))
+
+(define (hash-table-copy hash-table)
+ (let ((new (make-hash-table (hash-table-equivalence-function hash-table)
+ (hash-table-hash-function hash-table)
+ (max *default-table-size*
+ (* 2 (hash-table-size hash-table))))))
+ (hash-table-walk hash-table
+ (lambda (key value) (hash-table-set! new key value)))
+ new))
+
+(define (hash-table-merge! hash-table1 hash-table2)
+ (hash-table-walk
+ hash-table2
+ (lambda (key value) (hash-table-set! hash-table1 key value)))
+ hash-table1)
+
+(define (hash-table-keys hash-table)
+ (hash-table-fold hash-table (lambda (key val acc) (cons key acc)) '()))
+
+(define (hash-table-values hash-table)
+ (hash-table-fold hash-table (lambda (key val acc) (cons val acc)) '()))
+(define-library (srfi 71)
+ (export
+ (rename srfi-letrec* letrec*)
+ (rename srfi-letrec letrec)
+ (rename srfi-let* let*)
+ (rename srfi-let let)
+ uncons
+ uncons-2
+ uncons-3
+ uncons-4
+ uncons-cons
+ unlist
+ unvector
+ )
+ (import
+ (rename (scheme base)
+ (let r5rs-let)
+ (letrec r5rs-letrec))
+ (scheme cxr))
+ (include "71.upstream.scm"))
+;;; Copyright (c) 2005 Sebastian Egner.
+
+;;; Permission is hereby granted, free of charge, to any person obtaining a copy
+;;; of this software and associated documentation files (the ``Software''), to
+;;; deal in the Software without restriction, including without limitation the
+;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+;;; sell copies of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+
+;;; The above copyright notice and this permission notice shall be included in
+;;; all copies or substantial portions of the Software.
+
+;;; THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+;;; IN THE SOFTWARE.
+
+; Reference implementation of SRFI-71 (generic part)
+; Sebastian.Egner@philips.com, 20-May-2005, PLT 208
+;
+; In order to avoid conflicts with the existing let etc.
+; the macros defined here are called srfi-let etc.,
+; and they are defined in terms of r5rs-let etc.
+; It is up to the actual implementation to save let/*/rec
+; in r5rs-let/*/rec first and redefine let/*/rec
+; by srfi-let/*/rec then.
+;
+; There is also a srfi-letrec* being defined (in view of R6RS.)
+;
+; Macros used internally are named i:<something>.
+;
+; Abbreviations for macro arguments:
+; bs - <binding spec>
+; b - component of a binding spec (values, <variable>, or <expression>)
+; v - <variable>
+; vr - <variable> for rest list
+; x - <expression>
+; t - newly introduced temporary variable
+; vx - (<variable> <expression>)
+; rec - flag if letrec is produced (and not let)
+; cwv - call-with-value skeleton of the form (x formals)
+; (call-with-values (lambda () x) (lambda formals /payload/))
+; where /payload/ is of the form (let (vx ...) body1 body ...).
+;
+; Remark (*):
+; We bind the variables of a letrec to i:undefined since there is
+; no portable (R5RS) way of binding a variable to a values that
+; raises an error when read uninitialized.
+
+(define i:undefined 'undefined)
+
+(define-syntax srfi-letrec* ; -> srfi-letrec
+ (syntax-rules ()
+ ((srfi-letrec* () body1 body ...)
+ (srfi-letrec () body1 body ...))
+ ((srfi-letrec* (bs) body1 body ...)
+ (srfi-letrec (bs) body1 body ...))
+ ((srfi-letrec* (bs1 bs2 bs ...) body1 body ...)
+ (srfi-letrec (bs1) (srfi-letrec* (bs2 bs ...) body1 body ...)))))
+
+(define-syntax srfi-letrec ; -> i:let
+ (syntax-rules ()
+ ((srfi-letrec ((b1 b2 b ...) ...) body1 body ...)
+ (i:let "bs" #t () () (body1 body ...) ((b1 b2 b ...) ...)))))
+
+(define-syntax srfi-let* ; -> srfi-let
+ (syntax-rules ()
+ ((srfi-let* () body1 body ...)
+ (srfi-let () body1 body ...))
+ ((srfi-let* (bs) body1 body ...)
+ (srfi-let (bs) body1 body ...))
+ ((srfi-let* (bs1 bs2 bs ...) body1 body ...)
+ (srfi-let (bs1) (srfi-let* (bs2 bs ...) body1 body ...)))))
+
+(define-syntax srfi-let ; -> i:let or i:named-let
+ (syntax-rules ()
+ ((srfi-let ((b1 b2 b ...) ...) body1 body ...)
+ (i:let "bs" #f () () (body1 body ...) ((b1 b2 b ...) ...)))
+ ((srfi-let tag ((b1 b2 b ...) ...) body1 body ...)
+ (i:named-let tag () (body1 body ...) ((b1 b2 b ...) ...)))))
+
+(define-syntax i:let
+ (syntax-rules (values)
+
+; (i:let "bs" rec (cwv ...) (vx ...) body (bs ...))
+; processes the binding specs bs ... by adding call-with-values
+; skeletons to cwv ... and bindings to vx ..., and afterwards
+; wrapping the skeletons around the payload (let (vx ...) . body).
+
+ ; no more bs to process -> wrap call-with-values skeletons
+ ((i:let "bs" rec (cwv ...) vxs body ())
+ (i:let "wrap" rec vxs body cwv ...))
+
+ ; recognize form1 without variable -> dummy binding for side-effects
+ ((i:let "bs" rec cwvs (vx ...) body (((values) x) bs ...))
+ (i:let "bs" rec cwvs (vx ... (dummy (begin x #f))) body (bs ...)))
+
+ ; recognize form1 with single variable -> just extend vx ...
+ ((i:let "bs" rec cwvs (vx ...) body (((values v) x) bs ...))
+ (i:let "bs" rec cwvs (vx ... (v x)) body (bs ...)))
+
+ ; recognize form1 without rest arg -> generate cwv
+ ((i:let "bs" rec cwvs vxs body (((values v ...) x) bs ...))
+ (i:let "form1" rec cwvs vxs body (bs ...) (x ()) (values v ...)))
+
+ ; recognize form1 with rest arg -> generate cwv
+ ((i:let "bs" rec cwvs vxs body (((values . vs) x) bs ...))
+ (i:let "form1+" rec cwvs vxs body (bs ...) (x ()) (values . vs)))
+
+ ; recognize form2 with single variable -> just extend vx ...
+ ((i:let "bs" rec cwvs (vx ...) body ((v x) bs ...))
+ (i:let "bs" rec cwvs (vx ... (v x)) body (bs ...)))
+
+ ; recognize form2 with >=2 variables -> transform to form1
+ ((i:let "bs" rec cwvs vxs body ((b1 b2 b3 b ...) bs ...))
+ (i:let "form2" rec cwvs vxs body (bs ...) (b1 b2) (b3 b ...)))
+
+; (i:let "form1" rec cwvs vxs body bss (x (t ...)) (values v1 v2 v ...))
+; processes the variables in v1 v2 v ... adding them to (t ...)
+; and producing a cwv when finished. There is not rest argument.
+
+ ((i:let "form1" rec (cwv ...) vxs body bss (x ts) (values))
+ (i:let "bs" rec (cwv ... (x ts)) vxs body bss))
+ ((i:let "form1" rec cwvs (vx ...) body bss (x (t ...)) (values v1 v ...))
+ (i:let "form1" rec cwvs (vx ... (v1 t1)) body bss (x (t ... t1)) (values v ...)))
+
+; (i:let "form1+" rec cwvs vxs body bss (x (t ...)) (values v ... . vr))
+; processes the variables in v ... . vr adding them to (t ...)
+; and producing a cwv when finished. The rest arg is vr.
+
+ ((i:let "form1+" rec cwvs (vx ...) body bss (x (t ...)) (values v1 v2 . vs))
+ (i:let "form1+" rec cwvs (vx ... (v1 t1)) body bss (x (t ... t1)) (values v2 . vs)))
+ ((i:let "form1+" rec (cwv ...) (vx ...) body bss (x (t ...)) (values v1 . vr))
+ (i:let "bs" rec (cwv ... (x (t ... t1 . tr))) (vx ... (v1 t1) (vr tr)) body bss))
+ ((i:let "form1+" rec (cwv ...) (vx ...) body bss (x ()) (values . vr))
+ (i:let "bs" rec (cwv ... (x tr)) (vx ... (vr tr)) body bss))
+
+; (i:let "form2" rec cwvs vxs body bss (v ...) (b ... x))
+; processes the binding items (b ... x) from form2 as in
+; (v ... b ... x) into ((values v ... b ...) x), i.e. form1.
+; Then call "bs" recursively.
+
+ ((i:let "form2" rec cwvs vxs body (bs ...) (v ...) (x))
+ (i:let "bs" rec cwvs vxs body (((values v ...) x) bs ...)))
+ ((i:let "form2" rec cwvs vxs body bss (v ...) (b1 b2 b ...))
+ (i:let "form2" rec cwvs vxs body bss (v ... b1) (b2 b ...)))
+
+; (i:let "wrap" rec ((v x) ...) (body ...) cwv ...)
+; wraps cwv ... around the payload generating the actual code.
+; For letrec this is of course different than for let.
+
+ ((i:let "wrap" #f vxs body)
+ (r5rs-let vxs . body))
+ ((i:let "wrap" #f vxs body (x formals) cwv ...)
+ (call-with-values
+ (lambda () x)
+ (lambda formals (i:let "wrap" #f vxs body cwv ...))))
+
+ ((i:let "wrap" #t vxs body)
+ (r5rs-letrec vxs . body))
+ ((i:let "wrap" #t ((v t) ...) body cwv ...)
+ (r5rs-let ((v i:undefined) ...) ; (*)
+ (i:let "wraprec" ((v t) ...) body cwv ...)))
+
+; (i:let "wraprec" ((v t) ...) body cwv ...)
+; generate the inner code for a letrec. The variables v ...
+; are the user-visible variables (bound outside), and t ...
+; are the temporary variables bound by the cwv consumers.
+
+ ((i:let "wraprec" ((v t) ...) (body ...))
+ (begin (set! v t) ... (r5rs-let () body ...)))
+ ((i:let "wraprec" vxs body (x formals) cwv ...)
+ (call-with-values
+ (lambda () x)
+ (lambda formals (i:let "wraprec" vxs body cwv ...))))
+
+ ))
+
+(define-syntax i:named-let
+ (syntax-rules (values)
+
+; (i:named-let tag (vx ...) body (bs ...))
+; processes the binding specs bs ... by extracting the variable
+; and expression, adding them to vx and turning the result into
+; an ordinary named let.
+
+ ((i:named-let tag vxs body ())
+ (r5rs-let tag vxs . body))
+ ((i:named-let tag (vx ...) body (((values v) x) bs ...))
+ (i:named-let tag (vx ... (v x)) body (bs ...)))
+ ((i:named-let tag (vx ...) body ((v x) bs ...))
+ (i:named-let tag (vx ... (v x)) body (bs ...)))))
+
+; --- standard procedures ---
+
+(define (uncons pair)
+ (values (car pair) (cdr pair)))
+
+(define (uncons-2 list)
+ (values (car list) (cadr list) (cddr list)))
+
+(define (uncons-3 list)
+ (values (car list) (cadr list) (caddr list) (cdddr list)))
+
+(define (uncons-4 list)
+ (values (car list) (cadr list) (caddr list) (cadddr list) (cddddr list)))
+
+(define (uncons-cons alist)
+ (values (caar alist) (cdar alist) (cdr alist)))
+
+(define (unlist list)
+ (apply values list))
+
+(define (unvector vector)
+ (apply values (vector->list vector)))
+
+; --- standard macros ---
+
+(define-syntax values->list
+ (syntax-rules ()
+ ((values->list x)
+ (call-with-values (lambda () x) list))))
+
+(define-syntax values->vector
+ (syntax-rules ()
+ ((values->vector x)
+ (call-with-values (lambda () x) vector))))
+(define-library (srfi 78)
+ (export
+ check
+ check-ec
+ check-report
+ check-set-mode!
+ check-reset!
+ check-passed?
+ )
+ (import
+ (scheme base)
+ (scheme cxr)
+ (scheme write)
+ (srfi 42))
+ (include "78.upstream.scm"))
+; <PLAINTEXT>
+; Copyright (c) 2005-2006 Sebastian Egner.
+;
+; Permission is hereby granted, free of charge, to any person obtaining
+; a copy of this software and associated documentation files (the
+; ``Software''), to deal in the Software without restriction, including
+; without limitation the rights to use, copy, modify, merge, publish,
+; distribute, sublicense, and/or sell copies of the Software, and to
+; permit persons to whom the Software is furnished to do so, subject to
+; the following conditions:
+;
+; The above copyright notice and this permission notice shall be
+; included in all copies or substantial portions of the Software.
+;
+; THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
+; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+;
+; -----------------------------------------------------------------------
+;
+; Lightweight testing (reference implementation)
+; ==============================================
+;
+; Sebastian.Egner@philips.com
+; in R5RS + SRFI 23 (error) + SRFI 42 (comprehensions)
+;
+; history of this file:
+; SE, 25-Oct-2004: first version based on code used in SRFIs 42 and 67
+; SE, 19-Jan-2006: (arg ...) made optional in check-ec
+;
+; Naming convention "check:<identifier>" is used only internally.
+
+; -- portability --
+
+; PLT: (require (lib "23.ss" "srfi") (lib "42.ss" "srfi"))
+; Scheme48: ,open srfi-23 srfi-42
+
+; -- utilities --
+
+(define check:write write)
+
+; You can also use a pretty printer if you have one.
+; However, the output might not improve for most cases
+; because the pretty printers usually output a trailing
+; newline.
+
+; PLT: (require (lib "pretty.ss")) (define check:write pretty-print)
+; Scheme48: ,open pp (define check:write p)
+
+; -- mode --
+
+(define check:mode #f)
+
+(define (check-set-mode! mode)
+ (set! check:mode
+ (case mode
+ ((off) 0)
+ ((summary) 1)
+ ((report-failed) 10)
+ ((report) 100)
+ (else (error "unrecognized mode" mode)))))
+
+(check-set-mode! 'report)
+
+; -- state --
+
+(define check:correct #f)
+(define check:failed #f)
+
+(define (check-reset!)
+ (set! check:correct 0)
+ (set! check:failed '()))
+
+(define (check:add-correct!)
+ (set! check:correct (+ check:correct 1)))
+
+(define (check:add-failed! expression actual-result expected-result)
+ (set! check:failed
+ (cons (list expression actual-result expected-result)
+ check:failed)))
+
+(check-reset!)
+
+; -- reporting --
+
+(define (check:report-expression expression)
+ (newline)
+ (check:write expression)
+ (display " => "))
+
+(define (check:report-actual-result actual-result)
+ (check:write actual-result)
+ (display " ; "))
+
+(define (check:report-correct cases)
+ (display "correct")
+ (if (not (= cases 1))
+ (begin (display " (")
+ (display cases)
+ (display " cases checked)")))
+ (newline))
+
+(define (check:report-failed expected-result)
+ (display "*** failed ***")
+ (newline)
+ (display " ; expected result: ")
+ (check:write expected-result)
+ (newline))
+
+(define (check-report)
+ (if (>= check:mode 1)
+ (begin
+ (newline)
+ (display "; *** checks *** : ")
+ (display check:correct)
+ (display " correct, ")
+ (display (length check:failed))
+ (display " failed.")
+ (if (or (null? check:failed) (<= check:mode 1))
+ (newline)
+ (let* ((w (car (reverse check:failed)))
+ (expression (car w))
+ (actual-result (cadr w))
+ (expected-result (caddr w)))
+ (display " First failed example:")
+ (newline)
+ (check:report-expression expression)
+ (check:report-actual-result actual-result)
+ (check:report-failed expected-result))))))
+
+(define (check-passed? expected-total-count)
+ (and (= (length check:failed) 0)
+ (= check:correct expected-total-count)))
+
+; -- simple checks --
+
+(define (check:proc expression thunk equal expected-result)
+ (case check:mode
+ ((0) #f)
+ ((1)
+ (let ((actual-result (thunk)))
+ (if (equal actual-result expected-result)
+ (check:add-correct!)
+ (check:add-failed! expression actual-result expected-result))))
+ ((10)
+ (let ((actual-result (thunk)))
+ (if (equal actual-result expected-result)
+ (check:add-correct!)
+ (begin
+ (check:report-expression expression)
+ (check:report-actual-result actual-result)
+ (check:report-failed expected-result)
+ (check:add-failed! expression actual-result expected-result)))))
+ ((100)
+ (check:report-expression expression)
+ (let ((actual-result (thunk)))
+ (check:report-actual-result actual-result)
+ (if (equal actual-result expected-result)
+ (begin (check:report-correct 1)
+ (check:add-correct!))
+ (begin (check:report-failed expected-result)
+ (check:add-failed! expression
+ actual-result
+ expected-result)))))
+ (else (error "unrecognized check:mode" check:mode)))
+ (if #f #f))
+
+(define-syntax check
+ (syntax-rules (=>)
+ ((check expr => expected)
+ (check expr (=> equal?) expected))
+ ((check expr (=> equal) expected)
+ (if (>= check:mode 1)
+ (check:proc 'expr (lambda () expr) equal expected)))))
+
+; -- parametric checks --
+
+(define (check:proc-ec w)
+ (let ((correct? (car w))
+ (expression (cadr w))
+ (actual-result (caddr w))
+ (expected-result (cadddr w))
+ (cases (car (cddddr w))))
+ (if correct?
+ (begin (if (>= check:mode 100)
+ (begin (check:report-expression expression)
+ (check:report-actual-result actual-result)
+ (check:report-correct cases)))
+ (check:add-correct!))
+ (begin (if (>= check:mode 10)
+ (begin (check:report-expression expression)
+ (check:report-actual-result actual-result)
+ (check:report-failed expected-result)))
+ (check:add-failed! expression
+ actual-result
+ expected-result)))))
+
+(define-syntax check-ec:make
+ (syntax-rules (=>)
+ ((check-ec:make qualifiers expr (=> equal) expected (arg ...))
+ (if (>= check:mode 1)
+ (check:proc-ec
+ (let ((cases 0))
+ (let ((w (first-ec
+ #f
+ qualifiers
+ (\:let equal-pred equal)
+ (\:let expected-result expected)
+ (\:let actual-result
+ (let ((arg arg) ...) ; (*)
+ expr))
+ (begin (set! cases (+ cases 1)))
+ (if (not (equal-pred actual-result expected-result)))
+ (list (list 'let (list (list 'arg arg) ...) 'expr)
+ actual-result
+ expected-result
+ cases))))
+ (if w
+ (cons #f w)
+ (list #t
+ '(check-ec qualifiers
+ expr (=> equal)
+ expected (arg ...))
+ (if #f #f)
+ (if #f #f)
+ cases)))))))))
+
+; (*) is a compile-time check that (arg ...) is a list
+; of pairwise disjoint bound variables at this point.
+
+(define-syntax check-ec
+ (syntax-rules (nested =>)
+ ((check-ec expr => expected)
+ (check-ec:make (nested) expr (=> equal?) expected ()))
+ ((check-ec expr (=> equal) expected)
+ (check-ec:make (nested) expr (=> equal) expected ()))
+ ((check-ec expr => expected (arg ...))
+ (check-ec:make (nested) expr (=> equal?) expected (arg ...)))
+ ((check-ec expr (=> equal) expected (arg ...))
+ (check-ec:make (nested) expr (=> equal) expected (arg ...)))
+
+ ((check-ec qualifiers expr => expected)
+ (check-ec:make qualifiers expr (=> equal?) expected ()))
+ ((check-ec qualifiers expr (=> equal) expected)
+ (check-ec:make qualifiers expr (=> equal) expected ()))
+ ((check-ec qualifiers expr => expected (arg ...))
+ (check-ec:make qualifiers expr (=> equal?) expected (arg ...)))
+ ((check-ec qualifiers expr (=> equal) expected (arg ...))
+ (check-ec:make qualifiers expr (=> equal) expected (arg ...)))
+
+ ((check-ec (nested q1 ...) q etc ...)
+ (check-ec (nested q1 ... q) etc ...))
+ ((check-ec q1 q2 etc ...)
+ (check-ec (nested q1 q2) etc ...))))
+(import (scheme base)
+ (scheme eval)
+ (scheme file)
+ (srfi 1)
+ (srfi 48)
+ (srfi 64))
+
+(test-runner-current (test-runner-simple "tests.log"))
+
+(test-begin "SRFI")
+
+(for-each
+ (lambda (n)
+ (let ((srfi-n (string->symbol (format #f "srfi-~s" n)))
+ (file-name (format #f "srfi-tests/srfi-~s.sld" n))
+ (test-name (format #f "SRFI-~s" n)))
+ (when (file-exists? file-name)
+ (test-assert test-name
+ (guard (err (else #f))
+ (eval '(run-tests) (environment `(srfi-tests ,srfi-n))))))))
+ (iota 200))
+
+(test-end "SRFI")
+
+(test-exit)
+;;; SRFI 13 string library reference implementation -*- Scheme -*-
+;;; Olin Shivers 7/2000
+;;;
+;;; Copyright (c) 1988-1994 Massachusetts Institute of Technology.
+;;; Copyright (c) 1998, 1999, 2000 Olin Shivers. All rights reserved.
+;;; The details of the copyrights appear at the end of the file. Short
+;;; summary: BSD-style open source.
+
+;;; Exports:
+;;; string-map string-map!
+;;; string-fold string-unfold
+;;; string-fold-right string-unfold-right
+;;; string-tabulate string-for-each string-for-each-index
+;;; string-every string-any
+;;; string-hash string-hash-ci
+;;; string-compare string-compare-ci
+;;; string= string< string> string<= string>= string<>
+;;; string-ci= string-ci< string-ci> string-ci<= string-ci>= string-ci<>
+;;; string-downcase string-upcase string-titlecase
+;;; string-downcase! string-upcase! string-titlecase!
+;;; string-take string-take-right
+;;; string-drop string-drop-right
+;;; string-pad string-pad-right
+;;; string-trim string-trim-right string-trim-both
+;;; string-filter string-delete
+;;; string-index string-index-right
+;;; string-skip string-skip-right
+;;; string-count
+;;; string-prefix-length string-prefix-length-ci
+;;; string-suffix-length string-suffix-length-ci
+;;; string-prefix? string-prefix-ci?
+;;; string-suffix? string-suffix-ci?
+;;; string-contains string-contains-ci
+;;; string-copy! substring/shared
+;;; string-reverse string-reverse! reverse-list->string
+;;; string-concatenate string-concatenate/shared string-concatenate-reverse
+;;; string-append/shared
+;;; xsubstring string-xcopy!
+;;; string-null?
+;;; string-join
+;;; string-tokenize
+;;; string-replace
+;;;
+;;; R5RS extended:
+;;; string->list string-copy string-fill!
+;;;
+;;; R5RS re-exports:
+;;; string? make-string string-length string-ref string-set!
+;;;
+;;; R5RS re-exports (also defined here but commented-out):
+;;; string string-append list->string
+;;;
+;;; Low-level routines:
+;;; make-kmp-restart-vector string-kmp-partial-search kmp-step
+;;; string-parse-start+end
+;;; string-parse-final-start+end
+;;; let-string-start+end
+;;; check-substring-spec
+;;; substring-spec-ok?
+
+;;; Imports
+;;; This is a fairly large library. While it was written for portability, you
+;;; must be aware of its dependencies in order to run it in a given scheme
+;;; implementation. Here is a complete list of the dependencies it has and the
+;;; assumptions it makes beyond stock R5RS Scheme:
+;;;
+;;; This code has the following non-R5RS dependencies:
+;;; - (RECEIVE (var ...) mv-exp body ...) multiple-value binding macro;
+;;;
+;;; - Various imports from the char-set library for the routines that can
+;;; take char-set arguments;
+;;;
+;;; - An n-ary ERROR procedure;
+;;;
+;;; - BITWISE-AND for the hash functions;
+;;;
+;;; - A simple CHECK-ARG procedure for checking parameter values; it is
+;;; (lambda (pred val proc)
+;;; (if (pred val) val (error "Bad arg" val pred proc)))
+;;;
+;;; - #\:OPTIONAL and LET-OPTIONALS* macros for parsing, defaulting &
+;;; type-checking optional parameters from a rest argument;
+;;;
+;;; - CHAR-CASED? and CHAR-TITLECASE for the STRING-TITLECASE &
+;;; STRING-TITLECASE! procedures. The former returns true iff a character is
+;;; one that has case distinctions; in ASCII it returns true on a-z and A-Z.
+;;; CHAR-TITLECASE is analagous to CHAR-UPCASE and CHAR-DOWNCASE. In ASCII &
+;;; Latin-1, it is the same as CHAR-UPCASE.
+;;;
+;;; The code depends upon a small set of core string primitives from R5RS:
+;;; MAKE-STRING STRING-REF STRING-SET! STRING? STRING-LENGTH SUBSTRING
+;;; (Actually, SUBSTRING is not a primitive, but we assume that an
+;;; implementation's native version is probably faster than one we could
+;;; define, so we import it from R5RS.)
+;;;
+;;; The code depends upon a small set of R5RS character primitives:
+;;; char? char=? char-ci=? char<? char-ci<?
+;;; char-upcase char-downcase
+;;; char->integer (for the hash functions)
+;;;
+;;; We assume the following:
+;;; - CHAR-DOWNCASE o CHAR-UPCASE = CHAR-DOWNCASE
+;;; - CHAR-CI=? is equivalent to
+;;; (lambda (c1 c2) (char=? (char-downcase (char-upcase c1))
+;;; (char-downcase (char-upcase c2))))
+;;; - CHAR-UPCASE, CHAR-DOWNCASE and CHAR-TITLECASE are locale-insensitive
+;;; and consistent with Unicode's 1-1 char-mapping spec.
+;;; These things are typically true, but if not, you would need to modify
+;;; the case-mapping and case-insensitive routines.
+
+;;; Enough introductory blather. On to the source code. (But see the end of
+;;; the file for further notes on porting & performance tuning.)
+
+
+;;; Support for START/END substring specs
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; This macro parses optional start/end arguments from arg lists, defaulting
+;;; them to 0/(string-length s), and checks them for correctness.
+
+(define-syntax let-string-start+end
+ (syntax-rules ()
+ ((let-string-start+end (start end) proc s-exp args-exp body ...)
+ (receive (start end) (string-parse-final-start+end proc s-exp args-exp)
+ body ...))
+ ((let-string-start+end (start end rest) proc s-exp args-exp body ...)
+ (receive (rest start end) (string-parse-start+end proc s-exp args-exp)
+ body ...))))
+
+;;; This one parses out a *pair* of final start/end indices.
+;;; Not exported; for internal use.
+(define-syntax let-string-start+end2
+ (syntax-rules ()
+ ((l-s-s+e2 (start1 end1 start2 end2) proc s1 s2 args body ...)
+ (let ((procv proc)) ; Make sure PROC is only evaluated once.
+ (let-string-start+end (start1 end1 rest) procv s1 args
+ (let-string-start+end (start2 end2) procv s2 rest
+ body ...))))))
+
+
+;;; Returns three values: rest start end
+
+(define (string-parse-start+end proc s args)
+ (if (not (string? s)) (error "Non-string value" proc s))
+ (let ((slen (string-length s)))
+ (if (pair? args)
+
+ (let ((start (car args))
+ (args (cdr args)))
+ (if (and (integer? start) (exact? start) (>= start 0))
+ (receive (end args)
+ (if (pair? args)
+ (let ((end (car args))
+ (args (cdr args)))
+ (if (and (integer? end) (exact? end) (<= end slen))
+ (values end args)
+ (error "Illegal substring END spec" proc end s)))
+ (values slen args))
+ (if (<= start end) (values args start end)
+ (error "Illegal substring START/END spec"
+ proc start end s)))
+ (error "Illegal substring START spec" proc start s)))
+
+ (values '() 0 slen))))
+
+(define (string-parse-final-start+end proc s args)
+ (receive (rest start end) (string-parse-start+end proc s args)
+ (if (pair? rest) (error "Extra arguments to procedure" proc rest)
+ (values start end))))
+
+(define (substring-spec-ok? s start end)
+ (and (string? s)
+ (integer? start)
+ (exact? start)
+ (integer? end)
+ (exact? end)
+ (<= 0 start)
+ (<= start end)
+ (<= end (string-length s))))
+
+(define (check-substring-spec proc s start end)
+ (if (not (substring-spec-ok? s start end))
+ (error "Illegal substring spec." proc s start end)))
+
+
+;;; Defined by R5RS, so commented out here.
+;(define (string . chars)
+; (let* ((len (length chars))
+; (ans (make-string len)))
+; (do ((i 0 (+ i 1))
+; (chars chars (cdr chars)))
+; ((>= i len))
+; (string-set! ans i (car chars)))
+; ans))
+;
+;(define (string . chars) (string-unfold null? car cdr chars))
+
+
+
+;;; substring/shared S START [END]
+;;; string-copy S [START END]
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; All this goop is just arg parsing & checking surrounding a call to the
+;;; actual primitive, %SUBSTRING/SHARED.
+
+(define (substring/shared s start . maybe-end)
+ (check-arg string? s substring/shared)
+ (let ((slen (string-length s)))
+ (check-arg (lambda (start) (and (integer? start) (exact? start) (<= 0 start)))
+ start substring/shared)
+ (%substring/shared s start
+ (#\:optional maybe-end slen
+ (lambda (end) (and (integer? end)
+ (exact? end)
+ (<= start end)
+ (<= end slen)))))))
+
+;;; Split out so that other routines in this library can avoid arg-parsing
+;;; overhead for END parameter.
+(define (%substring/shared s start end)
+ (if (and (zero? start) (= end (string-length s))) s
+ (substring s start end)))
+
+(define (string-copy s . maybe-start+end)
+ (let-string-start+end (start end) string-copy s maybe-start+end
+ (substring s start end)))
+
+;This library uses the R5RS SUBSTRING, but doesn't export it.
+;Here is a definition, just for completeness.
+;(define (substring s start end)
+; (check-substring-spec substring s start end)
+; (let* ((slen (- end start))
+; (ans (make-string slen)))
+; (do ((i 0 (+ i 1))
+; (j start (+ j 1)))
+; ((>= i slen) ans)
+; (string-set! ans i (string-ref s j)))))
+
+;;; Basic iterators and other higher-order abstractions
+;;; (string-map proc s [start end])
+;;; (string-map! proc s [start end])
+;;; (string-fold kons knil s [start end])
+;;; (string-fold-right kons knil s [start end])
+;;; (string-unfold p f g seed [base make-final])
+;;; (string-unfold-right p f g seed [base make-final])
+;;; (string-for-each proc s [start end])
+;;; (string-for-each-index proc s [start end])
+;;; (string-every char-set/char/pred s [start end])
+;;; (string-any char-set/char/pred s [start end])
+;;; (string-tabulate proc len)
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; You want compiler support for high-level transforms on fold and unfold ops.
+;;; You'd at least like a lot of inlining for clients of these procedures.
+;;; Don't hold your breath.
+
+(define (string-map proc s . maybe-start+end)
+ (check-arg procedure? proc string-map)
+ (let-string-start+end (start end) string-map s maybe-start+end
+ (%string-map proc s start end)))
+
+(define (%string-map proc s start end) ; Internal utility
+ (let* ((len (- end start))
+ (ans (make-string len)))
+ (do ((i (- end 1) (- i 1))
+ (j (- len 1) (- j 1)))
+ ((< j 0))
+ (string-set! ans j (proc (string-ref s i))))
+ ans))
+
+(define (string-map! proc s . maybe-start+end)
+ (check-arg procedure? proc string-map!)
+ (let-string-start+end (start end) string-map! s maybe-start+end
+ (%string-map! proc s start end)))
+
+(define (%string-map! proc s start end)
+ (do ((i (- end 1) (- i 1)))
+ ((< i start))
+ (string-set! s i (proc (string-ref s i)))))
+
+(define (string-fold kons knil s . maybe-start+end)
+ (check-arg procedure? kons string-fold)
+ (let-string-start+end (start end) string-fold s maybe-start+end
+ (let lp ((v knil) (i start))
+ (if (< i end) (lp (kons (string-ref s i) v) (+ i 1))
+ v))))
+
+(define (string-fold-right kons knil s . maybe-start+end)
+ (check-arg procedure? kons string-fold-right)
+ (let-string-start+end (start end) string-fold-right s maybe-start+end
+ (let lp ((v knil) (i (- end 1)))
+ (if (>= i start) (lp (kons (string-ref s i) v) (- i 1))
+ v))))
+
+;;; (string-unfold p f g seed [base make-final])
+;;; This is the fundamental constructor for strings.
+;;; - G is used to generate a series of "seed" values from the initial seed:
+;;; SEED, (G SEED), (G^2 SEED), (G^3 SEED), ...
+;;; - P tells us when to stop -- when it returns true when applied to one
+;;; of these seed values.
+;;; - F maps each seed value to the corresponding character
+;;; in the result string. These chars are assembled into the
+;;; string in a left-to-right order.
+;;; - BASE is the optional initial/leftmost portion of the constructed string;
+;;; it defaults to the empty string "".
+;;; - MAKE-FINAL is applied to the terminal seed value (on which P returns
+;;; true) to produce the final/rightmost portion of the constructed string.
+;;; It defaults to (LAMBDA (X) "").
+;;;
+;;; In other words, the following (simple, inefficient) definition holds:
+;;; (define (string-unfold p f g seed base make-final)
+;;; (string-append base
+;;; (let recur ((seed seed))
+;;; (if (p seed) (make-final seed)
+;;; (string-append (string (f seed))
+;;; (recur (g seed)))))))
+;;;
+;;; STRING-UNFOLD is a fairly powerful constructor -- you can use it to
+;;; reverse a string, copy a string, convert a list to a string, read
+;;; a port into a string, and so forth. Examples:
+;;; (port->string port) =
+;;; (string-unfold (compose eof-object? peek-char)
+;;; read-char values port)
+;;;
+;;; (list->string lis) = (string-unfold null? car cdr lis)
+;;;
+;;; (tabulate-string f size) = (string-unfold (lambda (i) (= i size)) f add1 0)
+
+;;; A problem with the following simple formulation is that it pushes one
+;;; stack frame for every char in the result string -- an issue if you are
+;;; using it to read a 100kchar string. So we don't use it -- but I include
+;;; it to give a clear, straightforward description of what the function
+;;; does.
+
+;(define (string-unfold p f g seed base make-final)
+; (let ((ans (let recur ((seed seed) (i (string-length base)))
+; (if (p seed)
+; (let* ((final (make-final seed))
+; (ans (make-string (+ i (string-length final)))))
+; (string-copy! ans i final)
+; ans)
+;
+; (let* ((c (f seed))
+; (s (recur (g seed) (+ i 1))))
+; (string-set! s i c)
+; s)))))
+; (string-copy! ans 0 base)
+; ans))
+
+;;; The strategy is to allocate a series of chunks into which we stash the
+;;; chars as we generate them. Chunk size goes up in powers of two starting
+;;; with 40 and levelling out at 4k, i.e.
+;;; 40 40 80 160 320 640 1280 2560 4096 4096 4096 4096 4096...
+;;; This should work pretty well for short strings, 1-line (80 char) strings,
+;;; and longer ones. When done, we allocate an answer string and copy the
+;;; chars over from the chunk buffers.
+
+(define (string-unfold p f g seed . base+make-final)
+ (check-arg procedure? p string-unfold)
+ (check-arg procedure? f string-unfold)
+ (check-arg procedure? g string-unfold)
+ (let-optionals* base+make-final
+ ((base "" (string? base))
+ (make-final (lambda (x) "") (procedure? make-final)))
+ (let lp ((chunks '()) ; Previously filled chunks
+ (nchars 0) ; Number of chars in CHUNKS
+ (chunk (make-string 40)) ; Current chunk into which we write
+ (chunk-len 40)
+ (i 0) ; Number of chars written into CHUNK
+ (seed seed))
+ (let lp2 ((i i) (seed seed))
+ (if (not (p seed))
+ (let ((c (f seed))
+ (seed (g seed)))
+ (if (< i chunk-len)
+ (begin (string-set! chunk i c)
+ (lp2 (+ i 1) seed))
+
+ (let* ((nchars2 (+ chunk-len nchars))
+ (chunk-len2 (min 4096 nchars2))
+ (new-chunk (make-string chunk-len2)))
+ (string-set! new-chunk 0 c)
+ (lp (cons chunk chunks) (+ nchars chunk-len)
+ new-chunk chunk-len2 1 seed))))
+
+ ;; We're done. Make the answer string & install the bits.
+ (let* ((final (make-final seed))
+ (flen (string-length final))
+ (base-len (string-length base))
+ (j (+ base-len nchars i))
+ (ans (make-string (+ j flen))))
+ (%string-copy! ans j final 0 flen) ; Install FINAL.
+ (let ((j (- j i)))
+ (%string-copy! ans j chunk 0 i) ; Install CHUNK[0,I).
+ (let lp ((j j) (chunks chunks)) ; Install CHUNKS.
+ (if (pair? chunks)
+ (let* ((chunk (car chunks))
+ (chunks (cdr chunks))
+ (chunk-len (string-length chunk))
+ (j (- j chunk-len)))
+ (%string-copy! ans j chunk 0 chunk-len)
+ (lp j chunks)))))
+ (%string-copy! ans 0 base 0 base-len) ; Install BASE.
+ ans))))))
+
+(define (string-unfold-right p f g seed . base+make-final)
+ (let-optionals* base+make-final
+ ((base "" (string? base))
+ (make-final (lambda (x) "") (procedure? make-final)))
+ (let lp ((chunks '()) ; Previously filled chunks
+ (nchars 0) ; Number of chars in CHUNKS
+ (chunk (make-string 40)) ; Current chunk into which we write
+ (chunk-len 40)
+ (i 40) ; Number of chars available in CHUNK
+ (seed seed))
+ (let lp2 ((i i) (seed seed)) ; Fill up CHUNK from right
+ (if (not (p seed)) ; to left.
+ (let ((c (f seed))
+ (seed (g seed)))
+ (if (> i 0)
+ (let ((i (- i 1)))
+ (string-set! chunk i c)
+ (lp2 i seed))
+
+ (let* ((nchars2 (+ chunk-len nchars))
+ (chunk-len2 (min 4096 nchars2))
+ (new-chunk (make-string chunk-len2))
+ (i (- chunk-len2 1)))
+ (string-set! new-chunk i c)
+ (lp (cons chunk chunks) (+ nchars chunk-len)
+ new-chunk chunk-len2 i seed))))
+
+ ;; We're done. Make the answer string & install the bits.
+ (let* ((final (make-final seed))
+ (flen (string-length final))
+ (base-len (string-length base))
+ (chunk-used (- chunk-len i))
+ (j (+ base-len nchars chunk-used))
+ (ans (make-string (+ j flen))))
+ (%string-copy! ans 0 final 0 flen) ; Install FINAL.
+ (%string-copy! ans flen chunk i chunk-len); Install CHUNK[I,).
+ (let lp ((j (+ flen chunk-used)) ; Install CHUNKS.
+ (chunks chunks))
+ (if (pair? chunks)
+ (let* ((chunk (car chunks))
+ (chunks (cdr chunks))
+ (chunk-len (string-length chunk)))
+ (%string-copy! ans j chunk 0 chunk-len)
+ (lp (+ j chunk-len) chunks))
+ (%string-copy! ans j base 0 base-len))); Install BASE.
+ ans))))))
+
+
+(define (string-for-each proc s . maybe-start+end)
+ (check-arg procedure? proc string-for-each)
+ (let-string-start+end (start end) string-for-each s maybe-start+end
+ (let lp ((i start))
+ (if (< i end)
+ (begin (proc (string-ref s i))
+ (lp (+ i 1)))))))
+
+(define (string-for-each-index proc s . maybe-start+end)
+ (check-arg procedure? proc string-for-each-index)
+ (let-string-start+end (start end) string-for-each-index s maybe-start+end
+ (let lp ((i start))
+ (if (< i end) (begin (proc i) (lp (+ i 1)))))))
+
+(define (string-every criterion s . maybe-start+end)
+ (let-string-start+end (start end) string-every s maybe-start+end
+ (cond ((char? criterion)
+ (let lp ((i start))
+ (or (>= i end)
+ (and (char=? criterion (string-ref s i))
+ (lp (+ i 1))))))
+
+ ((char-set? criterion)
+ (let lp ((i start))
+ (or (>= i end)
+ (and (char-set-contains? criterion (string-ref s i))
+ (lp (+ i 1))))))
+
+ ((procedure? criterion) ; Slightly funky loop so that
+ (or (= start end) ; final (PRED S[END-1]) call
+ (let lp ((i start)) ; is a tail call.
+ (let ((c (string-ref s i))
+ (i1 (+ i 1)))
+ (if (= i1 end) (criterion c) ; Tail call.
+ (and (criterion c) (lp i1)))))))
+
+ (else (error "Second param is neither char-set, char, or predicate procedure."
+ string-every criterion)))))
+
+
+(define (string-any criterion s . maybe-start+end)
+ (let-string-start+end (start end) string-any s maybe-start+end
+ (cond ((char? criterion)
+ (let lp ((i start))
+ (and (< i end)
+ (or (char=? criterion (string-ref s i))
+ (lp (+ i 1))))))
+
+ ((char-set? criterion)
+ (let lp ((i start))
+ (and (< i end)
+ (or (char-set-contains? criterion (string-ref s i))
+ (lp (+ i 1))))))
+
+ ((procedure? criterion) ; Slightly funky loop so that
+ (and (< start end) ; final (PRED S[END-1]) call
+ (let lp ((i start)) ; is a tail call.
+ (let ((c (string-ref s i))
+ (i1 (+ i 1)))
+ (if (= i1 end) (criterion c) ; Tail call
+ (or (criterion c) (lp i1)))))))
+
+ (else (error "Second param is neither char-set, char, or predicate procedure."
+ string-any criterion)))))
+
+
+(define (string-tabulate proc len)
+ (check-arg procedure? proc string-tabulate)
+ (check-arg (lambda (val) (and (integer? val) (exact? val) (<= 0 val)))
+ len string-tabulate)
+ (let ((s (make-string len)))
+ (do ((i (- len 1) (- i 1)))
+ ((< i 0))
+ (string-set! s i (proc i)))
+ s))
+
+
+
+;;; string-prefix-length[-ci] s1 s2 [start1 end1 start2 end2]
+;;; string-suffix-length[-ci] s1 s2 [start1 end1 start2 end2]
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Find the length of the common prefix/suffix.
+;;; It is not required that the two substrings passed be of equal length.
+;;; This was microcode in MIT Scheme -- a very tightly bummed primitive.
+;;; %STRING-PREFIX-LENGTH is the core routine of all string-comparisons,
+;;; so should be as tense as possible.
+
+(define (%string-prefix-length s1 start1 end1 s2 start2 end2)
+ (let* ((delta (min (- end1 start1) (- end2 start2)))
+ (end1 (+ start1 delta)))
+
+ (if (and (eq? s1 s2) (= start1 start2)) ; EQ fast path
+ delta
+
+ (let lp ((i start1) (j start2)) ; Regular path
+ (if (or (>= i end1)
+ (not (char=? (string-ref s1 i)
+ (string-ref s2 j))))
+ (- i start1)
+ (lp (+ i 1) (+ j 1)))))))
+
+(define (%string-suffix-length s1 start1 end1 s2 start2 end2)
+ (let* ((delta (min (- end1 start1) (- end2 start2)))
+ (start1 (- end1 delta)))
+
+ (if (and (eq? s1 s2) (= end1 end2)) ; EQ fast path
+ delta
+
+ (let lp ((i (- end1 1)) (j (- end2 1))) ; Regular path
+ (if (or (< i start1)
+ (not (char=? (string-ref s1 i)
+ (string-ref s2 j))))
+ (- (- end1 i) 1)
+ (lp (- i 1) (- j 1)))))))
+
+(define (%string-prefix-length-ci s1 start1 end1 s2 start2 end2)
+ (let* ((delta (min (- end1 start1) (- end2 start2)))
+ (end1 (+ start1 delta)))
+
+ (if (and (eq? s1 s2) (= start1 start2)) ; EQ fast path
+ delta
+
+ (let lp ((i start1) (j start2)) ; Regular path
+ (if (or (>= i end1)
+ (not (char-ci=? (string-ref s1 i)
+ (string-ref s2 j))))
+ (- i start1)
+ (lp (+ i 1) (+ j 1)))))))
+
+(define (%string-suffix-length-ci s1 start1 end1 s2 start2 end2)
+ (let* ((delta (min (- end1 start1) (- end2 start2)))
+ (start1 (- end1 delta)))
+
+ (if (and (eq? s1 s2) (= end1 end2)) ; EQ fast path
+ delta
+
+ (let lp ((i (- end1 1)) (j (- end2 1))) ; Regular path
+ (if (or (< i start1)
+ (not (char-ci=? (string-ref s1 i)
+ (string-ref s2 j))))
+ (- (- end1 i) 1)
+ (lp (- i 1) (- j 1)))))))
+
+
+(define (string-prefix-length s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-prefix-length s1 s2 maybe-starts+ends
+ (%string-prefix-length s1 start1 end1 s2 start2 end2)))
+
+(define (string-suffix-length s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-suffix-length s1 s2 maybe-starts+ends
+ (%string-suffix-length s1 start1 end1 s2 start2 end2)))
+
+(define (string-prefix-length-ci s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-prefix-length-ci s1 s2 maybe-starts+ends
+ (%string-prefix-length-ci s1 start1 end1 s2 start2 end2)))
+
+(define (string-suffix-length-ci s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-suffix-length-ci s1 s2 maybe-starts+ends
+ (%string-suffix-length-ci s1 start1 end1 s2 start2 end2)))
+
+
+;;; string-prefix? s1 s2 [start1 end1 start2 end2]
+;;; string-suffix? s1 s2 [start1 end1 start2 end2]
+;;; string-prefix-ci? s1 s2 [start1 end1 start2 end2]
+;;; string-suffix-ci? s1 s2 [start1 end1 start2 end2]
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; These are all simple derivatives of the previous counting funs.
+
+(define (string-prefix? s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-prefix? s1 s2 maybe-starts+ends
+ (%string-prefix? s1 start1 end1 s2 start2 end2)))
+
+(define (string-suffix? s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-suffix? s1 s2 maybe-starts+ends
+ (%string-suffix? s1 start1 end1 s2 start2 end2)))
+
+(define (string-prefix-ci? s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-prefix-ci? s1 s2 maybe-starts+ends
+ (%string-prefix-ci? s1 start1 end1 s2 start2 end2)))
+
+(define (string-suffix-ci? s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-suffix-ci? s1 s2 maybe-starts+ends
+ (%string-suffix-ci? s1 start1 end1 s2 start2 end2)))
+
+
+;;; Here are the internal routines that do the real work.
+
+(define (%string-prefix? s1 start1 end1 s2 start2 end2)
+ (let ((len1 (- end1 start1)))
+ (and (<= len1 (- end2 start2)) ; Quick check
+ (= (%string-prefix-length s1 start1 end1
+ s2 start2 end2)
+ len1))))
+
+(define (%string-suffix? s1 start1 end1 s2 start2 end2)
+ (let ((len1 (- end1 start1)))
+ (and (<= len1 (- end2 start2)) ; Quick check
+ (= len1 (%string-suffix-length s1 start1 end1
+ s2 start2 end2)))))
+
+(define (%string-prefix-ci? s1 start1 end1 s2 start2 end2)
+ (let ((len1 (- end1 start1)))
+ (and (<= len1 (- end2 start2)) ; Quick check
+ (= len1 (%string-prefix-length-ci s1 start1 end1
+ s2 start2 end2)))))
+
+(define (%string-suffix-ci? s1 start1 end1 s2 start2 end2)
+ (let ((len1 (- end1 start1)))
+ (and (<= len1 (- end2 start2)) ; Quick check
+ (= len1 (%string-suffix-length-ci s1 start1 end1
+ s2 start2 end2)))))
+
+
+;;; string-compare s1 s2 proc< proc= proc> [start1 end1 start2 end2]
+;;; string-compare-ci s1 s2 proc< proc= proc> [start1 end1 start2 end2]
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Primitive string-comparison functions.
+;;; Continuation order is different from MIT Scheme.
+;;; Continuations are applied to s1's mismatch index;
+;;; in the case of equality, this is END1.
+
+(define (%string-compare s1 start1 end1 s2 start2 end2
+ proc< proc= proc>)
+ (let ((size1 (- end1 start1))
+ (size2 (- end2 start2)))
+ (let ((match (%string-prefix-length s1 start1 end1 s2 start2 end2)))
+ (if (= match size1)
+ ((if (= match size2) proc= proc<) end1)
+ ((if (= match size2)
+ proc>
+ (if (char<? (string-ref s1 (+ start1 match))
+ (string-ref s2 (+ start2 match)))
+ proc< proc>))
+ (+ match start1))))))
+
+(define (%string-compare-ci s1 start1 end1 s2 start2 end2
+ proc< proc= proc>)
+ (let ((size1 (- end1 start1))
+ (size2 (- end2 start2)))
+ (let ((match (%string-prefix-length-ci s1 start1 end1 s2 start2 end2)))
+ (if (= match size1)
+ ((if (= match size2) proc= proc<) end1)
+ ((if (= match size2) proc>
+ (if (char-ci<? (string-ref s1 (+ start1 match))
+ (string-ref s2 (+ start2 match)))
+ proc< proc>))
+ (+ start1 match))))))
+
+(define (string-compare s1 s2 proc< proc= proc> . maybe-starts+ends)
+ (check-arg procedure? proc< string-compare)
+ (check-arg procedure? proc= string-compare)
+ (check-arg procedure? proc> string-compare)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-compare s1 s2 maybe-starts+ends
+ (%string-compare s1 start1 end1 s2 start2 end2 proc< proc= proc>)))
+
+(define (string-compare-ci s1 s2 proc< proc= proc> . maybe-starts+ends)
+ (check-arg procedure? proc< string-compare-ci)
+ (check-arg procedure? proc= string-compare-ci)
+ (check-arg procedure? proc> string-compare-ci)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-compare-ci s1 s2 maybe-starts+ends
+ (%string-compare-ci s1 start1 end1 s2 start2 end2 proc< proc= proc>)))
+
+
+
+;;; string= string<> string-ci= string-ci<>
+;;; string< string> string-ci< string-ci>
+;;; string<= string>= string-ci<= string-ci>=
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Simple definitions in terms of the previous comparison funs.
+;;; I sure hope the %STRING-COMPARE calls get integrated.
+
+(define (string= s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string= s1 s2 maybe-starts+ends
+ (and (= (- end1 start1) (- end2 start2)) ; Quick filter
+ (or (and (eq? s1 s2) (= start1 start2)) ; Fast path
+ (%string-compare s1 start1 end1 s2 start2 end2 ; Real test
+ (lambda (i) #f)
+ values
+ (lambda (i) #f))))))
+
+(define (string<> s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string<> s1 s2 maybe-starts+ends
+ (or (not (= (- end1 start1) (- end2 start2))) ; Fast path
+ (and (not (and (eq? s1 s2) (= start1 start2))) ; Quick filter
+ (%string-compare s1 start1 end1 s2 start2 end2 ; Real test
+ values
+ (lambda (i) #f)
+ values)))))
+
+(define (string< s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string< s1 s2 maybe-starts+ends
+ (if (and (eq? s1 s2) (= start1 start2)) ; Fast path
+ (< end1 end2)
+
+ (%string-compare s1 start1 end1 s2 start2 end2 ; Real test
+ values
+ (lambda (i) #f)
+ (lambda (i) #f)))))
+
+(define (string> s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string> s1 s2 maybe-starts+ends
+ (if (and (eq? s1 s2) (= start1 start2)) ; Fast path
+ (> end1 end2)
+
+ (%string-compare s1 start1 end1 s2 start2 end2 ; Real test
+ (lambda (i) #f)
+ (lambda (i) #f)
+ values))))
+
+(define (string<= s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string<= s1 s2 maybe-starts+ends
+ (if (and (eq? s1 s2) (= start1 start2)) ; Fast path
+ (<= end1 end2)
+
+ (%string-compare s1 start1 end1 s2 start2 end2 ; Real test
+ values
+ values
+ (lambda (i) #f)))))
+
+(define (string>= s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string>= s1 s2 maybe-starts+ends
+ (if (and (eq? s1 s2) (= start1 start2)) ; Fast path
+ (>= end1 end2)
+
+ (%string-compare s1 start1 end1 s2 start2 end2 ; Real test
+ (lambda (i) #f)
+ values
+ values))))
+
+(define (string-ci= s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-ci= s1 s2 maybe-starts+ends
+ (and (= (- end1 start1) (- end2 start2)) ; Quick filter
+ (or (and (eq? s1 s2) (= start1 start2)) ; Fast path
+ (%string-compare-ci s1 start1 end1 s2 start2 end2 ; Real test
+ (lambda (i) #f)
+ values
+ (lambda (i) #f))))))
+
+(define (string-ci<> s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-ci<> s1 s2 maybe-starts+ends
+ (or (not (= (- end1 start1) (- end2 start2))) ; Fast path
+ (and (not (and (eq? s1 s2) (= start1 start2))) ; Quick filter
+ (%string-compare-ci s1 start1 end1 s2 start2 end2 ; Real test
+ values
+ (lambda (i) #f)
+ values)))))
+
+(define (string-ci< s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-ci< s1 s2 maybe-starts+ends
+ (if (and (eq? s1 s2) (= start1 start2)) ; Fast path
+ (< end1 end2)
+
+ (%string-compare-ci s1 start1 end1 s2 start2 end2 ; Real test
+ values
+ (lambda (i) #f)
+ (lambda (i) #f)))))
+
+(define (string-ci> s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-ci> s1 s2 maybe-starts+ends
+ (if (and (eq? s1 s2) (= start1 start2)) ; Fast path
+ (> end1 end2)
+
+ (%string-compare-ci s1 start1 end1 s2 start2 end2 ; Real test
+ (lambda (i) #f)
+ (lambda (i) #f)
+ values))))
+
+(define (string-ci<= s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-ci<= s1 s2 maybe-starts+ends
+ (if (and (eq? s1 s2) (= start1 start2)) ; Fast path
+ (<= end1 end2)
+
+ (%string-compare-ci s1 start1 end1 s2 start2 end2 ; Real test
+ values
+ values
+ (lambda (i) #f)))))
+
+(define (string-ci>= s1 s2 . maybe-starts+ends)
+ (let-string-start+end2 (start1 end1 start2 end2)
+ string-ci>= s1 s2 maybe-starts+ends
+ (if (and (eq? s1 s2) (= start1 start2)) ; Fast path
+ (>= end1 end2)
+
+ (%string-compare-ci s1 start1 end1 s2 start2 end2 ; Real test
+ (lambda (i) #f)
+ values
+ values))))
+
+
+;;; Hash
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Compute (c + 37 c + 37^2 c + ...) modulo BOUND, with sleaze thrown in
+;;; to keep the intermediate values small. (We do the calculation with just
+;;; enough bits to represent BOUND, masking off high bits at each step in
+;;; calculation. If this screws up any important properties of the hash
+;;; function I'd like to hear about it. -Olin)
+;;;
+;;; If you keep BOUND small enough, the intermediate calculations will
+;;; always be fixnums. How small is dependent on the underlying Scheme system;
+;;; we use a default BOUND of 2^22 = 4194304, which should hack it in
+;;; Schemes that give you at least 29 signed bits for fixnums. The core
+;;; calculation that you don't want to overflow is, worst case,
+;;; (+ 65535 (* 37 (- bound 1)))
+;;; where 65535 is the max character code. Choose the default BOUND to be the
+;;; biggest power of two that won't cause this expression to fixnum overflow,
+;;; and everything will be copacetic.
+
+(define (%string-hash s char->int bound start end)
+ (let ((iref (lambda (s i) (char->int (string-ref s i))))
+ ;; Compute a 111...1 mask that will cover BOUND-1:
+ (mask (let lp ((i #x10000)) ; Let's skip first 16 iterations, eh?
+ (if (>= i bound) (- i 1) (lp (+ i i))))))
+ (let lp ((i start) (ans 0))
+ (if (>= i end) (modulo ans bound)
+ (lp (+ i 1) (bitwise-and mask (+ (* 37 ans) (iref s i))))))))
+
+(define (string-hash s . maybe-bound+start+end)
+ (let-optionals* maybe-bound+start+end ((bound 4194304 (and (integer? bound)
+ (exact? bound)
+ (<= 0 bound)))
+ rest)
+ (let ((bound (if (zero? bound) 4194304 bound))) ; 0 means default.
+ (let-string-start+end (start end) string-hash s rest
+ (%string-hash s char->integer bound start end)))))
+
+(define (string-hash-ci s . maybe-bound+start+end)
+ (let-optionals* maybe-bound+start+end ((bound 4194304 (and (integer? bound)
+ (exact? bound)
+ (<= 0 bound)))
+ rest)
+ (let ((bound (if (zero? bound) 4194304 bound))) ; 0 means default.
+ (let-string-start+end (start end) string-hash-ci s rest
+ (%string-hash s (lambda (c) (char->integer (char-downcase c)))
+ bound start end)))))
+
+;;; Case hacking
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; string-upcase s [start end]
+;;; string-upcase! s [start end]
+;;; string-downcase s [start end]
+;;; string-downcase! s [start end]
+;;;
+;;; string-titlecase s [start end]
+;;; string-titlecase! s [start end]
+;;; Capitalize every contiguous alpha sequence: capitalise
+;;; first char, lowercase rest.
+
+(define (string-upcase s . maybe-start+end)
+ (let-string-start+end (start end) string-upcase s maybe-start+end
+ (%string-map char-upcase s start end)))
+
+(define (string-upcase! s . maybe-start+end)
+ (let-string-start+end (start end) string-upcase! s maybe-start+end
+ (%string-map! char-upcase s start end)))
+
+(define (string-downcase s . maybe-start+end)
+ (let-string-start+end (start end) string-downcase s maybe-start+end
+ (%string-map char-downcase s start end)))
+
+(define (string-downcase! s . maybe-start+end)
+ (let-string-start+end (start end) string-downcase! s maybe-start+end
+ (%string-map! char-downcase s start end)))
+
+(define (%string-titlecase! s start end)
+ (let lp ((i start))
+ (cond ((string-index s char-cased? i end) =>
+ (lambda (i)
+ (string-set! s i (char-titlecase (string-ref s i)))
+ (let ((i1 (+ i 1)))
+ (cond ((string-skip s char-cased? i1 end) =>
+ (lambda (j)
+ (string-downcase! s i1 j)
+ (lp (+ j 1))))
+ (else (string-downcase! s i1 end)))))))))
+
+(define (string-titlecase! s . maybe-start+end)
+ (let-string-start+end (start end) string-titlecase! s maybe-start+end
+ (%string-titlecase! s start end)))
+
+(define (string-titlecase s . maybe-start+end)
+ (let-string-start+end (start end) string-titlecase! s maybe-start+end
+ (let ((ans (substring s start end)))
+ (%string-titlecase! ans 0 (- end start))
+ ans)))
+
+
+;;; Cutting & pasting strings
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; string-take string nchars
+;;; string-drop string nchars
+;;;
+;;; string-take-right string nchars
+;;; string-drop-right string nchars
+;;;
+;;; string-pad string k [char start end]
+;;; string-pad-right string k [char start end]
+;;;
+;;; string-trim string [char/char-set/pred start end]
+;;; string-trim-right string [char/char-set/pred start end]
+;;; string-trim-both string [char/char-set/pred start end]
+;;;
+;;; These trimmers invert the char-set meaning from MIT Scheme -- you
+;;; say what you want to trim.
+
+(define (string-take s n)
+ (check-arg string? s string-take)
+ (check-arg (lambda (val) (and (integer? n) (exact? n)
+ (<= 0 n (string-length s))))
+ n string-take)
+ (%substring/shared s 0 n))
+
+(define (string-take-right s n)
+ (check-arg string? s string-take-right)
+ (let ((len (string-length s)))
+ (check-arg (lambda (val) (and (integer? n) (exact? n) (<= 0 n len)))
+ n string-take-right)
+ (%substring/shared s (- len n) len)))
+
+(define (string-drop s n)
+ (check-arg string? s string-drop)
+ (let ((len (string-length s)))
+ (check-arg (lambda (val) (and (integer? n) (exact? n) (<= 0 n len)))
+ n string-drop)
+ (%substring/shared s n len)))
+
+(define (string-drop-right s n)
+ (check-arg string? s string-drop-right)
+ (let ((len (string-length s)))
+ (check-arg (lambda (val) (and (integer? n) (exact? n) (<= 0 n len)))
+ n string-drop-right)
+ (%substring/shared s 0 (- len n))))
+
+
+(define (string-trim s . criterion+start+end)
+ (let-optionals* criterion+start+end ((criterion char-set:whitespace) rest)
+ (let-string-start+end (start end) string-trim s rest
+ (cond ((string-skip s criterion start end) =>
+ (lambda (i) (%substring/shared s i end)))
+ (else "")))))
+
+(define (string-trim-right s . criterion+start+end)
+ (let-optionals* criterion+start+end ((criterion char-set:whitespace) rest)
+ (let-string-start+end (start end) string-trim-right s rest
+ (cond ((string-skip-right s criterion start end) =>
+ (lambda (i) (%substring/shared s start (+ 1 i))))
+ (else "")))))
+
+(define (string-trim-both s . criterion+start+end)
+ (let-optionals* criterion+start+end ((criterion char-set:whitespace) rest)
+ (let-string-start+end (start end) string-trim-both s rest
+ (cond ((string-skip s criterion start end) =>
+ (lambda (i)
+ (%substring/shared s i (+ 1 (string-skip-right s criterion i end)))))
+ (else "")))))
+
+
+(define (string-pad-right s n . char+start+end)
+ (let-optionals* char+start+end ((char #\space (char? char)) rest)
+ (let-string-start+end (start end) string-pad-right s rest
+ (check-arg (lambda (n) (and (integer? n) (exact? n) (<= 0 n)))
+ n string-pad-right)
+ (let ((len (- end start)))
+ (if (<= n len)
+ (%substring/shared s start (+ start n))
+ (let ((ans (make-string n char)))
+ (%string-copy! ans 0 s start end)
+ ans))))))
+
+(define (string-pad s n . char+start+end)
+ (let-optionals* char+start+end ((char #\space (char? char)) rest)
+ (let-string-start+end (start end) string-pad s rest
+ (check-arg (lambda (n) (and (integer? n) (exact? n) (<= 0 n)))
+ n string-pad)
+ (let ((len (- end start)))
+ (if (<= n len)
+ (%substring/shared s (- end n) end)
+ (let ((ans (make-string n char)))
+ (%string-copy! ans (- n len) s start end)
+ ans))))))
+
+
+
+;;; Filtering strings
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; string-delete char/char-set/pred string [start end]
+;;; string-filter char/char-set/pred string [start end]
+;;;
+;;; If the criterion is a char or char-set, we scan the string twice with
+;;; string-fold -- once to determine the length of the result string,
+;;; and once to do the filtered copy.
+;;; If the criterion is a predicate, we don't do this double-scan strategy,
+;;; because the predicate might have side-effects or be very expensive to
+;;; compute. So we preallocate a temp buffer pessimistically, and only do
+;;; one scan over S. This is likely to be faster and more space-efficient
+;;; than consing a list.
+
+(define (string-delete criterion s . maybe-start+end)
+ (let-string-start+end (start end) string-delete s maybe-start+end
+ (if (procedure? criterion)
+ (let* ((slen (- end start))
+ (temp (make-string slen))
+ (ans-len (string-fold (lambda (c i)
+ (if (criterion c) i
+ (begin (string-set! temp i c)
+ (+ i 1))))
+ 0 s start end)))
+ (if (= ans-len slen) temp (substring temp 0 ans-len)))
+
+ (let* ((cset (cond ((char-set? criterion) criterion)
+ ((char? criterion) (char-set criterion))
+ (else (error "string-delete criterion not predicate, char or char-set" criterion))))
+ (len (string-fold (lambda (c i) (if (char-set-contains? cset c)
+ i
+ (+ i 1)))
+ 0 s start end))
+ (ans (make-string len)))
+ (string-fold (lambda (c i) (if (char-set-contains? cset c)
+ i
+ (begin (string-set! ans i c)
+ (+ i 1))))
+ 0 s start end)
+ ans))))
+
+(define (string-filter criterion s . maybe-start+end)
+ (let-string-start+end (start end) string-filter s maybe-start+end
+ (if (procedure? criterion)
+ (let* ((slen (- end start))
+ (temp (make-string slen))
+ (ans-len (string-fold (lambda (c i)
+ (if (criterion c)
+ (begin (string-set! temp i c)
+ (+ i 1))
+ i))
+ 0 s start end)))
+ (if (= ans-len slen) temp (substring temp 0 ans-len)))
+
+ (let* ((cset (cond ((char-set? criterion) criterion)
+ ((char? criterion) (char-set criterion))
+ (else (error "string-delete criterion not predicate, char or char-set" criterion))))
+
+ (len (string-fold (lambda (c i) (if (char-set-contains? cset c)
+ (+ i 1)
+ i))
+ 0 s start end))
+ (ans (make-string len)))
+ (string-fold (lambda (c i) (if (char-set-contains? cset c)
+ (begin (string-set! ans i c)
+ (+ i 1))
+ i))
+ 0 s start end)
+ ans))))
+
+
+;;; String search
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; string-index string char/char-set/pred [start end]
+;;; string-index-right string char/char-set/pred [start end]
+;;; string-skip string char/char-set/pred [start end]
+;;; string-skip-right string char/char-set/pred [start end]
+;;; string-count string char/char-set/pred [start end]
+;;; There's a lot of replicated code here for efficiency.
+;;; For example, the char/char-set/pred discrimination has
+;;; been lifted above the inner loop of each proc.
+
+(define (string-index str criterion . maybe-start+end)
+ (let-string-start+end (start end) string-index str maybe-start+end
+ (cond ((char? criterion)
+ (let lp ((i start))
+ (and (< i end)
+ (if (char=? criterion (string-ref str i)) i
+ (lp (+ i 1))))))
+ ((char-set? criterion)
+ (let lp ((i start))
+ (and (< i end)
+ (if (char-set-contains? criterion (string-ref str i)) i
+ (lp (+ i 1))))))
+ ((procedure? criterion)
+ (let lp ((i start))
+ (and (< i end)
+ (if (criterion (string-ref str i)) i
+ (lp (+ i 1))))))
+ (else (error "Second param is neither char-set, char, or predicate procedure."
+ string-index criterion)))))
+
+(define (string-index-right str criterion . maybe-start+end)
+ (let-string-start+end (start end) string-index-right str maybe-start+end
+ (cond ((char? criterion)
+ (let lp ((i (- end 1)))
+ (and (>= i start)
+ (if (char=? criterion (string-ref str i)) i
+ (lp (- i 1))))))
+ ((char-set? criterion)
+ (let lp ((i (- end 1)))
+ (and (>= i start)
+ (if (char-set-contains? criterion (string-ref str i)) i
+ (lp (- i 1))))))
+ ((procedure? criterion)
+ (let lp ((i (- end 1)))
+ (and (>= i start)
+ (if (criterion (string-ref str i)) i
+ (lp (- i 1))))))
+ (else (error "Second param is neither char-set, char, or predicate procedure."
+ string-index-right criterion)))))
+
+(define (string-skip str criterion . maybe-start+end)
+ (let-string-start+end (start end) string-skip str maybe-start+end
+ (cond ((char? criterion)
+ (let lp ((i start))
+ (and (< i end)
+ (if (char=? criterion (string-ref str i))
+ (lp (+ i 1))
+ i))))
+ ((char-set? criterion)
+ (let lp ((i start))
+ (and (< i end)
+ (if (char-set-contains? criterion (string-ref str i))
+ (lp (+ i 1))
+ i))))
+ ((procedure? criterion)
+ (let lp ((i start))
+ (and (< i end)
+ (if (criterion (string-ref str i)) (lp (+ i 1))
+ i))))
+ (else (error "Second param is neither char-set, char, or predicate procedure."
+ string-skip criterion)))))
+
+(define (string-skip-right str criterion . maybe-start+end)
+ (let-string-start+end (start end) string-skip-right str maybe-start+end
+ (cond ((char? criterion)
+ (let lp ((i (- end 1)))
+ (and (>= i start)
+ (if (char=? criterion (string-ref str i))
+ (lp (- i 1))
+ i))))
+ ((char-set? criterion)
+ (let lp ((i (- end 1)))
+ (and (>= i start)
+ (if (char-set-contains? criterion (string-ref str i))
+ (lp (- i 1))
+ i))))
+ ((procedure? criterion)
+ (let lp ((i (- end 1)))
+ (and (>= i start)
+ (if (criterion (string-ref str i)) (lp (- i 1))
+ i))))
+ (else (error "CRITERION param is neither char-set or char."
+ string-skip-right criterion)))))
+
+
+(define (string-count s criterion . maybe-start+end)
+ (let-string-start+end (start end) string-count s maybe-start+end
+ (cond ((char? criterion)
+ (do ((i start (+ i 1))
+ (count 0 (if (char=? criterion (string-ref s i))
+ (+ count 1)
+ count)))
+ ((>= i end) count)))
+
+ ((char-set? criterion)
+ (do ((i start (+ i 1))
+ (count 0 (if (char-set-contains? criterion (string-ref s i))
+ (+ count 1)
+ count)))
+ ((>= i end) count)))
+
+ ((procedure? criterion)
+ (do ((i start (+ i 1))
+ (count 0 (if (criterion (string-ref s i)) (+ count 1) count)))
+ ((>= i end) count)))
+
+ (else (error "CRITERION param is neither char-set or char."
+ string-count criterion)))))
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; string-fill! string char [start end]
+;;;
+;;; string-copy! to tstart from [fstart fend]
+;;; Guaranteed to work, even if s1 eq s2.
+
+(define (string-fill! s char . maybe-start+end)
+ (check-arg char? char string-fill!)
+ (let-string-start+end (start end) string-fill! s maybe-start+end
+ (do ((i (- end 1) (- i 1)))
+ ((< i start))
+ (string-set! s i char))))
+
+(define (string-copy! to tstart from . maybe-fstart+fend)
+ (let-string-start+end (fstart fend) string-copy! from maybe-fstart+fend
+ (check-arg integer? tstart string-copy!)
+ (check-substring-spec string-copy! to tstart (+ tstart (- fend fstart)))
+ (%string-copy! to tstart from fstart fend)))
+
+;;; Library-internal routine
+(define (%string-copy! to tstart from fstart fend)
+ (if (> fstart tstart)
+ (do ((i fstart (+ i 1))
+ (j tstart (+ j 1)))
+ ((>= i fend))
+ (string-set! to j (string-ref from i)))
+
+ (do ((i (- fend 1) (- i 1))
+ (j (+ -1 tstart (- fend fstart)) (- j 1)))
+ ((< i fstart))
+ (string-set! to j (string-ref from i)))))
+
+
+
+;;; Returns starting-position in STRING or #f if not true.
+;;; This implementation is slow & simple. It is useful as a "spec" or for
+;;; comparison testing with fancier implementations.
+;;; See below for fast KMP version.
+
+;(define (string-contains string substring . maybe-starts+ends)
+; (let-string-start+end2 (start1 end1 start2 end2)
+; string-contains string substring maybe-starts+ends
+; (let* ((len (- end2 start2))
+; (i-bound (- end1 len)))
+; (let lp ((i start1))
+; (and (< i i-bound)
+; (if (string= string substring i (+ i len) start2 end2)
+; i
+; (lp (+ i 1))))))))
+
+
+;;; Searching for an occurrence of a substring
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (string-contains text pattern . maybe-starts+ends)
+ (let-string-start+end2 (t-start t-end p-start p-end)
+ string-contains text pattern maybe-starts+ends
+ (%kmp-search pattern text char=? p-start p-end t-start t-end)))
+
+(define (string-contains-ci text pattern . maybe-starts+ends)
+ (let-string-start+end2 (t-start t-end p-start p-end)
+ string-contains-ci text pattern maybe-starts+ends
+ (%kmp-search pattern text char-ci=? p-start p-end t-start t-end)))
+
+
+;;; Knuth-Morris-Pratt string searching
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; See
+;;; "Fast pattern matching in strings"
+;;; SIAM J. Computing 6(2):323-350 1977
+;;; D. E. Knuth, J. H. Morris and V. R. Pratt
+;;; also described in
+;;; "Pattern matching in strings"
+;;; Alfred V. Aho
+;;; Formal Language Theory - Perspectives and Open Problems
+;;; Ronald V. Brook (editor)
+;;; This algorithm is O(m + n) where m and n are the
+;;; lengths of the pattern and string respectively
+
+;;; KMP search source[start,end) for PATTERN. Return starting index of
+;;; leftmost match or #f.
+
+(define (%kmp-search pattern text c= p-start p-end t-start t-end)
+ (let ((plen (- p-end p-start))
+ (rv (make-kmp-restart-vector pattern c= p-start p-end)))
+
+ ;; The search loop. TJ & PJ are redundant state.
+ (let lp ((ti t-start) (pi 0)
+ (tj (- t-end t-start)) ; (- tlen ti) -- how many chars left.
+ (pj plen)) ; (- plen pi) -- how many chars left.
+
+ (if (= pi plen)
+ (- ti plen) ; Win.
+ (and (<= pj tj) ; Lose.
+ (if (c= (string-ref text ti) ; Search.
+ (string-ref pattern (+ p-start pi)))
+ (lp (+ 1 ti) (+ 1 pi) (- tj 1) (- pj 1)) ; Advance.
+
+ (let ((pi (vector-ref rv pi))) ; Retreat.
+ (if (= pi -1)
+ (lp (+ ti 1) 0 (- tj 1) plen) ; Punt.
+ (lp ti pi tj (- plen pi))))))))))
+
+;;; (make-kmp-restart-vector pattern [c= start end]) -> integer-vector
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Compute the KMP restart vector RV for string PATTERN. If
+;;; we have matched chars 0..i-1 of PATTERN against a search string S, and
+;;; PATTERN[i] doesn't match S[k], then reset i := RV[i], and try again to
+;;; match S[k]. If RV[i] = -1, then punt S[k] completely, and move on to
+;;; S[k+1] and PATTERN[0] -- no possible match of PAT[0..i] contains S[k].
+;;;
+;;; In other words, if you have matched the first i chars of PATTERN, but
+;;; the i+1'th char doesn't match, RV[i] tells you what the next-longest
+;;; prefix of PATTERN is that you have matched.
+;;;
+;;; - C= (default CHAR=?) is used to compare characters for equality.
+;;; Pass in CHAR-CI=? for case-folded string search.
+;;;
+;;; - START & END restrict the pattern to the indicated substring; the
+;;; returned vector will be of length END - START. The numbers stored
+;;; in the vector will be values in the range [0,END-START) -- that is,
+;;; they are valid indices into the restart vector; you have to add START
+;;; to them to use them as indices into PATTERN.
+;;;
+;;; I've split this out as a separate function in case other constant-string
+;;; searchers might want to use it.
+;;;
+;;; E.g.:
+;;; a b d a b x
+;;; #(-1 0 0 -1 1 2)
+
+(define (make-kmp-restart-vector pattern . maybe-c=+start+end)
+ (let-optionals* maybe-c=+start+end
+ ((c= char=? (procedure? c=))
+ ((start end) (lambda (args)
+ (string-parse-start+end make-kmp-restart-vector
+ pattern args))))
+ (let* ((rvlen (- end start))
+ (rv (make-vector rvlen -1)))
+ (if (> rvlen 0)
+ (let ((rvlen-1 (- rvlen 1))
+ (c0 (string-ref pattern start)))
+
+ ;; Here's the main loop. We have set rv[0] ... rv[i].
+ ;; K = I + START -- it is the corresponding index into PATTERN.
+ (let lp1 ((i 0) (j -1) (k start))
+ (if (< i rvlen-1)
+ ;; lp2 invariant:
+ ;; pat[(k-j) .. k-1] matches pat[start .. start+j-1]
+ ;; or j = -1.
+ (let lp2 ((j j))
+ (cond ((= j -1)
+ (let ((i1 (+ 1 i)))
+ (if (not (c= (string-ref pattern (+ k 1)) c0))
+ (vector-set! rv i1 0))
+ (lp1 i1 0 (+ k 1))))
+ ;; pat[(k-j) .. k] matches pat[start..start+j].
+ ((c= (string-ref pattern k) (string-ref pattern (+ j start)))
+ (let* ((i1 (+ 1 i))
+ (j1 (+ 1 j)))
+ (vector-set! rv i1 j1)
+ (lp1 i1 j1 (+ k 1))))
+
+ (else (lp2 (vector-ref rv j)))))))))
+ rv)))
+
+
+;;; We've matched I chars from PAT. C is the next char from the search string.
+;;; Return the new I after handling C.
+;;;
+;;; The pattern is (VECTOR-LENGTH RV) chars long, beginning at index PAT-START
+;;; in PAT (PAT-START is usually 0). The I chars of the pattern we've matched
+;;; are
+;;; PAT[PAT-START .. PAT-START + I].
+;;;
+;;; It's *not* an oversight that there is no friendly error checking or
+;;; defaulting of arguments. This is a low-level, inner-loop procedure
+;;; that we want integrated/inlined into the point of call.
+
+(define (kmp-step pat rv c i c= p-start)
+ (let lp ((i i))
+ (if (c= c (string-ref pat (+ i p-start))) ; Match =>
+ (+ i 1) ; Done.
+ (let ((i (vector-ref rv i))) ; Back up in PAT.
+ (if (= i -1) 0 ; Can't back up further.
+ (lp i)))))) ; Keep trying for match.
+
+;;; Zip through S[start,end), looking for a match of PAT. Assume we've
+;;; already matched the first I chars of PAT when we commence at S[start].
+;;; - <0: If we find a match *ending* at index J, return -J.
+;;; - >=0: If we get to the end of the S[start,end) span without finding
+;;; a complete match, return the number of chars from PAT we'd matched
+;;; when we ran off the end.
+;;;
+;;; This is useful for searching *across* buffers -- that is, when your
+;;; input comes in chunks of text. We hand-integrate the KMP-STEP loop
+;;; for speed.
+
+(define (string-kmp-partial-search pat rv s i . c=+p-start+s-start+s-end)
+ (check-arg vector? rv string-kmp-partial-search)
+ (let-optionals* c=+p-start+s-start+s-end
+ ((c= char=? (procedure? c=))
+ (p-start 0 (and (integer? p-start) (exact? p-start) (<= 0 p-start)))
+ ((s-start s-end) (lambda (args)
+ (string-parse-start+end string-kmp-partial-search
+ s args))))
+ (let ((patlen (vector-length rv)))
+ (check-arg (lambda (i) (and (integer? i) (exact? i) (<= 0 i) (< i patlen)))
+ i string-kmp-partial-search)
+
+ ;; Enough prelude. Here's the actual code.
+ (let lp ((si s-start) ; An index into S.
+ (vi i)) ; An index into RV.
+ (cond ((= vi patlen) (- si)) ; Win.
+ ((= si s-end) vi) ; Ran off the end.
+ (else ; Match s[si] & loop.
+ (let ((c (string-ref s si)))
+ (lp (+ si 1)
+ (let lp2 ((vi vi)) ; This is just KMP-STEP.
+ (if (c= c (string-ref pat (+ vi p-start)))
+ (+ vi 1)
+ (let ((vi (vector-ref rv vi)))
+ (if (= vi -1) 0
+ (lp2 vi)))))))))))))
+
+
+;;; Misc
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; (string-null? s)
+;;; (string-reverse s [start end])
+;;; (string-reverse! s [start end])
+;;; (reverse-list->string clist)
+;;; (string->list s [start end])
+
+(define (string-null? s) (zero? (string-length s)))
+
+(define (string-reverse s . maybe-start+end)
+ (let-string-start+end (start end) string-reverse s maybe-start+end
+ (let* ((len (- end start))
+ (ans (make-string len)))
+ (do ((i start (+ i 1))
+ (j (- len 1) (- j 1)))
+ ((< j 0))
+ (string-set! ans j (string-ref s i)))
+ ans)))
+
+(define (string-reverse! s . maybe-start+end)
+ (let-string-start+end (start end) string-reverse! s maybe-start+end
+ (do ((i (- end 1) (- i 1))
+ (j start (+ j 1)))
+ ((<= i j))
+ (let ((ci (string-ref s i)))
+ (string-set! s i (string-ref s j))
+ (string-set! s j ci)))))
+
+
+(define (reverse-list->string clist)
+ (let* ((len (length clist))
+ (s (make-string len)))
+ (do ((i (- len 1) (- i 1)) (clist clist (cdr clist)))
+ ((not (pair? clist)))
+ (string-set! s i (car clist)))
+ s))
+
+
+;(define (string->list s . maybe-start+end)
+; (apply string-fold-right cons '() s maybe-start+end))
+
+(define (string->list s . maybe-start+end)
+ (let-string-start+end (start end) string->list s maybe-start+end
+ (do ((i (- end 1) (- i 1))
+ (ans '() (cons (string-ref s i) ans)))
+ ((< i start) ans))))
+
+;;; Defined by R5RS, so commented out here.
+;(define (list->string lis) (string-unfold null? car cdr lis))
+
+
+;;; string-concatenate string-list -> string
+;;; string-concatenate/shared string-list -> string
+;;; string-append/shared s ... -> string
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; STRING-APPEND/SHARED has license to return a string that shares storage
+;;; with any of its arguments. In particular, if there is only one non-empty
+;;; string amongst its parameters, it is permitted to return that string as
+;;; its result. STRING-APPEND, by contrast, always allocates new storage.
+;;;
+;;; STRING-CONCATENATE & STRING-CONCATENATE/SHARED are passed a list of
+;;; strings, which they concatenate into a result string. STRING-CONCATENATE
+;;; always allocates a fresh string; STRING-CONCATENATE/SHARED may (or may
+;;; not) return a result that shares storage with any of its arguments. In
+;;; particular, if it is applied to a singleton list, it is permitted to
+;;; return the car of that list as its value.
+
+(define (string-append/shared . strings) (string-concatenate/shared strings))
+
+(define (string-concatenate/shared strings)
+ (let lp ((strings strings) (nchars 0) (first #f))
+ (cond ((pair? strings) ; Scan the args, add up total
+ (let* ((string (car strings)) ; length, remember 1st
+ (tail (cdr strings)) ; non-empty string.
+ (slen (string-length string)))
+ (if (zero? slen)
+ (lp tail nchars first)
+ (lp tail (+ nchars slen) (or first strings)))))
+
+ ((zero? nchars) "")
+
+ ;; Just one non-empty string! Return it.
+ ((= nchars (string-length (car first))) (car first))
+
+ (else (let ((ans (make-string nchars)))
+ (let lp ((strings first) (i 0))
+ (if (pair? strings)
+ (let* ((s (car strings))
+ (slen (string-length s)))
+ (%string-copy! ans i s 0 slen)
+ (lp (cdr strings) (+ i slen)))))
+ ans)))))
+
+
+; Alas, Scheme 48's APPLY blows up if you have many, many arguments.
+;(define (string-concatenate strings) (apply string-append strings))
+
+;;; Here it is written out. I avoid using REDUCE to add up string lengths
+;;; to avoid non-R5RS dependencies.
+(define (string-concatenate strings)
+ (let* ((total (do ((strings strings (cdr strings))
+ (i 0 (+ i (string-length (car strings)))))
+ ((not (pair? strings)) i)))
+ (ans (make-string total)))
+ (let lp ((i 0) (strings strings))
+ (if (pair? strings)
+ (let* ((s (car strings))
+ (slen (string-length s)))
+ (%string-copy! ans i s 0 slen)
+ (lp (+ i slen) (cdr strings)))))
+ ans))
+
+
+;;; Defined by R5RS, so commented out here.
+;(define (string-append . strings) (string-concatenate strings))
+
+;;; string-concatenate-reverse string-list [final-string end] -> string
+;;; string-concatenate-reverse/shared string-list [final-string end] -> string
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Return
+;;; (string-concatenate
+;;; (reverse
+;;; (cons (substring final-string 0 end) string-list)))
+
+(define (string-concatenate-reverse string-list . maybe-final+end)
+ (let-optionals* maybe-final+end ((final "" (string? final))
+ (end (string-length final)
+ (and (integer? end)
+ (exact? end)
+ (<= 0 end (string-length final)))))
+ (let ((len (let lp ((sum 0) (lis string-list))
+ (if (pair? lis)
+ (lp (+ sum (string-length (car lis))) (cdr lis))
+ sum))))
+
+ (%finish-string-concatenate-reverse len string-list final end))))
+
+(define (string-concatenate-reverse/shared string-list . maybe-final+end)
+ (let-optionals* maybe-final+end ((final "" (string? final))
+ (end (string-length final)
+ (and (integer? end)
+ (exact? end)
+ (<= 0 end (string-length final)))))
+ ;; Add up the lengths of all the strings in STRING-LIST; also get a
+ ;; pointer NZLIST into STRING-LIST showing where the first non-zero-length
+ ;; string starts.
+ (let lp ((len 0) (nzlist #f) (lis string-list))
+ (if (pair? lis)
+ (let ((slen (string-length (car lis))))
+ (lp (+ len slen)
+ (if (or nzlist (zero? slen)) nzlist lis)
+ (cdr lis)))
+
+ (cond ((zero? len) (substring/shared final 0 end))
+
+ ;; LEN > 0, so NZLIST is non-empty.
+
+ ((and (zero? end) (= len (string-length (car nzlist))))
+ (car nzlist))
+
+ (else (%finish-string-concatenate-reverse len nzlist final end)))))))
+
+(define (%finish-string-concatenate-reverse len string-list final end)
+ (let ((ans (make-string (+ end len))))
+ (%string-copy! ans len final 0 end)
+ (let lp ((i len) (lis string-list))
+ (if (pair? lis)
+ (let* ((s (car lis))
+ (lis (cdr lis))
+ (slen (string-length s))
+ (i (- i slen)))
+ (%string-copy! ans i s 0 slen)
+ (lp i lis))))
+ ans))
+
+
+
+
+;;; string-replace s1 s2 start1 end1 [start2 end2] -> string
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Replace S1[START1,END1) with S2[START2,END2).
+
+(define (string-replace s1 s2 start1 end1 . maybe-start+end)
+ (check-substring-spec string-replace s1 start1 end1)
+ (let-string-start+end (start2 end2) string-replace s2 maybe-start+end
+ (let* ((slen1 (string-length s1))
+ (sublen2 (- end2 start2))
+ (alen (+ (- slen1 (- end1 start1)) sublen2))
+ (ans (make-string alen)))
+ (%string-copy! ans 0 s1 0 start1)
+ (%string-copy! ans start1 s2 start2 end2)
+ (%string-copy! ans (+ start1 sublen2) s1 end1 slen1)
+ ans)))
+
+
+;;; string-tokenize s [token-set start end] -> list
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Break S up into a list of token strings, where a token is a maximal
+;;; non-empty contiguous sequence of chars belonging to TOKEN-SET.
+;;; (string-tokenize "hello, world") => ("hello," "world")
+
+(define (string-tokenize s . token-chars+start+end)
+ (let-optionals* token-chars+start+end
+ ((token-chars char-set:graphic (char-set? token-chars)) rest)
+ (let-string-start+end (start end) string-tokenize s rest
+ (let lp ((i end) (ans '()))
+ (cond ((and (< start i) (string-index-right s token-chars start i)) =>
+ (lambda (tend-1)
+ (let ((tend (+ 1 tend-1)))
+ (cond ((string-skip-right s token-chars start tend-1) =>
+ (lambda (tstart-1)
+ (lp tstart-1
+ (cons (substring s (+ 1 tstart-1) tend)
+ ans))))
+ (else (cons (substring s start tend) ans))))))
+ (else ans))))))
+
+
+;;; xsubstring s from [to start end] -> string
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; S is a string; START and END are optional arguments that demarcate
+;;; a substring of S, defaulting to 0 and the length of S (e.g., the whole
+;;; string). Replicate this substring up and down index space, in both the
+;; positive and negative directions. For example, if S = "abcdefg", START=3,
+;;; and END=6, then we have the conceptual bidirectionally-infinite string
+;;; ... d e f d e f d e f d e f d e f d e f d e f ...
+;;; ... -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 ...
+;;; XSUBSTRING returns the substring of this string beginning at index FROM,
+;;; and ending at TO (which defaults to FROM+(END-START)).
+;;;
+;;; You can use XSUBSTRING in many ways:
+;;; - To rotate a string left: (xsubstring "abcdef" 2) => "cdefab"
+;;; - To rotate a string right: (xsubstring "abcdef" -2) => "efabcd"
+;;; - To replicate a string: (xsubstring "abc" 0 7) => "abcabca"
+;;;
+;;; Note that
+;;; - The FROM/TO indices give a half-open range -- the characters from
+;;; index FROM up to, but not including index TO.
+;;; - The FROM/TO indices are not in terms of the index space for string S.
+;;; They are in terms of the replicated index space of the substring
+;;; defined by S, START, and END.
+;;;
+;;; It is an error if START=END -- although this is allowed by special
+;;; dispensation when FROM=TO.
+
+(define (xsubstring s from . maybe-to+start+end)
+ (check-arg (lambda (val) (and (integer? val) (exact? val)))
+ from xsubstring)
+ (receive (to start end)
+ (if (pair? maybe-to+start+end)
+ (let-string-start+end (start end) xsubstring s (cdr maybe-to+start+end)
+ (let ((to (car maybe-to+start+end)))
+ (check-arg (lambda (val) (and (integer? val)
+ (exact? val)
+ (<= from val)))
+ to xsubstring)
+ (values to start end)))
+ (let ((slen (string-length (check-arg string? s xsubstring))))
+ (values (+ from slen) 0 slen)))
+ (let ((slen (- end start))
+ (anslen (- to from)))
+ (cond ((zero? anslen) "")
+ ((zero? slen) (error "Cannot replicate empty (sub)string"
+ xsubstring s from to start end))
+
+ ((= 1 slen) ; Fast path for 1-char replication.
+ (make-string anslen (string-ref s start)))
+
+ ;; Selected text falls entirely within one span.
+ ((= (floor (/ from slen)) (floor (/ to slen)))
+ (substring s (+ start (modulo from slen))
+ (+ start (modulo to slen))))
+
+ ;; Selected text requires multiple spans.
+ (else (let ((ans (make-string anslen)))
+ (%multispan-repcopy! ans 0 s from to start end)
+ ans))))))
+
+
+;;; string-xcopy! target tstart s sfrom [sto start end] -> unspecific
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Exactly the same as xsubstring, but the extracted text is written
+;;; into the string TARGET starting at index TSTART.
+;;; This operation is not defined if (EQ? TARGET S) -- you cannot copy
+;;; a string on top of itself.
+
+(define (string-xcopy! target tstart s sfrom . maybe-sto+start+end)
+ (check-arg (lambda (val) (and (integer? val) (exact? val)))
+ sfrom string-xcopy!)
+ (receive (sto start end)
+ (if (pair? maybe-sto+start+end)
+ (let-string-start+end (start end) string-xcopy! s (cdr maybe-sto+start+end)
+ (let ((sto (car maybe-sto+start+end)))
+ (check-arg (lambda (val) (and (integer? val) (exact? val)))
+ sto string-xcopy!)
+ (values sto start end)))
+ (let ((slen (string-length s)))
+ (values (+ sfrom slen) 0 slen)))
+
+ (let* ((tocopy (- sto sfrom))
+ (tend (+ tstart tocopy))
+ (slen (- end start)))
+ (check-substring-spec string-xcopy! target tstart tend)
+ (cond ((zero? tocopy))
+ ((zero? slen) (error "Cannot replicate empty (sub)string"
+ string-xcopy!
+ target tstart s sfrom sto start end))
+
+ ((= 1 slen) ; Fast path for 1-char replication.
+ (string-fill! target (string-ref s start) tstart tend))
+
+ ;; Selected text falls entirely within one span.
+ ((= (floor (/ sfrom slen)) (floor (/ sto slen)))
+ (%string-copy! target tstart s
+ (+ start (modulo sfrom slen))
+ (+ start (modulo sto slen))))
+
+ ;; Multi-span copy.
+ (else (%multispan-repcopy! target tstart s sfrom sto start end))))))
+
+;;; This is the core copying loop for XSUBSTRING and STRING-XCOPY!
+;;; Internal -- not exported, no careful arg checking.
+(define (%multispan-repcopy! target tstart s sfrom sto start end)
+ (let* ((slen (- end start))
+ (i0 (+ start (modulo sfrom slen)))
+ (total-chars (- sto sfrom)))
+
+ ;; Copy the partial span @ the beginning
+ (%string-copy! target tstart s i0 end)
+
+ (let* ((ncopied (- end i0)) ; We've copied this many.
+ (nleft (- total-chars ncopied)) ; # chars left to copy.
+ (nspans (quotient nleft slen))) ; # whole spans to copy
+
+ ;; Copy the whole spans in the middle.
+ (do ((i (+ tstart ncopied) (+ i slen)) ; Current target index.
+ (nspans nspans (- nspans 1))) ; # spans to copy
+ ((zero? nspans)
+ ;; Copy the partial-span @ the end & we're done.
+ (%string-copy! target i s start (+ start (- total-chars (- i tstart)))))
+
+ (%string-copy! target i s start end))))); Copy a whole span.
+
+
+
+;;; (string-join string-list [delimiter grammar]) => string
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Paste strings together using the delimiter string.
+;;;
+;;; (join-strings '("foo" "bar" "baz") ":") => "foo:bar:baz"
+;;;
+;;; DELIMITER defaults to a single space " "
+;;; GRAMMAR is one of the symbols {prefix, infix, strict-infix, suffix}
+;;; and defaults to 'infix.
+;;;
+;;; I could rewrite this more efficiently -- precompute the length of the
+;;; answer string, then allocate & fill it in iteratively. Using
+;;; STRING-CONCATENATE is less efficient.
+
+(define (string-join strings . delim+grammar)
+ (let-optionals* delim+grammar ((delim " " (string? delim))
+ (grammar 'infix))
+ (let ((buildit (lambda (lis final)
+ (let recur ((lis lis))
+ (if (pair? lis)
+ (cons delim (cons (car lis) (recur (cdr lis))))
+ final)))))
+
+ (cond ((pair? strings)
+ (string-concatenate
+ (case grammar
+
+ ((infix strict-infix)
+ (cons (car strings) (buildit (cdr strings) '())))
+
+ ((prefix) (buildit strings '()))
+
+ ((suffix)
+ (cons (car strings) (buildit (cdr strings) (list delim))))
+
+ (else (error "Illegal join grammar"
+ grammar string-join)))))
+
+ ((not (null? strings))
+ (error "STRINGS parameter not list." strings string-join))
+
+ ;; STRINGS is ()
+
+ ((eq? grammar 'strict-infix)
+ (error "Empty list cannot be joined with STRICT-INFIX grammar."
+ string-join))
+
+ (else ""))))) ; Special-cased for infix grammar.
+
+
+;;; Porting & performance-tuning notes
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; See the section at the beginning of this file on external dependencies.
+;;;
+;;; The biggest issue with respect to porting is the LET-OPTIONALS* macro.
+;;; There are many, many optional arguments in this library; the complexity
+;;; of parsing, defaulting & type-testing these parameters is handled with the
+;;; aid of this macro. There are about 15 uses of LET-OPTIONALS*. You can
+;;; rewrite the uses, port the hairy macro definition (which is implemented
+;;; using a Clinger-Rees low-level explicit-renaming macro system), or port
+;;; the simple, high-level definition, which is less efficient.
+;;;
+;;; There is a fair amount of argument checking. This is, strictly speaking,
+;;; unnecessary -- the actual body of the procedures will blow up if, say, a
+;;; START/END index is improper. However, the error message will not be as
+;;; good as if the error were caught at the "higher level." Also, a very, very
+;;; smart Scheme compiler may be able to exploit having the type checks done
+;;; early, so that the actual body of the procedures can assume proper values.
+;;; This isn't likely; this kind of compiler technology isn't common any
+;;; longer.
+;;;
+;;; The overhead of optional-argument parsing is irritating. The optional
+;;; arguments must be consed into a rest list on entry, and then parsed out.
+;;; Function call should be a matter of a few register moves and a jump; it
+;;; should not involve heap allocation! Your Scheme system may have a superior
+;;; non-R5RS optional-argument system that can eliminate this overhead. If so,
+;;; then this is a prime candidate for optimising these procedures,
+;;; *especially* the many optional START/END index parameters.
+;;;
+;;; Note that optional arguments are also a barrier to procedure integration.
+;;; If your Scheme system permits you to specify alternate entry points
+;;; for a call when the number of optional arguments is known in a manner
+;;; that enables inlining/integration, this can provide performance
+;;; improvements.
+;;;
+;;; There is enough *explicit* error checking that *all* string-index
+;;; operations should *never* produce a bounds error. Period. Feel like
+;;; living dangerously? *Big* performance win to be had by replacing
+;;; STRING-REF's and STRING-SET!'s with unsafe equivalents in the loops.
+;;; Similarly, fixnum-specific operators can speed up the arithmetic done on
+;;; the index values in the inner loops. The only arguments that are not
+;;; completely error checked are
+;;; - string lists (complete checking requires time proportional to the
+;;; length of the list)
+;;; - procedure arguments, such as char->char maps & predicates.
+;;; There is no way to check the range & domain of procedures in Scheme.
+;;; Procedures that take these parameters cannot fully check their
+;;; arguments. But all other types to all other procedures are fully
+;;; checked.
+;;;
+;;; This does open up the alternate possibility of simply *removing* these
+;;; checks, and letting the safe primitives raise the errors. On a dumb
+;;; Scheme system, this would provide speed (by eliminating the redundant
+;;; error checks) at the cost of error-message clarity.
+;;;
+;;; See the comments preceding the hash function code for notes on tuning
+;;; the default bound so that the code never overflows your implementation's
+;;; fixnum size into bignum calculation.
+;;;
+;;; In an interpreted Scheme, some of these procedures, or the internal
+;;; routines with % prefixes, are excellent candidates for being rewritten
+;;; in C. Consider STRING-HASH, %STRING-COMPARE, the
+;;; %STRING-{SUF,PRE}FIX-LENGTH routines, STRING-COPY!, STRING-INDEX &
+;;; STRING-SKIP (char-set & char cases), SUBSTRING and SUBSTRING/SHARED,
+;;; %KMP-SEARCH, and %MULTISPAN-REPCOPY!.
+;;;
+;;; It would also be nice to have the ability to mark some of these
+;;; routines as candidates for inlining/integration.
+;;;
+;;; All the %-prefixed routines in this source code are written
+;;; to be called internally to this library. They do *not* perform
+;;; friendly error checks on the inputs; they assume everything is
+;;; proper. They also do not take optional arguments. These two properties
+;;; save calling overhead and enable procedure integration -- but they
+;;; are not appropriate for exported routines.
+
+
+;;; Copyright details
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; The prefix/suffix and comparison routines in this code had (extremely
+;;; distant) origins in MIT Scheme's string lib, and was substantially
+;;; reworked by Olin Shivers (shivers@ai.mit.edu) 9/98. As such, it is
+;;; covered by MIT Scheme's open source copyright. See below for details.
+;;;
+;;; The KMP string-search code was influenced by implementations written
+;;; by Stephen Bevan, Brian Dehneyer and Will Fitzgerald. However, this
+;;; version was written from scratch by myself.
+;;;
+;;; The remainder of this code was written from scratch by myself for scsh.
+;;; The scsh copyright is a BSD-style open source copyright. See below for
+;;; details.
+;;; -Olin Shivers
+
+;;; MIT Scheme copyright terms
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; This material was developed by the Scheme project at the Massachusetts
+;;; Institute of Technology, Department of Electrical Engineering and
+;;; Computer Science. Permission to copy and modify this software, to
+;;; redistribute either the original software or a modified version, and
+;;; to use this software 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. Users of this software agree to make their best efforts (a) to
+;;; return to the MIT Scheme project any improvements or extensions that
+;;; they make, so that these may be included in future releases; and (b)
+;;; to inform MIT of noteworthy uses of this software.
+;;;
+;;; 3. All materials developed as a consequence of the use of this
+;;; software shall duly acknowledge such use, in accordance with the usual
+;;; standards of acknowledging credit in academic research.
+;;;
+;;; 4. MIT has made no warrantee or representation that the operation of
+;;; this software will be error-free, and MIT is under no obligation to
+;;; provide any services, by way of maintenance, update, or otherwise.
+;;;
+;;; 5. In conjunction with products arising from the use of this material,
+;;; there shall be no use of the name of the Massachusetts Institute of
+;;; Technology nor of any adaptation thereof in any advertising,
+;;; promotional, or sales literature without prior written consent from
+;;; MIT in each case.
+
+;;; Scsh copyright terms
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; All rights reserved.
+;;;
+;;; Redistribution and use in source and binary forms, with or without
+;;; modification, are permitted provided that the following conditions
+;;; are met:
+;;; 1. Redistributions of source code must retain the above copyright
+;;; notice, this list of conditions and the following disclaimer.
+;;; 2. Redistributions in binary form must reproduce the above copyright
+;;; notice, this list of conditions and the following disclaimer in the
+;;; documentation and/or other materials provided with the distribution.
+;;; 3. The name of the authors may not be used to endorse or promote products
+;;; derived from this software without specific prior written permission.
+;;;
+;;; THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+;;; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+;;; OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+;;; IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+;;; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+;;; NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+;;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+;;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+;;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+;;; THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+(define-library (srfi 14)
+ (export
+ ;; Predicates & comparison
+ char-set? char-set= char-set<= char-set-hash
+
+ ;; Iterating over character sets
+ char-set-cursor char-set-ref char-set-cursor-next end-of-char-set?
+ char-set-fold char-set-unfold char-set-unfold!
+ char-set-for-each char-set-map
+
+ ;; Creating character sets
+ char-set-copy char-set
+
+ list->char-set string->char-set
+ list->char-set! string->char-set!
+
+ char-set-filter ucs-range->char-set
+ char-set-filter! ucs-range->char-set!
+
+ ->char-set
+
+ ;; Querying character sets
+ char-set->list char-set->string
+ char-set-size char-set-count char-set-contains?
+ char-set-every char-set-any
+
+ ;; Character-set algebra
+ char-set-adjoin char-set-delete
+ char-set-adjoin! char-set-delete!
+
+ char-set-complement char-set-union char-set-intersection
+ char-set-complement! char-set-union! char-set-intersection!
+
+ char-set-difference char-set-xor char-set-diff+intersection
+ char-set-difference! char-set-xor! char-set-diff+intersection!
+
+ ;; Standard character sets
+ char-set:lower-case char-set:upper-case char-set:title-case
+ char-set:letter char-set:digit char-set:letter+digit
+ char-set:graphic char-set:printing char-set:whitespace
+ char-set:iso-control char-set:punctuation char-set:symbol
+ char-set:hex-digit char-set:blank char-set:ascii
+ char-set:empty char-set:full
+ )
+ (import
+ (scheme base)
+ (srfi 60)
+ (srfi aux))
+ (include "14.upstream.scm"))
+;;; SRFI-14 character-sets library -*- Scheme -*-
+;;;
+;;; - Ported from MIT Scheme runtime by Brian D. Carlstrom.
+;;; - Massively rehacked & extended by Olin Shivers 6/98.
+;;; - Massively redesigned and rehacked 5/2000 during SRFI process.
+;;; At this point, the code bears the following relationship to the
+;;; MIT Scheme code: "This is my grandfather's axe. My father replaced
+;;; the head, and I have replaced the handle." Nonetheless, we preserve
+;;; the MIT Scheme copyright:
+;;; Copyright (c) 1988-1995 Massachusetts Institute of Technology
+;;; The MIT Scheme license is a "free software" license. See the end of
+;;; this file for the tedious details.
+
+;;; Exports:
+;;; char-set? char-set= char-set<=
+;;; char-set-hash
+;;; char-set-cursor char-set-ref char-set-cursor-next end-of-char-set?
+;;; char-set-fold char-set-unfold char-set-unfold!
+;;; char-set-for-each char-set-map
+;;; char-set-copy char-set
+;;;
+;;; list->char-set string->char-set
+;;; list->char-set! string->char-set!
+;;;
+;;; filterchar-set ucs-range->char-set ->char-set
+;;; filterchar-set! ucs-range->char-set!
+;;;
+;;; char-set->list char-set->string
+;;;
+;;; char-set-size char-set-count char-set-contains?
+;;; char-set-every char-set-any
+;;;
+;;; char-set-adjoin char-set-delete
+;;; char-set-adjoin! char-set-delete!
+;;;
+
+;;; char-set-complement char-set-union char-set-intersection
+;;; char-set-complement! char-set-union! char-set-intersection!
+;;;
+;;; char-set-difference char-set-xor char-set-diff+intersection
+;;; char-set-difference! char-set-xor! char-set-diff+intersection!
+;;;
+;;; char-set:lower-case char-set:upper-case char-set:title-case
+;;; char-set:letter char-set:digit char-set:letter+digit
+;;; char-set:graphic char-set:printing char-set:whitespace
+;;; char-set:iso-control char-set:punctuation char-set:symbol
+;;; char-set:hex-digit char-set:blank char-set:ascii
+;;; char-set:empty char-set:full
+
+;;; Imports
+;;; This code has the following non-R5RS dependencies:
+;;; - ERROR
+;;; - %LATIN1->CHAR %CHAR->LATIN1
+;;; - LET-OPTIONALS* and #\:OPTIONAL macros for parsing, checking & defaulting
+;;; optional arguments from rest lists.
+;;; - BITWISE-AND for CHAR-SET-HASH
+;;; - The SRFI-19 DEFINE-RECORD-TYPE record macro
+;;; - A simple CHECK-ARG procedure:
+;;; (lambda (pred val caller) (if (not (pred val)) (error val caller)))
+
+;;; This is simple code, not great code. Char sets are represented as 256-char
+;;; strings. If char I is ASCII/Latin-1 0, then it isn't in the set; if char I
+;;; is ASCII/Latin-1 1, then it is in the set.
+;;; - Should be rewritten to use bit strings or byte vecs.
+;;; - Is Latin-1 specific. Would certainly have to be rewritten for Unicode.
+
+;;; See the end of the file for porting and performance-tuning notes.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define-record-type \:char-set
+ (make-char-set s)
+ char-set?
+ (s char-set:s))
+
+
+(define (%string-copy s) (substring s 0 (string-length s)))
+
+;;; Parse, type-check & default a final optional BASE-CS parameter from
+;;; a rest argument. Return a *fresh copy* of the underlying string.
+;;; The default is the empty set. The PROC argument is to help us
+;;; generate informative error exceptions.
+
+(define (%default-base maybe-base proc)
+ (if (pair? maybe-base)
+ (let ((bcs (car maybe-base))
+ (tail (cdr maybe-base)))
+ (if (null? tail)
+ (if (char-set? bcs) (%string-copy (char-set:s bcs))
+ (error "BASE-CS parameter not a char-set" proc bcs))
+ (error "Expected final base char set -- too many parameters"
+ proc maybe-base)))
+ (make-string 256 (%latin1->char 0))))
+
+;;; If CS is really a char-set, do CHAR-SET:S, otw report an error msg on
+;;; behalf of our caller, PROC. This procedure exists basically to provide
+;;; explicit error-checking & reporting.
+
+(define (%char-set:s/check cs proc)
+ (let lp ((cs cs))
+ (if (char-set? cs) (char-set:s cs)
+ (lp (error "Not a char-set" cs proc)))))
+
+
+
+;;; These internal functions hide a lot of the dependency on the
+;;; underlying string representation of char sets. They should be
+;;; inlined if possible.
+
+(define (si=0? s i) (zero? (%char->latin1 (string-ref s i))))
+(define (si=1? s i) (not (si=0? s i)))
+(define c0 (%latin1->char 0))
+(define c1 (%latin1->char 1))
+(define (si s i) (%char->latin1 (string-ref s i)))
+(define (%set0! s i) (string-set! s i c0))
+(define (%set1! s i) (string-set! s i c1))
+
+;;; These do various "s[i] := s[i] op val" operations -- see
+;;; %CHAR-SET-ALGEBRA. They are used to implement the various
+;;; set-algebra procedures.
+(define (setv! s i v) (string-set! s i (%latin1->char v))) ; SET to a Value.
+(define (%not! s i v) (setv! s i (- 1 v)))
+(define (%and! s i v) (if (zero? v) (%set0! s i)))
+(define (%or! s i v) (if (not (zero? v)) (%set1! s i)))
+(define (%minus! s i v) (if (not (zero? v)) (%set0! s i)))
+(define (%xor! s i v) (if (not (zero? v)) (setv! s i (- 1 (si s i)))))
+
+
+(define (char-set-copy cs)
+ (make-char-set (%string-copy (%char-set:s/check cs char-set-copy))))
+
+(define (char-set= . rest)
+ (or (null? rest)
+ (let* ((cs1 (car rest))
+ (rest (cdr rest))
+ (s1 (%char-set:s/check cs1 char-set=)))
+ (let lp ((rest rest))
+ (or (not (pair? rest))
+ (and (string=? s1 (%char-set:s/check (car rest) char-set=))
+ (lp (cdr rest))))))))
+
+(define (char-set<= . rest)
+ (or (null? rest)
+ (let ((cs1 (car rest))
+ (rest (cdr rest)))
+ (let lp ((s1 (%char-set:s/check cs1 char-set<=)) (rest rest))
+ (or (not (pair? rest))
+ (let ((s2 (%char-set:s/check (car rest) char-set<=))
+ (rest (cdr rest)))
+ (if (eq? s1 s2) (lp s2 rest) ; Fast path
+ (let lp2 ((i 255)) ; Real test
+ (if (< i 0) (lp s2 rest)
+ (and (<= (si s1 i) (si s2 i))
+ (lp2 (- i 1))))))))))))
+
+;;; Hash
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Compute (c + 37 c + 37^2 c + ...) modulo BOUND, with sleaze thrown in
+;;; to keep the intermediate values small. (We do the calculation with just
+;;; enough bits to represent BOUND, masking off high bits at each step in
+;;; calculation. If this screws up any important properties of the hash
+;;; function I'd like to hear about it. -Olin)
+;;;
+;;; If you keep BOUND small enough, the intermediate calculations will
+;;; always be fixnums. How small is dependent on the underlying Scheme system;
+;;; we use a default BOUND of 2^22 = 4194304, which should hack it in
+;;; Schemes that give you at least 29 signed bits for fixnums. The core
+;;; calculation that you don't want to overflow is, worst case,
+;;; (+ 65535 (* 37 (- bound 1)))
+;;; where 65535 is the max character code. Choose the default BOUND to be the
+;;; biggest power of two that won't cause this expression to fixnum overflow,
+;;; and everything will be copacetic.
+
+(define (char-set-hash cs . maybe-bound)
+ (let* ((bound (#\:optional maybe-bound 4194304 (lambda (n) (and (integer? n)
+ (exact? n)
+ (<= 0 n)))))
+ (bound (if (zero? bound) 4194304 bound)) ; 0 means default.
+ (s (%char-set:s/check cs char-set-hash))
+ ;; Compute a 111...1 mask that will cover BOUND-1:
+ (mask (let lp ((i #x10000)) ; Let's skip first 16 iterations, eh?
+ (if (>= i bound) (- i 1) (lp (+ i i))))))
+
+ (let lp ((i 255) (ans 0))
+ (if (< i 0) (modulo ans bound)
+ (lp (- i 1)
+ (if (si=0? s i) ans
+ (bitwise-and mask (+ (* 37 ans) i))))))))
+
+
+(define (char-set-contains? cs char)
+ (si=1? (%char-set:s/check cs char-set-contains?)
+ (%char->latin1 (check-arg char? char char-set-contains?))))
+
+(define (char-set-size cs)
+ (let ((s (%char-set:s/check cs char-set-size)))
+ (let lp ((i 255) (size 0))
+ (if (< i 0) size
+ (lp (- i 1) (+ size (si s i)))))))
+
+(define (char-set-count pred cset)
+ (check-arg procedure? pred char-set-count)
+ (let ((s (%char-set:s/check cset char-set-count)))
+ (let lp ((i 255) (count 0))
+ (if (< i 0) count
+ (lp (- i 1)
+ (if (and (si=1? s i) (pred (%latin1->char i)))
+ (+ count 1)
+ count))))))
+
+
+;;; -- Adjoin & delete
+
+(define (%set-char-set set proc cs chars)
+ (let ((s (%string-copy (%char-set:s/check cs proc))))
+ (for-each (lambda (c) (set s (%char->latin1 c)))
+ chars)
+ (make-char-set s)))
+
+(define (%set-char-set! set proc cs chars)
+ (let ((s (%char-set:s/check cs proc)))
+ (for-each (lambda (c) (set s (%char->latin1 c)))
+ chars))
+ cs)
+
+(define (char-set-adjoin cs . chars)
+ (%set-char-set %set1! char-set-adjoin cs chars))
+(define (char-set-adjoin! cs . chars)
+ (%set-char-set! %set1! char-set-adjoin! cs chars))
+(define (char-set-delete cs . chars)
+ (%set-char-set %set0! char-set-delete cs chars))
+(define (char-set-delete! cs . chars)
+ (%set-char-set! %set0! char-set-delete! cs chars))
+
+
+;;; Cursors
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Simple implementation. A cursors is an integer index into the
+;;; mark vector, and -1 for the end-of-char-set cursor.
+;;;
+;;; If we represented char sets as a bit set, we could do the following
+;;; trick to pick the lowest bit out of the set:
+;;; (count-bits (xor (- cset 1) cset))
+;;; (But first mask out the bits already scanned by the cursor first.)
+
+(define (char-set-cursor cset)
+ (%char-set-cursor-next cset 256 char-set-cursor))
+
+(define (end-of-char-set? cursor) (< cursor 0))
+
+(define (char-set-ref cset cursor) (%latin1->char cursor))
+
+(define (char-set-cursor-next cset cursor)
+ (check-arg (lambda (i) (and (integer? i) (exact? i) (<= 0 i 255))) cursor
+ char-set-cursor-next)
+ (%char-set-cursor-next cset cursor char-set-cursor-next))
+
+(define (%char-set-cursor-next cset cursor proc) ; Internal
+ (let ((s (%char-set:s/check cset proc)))
+ (let lp ((cur cursor))
+ (let ((cur (- cur 1)))
+ (if (or (< cur 0) (si=1? s cur)) cur
+ (lp cur))))))
+
+
+;;; -- for-each map fold unfold every any
+
+(define (char-set-for-each proc cs)
+ (check-arg procedure? proc char-set-for-each)
+ (let ((s (%char-set:s/check cs char-set-for-each)))
+ (let lp ((i 255))
+ (cond ((>= i 0)
+ (if (si=1? s i) (proc (%latin1->char i)))
+ (lp (- i 1)))))))
+
+(define (char-set-map proc cs)
+ (check-arg procedure? proc char-set-map)
+ (let ((s (%char-set:s/check cs char-set-map))
+ (ans (make-string 256 c0)))
+ (let lp ((i 255))
+ (cond ((>= i 0)
+ (if (si=1? s i)
+ (%set1! ans (%char->latin1 (proc (%latin1->char i)))))
+ (lp (- i 1)))))
+ (make-char-set ans)))
+
+(define (char-set-fold kons knil cs)
+ (check-arg procedure? kons char-set-fold)
+ (let ((s (%char-set:s/check cs char-set-fold)))
+ (let lp ((i 255) (ans knil))
+ (if (< i 0) ans
+ (lp (- i 1)
+ (if (si=0? s i) ans
+ (kons (%latin1->char i) ans)))))))
+
+(define (char-set-every pred cs)
+ (check-arg procedure? pred char-set-every)
+ (let ((s (%char-set:s/check cs char-set-every)))
+ (let lp ((i 255))
+ (or (< i 0)
+ (and (or (si=0? s i) (pred (%latin1->char i)))
+ (lp (- i 1)))))))
+
+(define (char-set-any pred cs)
+ (check-arg procedure? pred char-set-any)
+ (let ((s (%char-set:s/check cs char-set-any)))
+ (let lp ((i 255))
+ (and (>= i 0)
+ (or (and (si=1? s i) (pred (%latin1->char i)))
+ (lp (- i 1)))))))
+
+
+(define (%char-set-unfold! proc p f g s seed)
+ (check-arg procedure? p proc)
+ (check-arg procedure? f proc)
+ (check-arg procedure? g proc)
+ (let lp ((seed seed))
+ (cond ((not (p seed)) ; P says we are done.
+ (%set1! s (%char->latin1 (f seed))) ; Add (F SEED) to set.
+ (lp (g seed)))))) ; Loop on (G SEED).
+
+(define (char-set-unfold p f g seed . maybe-base)
+ (let ((bs (%default-base maybe-base char-set-unfold)))
+ (%char-set-unfold! char-set-unfold p f g bs seed)
+ (make-char-set bs)))
+
+(define (char-set-unfold! p f g seed base-cset)
+ (%char-set-unfold! char-set-unfold! p f g
+ (%char-set:s/check base-cset char-set-unfold!)
+ seed)
+ base-cset)
+
+
+
+;;; list <--> char-set
+
+(define (%list->char-set! chars s)
+ (for-each (lambda (char) (%set1! s (%char->latin1 char)))
+ chars))
+
+(define (char-set . chars)
+ (let ((s (make-string 256 c0)))
+ (%list->char-set! chars s)
+ (make-char-set s)))
+
+(define (list->char-set chars . maybe-base)
+ (let ((bs (%default-base maybe-base list->char-set)))
+ (%list->char-set! chars bs)
+ (make-char-set bs)))
+
+(define (list->char-set! chars base-cs)
+ (%list->char-set! chars (%char-set:s/check base-cs list->char-set!))
+ base-cs)
+
+
+(define (char-set->list cs)
+ (let ((s (%char-set:s/check cs char-set->list)))
+ (let lp ((i 255) (ans '()))
+ (if (< i 0) ans
+ (lp (- i 1)
+ (if (si=0? s i) ans
+ (cons (%latin1->char i) ans)))))))
+
+
+
+;;; string <--> char-set
+
+(define (%string->char-set! str bs proc)
+ (check-arg string? str proc)
+ (do ((i (- (string-length str) 1) (- i 1)))
+ ((< i 0))
+ (%set1! bs (%char->latin1 (string-ref str i)))))
+
+(define (string->char-set str . maybe-base)
+ (let ((bs (%default-base maybe-base string->char-set)))
+ (%string->char-set! str bs string->char-set)
+ (make-char-set bs)))
+
+(define (string->char-set! str base-cs)
+ (%string->char-set! str (%char-set:s/check base-cs string->char-set!)
+ string->char-set!)
+ base-cs)
+
+
+(define (char-set->string cs)
+ (let* ((s (%char-set:s/check cs char-set->string))
+ (ans (make-string (char-set-size cs))))
+ (let lp ((i 255) (j 0))
+ (if (< i 0) ans
+ (let ((j (if (si=0? s i) j
+ (begin (string-set! ans j (%latin1->char i))
+ (+ j 1)))))
+ (lp (- i 1) j))))))
+
+
+;;; -- UCS-range -> char-set
+
+(define (%ucs-range->char-set! lower upper error? bs proc)
+ (check-arg (lambda (x) (and (integer? x) (exact? x) (<= 0 x))) lower proc)
+ (check-arg (lambda (x) (and (integer? x) (exact? x) (<= lower x))) upper proc)
+
+ (if (and (< lower upper) (< 256 upper) error?)
+ (error "Requested UCS range contains unavailable characters -- this implementation only supports Latin-1"
+ proc lower upper))
+
+ (let lp ((i (- (min upper 256) 1)))
+ (cond ((<= lower i) (%set1! bs i) (lp (- i 1))))))
+
+(define (ucs-range->char-set lower upper . rest)
+ (let-optionals* rest ((error? #f) rest)
+ (let ((bs (%default-base rest ucs-range->char-set)))
+ (%ucs-range->char-set! lower upper error? bs ucs-range->char-set)
+ (make-char-set bs))))
+
+(define (ucs-range->char-set! lower upper error? base-cs)
+ (%ucs-range->char-set! lower upper error?
+ (%char-set:s/check base-cs ucs-range->char-set!)
+ ucs-range->char-set)
+ base-cs)
+
+
+;;; -- predicate -> char-set
+
+(define (%char-set-filter! pred ds bs proc)
+ (check-arg procedure? pred proc)
+ (let lp ((i 255))
+ (cond ((>= i 0)
+ (if (and (si=1? ds i) (pred (%latin1->char i)))
+ (%set1! bs i))
+ (lp (- i 1))))))
+
+(define (char-set-filter predicate domain . maybe-base)
+ (let ((bs (%default-base maybe-base char-set-filter)))
+ (%char-set-filter! predicate
+ (%char-set:s/check domain char-set-filter!)
+ bs
+ char-set-filter)
+ (make-char-set bs)))
+
+(define (char-set-filter! predicate domain base-cs)
+ (%char-set-filter! predicate
+ (%char-set:s/check domain char-set-filter!)
+ (%char-set:s/check base-cs char-set-filter!)
+ char-set-filter!)
+ base-cs)
+
+
+;;; {string, char, char-set, char predicate} -> char-set
+
+(define (->char-set x)
+ (cond ((char-set? x) x)
+ ((string? x) (string->char-set x))
+ ((char? x) (char-set x))
+ (else (error "->char-set: Not a charset, string or char." x))))
+
+
+
+;;; Set algebra
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; The exported ! procs are "linear update" -- allowed, but not required, to
+;;; side-effect their first argument when computing their result. In other
+;;; words, you must use them as if they were completely functional, just like
+;;; their non-! counterparts, and you must additionally ensure that their
+;;; first arguments are "dead" at the point of call. In return, we promise a
+;;; more efficient result, plus allowing you to always assume char-sets are
+;;; unchangeable values.
+
+;;; Apply P to each index and its char code in S: (P I VAL).
+;;; Used by the set-algebra ops.
+
+(define (%string-iter p s)
+ (let lp ((i (- (string-length s) 1)))
+ (cond ((>= i 0)
+ (p i (%char->latin1 (string-ref s i)))
+ (lp (- i 1))))))
+
+;;; String S represents some initial char-set. (OP s i val) does some
+;;; kind of s[i] := s[i] op val update. Do
+;;; S := S OP CSETi
+;;; for all the char-sets in the list CSETS. The n-ary set-algebra ops
+;;; all use this internal proc.
+
+(define (%char-set-algebra s csets op proc)
+ (for-each (lambda (cset)
+ (let ((s2 (%char-set:s/check cset proc)))
+ (let lp ((i 255))
+ (cond ((>= i 0)
+ (op s i (si s2 i))
+ (lp (- i 1)))))))
+ csets))
+
+
+;;; -- Complement
+
+(define (char-set-complement cs)
+ (let ((s (%char-set:s/check cs char-set-complement))
+ (ans (make-string 256)))
+ (%string-iter (lambda (i v) (%not! ans i v)) s)
+ (make-char-set ans)))
+
+(define (char-set-complement! cset)
+ (let ((s (%char-set:s/check cset char-set-complement!)))
+ (%string-iter (lambda (i v) (%not! s i v)) s))
+ cset)
+
+
+;;; -- Union
+
+(define (char-set-union! cset1 . csets)
+ (%char-set-algebra (%char-set:s/check cset1 char-set-union!)
+ csets %or! char-set-union!)
+ cset1)
+
+(define (char-set-union . csets)
+ (if (pair? csets)
+ (let ((s (%string-copy (%char-set:s/check (car csets) char-set-union))))
+ (%char-set-algebra s (cdr csets) %or! char-set-union)
+ (make-char-set s))
+ (char-set-copy char-set:empty)))
+
+
+;;; -- Intersection
+
+(define (char-set-intersection! cset1 . csets)
+ (%char-set-algebra (%char-set:s/check cset1 char-set-intersection!)
+ csets %and! char-set-intersection!)
+ cset1)
+
+(define (char-set-intersection . csets)
+ (if (pair? csets)
+ (let ((s (%string-copy (%char-set:s/check (car csets) char-set-intersection))))
+ (%char-set-algebra s (cdr csets) %and! char-set-intersection)
+ (make-char-set s))
+ (char-set-copy char-set:full)))
+
+
+;;; -- Difference
+
+(define (char-set-difference! cset1 . csets)
+ (%char-set-algebra (%char-set:s/check cset1 char-set-difference!)
+ csets %minus! char-set-difference!)
+ cset1)
+
+(define (char-set-difference cs1 . csets)
+ (if (pair? csets)
+ (let ((s (%string-copy (%char-set:s/check cs1 char-set-difference))))
+ (%char-set-algebra s csets %minus! char-set-difference)
+ (make-char-set s))
+ (char-set-copy cs1)))
+
+
+;;; -- Xor
+
+(define (char-set-xor! cset1 . csets)
+ (%char-set-algebra (%char-set:s/check cset1 char-set-xor!)
+ csets %xor! char-set-xor!)
+ cset1)
+
+(define (char-set-xor . csets)
+ (if (pair? csets)
+ (let ((s (%string-copy (%char-set:s/check (car csets) char-set-xor))))
+ (%char-set-algebra s (cdr csets) %xor! char-set-xor)
+ (make-char-set s))
+ (char-set-copy char-set:empty)))
+
+
+;;; -- Difference & intersection
+
+(define (%char-set-diff+intersection! diff int csets proc)
+ (for-each (lambda (cs)
+ (%string-iter (lambda (i v)
+ (if (not (zero? v))
+ (cond ((si=1? diff i)
+ (%set0! diff i)
+ (%set1! int i)))))
+ (%char-set:s/check cs proc)))
+ csets))
+
+(define (char-set-diff+intersection! cs1 cs2 . csets)
+ (let ((s1 (%char-set:s/check cs1 char-set-diff+intersection!))
+ (s2 (%char-set:s/check cs2 char-set-diff+intersection!)))
+ (%string-iter (lambda (i v) (if (zero? v)
+ (%set0! s2 i)
+ (if (si=1? s2 i) (%set0! s1 i))))
+ s1)
+ (%char-set-diff+intersection! s1 s2 csets char-set-diff+intersection!))
+ (values cs1 cs2))
+
+(define (char-set-diff+intersection cs1 . csets)
+ (let ((diff (string-copy (%char-set:s/check cs1 char-set-diff+intersection)))
+ (int (make-string 256 c0)))
+ (%char-set-diff+intersection! diff int csets char-set-diff+intersection)
+ (values (make-char-set diff) (make-char-set int))))
+
+
+;;;; System character sets
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; These definitions are for Latin-1.
+;;;
+;;; If your Scheme implementation allows you to mark the underlying strings
+;;; as immutable, you should do so -- it would be very, very bad if a client's
+;;; buggy code corrupted these constants.
+
+(define char-set:empty (char-set))
+(define char-set:full (char-set-complement char-set:empty))
+
+(define char-set:lower-case
+ (let* ((a-z (ucs-range->char-set #x61 #x7B))
+ (latin1 (ucs-range->char-set! #xdf #xf7 #t a-z))
+ (latin2 (ucs-range->char-set! #xf8 #x100 #t latin1)))
+ (char-set-adjoin! latin2 (%latin1->char #xb5))))
+
+(define char-set:upper-case
+ (let ((A-Z (ucs-range->char-set #x41 #x5B)))
+ ;; Add in the Latin-1 upper-case chars.
+ (ucs-range->char-set! #xd8 #xdf #t
+ (ucs-range->char-set! #xc0 #xd7 #t A-Z))))
+
+(define char-set:title-case char-set:empty)
+
+(define char-set:letter
+ (let ((u/l (char-set-union char-set:upper-case char-set:lower-case)))
+ (char-set-adjoin! u/l
+ (%latin1->char #xaa) ; FEMININE ORDINAL INDICATOR
+ (%latin1->char #xba)))) ; MASCULINE ORDINAL INDICATOR
+
+(define char-set:digit (string->char-set "0123456789"))
+(define char-set:hex-digit (string->char-set "0123456789abcdefABCDEF"))
+
+(define char-set:letter+digit
+ (char-set-union char-set:letter char-set:digit))
+
+(define char-set:punctuation
+ (let ((ascii (string->char-set "!\"#%&'()*,-./:;?@[\\]_{}"))
+ (latin-1-chars (map %latin1->char '(#xA1 ; INVERTED EXCLAMATION MARK
+ #xAB ; LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ #xAD ; SOFT HYPHEN
+ #xB7 ; MIDDLE DOT
+ #xBB ; RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ #xBF)))) ; INVERTED QUESTION MARK
+ (list->char-set! latin-1-chars ascii)))
+
+(define char-set:symbol
+ (let ((ascii (string->char-set "$+<=>^`|~"))
+ (latin-1-chars (map %latin1->char '(#x00A2 ; CENT SIGN
+ #x00A3 ; POUND SIGN
+ #x00A4 ; CURRENCY SIGN
+ #x00A5 ; YEN SIGN
+ #x00A6 ; BROKEN BAR
+ #x00A7 ; SECTION SIGN
+ #x00A8 ; DIAERESIS
+ #x00A9 ; COPYRIGHT SIGN
+ #x00AC ; NOT SIGN
+ #x00AE ; REGISTERED SIGN
+ #x00AF ; MACRON
+ #x00B0 ; DEGREE SIGN
+ #x00B1 ; PLUS-MINUS SIGN
+ #x00B4 ; ACUTE ACCENT
+ #x00B6 ; PILCROW SIGN
+ #x00B8 ; CEDILLA
+ #x00D7 ; MULTIPLICATION SIGN
+ #x00F7)))) ; DIVISION SIGN
+ (list->char-set! latin-1-chars ascii)))
+
+
+(define char-set:graphic
+ (char-set-union char-set:letter+digit char-set:punctuation char-set:symbol))
+
+(define char-set:whitespace
+ (list->char-set (map %latin1->char '(#x09 ; HORIZONTAL TABULATION
+ #x0A ; LINE FEED
+ #x0B ; VERTICAL TABULATION
+ #x0C ; FORM FEED
+ #x0D ; CARRIAGE RETURN
+ #x20 ; SPACE
+ #xA0))))
+
+(define char-set:printing (char-set-union char-set:whitespace char-set:graphic)) ; NO-BREAK SPACE
+
+(define char-set:blank
+ (list->char-set (map %latin1->char '(#x09 ; HORIZONTAL TABULATION
+ #x20 ; SPACE
+ #xA0)))) ; NO-BREAK SPACE
+
+
+(define char-set:iso-control
+ (ucs-range->char-set! #x7F #xA0 #t (ucs-range->char-set 0 32)))
+
+(define char-set:ascii (ucs-range->char-set 0 128))
+
+
+;;; Porting & performance-tuning notes
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; See the section at the beginning of this file on external dependencies.
+;;;
+;;; First and foremost, rewrite this code to use bit vectors of some sort.
+;;; This will give big speedup and memory savings.
+;;;
+;;; - LET-OPTIONALS* macro.
+;;; This is only used once. You can rewrite the use, port the hairy macro
+;;; definition (which is implemented using a Clinger-Rees low-level
+;;; explicit-renaming macro system), or port the simple, high-level
+;;; definition, which is less efficient.
+;;;
+;;; - #\:OPTIONAL macro
+;;; Very simply defined using an R5RS high-level macro.
+;;;
+;;; Implementations that can arrange for the base char sets to be immutable
+;;; should do so. (E.g., Scheme 48 allows one to mark a string as immutable,
+;;; which can be used to protect the underlying strings.) It would be very,
+;;; very bad if a client's buggy code corrupted these constants.
+;;;
+;;; There is a fair amount of argument checking. This is, strictly speaking,
+;;; unnecessary -- the actual body of the procedures will blow up if an
+;;; illegal value is passed in. However, the error message will not be as good
+;;; as if the error were caught at the "higher level." Also, a very, very
+;;; smart Scheme compiler may be able to exploit having the type checks done
+;;; early, so that the actual body of the procedures can assume proper values.
+;;; This isn't likely; this kind of compiler technology isn't common any
+;;; longer.
+;;;
+;;; The overhead of optional-argument parsing is irritating. The optional
+;;; arguments must be consed into a rest list on entry, and then parsed out.
+;;; Function call should be a matter of a few register moves and a jump; it
+;;; should not involve heap allocation! Your Scheme system may have a superior
+;;; non-R5RS optional-argument system that can eliminate this overhead. If so,
+;;; then this is a prime candidate for optimising these procedures,
+;;; *especially* the many optional BASE-CS parameters.
+;;;
+;;; Note that optional arguments are also a barrier to procedure integration.
+;;; If your Scheme system permits you to specify alternate entry points
+;;; for a call when the number of optional arguments is known in a manner
+;;; that enables inlining/integration, this can provide performance
+;;; improvements.
+;;;
+;;; There is enough *explicit* error checking that *all* internal operations
+;;; should *never* produce a type or index-range error. Period. Feel like
+;;; living dangerously? *Big* performance win to be had by replacing string
+;;; and record-field accessors and setters with unsafe equivalents in the
+;;; code. Similarly, fixnum-specific operators can speed up the arithmetic
+;;; done on the index values in the inner loops. The only arguments that are
+;;; not completely error checked are
+;;; - string lists (complete checking requires time proportional to the
+;;; length of the list)
+;;; - procedure arguments, such as char->char maps & predicates.
+;;; There is no way to check the range & domain of procedures in Scheme.
+;;; Procedures that take these parameters cannot fully check their
+;;; arguments. But all other types to all other procedures are fully
+;;; checked.
+;;;
+;;; This does open up the alternate possibility of simply *removing* these
+;;; checks, and letting the safe primitives raise the errors. On a dumb
+;;; Scheme system, this would provide speed (by eliminating the redundant
+;;; error checks) at the cost of error-message clarity.
+;;;
+;;; In an interpreted Scheme, some of these procedures, or the internal
+;;; routines with % prefixes, are excellent candidates for being rewritten
+;;; in C.
+;;;
+;;; It would also be nice to have the ability to mark some of these
+;;; routines as candidates for inlining/integration.
+;;;
+;;; See the comments preceding the hash function code for notes on tuning
+;;; the default bound so that the code never overflows your implementation's
+;;; fixnum size into bignum calculation.
+;;;
+;;; All the %-prefixed routines in this source code are written
+;;; to be called internally to this library. They do *not* perform
+;;; friendly error checks on the inputs; they assume everything is
+;;; proper. They also do not take optional arguments. These two properties
+;;; save calling overhead and enable procedure integration -- but they
+;;; are not appropriate for exported routines.
+
+;;; Copyright notice
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Copyright (c) 1988-1995 Massachusetts Institute of Technology
+;;;
+;;; This material was developed by the Scheme project at the Massachusetts
+;;; Institute of Technology, Department of Electrical Engineering and
+;;; Computer Science. Permission to copy and modify this software, to
+;;; redistribute either the original software or a modified version, and
+;;; to use this software 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. Users of this software agree to make their best efforts (a) to
+;;; return to the MIT Scheme project any improvements or extensions that
+;;; they make, so that these may be included in future releases; and (b)
+;;; to inform MIT of noteworthy uses of this software.
+;;;
+;;; 3. All materials developed as a consequence of the use of this
+;;; software shall duly acknowledge such use, in accordance with the usual
+;;; standards of acknowledging credit in academic research.
+;;;
+;;; 4. MIT has made no warrantee or representation that the operation of
+;;; this software will be error-free, and MIT is under no obligation to
+;;; provide any services, by way of maintenance, update, or otherwise.
+;;;
+;;; 5. In conjunction with products arising from the use of this material,
+;;; there shall be no use of the name of the Massachusetts Institute of
+;;; Technology nor of any adaptation thereof in any advertising,
+;;; promotional, or sales literature without prior written consent from
+;;; MIT in each case.
+(define-library (srfi test) ; -*- scheme -*-
+ (import (except (scheme base) cond)
+ (scheme write)
+ (srfi 61))
+ (begin
+ (display (cond
+ ((values 0 1) (lambda (x y) #t)
+ => list)))
+ (newline)))
+(use-modules (srfi srfi-11))
+
+(define (assert bool explanation)
+ (unless bool
+ (error explanation)))
+
+(define (id= x y)
+ (and (identifier? x)
+ (identifier? y)
+ (free-identifier=? x y)))
+
+(define-syntax uq
+ (syntax-rules ()
+ ((uq . x) (syntax-error "Unquote used outside quasiquote."))))
+
+(define-syntax uq-s
+ (syntax-rules ()
+ ((uq-s . x) (syntax-error "Unquote-splicing used outside quasiquote."))))
+
+(define-syntax qq
+ (lambda (stx)
+ (define (handle-node node level splicable?)
+ (if (zero? level)
+ (values 'one node)
+ (let ((node (syntax->datum node)))
+ (if (pair? node)
+ (handle-pair node level splicable?)
+ (handle-atom node level)))))
+ (define (handle-pair pair level splicable?)
+ (let ((car (datum->syntax stx (car pair)))
+ (cdr (datum->syntax stx (cdr pair))))
+ (cond
+ ((id= car #'qq)
+ (handle-qq pair level))
+ ((id= car #'uq)
+ (handle-uq pair level))
+ ((and splicable? (id= car #'uq-s))
+ (handle-uq-s pair level))
+ (else
+ (let-values (((type car) (handle-node car level #t))
+ ((_ cdr) (handle-node cdr level #f)))
+ (case type
+ ((one)
+ (values 'one #`(cons #,car #,cdr)))
+ ((many)
+ (values 'one #`(append #,car #,cdr)))))))))
+ (define (handle-qq qq-form level)
+ (assert (and (list? qq-form) (= 2 (length qq-form)))
+ "Quasiquote expects exactly one operand.")
+ (let ((operand (datum->syntax stx (cadr qq-form))))
+ (let-values (((_ val) (handle-node operand (+ level 1) #f)))
+ (values 'one #`(list 'qq #,val)))))
+ (define (handle-uq uq-form level)
+ (assert (and (list? uq-form) (= 2 (length uq-form)))
+ "Unquote expects exactly one operand.")
+ (let ((operand (datum->syntax stx (cadr uq-form))))
+ (let-values (((type val) (handle-node operand (- level 1) #t)))
+ (if (= level 1)
+ (values type val)
+ (case type
+ ((one)
+ (values 'one #`(list 'uq #,val)))
+ ((many)
+ (values 'one #`(apply list 'uq #,val))))))))
+ (define (handle-uq-s uq-s-form level)
+ (assert (and (list? uq-s-form) (= 2 (length uq-s-form)))
+ "Unquote-splicing expects exactly one operand.")
+ (let ((operand (datum->syntax stx (cadr uq-s-form))))
+ (let-values (((type val) (handle-node operand (- level 1) #t)))
+ (if (= 1 level)
+ (values 'many val)
+ (values 'one #`(list 'uq-s #,val))))))
+ (define (handle-atom atom level)
+ (let ((atom (datum->syntax stx atom)))
+ (values 'one #`(quote #,atom))))
+ (syntax-case stx ()
+ ((qq operand)
+ (let-values (((_ val) (handle-node #'operand 1 #f)))
+ val))
+ ((qq . x)
+ (error "Quasiquote expects exactly one operand.")))))
+;;;; benchmark-suite/lib.scm --- generic support for benchmarking
+;;;; Copyright (C) 2002, 2006, 2011, 2012 Free Software Foundation, Inc.
+;;;;
+;;;; 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, 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 software; see the file COPYING.LESSER.
+;;;; If not, write to the Free Software Foundation, Inc., 51 Franklin
+;;;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (benchmark-suite lib)
+ #\use-module (srfi srfi-9)
+ #\export (;; Controlling the execution.
+ iteration-factor
+ scale-iterations
+
+ ;; Running benchmarks.
+ run-benchmark
+ benchmark
+
+ ;; Naming groups of benchmarks in a regular fashion.
+ with-benchmark-prefix with-benchmark-prefix*
+ current-benchmark-prefix format-benchmark-name
+
+ ;; <benchmark-result> accessors
+ benchmark-result:name
+ benchmark-result:iterations
+ benchmark-result:real-time
+ benchmark-result:run-time
+ benchmark-result:gc-time
+ benchmark-result:core-time
+
+ ;; Reporting results in various ways.
+ report current-reporter
+ register-reporter unregister-reporter reporter-registered?
+ make-log-reporter
+ full-reporter
+ user-reporter))
+
+
+;;;; If you're using Emacs's Scheme mode:
+;;;; (put 'with-benchmark-prefix 'scheme-indent-function 1)
+;;;; (put 'benchmark 'scheme-indent-function 1)
+
+
+;;;; CORE FUNCTIONS
+;;;;
+;;;; The function (run-benchmark name iterations thunk) is the heart of the
+;;;; benchmarking environment. The first parameter NAME is a unique name for
+;;;; the benchmark to be executed (for an explanation of this parameter see
+;;;; below under ;;;; NAMES. The second parameter ITERATIONS is a positive
+;;;; integer value that indicates how often the thunk shall be executed (for
+;;;; an explanation of how iteration counts should be used, see below under
+;;;; ;;;; ITERATION COUNTS). For example:
+;;;;
+;;;; (run-benchmark "small integer addition" 100000 (lambda () (+ 1 1)))
+;;;;
+;;;; This will run the function (lambda () (+ 1 1)) a 100000 times (the
+;;;; iteration count can, however be scaled. See below for details). Some
+;;;; different time data for running the thunk for the given number of
+;;;; iterations is measured and reported.
+;;;;
+;;;; Convenience macro
+;;;;
+;;;; * (benchmark name iterations body) is a short form for
+;;;; (run-benchmark name iterations (lambda () body))
+
+
+;;;; NAMES
+;;;;
+;;;; Every benchmark in the benchmark suite has a unique name to be able to
+;;;; compare the results of individual benchmarks across several runs of the
+;;;; benchmark suite.
+;;;;
+;;;; A benchmark name is a list of printable objects. For example:
+;;;; ("ports.scm" "file" "read and write back list of strings")
+;;;; ("ports.scm" "pipe" "read")
+;;;;
+;;;; Benchmark names may contain arbitrary objects, but they always have
+;;;; the following properties:
+;;;; - Benchmark names can be compared with EQUAL?.
+;;;; - Benchmark names can be reliably stored and retrieved with the standard
+;;;; WRITE and READ procedures; doing so preserves their identity.
+;;;;
+;;;; For example:
+;;;;
+;;;; (benchmark "simple addition" 100000 (+ 2 2))
+;;;;
+;;;; In that case, the benchmark name is the list ("simple addition").
+;;;;
+;;;; The WITH-BENCHMARK-PREFIX syntax and WITH-BENCHMARK-PREFIX* procedure
+;;;; establish a prefix for the names of all benchmarks whose results are
+;;;; reported within their dynamic scope. For example:
+;;;;
+;;;; (begin
+;;;; (with-benchmark-prefix "basic arithmetic"
+;;;; (benchmark "addition" 100000 (+ 2 2))
+;;;; (benchmark "subtraction" 100000 (- 4 2)))
+;;;; (benchmark "multiplication" 100000 (* 2 2))))
+;;;;
+;;;; In that example, the three benchmark names are:
+;;;; ("basic arithmetic" "addition"),
+;;;; ("basic arithmetic" "subtraction"), and
+;;;; ("multiplication").
+;;;;
+;;;; WITH-BENCHMARK-PREFIX can be nested. Each WITH-BENCHMARK-PREFIX
+;;;; appends a new element to the current prefix:
+;;;;
+;;;; (with-benchmark-prefix "arithmetic"
+;;;; (with-benchmark-prefix "addition"
+;;;; (benchmark "integer" 100000 (+ 2 2))
+;;;; (benchmark "complex" 100000 (+ 2+3i 4+5i)))
+;;;; (with-benchmark-prefix "subtraction"
+;;;; (benchmark "integer" 100000 (- 2 2))
+;;;; (benchmark "complex" 100000 (- 2+3i 1+2i))))
+;;;;
+;;;; The four benchmark names here are:
+;;;; ("arithmetic" "addition" "integer")
+;;;; ("arithmetic" "addition" "complex")
+;;;; ("arithmetic" "subtraction" "integer")
+;;;; ("arithmetic" "subtraction" "complex")
+;;;;
+;;;; To print a name for a human reader, we DISPLAY its elements,
+;;;; separated by ": ". So, the last set of benchmark names would be
+;;;; reported as:
+;;;;
+;;;; arithmetic: addition: integer
+;;;; arithmetic: addition: complex
+;;;; arithmetic: subtraction: integer
+;;;; arithmetic: subtraction: complex
+;;;;
+;;;; The Guile benchmarks use with-benchmark-prefix to include the name of
+;;;; the source file containing the benchmark in the benchmark name, to
+;;;; provide each file with its own namespace.
+
+
+;;;; ITERATION COUNTS
+;;;;
+;;;; Every benchmark has to be given an iteration count that indicates how
+;;;; often it should be executed. The reason is, that in most cases a single
+;;;; execution of the benchmark code would not deliver usable timing results:
+;;;; The resolution of the system time is not arbitrarily fine. Thus, some
+;;;; benchmarks would be executed too quickly to be measured at all. A rule
+;;;; of thumb is, that the longer a benchmark runs, the more exact is the
+;;;; information about the execution time.
+;;;;
+;;;; However, execution time depends on several influences: First, the
+;;;; machine you are running the benchmark on. Second, the compiler you use.
+;;;; Third, which compiler options you use. Fourth, which version of guile
+;;;; you are using. Fifth, which guile options you are using (for example if
+;;;; you are using the debugging evaluator or not). There are even more
+;;;; influences.
+;;;;
+;;;; For this reason, the same number of iterations for a single benchmark may
+;;;; lead to completely different execution times in different
+;;;; constellations. For someone working on a slow machine, the default
+;;;; execution counts may lead to an inacceptable execution time of the
+;;;; benchmark suite. For someone on a very fast machine, however, it may be
+;;;; desireable to increase the number of iterations in order to increase the
+;;;; accuracy of the time data.
+;;;;
+;;;; For this reason, the benchmark suite allows to scale the number of
+;;;; executions by a global factor, stored in the exported variable
+;;;; iteration-factor. The default for iteration-factor is 1. A number of 2
+;;;; means, that all benchmarks are executed twice as often, which will also
+;;;; roughly double the execution time for the benchmark suite. Similarly, if
+;;;; iteration-factor holds a value of 0.5, only about half the execution time
+;;;; will be required.
+;;;;
+;;;; It is probably a good idea to choose the iteration count for each
+;;;; benchmark such that all benchmarks will take about the same time, for
+;;;; example one second. To achieve this, the benchmark suite holds an empty
+;;;; benchmark in the file 0-reference.bm named "reference benchmark for
+;;;; iteration counts". It's iteration count is calibrated to make the
+;;;; benchmark run about one second on Dirk's laptop :-) If you are adding
+;;;; benchmarks to the suite, it would be nice if you could calibrate the
+;;;; number of iterations such that each of your added benchmarks takes about
+;;;; as long to run as the reference benchmark. But: Don't be too accurate
+;;;; to figure out the correct iteration count.
+
+
+;;;; REPORTERS
+;;;;
+;;;; A reporter is a function which we apply to each benchmark outcome.
+;;;; Reporters can log results, print interesting results to the standard
+;;;; output, collect statistics, etc.
+;;;;
+;;;; A reporter function takes the following arguments: NAME ITERATIONS
+;;;; BEFORE AFTER GC-TIME. The argument NAME holds the name of the benchmark,
+;;;; ITERATIONS holds the actual number of iterations that were performed.
+;;;; BEFORE holds the result of the function (times) at the very beginning of
+;;;; the excution of the benchmark, AFTER holds the result of the function
+;;;; (times) after the execution of the benchmark. GC-TIME, finally, holds
+;;;; the difference of calls to (gc-run-time) before and after the execution
+;;;; of the benchmark.
+;;;;
+;;;; This library provides some standard reporters for logging results
+;;;; to a file, reporting interesting results to the user, (FIXME: and
+;;;; collecting totals).
+;;;;
+;;;; You can use the REGISTER-REPORTER function and friends to add whatever
+;;;; reporting functions you like. See under ;;;; TIMING DATA to see how the
+;;;; library helps you to extract relevant timing information from the values
+;;;; ITERATIONS, BEFORE, AFTER and GC-TIME. If you don't register any
+;;;; reporters, the library uses USER-REPORTER, which writes the most
+;;;; interesting results to the standard output.
+
+
+;;;; TIME CALCULATION
+;;;;
+;;;; The library uses the guile functions `get-internal-run-time',
+;;;; `get-internal-real-time', and `gc-run-time' to determine the
+;;;; execution time for a single benchmark. Based on these functions,
+;;;; Guile makes a <benchmark-result>, a record containing the elapsed
+;;;; run time, real time, gc time, and possibly other metrics. These
+;;;; times include the time needed to executed the benchmark code
+;;;; itself, but also the surrounding code that implements the loop to
+;;;; run the benchmark code for the given number of times. This is
+;;;; undesirable, since one would prefer to only get the timing data for
+;;;; the benchmarking code.
+;;;;
+;;;; To cope with this, the benchmarking framework uses a trick: During
+;;;; initialization of the library, the time for executing an empty
+;;;; benchmark is measured and stored. This is an estimate for the time
+;;;; needed by the benchmarking framework itself. For later benchmarks,
+;;;; this time can then be subtracted from the measured execution times.
+;;;; Note that for very short benchmarks, this may result in a negative
+;;;; number.
+;;;;
+;;;; The benchmarking framework provides the following accessors for
+;;;; <benchmark-result> values. Note that all time values are in
+;;;; internal time units; divide by internal-time-units-per-second to
+;;;; get seconds.
+;;;;
+;;;; benchmark-result:name : Return the name of the benchmark.
+;;;;
+;;;; benchmark-result:iterations : Return the number of iterations that
+;;;; this benchmark ran for.
+;;;;
+;;;; benchmark-result:real-time : Return the clock time elapsed while
+;;;; this benchmark executed.
+;;;;
+;;;; benchmark-result:run-time : Return the CPU time elapsed while this
+;;;; benchmark executed, both in user and kernel space.
+;;;;
+;;;; benchmark-result:gc-time : Return the approximate amount of time
+;;;; spent in garbage collection while this benchmark executed, both
+;;;; in user and kernel space.
+;;;;
+;;;; benchmark-result:core-time : Like benchmark-result:run-time, but
+;;;; also estimates the time spent by the framework for the number
+;;;; of iterations, and subtracts off that time from the result.
+;;;;
+
+;;;; This module is used when benchmarking different Guiles, and so it
+;;;; should run on all the Guiles of interest. Currently this set
+;;;; includes Guile 1.8, so be careful with introducing features that
+;;;; only Guile 2.0 supports.
+
+
+;;;; MISCELLANEOUS
+;;;;
+
+(define-record-type <benchmark-result>
+ (make-benchmark-result name iterations real-time run-time gc-time)
+ benchmark-result?
+ (name benchmark-result:name)
+ (iterations benchmark-result:iterations)
+ (real-time benchmark-result:real-time)
+ (run-time benchmark-result:run-time)
+ (gc-time benchmark-result:gc-time))
+
+;;; Perform a division and convert the result to inexact.
+(define (->seconds time)
+ (/ time 1.0 internal-time-units-per-second))
+
+;;; Scale the number of iterations according to the given scaling factor.
+(define iteration-factor 1)
+(define (scale-iterations iterations)
+ (let* ((i (inexact->exact (round (* iterations iteration-factor)))))
+ (if (< i 1) 1 i)))
+
+;;; Parameters.
+(cond-expand
+ (srfi-39 #t)
+ (else (use-modules (srfi srfi-39))))
+
+;;;; CORE FUNCTIONS
+;;;;
+
+;;; The central routine for executing benchmarks.
+;;; The idea is taken from Greg, the GNUstep regression test environment.
+(define benchmark-running? (make-parameter #f))
+(define (run-benchmark name iterations thunk)
+ (if (benchmark-running?)
+ (error "Nested calls to run-benchmark are not permitted."))
+ (if (not (and (integer? iterations) (exact? iterations)))
+ (error "Expected exact integral number of iterations"))
+ (parameterize ((benchmark-running? #t))
+ ;; Warm up the benchmark first. This will resolve any toplevel-ref
+ ;; forms.
+ (thunk)
+ (gc)
+ (let* ((before-gc-time (gc-run-time))
+ (before-real-time (get-internal-real-time))
+ (before-run-time (get-internal-run-time)))
+ (do ((i iterations (1- i)))
+ ((zero? i))
+ (thunk))
+ (let ((after-run-time (get-internal-run-time))
+ (after-real-time (get-internal-real-time))
+ (after-gc-time (gc-run-time)))
+ (report (make-benchmark-result (full-name name) iterations
+ (- after-real-time before-real-time)
+ (- after-run-time before-run-time)
+ (- after-gc-time before-gc-time)))))))
+
+;;; A short form for benchmarks.
+(cond-expand
+ (guile-2
+ (define-syntax-rule (benchmark name iterations body body* ...)
+ (run-benchmark name iterations (lambda () body body* ...))))
+ (else
+ (defmacro benchmark (name iterations body . rest)
+ `(run-benchmark ,name ,iterations (lambda () ,body ,@rest)))))
+
+
+;;;; BENCHMARK NAMES
+;;;;
+
+;;;; Turn a benchmark name into a nice human-readable string.
+(define (format-benchmark-name name)
+ (string-join name ": "))
+
+;;;; For a given benchmark-name, deliver the full name including all prefixes.
+(define (full-name name)
+ (append (current-benchmark-prefix) (list name)))
+
+;;; A parameter containing the current benchmark prefix, as a list.
+(define current-benchmark-prefix
+ (make-parameter '()))
+
+;;; Postpend PREFIX to the current name prefix while evaluting THUNK.
+;;; The name prefix is only changed within the dynamic scope of the
+;;; call to with-benchmark-prefix*. Return the value returned by THUNK.
+(define (with-benchmark-prefix* prefix thunk)
+ (parameterize ((current-benchmark-prefix (full-name prefix)))
+ (thunk)))
+
+;;; (with-benchmark-prefix PREFIX BODY ...)
+;;; Postpend PREFIX to the current name prefix while evaluating BODY ...
+;;; The name prefix is only changed within the dynamic scope of the
+;;; with-benchmark-prefix expression. Return the value returned by the last
+;;; BODY expression.
+(cond-expand
+ (guile-2
+ (define-syntax-rule (with-benchmark-prefix prefix body body* ...)
+ (with-benchmark-prefix* prefix (lambda () body body* ...))))
+ (else
+ (defmacro with-benchmark-prefix (prefix . body)
+ `(with-benchmark-prefix* ,prefix (lambda () ,@body)))))
+
+
+;;;; Benchmark results
+;;;;
+
+(define *calibration-result*
+ "<will be set during initialization>")
+
+(define (benchmark-overhead iterations accessor)
+ (* (/ iterations 1.0 (benchmark-result:iterations *calibration-result*))
+ (accessor *calibration-result*)))
+
+(define (benchmark-result:core-time result)
+ (- (benchmark-result:run-time result)
+ (benchmark-overhead (benchmark-result:iterations result)
+ benchmark-result:run-time)))
+
+
+;;;; REPORTERS
+;;;;
+
+;;; The global set of reporters.
+(define report-hook (make-hook 1))
+
+(define (default-reporter result)
+ (if (hook-empty? report-hook)
+ (user-reporter result)
+ (run-hook report-hook result)))
+
+(define current-reporter
+ (make-parameter default-reporter))
+
+(define (register-reporter reporter)
+ (add-hook! report-hook reporter))
+
+(define (unregister-reporter reporter)
+ (remove-hook! report-hook reporter))
+
+;;; Return true iff REPORTER is in the current set of reporter functions.
+(define (reporter-registered? reporter)
+ (if (memq reporter (hook->list report-hook)) #t #f))
+
+;;; Send RESULT to all currently registered reporter functions.
+(define (report result)
+ ((current-reporter) result))
+
+
+;;;; Some useful standard reporters:
+;;;; Log reporters write all benchmark results to a given log file.
+;;;; Full reporters write all benchmark results to the standard output.
+;;;; User reporters write some interesting results to the standard output.
+
+;;; Display a single benchmark result to the given port
+(define (print-result port result)
+ (let ((name (format-benchmark-name (benchmark-result:name result)))
+ (iterations (benchmark-result:iterations result))
+ (real-time (benchmark-result:real-time result))
+ (run-time (benchmark-result:run-time result))
+ (gc-time (benchmark-result:gc-time result))
+ (core-time (benchmark-result:core-time result)))
+ (write (list name iterations
+ 'total (->seconds real-time)
+ 'user (->seconds run-time)
+ 'system 0
+ 'frame (->seconds (- run-time core-time))
+ 'benchmark (->seconds core-time)
+ 'user/interp (->seconds (- run-time gc-time))
+ 'bench/interp (->seconds (- core-time gc-time))
+ 'gc (->seconds gc-time))
+ port)
+ (newline port)))
+
+;;; Return a reporter procedure which prints all results to the file
+;;; FILE, in human-readable form. FILE may be a filename, or a port.
+(define (make-log-reporter file)
+ (let ((port (if (output-port? file) file
+ (open-output-file file))))
+ (lambda (result)
+ (print-result port result)
+ (force-output port))))
+
+;;; A reporter that reports all results to the user.
+(define (full-reporter result)
+ (print-result (current-output-port) result))
+
+;;; Display interesting results of a single benchmark to the given port
+(define (print-user-result port result)
+ (let ((name (format-benchmark-name (benchmark-result:name result)))
+ (iterations (benchmark-result:iterations result))
+ (real-time (benchmark-result:real-time result))
+ (run-time (benchmark-result:run-time result))
+ (gc-time (benchmark-result:gc-time result))
+ (core-time (benchmark-result:core-time result)))
+ (write (list name iterations
+ 'real (->seconds real-time)
+ 'real/iteration (->seconds (/ real-time iterations))
+ 'run/iteration (->seconds (/ run-time iterations))
+ 'core/iteration (->seconds (/ core-time iterations))
+ 'gc (->seconds gc-time))
+ port)
+ (newline port)))
+
+;;; A reporter that reports interesting results to the user.
+(define (user-reporter result)
+ (print-user-result (current-output-port) result))
+
+
+;;;; Initialize the benchmarking system:
+;;;;
+
+(define (calibrate-benchmark-framework)
+ (display ";; running guile version ")
+ (display (version))
+ (newline)
+ (display ";; calibrating the benchmarking framework...")
+ (newline)
+ (parameterize ((current-reporter
+ (lambda (result)
+ (set! *calibration-result* result)
+ (display ";; calibration: ")
+ (print-user-result (current-output-port) result))))
+ (benchmark "empty initialization benchmark" 10000000 #t)))
+
+(calibrate-benchmark-framework)
+;; -*- Scheme -*-
+;;
+;; A library of dumb functions that may be used to benchmark Guile-VM.
+
+
+;; The comments are from Ludovic, a while ago. The speedups now are much
+;; more significant (all over 2x, sometimes 8x).
+
+(define (fibo x)
+ (if (or (= x 1) (= x 2))
+ 1
+ (+ (fibo (- x 1))
+ (fibo (- x 2)))))
+
+(define (g-c-d x y)
+ (if (= x y)
+ x
+ (if (< x y)
+ (g-c-d x (- y x))
+ (g-c-d (- x y) y))))
+
+(define (loop n)
+ ;; This one shows that procedure calls are no faster than within the
+ ;; interpreter: the VM yields no performance improvement.
+ (if (= 0 n)
+ 0
+ (loop (1- n))))
+
+;; Disassembly of `loop'
+;;
+;; Disassembly of #<objcode b79bdf28>:
+
+;; nlocs = 0 nexts = 0
+
+;; 0 (make-int8 64) ;; 64
+;; 2 (load-symbol "guile-user") ;; guile-user
+;; 14 (list 0 1) ;; 1 element
+;; 17 (load-symbol "loop") ;; loop
+;; 23 (link-later)
+;; 24 (vector 0 1) ;; 1 element
+;; 27 (make-int8 0) ;; 0
+;; 29 (load-symbol "n") ;; n
+;; 32 (make-false) ;; #f
+;; 33 (make-int8 0) ;; 0
+;; 35 (list 0 3) ;; 3 elements
+;; 38 (list 0 2) ;; 2 elements
+;; 41 (list 0 1) ;; 1 element
+;; 44 (make-int8 5) ;; 5
+;; 46 (make-false) ;; #f
+;; 47 (cons)
+;; 48 (make-int8 18) ;; 18
+;; 50 (make-false) ;; #f
+;; 51 (cons)
+;; 52 (make-int8 20) ;; 20
+;; 54 (make-false) ;; #f
+;; 55 (cons)
+;; 56 (list 0 4) ;; 4 elements
+;; 59 (load-program ##{66}#)
+;; 81 (define "loop")
+;; 87 (variable-set)
+;; 88 (void)
+;; 89 (return)
+
+;; Bytecode ##{66}#\
+
+;; 0 (make-int8 0) ;; 0
+;; 2 (local-ref 0)
+;; 4 (ee?)
+;; 5 (br-if-not 0 3) ;; -> 11
+;; 8 (make-int8 0) ;; 0
+;; 10 (return)
+;; 11 (toplevel-ref 0)
+;; 13 (local-ref 0)
+;; 15 (make-int8 1) ;; 1
+;; 17 (sub)
+;; 18 (tail-call 1)
+
+(define (loopi n)
+ ;; Same as `loop'.
+ (let loopi ((n n))
+ (if (= 0 n)
+ 0
+ (loopi (1- n)))))
+
+(define (do-loop n)
+ ;; Same as `loop' using `do'.
+ (do ((i n (1- i)))
+ ((= 0 i))
+ ;; do nothing
+ ))
+
+
+(define (do-cons x)
+ ;; This one shows that the built-in `cons' instruction yields a significant
+ ;; improvement (speedup: 1.5).
+ (let loop ((x x)
+ (result '()))
+ (if (<= x 0)
+ result
+ (loop (1- x) (cons x result)))))
+
+(define big-list (iota 500000))
+
+(define (copy-list lst)
+ ;; Speedup: 5.9.
+ (let loop ((lst lst)
+ (result '()))
+ (if (null? lst)
+ result
+ (loop (cdr lst)
+ (cons (car lst) result)))))
+
+;; A simple interpreter vs. VM performance comparison tool
+;;
+
+(define-module (measure)
+ \:export (measure)
+ \:use-module (system vm vm)
+ \:use-module (system base compile)
+ \:use-module (system base language))
+
+
+(define (time-for-eval sexp eval)
+ (let ((before (tms:utime (times))))
+ (eval sexp)
+ (let ((elapsed (- (tms:utime (times)) before)))
+ (format #t "elapsed time: ~a~%" elapsed)
+ elapsed)))
+
+(define *scheme* (lookup-language 'scheme))
+
+
+(define (measure . args)
+ (if (< (length args) 2)
+ (begin
+ (format #t "Usage: measure SEXP FILE-TO-LOAD...~%")
+ (format #t "~%")
+ (format #t "Example: measure '(loop 23424)' lib.scm~%~%")
+ (exit 1)))
+ (for-each load (cdr args))
+ (let* ((sexp (with-input-from-string (car args)
+ (lambda ()
+ (read))))
+ (eval-here (lambda (sexp) (eval sexp (current-module))))
+ (proc-name (car sexp))
+ (proc-source (procedure-source (eval proc-name (current-module))))
+ (% (format #t "proc: ~a~%source: ~a~%" proc-name proc-source))
+ (time-interpreted (time-for-eval sexp eval-here))
+ (& (if (defined? proc-name)
+ (eval `(set! ,proc-name #f) (current-module))
+ (format #t "unbound~%")))
+ (the-program (compile proc-source))
+
+ (time-compiled (time-for-eval `(,proc-name ,@(cdr sexp))
+ (lambda (sexp)
+ (eval `(begin
+ (define ,proc-name
+ ,the-program)
+ ,sexp)
+ (current-module))))))
+
+ (format #t "proc: ~a => ~a~%"
+ proc-name (eval proc-name (current-module)))
+ (format #t "interpreted: ~a~%" time-interpreted)
+ (format #t "compiled: ~a~%" time-compiled)
+ (format #t "speedup: ~a~%"
+ (exact->inexact (/ time-interpreted time-compiled)))
+ 0))
+
+(define main measure)
+;;; guile-emacs.scm --- Guile Emacs interface
+
+;; Copyright (C) 2001, 2010 Keisuke Nishida <kxn30@po.cwru.edu>
+
+;;;; This library 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 library 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 library; if not, write to the Free
+;;;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+;;;; 02111-1307 USA
+
+;;; Code:
+
+(use-modules (ice-9 regex))
+(use-modules (ice-9 channel))
+(use-modules (ice-9 session))
+(use-modules (ice-9 documentation))
+
+
+;;;
+;;; Emacs Lisp channel
+;;;
+
+(define (emacs-lisp-channel)
+
+ (define (native-type? x)
+ (or (integer? x) (symbol? x) (string? x) (pair? x) (vector? x)))
+
+ (define (emacs-lisp-print ch val)
+ (cond
+ ((unspecified? val))
+ ((eq? val #t) (channel-print-value ch 't))
+ ((or (eq? val #f) (null? val)) (channel-print-value ch 'nil))
+ ((native-type? val) (channel-print-value ch val))
+ (else (channel-print-token ch val))))
+
+ (channel-open (make-object-channel emacs-lisp-print)))
+
+
+;;;
+;;; Scheme channel
+;;;
+
+(define (emacs-scheme-channel)
+ (define (print ch val) (channel-print-value ch (object->string val)))
+ (channel-open (make-object-channel print)))
+
+
+;;;
+;;; for guile-import and guile-import-module
+;;;
+
+(define (guile-emacs-export-procedure name proc docs)
+ (define (procedure-args proc)
+ (let ((source (procedure-source proc)))
+ (if source
+ ;; formals -> emacs args
+ (let loop ((formals (cadr source)))
+ (cond
+ ((null? formals) '())
+ ((symbol? formals) `(&rest ,formals))
+ (else (cons (car formals) (loop (cdr formals))))))
+ ;; arity -> emacs args
+ (let* ((arity (procedure-minimum-arity proc))
+ (nreqs (car arity))
+ (nopts (cadr arity))
+ (restp (caddr arity)))
+ (define (nsyms n)
+ (if (= n 0) '() (cons (gensym "a") (nsyms (1- n)))))
+ (append! (nsyms nreqs)
+ (if (> nopts 0) (cons '&optional (nsyms nopts)) '())
+ (if restp (cons '&rest (nsyms 1)) '()))))))
+
+ (define (procedure-call name args)
+ (let ((restp (memq '&rest args))
+ (args (delq '&rest (delq '&optional args))))
+ (if restp
+ `('apply ',name ,@args)
+ `(',name ,@args))))
+
+ (let ((args (procedure-args proc))
+ (docs (and docs (object-documentation proc))))
+ `(defun ,name ,args
+ ,@(if docs (list docs) '())
+ (guile-lisp-flat-eval ,@(procedure-call (procedure-name proc) args)))))
+
+(define (guile-emacs-export proc-name func-name docs)
+ (let ((proc (module-ref (current-module) proc-name)))
+ (guile-emacs-export-procedure func-name proc docs)))
+
+(define (guile-emacs-export-procedures module-name docs)
+ (define (module-public-procedures name)
+ (hash-fold (lambda (s v d)
+ (let ((val (variable-ref v)))
+ (if (procedure? val) (acons s val d) d)))
+ '() (module-obarray (resolve-interface name))))
+ `(progn ,@(map (lambda (n+p)
+ (guile-emacs-export-procedure (car n+p) (cdr n+p) docs))
+ (module-public-procedures module-name))))
+
+
+;;;
+;;; for guile-scheme-complete-symbol
+;;;
+
+(define (guile-emacs-complete-alist str)
+ (sort! (apropos-fold (lambda (module name val data)
+ (cons (list (symbol->string name)
+ (cond ((procedure? val) " <p>")
+ ((macro? val) " <m>")
+ (else "")))
+ data))
+ '() (string-append "^" (regexp-quote str))
+ apropos-fold-all)
+ (lambda (p1 p2) (string<? (car p1) (car p2)))))
+
+
+;;;
+;;; for guile-scheme-apropos
+;;;
+
+(define (guile-emacs-apropos regexp)
+ (with-output-to-string (lambda () (apropos regexp))))
+
+
+;;;
+;;; for guile-scheme-describe
+;;;
+
+(define (guile-emacs-describe sym)
+ (object-documentation (eval sym (current-module))))
+
+
+;;;
+;;; Guile 1.4 compatibility
+;;;
+
+(define object->string
+ (if (defined? 'object->string)
+ object->string
+ (lambda (x) (format #f "~S" x))))
+
+;;; guile-emacs.scm ends here
+;;; examples/box-dynamic-module/box-mixed.scm -- Scheme module using some
+;;; functionality from the shared library libbox-module, but do not
+;;; export procedures from the module.
+
+;;; Commentary:
+
+;;; This is the Scheme module box-mixed. It uses some functionality
+;;; from the shared library libbox-module, but does not export it.
+
+;;; Code:
+
+;;; Author: Thomas Wawrzinek
+;;; Date: 2001-06-08
+;;; Changed: 2001-06-14 by martin, some commenting, cleanup and integration.
+
+(define-module (box-mixed))
+
+;; First, load the library.
+;;
+(load-extension "libbox-module" "scm_init_box")
+
+;; Create a list of boxes, each containing one element from ARGS.
+;;
+(define (make-box-list . args)
+ (map (lambda (el)
+ (let ((b (make-box)))
+ (box-set! b el) b))
+ args))
+
+;; Map the procedure FUNC over all elements of LST, which must be a
+;; list of boxes. The result is a list of freshly allocated boxes,
+;; each containing the result of an application of FUNC.
+(define (box-map func lst)
+ (map (lambda (el)
+ (let ((b (make-box)))
+ (box-set! b (func (box-ref el)))
+ b))
+ lst))
+
+;; Export the procedures, so that they can be used by others.
+;;
+(export make-box-list box-map)
+
+;;; End of file.
+;;; examples/box-dynamic-module/box-module.scm -- Scheme module exporting
+;;; some functionality from the shared library libbox-module.
+
+;;; Commentary:
+
+;;; This is the Scheme part of the dynamic library module (box-module).
+;;; When you do a (use-modules (box-module)) in this directory,
+;;; this file gets loaded and will load the compiled extension.
+
+;;; Code:
+
+;;; Author: Martin Grabmueller
+;;; Date: 2001-06-06
+
+(define-module (box-module))
+
+;; First, load the library.
+;;
+(load-extension "libbox-module" "scm_init_box")
+
+;; Then export the procedures which should be visible to module users.
+;;
+(export make-box box-ref box-set!)
+
+;;; End of file.
+;;; examples/modules/module-0.scm -- Module system demo.
+
+;;; Commentary:
+
+;;; Module 0 of the module demo program.
+
+;;; Author: Martin Grabmueller
+;;; Date: 2001-05-29
+
+;;; Code:
+
+(define-module (module-0))
+
+(export foo bar)
+
+(define (foo)
+ (display "module-0 foo")
+ (newline))
+
+(define (bar)
+ (display "module-0 bar")
+ (newline))
+
+;;; End of file.
+;;; examples/modules/module-1.scm -- Module system demo.
+
+;;; Commentary:
+
+;;; Module 1 of the module demo program.
+
+;;; Author: Martin Grabmueller
+;;; Date: 2001-05-29
+
+;;; Code:
+
+(define-module (module-1))
+
+(export foo bar)
+
+(define (foo)
+ (display "module-1 foo")
+ (newline))
+
+(define (bar)
+ (display "module-1 bar")
+ (newline))
+
+;;; End of file.
+;;; examples/modules/module-2.scm -- Module system demo.
+
+;;; Commentary:
+
+;;; Module 2 of the module demo program.
+
+;;; Author: Martin Grabmueller
+;;; Date: 2001-05-29
+
+;;; Code:
+
+(define-module (module-2))
+
+(export foo bar braz)
+
+(define (foo)
+ (display "module-2 foo")
+ (newline))
+
+(define (bar)
+ (display "module-2 bar")
+ (newline))
+
+(define (braz)
+ (display "module-2 braz")
+ (newline))
+
+;;; End of file.
+;;; examples/safe/evil.scm -- Evil Scheme file to be run in a safe
+;;; environment.
+
+;;; Commentary:
+
+;;; This is an example file to be evaluated by the `safe' program in
+;;; this directory. This program, unlike the `untrusted.scm' (which
+;;; is untrusted, but a really nice fellow though), tries to do evil
+;;; things and will thus break in a safe environment.
+;;;
+;;; *Note* that the files in this directory are only suitable for
+;;; demonstration purposes, if you have to implement safe evaluation
+;;; mechanisms in important environments, you will have to do more
+;;; than shown here -- for example disabling input/output operations.
+
+;;; Author: Martin Grabmueller
+;;; Date: 2001-05-30
+
+;;; Code:
+
+(define passwd (open-input-file "/etc/passwd"))
+
+(let lp ((ch (read-char passwd)))
+ (if (not (eof-object? ch))
+ (lp (read-char passwd))))
+
+;;; End of file.
+;;; examples/safe/untrusted.scm -- Scheme file to be run in a safe
+;;; environment.
+
+;;; Commentary:
+
+;;; This is an example file to be evaluated by the `safe' program in
+;;; this directory.
+;;;
+;;; *Note* that the files in this directory are only suitable for
+;;; demonstration purposes, if you have to implement safe evaluation
+;;; mechanisms in important environments, you will have to do more
+;;; than shown here -- for example disabling input/output operations.
+
+;;; Author: Martin Grabmueller
+;;; Date: 2001-05-30
+
+;;; Code:
+
+;; fact -- the everlasting factorial function...
+;;
+(define (fact n)
+ (if (< n 2)
+ 1
+ (* n (fact (- n 1)))))
+
+;; Display the factorial of 0..9 to the terminal.
+;;
+(do ((x 0 (+ x 1)))
+ ((= x 11))
+ (display (fact x))
+ (newline))
+
+;;; End of file.
+;;; Commentary:
+
+;;; This is the famous Hello-World-program, written for Guile.
+;;;
+;;; For an advanced version, see the script `hello' in the same
+;;; directory.
+
+;;; Author: Martin Grabmueller
+;;; Date: 2001-05-29
+
+;;; Code:
+
+(display "Hello, World!")
+(newline)
+
+;;; End of file.
+;;; Commentary:
+
+;;; A simple debugging server that responds to all responses with a
+;;; table containing the headers given in the request.
+;;;
+;;; As a novelty, this server uses a little micro-framework to build up
+;;; the response as SXML. Instead of a string, the `respond' helper
+;;; returns a procedure for the body, which allows the `(web server)'
+;;; machinery to collect the output as a bytevector in the desired
+;;; encoding, instead of building an intermediate output string.
+;;;
+;;; In the future this will also allow for chunked transfer-encoding,
+;;; for HTTP/1.1 clients.
+
+;;; Code:
+
+(use-modules (web server)
+ (web request)
+ (web response)
+ (sxml simple))
+
+(define html5-doctype "<!DOCTYPE html>\n")
+(define default-title "Hello hello!")
+
+(define* (templatize #\key (title "No title") (body '((p "No body"))))
+ `(html (head (title ,title))
+ (body ,@body)))
+
+(define* (respond #\optional body #\key
+ (status 200)
+ (title default-title)
+ (doctype html5-doctype)
+ (content-type-params '((charset . "utf-8")))
+ (content-type 'text/html)
+ (extra-headers '())
+ (sxml (and body (templatize #\title title #\body body))))
+ (values (build-response
+ #\code status
+ #\headers `((content-type . (,content-type ,@content-type-params))
+ ,@extra-headers))
+ (lambda (port)
+ (if sxml
+ (begin
+ (if doctype (display doctype port))
+ (sxml->xml sxml port))))))
+
+(define (debug-page request body)
+ (respond `((h1 "hello world!")
+ (table
+ (tr (th "header") (th "value"))
+ ,@(map (lambda (pair)
+ `(tr (td (tt ,(with-output-to-string
+ (lambda () (display (car pair))))))
+ (td (tt ,(with-output-to-string
+ (lambda ()
+ (write (cdr pair))))))))
+ (request-headers request))))))
+
+(run-server debug-page)
+;;; Commentary:
+
+;;; A simple web server that responds to all requests with the eponymous
+;;; string. Visit http://localhost:8080 to test.
+
+;;; Code:
+
+(use-modules (web server))
+
+;; A handler receives two values as arguments: the request object, and
+;; the request body. It returns two values also: the response object,
+;; and the response body.
+;;
+;; In this simple example we don't actually access the request object,
+;; but if we wanted to, we would use the procedures from the `(web
+;; request)' module. If there is no body given in the request, the body
+;; argument will be false.
+;;
+;; To create a response object, use the `build-response' procedure from
+;; `(web response)'. Here we take advantage of a shortcut, in which we
+;; return an alist of headers for the response instead of returning a
+;; proper response object. In this case, a response object will be made
+;; for us with a 200 OK status.
+;;
+(define (handler request body)
+ (values '((content-type . (text/plain)))
+ "Hello, World!"))
+
+(run-server handler)
+;;; Copyright (C) 2008, 2011 Free Software Foundation, Inc.
+;;;
+;;; 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, 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 software; see the file COPYING.LESSER. If
+;;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(use-modules (ice-9 format)
+ (ice-9 rdelim)
+ (ice-9 regex)
+ (srfi srfi-1)
+ (srfi srfi-37)
+ (srfi srfi-39))
+
+
+;;;
+;;; Memory usage.
+;;;
+
+(define (memory-mappings pid)
+ "Return an list of alists, each of which contains information about a
+memory mapping of process @var{pid}. This information is obtained by reading
+@file{/proc/PID/smaps} on Linux. See `procs(5)' for details."
+
+ (define mapping-line-rx
+ ;; As of Linux 2.6.32.28, an `smaps' line looks like this:
+ ;; "00400000-00401000 r-xp 00000000 fe:00 108264 /home/ludo/soft/bin/guile"
+ (make-regexp
+ "^([[:xdigit:]]+)-([[:xdigit:]]+) ([rwx-]{3}[ps]) ([[:xdigit:]]+) [[:xdigit:]]{2}:[[:xdigit:]]{2} [0-9]+[[:blank:]]+(.*)$"))
+
+ (define rss-line-rx
+ (make-regexp
+ "^Rss:[[:blank:]]+([[:digit:]]+) kB$"))
+
+ (if (not (string-contains %host-type "-linux-"))
+ (error "this procedure only works on Linux-based systems" %host-type))
+
+ (with-input-from-port (open-input-file (format #f "/proc/~a/smaps" pid))
+ (lambda ()
+ (let loop ((line (read-line))
+ (result '()))
+ (if (eof-object? line)
+ (reverse result)
+ (cond ((regexp-exec mapping-line-rx line)
+ =>
+ (lambda (match)
+ (let ((mapping-start (string->number
+ (match:substring match 1)
+ 16))
+ (mapping-end (string->number
+ (match:substring match 2)
+ 16))
+ (access-bits (match:substring match 3))
+ (name (match:substring match 5)))
+ (loop (read-line)
+ (cons `((mapping-start . ,mapping-start)
+ (mapping-end . ,mapping-end)
+ (access-bits . ,access-bits)
+ (name . ,(if (string=? name "")
+ #f
+ name)))
+ result)))))
+ ((regexp-exec rss-line-rx line)
+ =>
+ (lambda (match)
+ (let ((section+ (cons (cons 'rss
+ (string->number
+ (match:substring match 1)))
+ (car result))))
+ (loop (read-line)
+ (cons section+ (cdr result))))))
+ (else
+ (loop (read-line) result))))))))
+
+(define (total-heap-size pid)
+ "Return a pair representing the total and RSS heap size of PID."
+
+ (define heap-or-anon-rx
+ (make-regexp "\\[(heap|anon)\\]"))
+
+ (define private-mapping-rx
+ (make-regexp "^[r-][w-][x-]p$"))
+
+ (fold (lambda (heap total+rss)
+ (let ((name (assoc-ref heap 'name))
+ (perm (assoc-ref heap 'access-bits)))
+ ;; Include anonymous private mappings.
+ (if (or (and (not name)
+ (regexp-exec private-mapping-rx perm))
+ (and name
+ (regexp-exec heap-or-anon-rx name)))
+ (let ((start (assoc-ref heap 'mapping-start))
+ (end (assoc-ref heap 'mapping-end))
+ (rss (assoc-ref heap 'rss)))
+ (cons (+ (car total+rss) (- end start))
+ (+ (cdr total+rss) rss)))
+ total+rss)))
+ '(0 . 0)
+ (memory-mappings pid)))
+
+
+(define (display-stats start end)
+ (define (->usecs sec+usecs)
+ (+ (* 1000000 (car sec+usecs))
+ (cdr sec+usecs)))
+
+ (let ((usecs (- (->usecs end) (->usecs start)))
+ (heap-size (total-heap-size (getpid)))
+ (gc-heap-size (assoc-ref (gc-stats) 'heap-size)))
+
+ (format #t "execution time: ~6,3f seconds~%"
+ (/ usecs 1000000.0))
+
+ (and gc-heap-size
+ (format #t "GC-reported heap size: ~8d B (~1,2f MiB)~%"
+ gc-heap-size
+ (/ gc-heap-size 1024.0 1024.0)))
+
+ (format #t "heap size: ~8d B (~1,2f MiB)~%"
+ (car heap-size)
+ (/ (car heap-size) 1024.0 1024.0))
+ (format #t "heap RSS: ~8d KiB (~1,2f MiB)~%"
+ (cdr heap-size)
+ (/ (cdr heap-size) 1024.0))
+;; (system (format #f "cat /proc/~a/smaps" (getpid)))
+;; (system (format #f "exmtool procs | grep -E '^(PID|~a)'" (getpid)))
+ ))
+
+
+;;;
+;;; Larceny/Twobit benchmarking compability layer.
+;;;
+
+(define *iteration-count*
+ (make-parameter #f))
+
+(define (run-benchmark name . args)
+ "A @code{run-benchmark} procedure compatible with Larceny's GC benchmarking
+framework. See
+@url{http://www.ccs.neu.edu/home/will/Twobit/benchmarksAbout.html} for
+details."
+
+ (define %concise-invocation?
+ ;; This procedure can be called with only two arguments, NAME and
+ ;; RUN-MAKER.
+ (procedure? (car args)))
+
+ (let ((count (or (*iteration-count*)
+ (if %concise-invocation? 0 (car args))))
+ (run-maker (if %concise-invocation? (car args) (cadr args)))
+ (ok? (if %concise-invocation?
+ (lambda (result) #t)
+ (caddr args)))
+ (args (if %concise-invocation? '() (cdddr args))))
+ (let loop ((i 0))
+ (and (< i count)
+ (let ((result (apply run-maker args)))
+ (if (not (ok? result))
+ (begin
+ (format (current-output-port) "invalid result for `~A'~%"
+ name)
+ (exit 1)))
+ (loop (1+ i)))))))
+
+(define (save-directory-excursion directory thunk)
+ (let ((previous-dir (getcwd)))
+ (dynamic-wind
+ (lambda ()
+ (chdir directory))
+ thunk
+ (lambda ()
+ (chdir previous-dir)))))
+
+(define (load-larceny-benchmark file)
+ "Load the Larceny benchmark from @var{file}."
+ (let ((name (let ((base (basename file)))
+ (substring base 0 (or (string-rindex base #\.)
+ (string-length base)))))
+ (module (let ((m (make-module)))
+ (beautify-user-module! m)
+ (module-use! m (resolve-interface '(ice-9 syncase)))
+ m)))
+ (save-directory-excursion (dirname file)
+ (lambda ()
+ (save-module-excursion
+ (lambda ()
+ (set-current-module module)
+ (module-define! module 'run-benchmark run-benchmark)
+ (load (basename file))
+
+ ;; Invoke the benchmark's entry point.
+ (let ((entry (module-ref (current-module)
+ (symbol-append (string->symbol name)
+ '-benchmark))))
+ (entry))))))))
+
+
+
+;;;
+;;; Option processing.
+;;;
+
+(define %options
+ (list (option '(#\h "help") #f #f
+ (lambda args
+ (show-help)
+ (exit 0)))
+ (option '(#\l "larceny") #f #f
+ (lambda (opt name arg result)
+ (alist-cons 'larceny? #t result)))
+ (option '(#\i "iterations") #t #f
+ (lambda (opt name arg result)
+ (alist-cons 'iterations (string->number arg) result)))))
+
+(define (show-help)
+ (format #t "Usage: gc-profile [OPTIONS] FILE.SCM
+Load FILE.SCM, a Guile Scheme source file, and report its execution time and
+final heap usage.
+
+ -h, --help Show this help message
+
+ -l, --larceny Provide mechanisms compatible with the Larceny/Twobit
+ GC benchmark suite.
+ -i, --iterations=COUNT
+ Run the given benchmark COUNT times, regardless of the
+ iteration count passed to `run-benchmark' (for Larceny
+ benchmarks).
+
+Report bugs to <bug-guile@gnu.org>.~%"))
+
+(define (parse-args args)
+ (define (leave fmt . args)
+ (apply format (current-error-port) (string-append fmt "~%") args)
+ (exit 1))
+
+ (args-fold args %options
+ (lambda (opt name arg result)
+ (leave "~A: unrecognized option" opt))
+ (lambda (file result)
+ (if (pair? (assoc 'input result))
+ (leave "~a: only one input file at a time" file)
+ (alist-cons 'input file result)))
+ '()))
+
+
+;;;
+;;; Main program.
+;;;
+
+(define (main . args)
+ (let* ((options (parse-args args))
+ (prog (assoc-ref options 'input))
+ (load (if (assoc-ref options 'larceny?)
+ load-larceny-benchmark
+ load)))
+
+ (parameterize ((*iteration-count* (assoc-ref options 'iterations)))
+ (format #t "running `~a' with Guile ~a...~%" prog (version))
+
+ (let ((start (gettimeofday)))
+ (dynamic-wind
+ (lambda ()
+ #t)
+ (lambda ()
+ (set! quit (lambda args args))
+ (load prog))
+ (lambda ()
+ (let ((end (gettimeofday)))
+ (format #t "done~%")
+ (display-stats start end))))))))
+; This is adapted from a benchmark written by John Ellis and Pete Kovac
+; of Post Communications.
+; It was modified by Hans Boehm of Silicon Graphics.
+; It was translated into Scheme by William D Clinger of Northeastern Univ;
+; the Scheme version uses (RUN-BENCHMARK <string> <thunk>)
+; Last modified 30 May 1997.
+;
+; This is no substitute for real applications. No actual application
+; is likely to behave in exactly this way. However, this benchmark was
+; designed to be more representative of real applications than other
+; Java GC benchmarks of which we are aware.
+; It attempts to model those properties of allocation requests that
+; are important to current GC techniques.
+; It is designed to be used either to obtain a single overall performance
+; number, or to give a more detailed estimate of how collector
+; performance varies with object lifetimes. It prints the time
+; required to allocate and collect balanced binary trees of various
+; sizes. Smaller trees result in shorter object lifetimes. Each cycle
+; allocates roughly the same amount of memory.
+; Two data structures are kept around during the entire process, so
+; that the measured performance is representative of applications
+; that maintain some live in-memory data. One of these is a tree
+; containing many pointers. The other is a large array containing
+; double precision floating point numbers. Both should be of comparable
+; size.
+;
+; The results are only really meaningful together with a specification
+; of how much memory was used. It is possible to trade memory for
+; better time performance. This benchmark should be run in a 32 MB
+; heap, though we don't currently know how to enforce that uniformly.
+
+; In the Java version, this routine prints the heap size and the amount
+; of free memory. There is no portable way to do this in Scheme; each
+; implementation needs its own version.
+
+(use-modules (ice-9 syncase))
+
+(define (PrintDiagnostics)
+ (display " Total memory available= ???????? bytes")
+ (display " Free memory= ???????? bytes")
+ (newline))
+
+
+
+(define (run-benchmark str thu)
+ (display str)
+ (thu))
+; Should we implement a Java class as procedures or hygienic macros?
+; Take your pick.
+
+(define-syntax let-class
+ (syntax-rules
+ ()
+
+ ;; Put this rule first to implement a class using procedures.
+ ((let-class (((method . args) . method-body) ...) . body)
+ (let () (define (method . args) . method-body) ... . body))
+
+
+ ;; Put this rule first to implement a class using hygienic macros.
+ ((let-class (((method . args) . method-body) ...) . body)
+ (letrec-syntax ((method (syntax-rules () ((method . args) (begin . method-body))))
+ ...)
+ . body))
+
+
+ ))
+
+
+(define (gcbench kStretchTreeDepth)
+
+ ; Nodes used by a tree of a given size
+ (define (TreeSize i)
+ (- (expt 2 (+ i 1)) 1))
+
+ ; Number of iterations to use for a given tree depth
+ (define (NumIters i)
+ (quotient (* 2 (TreeSize kStretchTreeDepth))
+ (TreeSize i)))
+
+ ; Parameters are determined by kStretchTreeDepth.
+ ; In Boehm's version the parameters were fixed as follows:
+ ; public static final int kStretchTreeDepth = 18; // about 16Mb
+ ; public static final int kLongLivedTreeDepth = 16; // about 4Mb
+ ; public static final int kArraySize = 500000; // about 4Mb
+ ; public static final int kMinTreeDepth = 4;
+ ; public static final int kMaxTreeDepth = 16;
+ ; In Larceny the storage numbers above would be 12 Mby, 3 Mby, 6 Mby.
+
+ (let* ((kLongLivedTreeDepth (- kStretchTreeDepth 2))
+ (kArraySize (* 4 (TreeSize kLongLivedTreeDepth)))
+ (kMinTreeDepth 4)
+ (kMaxTreeDepth kLongLivedTreeDepth))
+
+ ; Elements 3 and 4 of the allocated vectors are useless.
+
+ (let-class (((make-node l r)
+ (let ((v (make-empty-node)))
+ (vector-set! v 0 l)
+ (vector-set! v 1 r)
+ v))
+ ((make-empty-node) (make-vector 4 0))
+ ((node.left node) (vector-ref node 0))
+ ((node.right node) (vector-ref node 1))
+ ((node.left-set! node x) (vector-set! node 0 x))
+ ((node.right-set! node x) (vector-set! node 1 x)))
+
+ ; Build tree top down, assigning to older objects.
+ (define (Populate iDepth thisNode)
+ (if (<= iDepth 0)
+ #f
+ (let ((iDepth (- iDepth 1)))
+ (node.left-set! thisNode (make-empty-node))
+ (node.right-set! thisNode (make-empty-node))
+ (Populate iDepth (node.left thisNode))
+ (Populate iDepth (node.right thisNode)))))
+
+ ; Build tree bottom-up
+ (define (MakeTree iDepth)
+ (if (<= iDepth 0)
+ (make-empty-node)
+ (make-node (MakeTree (- iDepth 1))
+ (MakeTree (- iDepth 1)))))
+
+ (define (TimeConstruction depth)
+ (let ((iNumIters (NumIters depth)))
+ (display (string-append "Creating "
+ (number->string iNumIters)
+ " trees of depth "
+ (number->string depth)))
+ (newline)
+ (run-benchmark "GCBench: Top down construction"
+ (lambda ()
+ (do ((i 0 (+ i 1)))
+ ((>= i iNumIters))
+ (Populate depth (make-empty-node)))))
+ (run-benchmark "GCBench: Bottom up construction"
+ (lambda ()
+ (do ((i 0 (+ i 1)))
+ ((>= i iNumIters))
+ (MakeTree depth))))))
+
+ (define (main)
+ (display "Garbage Collector Test")
+ (newline)
+ (display (string-append
+ " Stretching memory with a binary tree of depth "
+ (number->string kStretchTreeDepth)))
+ (newline)
+ (run-benchmark "GCBench: Main"
+ (lambda ()
+ ; Stretch the memory space quickly
+ (MakeTree kStretchTreeDepth)
+
+ ; Create a long lived object
+ (display (string-append
+ " Creating a long-lived binary tree of depth "
+ (number->string kLongLivedTreeDepth)))
+ (newline)
+ (let ((longLivedTree (make-empty-node)))
+ (Populate kLongLivedTreeDepth longLivedTree)
+
+ ; Create long-lived array, filling half of it
+ (display (string-append
+ " Creating a long-lived array of "
+ (number->string kArraySize)
+ " inexact reals"))
+ (newline)
+ (let ((array (make-vector kArraySize 0.0)))
+ (do ((i 0 (+ i 1)))
+ ((>= i (quotient kArraySize 2)))
+ (vector-set! array i (/ 1.0 (exact->inexact i))))
+ (PrintDiagnostics)
+
+ (do ((d kMinTreeDepth (+ d 2)))
+ ((> d kMaxTreeDepth))
+ (TimeConstruction d))
+
+ (if (or (eq? longLivedTree '())
+ (let ((n (min 1000
+ (- (quotient (vector-length array)
+ 2)
+ 1))))
+ (not (= (vector-ref array n)
+ (/ 1.0 (exact->inexact
+n))))))
+ (begin (display "Failed") (newline)))
+ ; fake reference to LongLivedTree
+ ; and array
+ ; to keep them from being optimized away
+ ))))
+ (PrintDiagnostics))
+
+ (main))))
+
+(define (gc-benchmark . rest)
+ (let ((k (if (null? rest) 18 (car rest))))
+ (display "The garbage collector should touch about ")
+ (display (expt 2 (- k 13)))
+ (display " megabytes of heap storage.")
+ (newline)
+ (display "The use of more or less memory will skew the results.")
+ (newline)
+ (run-benchmark (string-append "GCBench" (number->string k))
+ (lambda () (gcbench k)))))
+
+
+
+(gc-benchmark )
+(display (gc-stats))
+(set! %load-path (cons (string-append (getenv "HOME") "/src/guile")
+ %load-path))
+
+(load "../test-suite/guile-test")
+
+(main `("guile-test"
+ "--test-suite" ,(string-append (getenv "HOME")
+ "/src/guile/test-suite/tests")
+ "--log-file" ",,test-suite.log"))
+;
+; GCOld.sch x.x 00/08/03
+; translated from GCOld.java 2.0a 00/08/23
+;
+; Copyright 2000 Sun Microsystems, Inc. All rights reserved.
+;
+;
+
+; Should be good enough for this benchmark.
+
+(define (newRandom)
+ (letrec ((random14
+ (lambda (n)
+ (set! x (remainder (+ (* a x) c) m))
+ (remainder (quotient x 8) n)))
+ (a 701)
+ (x 1)
+ (c 743483)
+ (m 524288)
+ (loop
+ (lambda (q r n)
+ (if (zero? q)
+ (remainder r n)
+ (loop (quotient q 16384)
+ (+ (* 16384 r) (random14 16384))
+ n)))))
+ (lambda (n)
+ (if (and (exact? n) (integer? n) (< n 16384))
+ (random14 n)
+ (loop n (random14 16384) n)))))
+
+; A TreeNode is a record with three fields: left, right, val.
+; The left and right fields contain a TreeNode or 0, and the
+; val field will contain the integer height of the tree.
+
+(define-syntax newTreeNode
+ (syntax-rules ()
+ ((newTreeNode left right val)
+ (vector left right val))
+ ((newTreeNode)
+ (vector 0 0 0))))
+
+(define-syntax TreeNode.left
+ (syntax-rules ()
+ ((TreeNode.left node)
+ (vector-ref node 0))))
+
+(define-syntax TreeNode.right
+ (syntax-rules ()
+ ((TreeNode.right node)
+ (vector-ref node 1))))
+
+(define-syntax TreeNode.val
+ (syntax-rules ()
+ ((TreeNode.val node)
+ (vector-ref node 2))))
+
+(define-syntax setf
+ (syntax-rules (TreeNode.left TreeNode.right TreeNode.val)
+ ((setf (TreeNode.left node) x)
+ (vector-set! node 0 x))
+ ((setf (TreeNode.right node) x)
+ (vector-set! node 1 x))
+ ((setf (TreeNode.val node) x)
+ (vector-set! node 2 x))))
+
+; Args:
+; live-data-size: in megabytes.
+; work: units of mutator non-allocation work per byte allocated,
+; (in unspecified units. This will affect the promotion rate
+; printed at the end of the run: more mutator work per step implies
+; fewer steps per second implies fewer bytes promoted per second.)
+; short/long ratio: ratio of short-lived bytes allocated to long-lived
+; bytes allocated.
+; pointer mutation rate: number of pointer mutations per step.
+; steps: number of steps to do.
+;
+
+(define (GCOld size workUnits promoteRate ptrMutRate steps)
+
+ (define (println . args)
+ (for-each display args)
+ (newline))
+
+ ; Rounds an inexact real to two decimal places.
+
+ (define (round2 x)
+ (/ (round (* 100.0 x)) 100.0))
+
+ ; Returns the height of the given tree.
+
+ (define (height t)
+ (if (eqv? t 0)
+ 0
+ (+ 1 (max (height (TreeNode.left t))
+ (height (TreeNode.right t))))))
+
+ ; Returns the length of the shortest path in the given tree.
+
+ (define (shortestPath t)
+ (if (eqv? t 0)
+ 0
+ (+ 1 (min (shortestPath (TreeNode.left t))
+ (shortestPath (TreeNode.right t))))))
+
+ ; Returns the number of nodes in a balanced tree of the given height.
+
+ (define (heightToNodes h)
+ (- (expt 2 h) 1))
+
+ ; Returns the height of the largest balanced tree
+ ; that has no more than the given number of nodes.
+
+ (define (nodesToHeight nodes)
+ (do ((h 1 (+ h 1))
+ (n 1 (+ n n)))
+ ((> (+ n n -1) nodes)
+ (- h 1))))
+
+ (let* (
+
+ ; Constants.
+
+ (null 0) ; Java's null
+ (pathBits 65536) ; to generate 16 random bits
+
+ (MEG 1000000)
+ (INSIGNIFICANT 999) ; this many bytes don't matter
+ (bytes/word 4)
+ (bytes/node 20) ; bytes per tree node in typical JVM
+ (words/dead 100) ; size of young garbage objects
+
+ ; Returns the number of bytes in a balanced tree of the given height.
+
+ (heightToBytes
+ (lambda (h)
+ (* bytes/node (heightToNodes h))))
+
+ ; Returns the height of the largest balanced tree
+ ; that occupies no more than the given number of bytes.
+
+ (bytesToHeight
+ (lambda (bytes)
+ (nodesToHeight (/ bytes bytes/node))))
+
+ (treeHeight 14)
+ (treeSize (heightToBytes treeHeight))
+
+ (msg1 "Usage: java GCOld <size> <work> <ratio> <mutation> <steps>")
+ (msg2 " where <size> is the live storage in megabytes")
+ (msg3 " <work> is the mutator work per step (arbitrary units)")
+ (msg4 " <ratio> is the ratio of short-lived to long-lived allocation")
+ (msg5 " <mutation> is the mutations per step")
+ (msg6 " <steps> is the number of steps")
+
+ ; Counters (and global variables that discourage optimization).
+
+ (youngBytes 0)
+ (nodes 0)
+ (actuallyMut 0)
+ (mutatorSum 0)
+ (aexport '#())
+
+ ; Global variables.
+
+ (trees '#())
+ (where 0)
+ (rnd (newRandom))
+
+ )
+
+ ; Returns a newly allocated balanced binary tree of height h.
+
+ (define (makeTree h)
+ (if (zero? h)
+ null
+ (let ((res (newTreeNode)))
+ (set! nodes (+ nodes 1))
+ (setf (TreeNode.left res) (makeTree (- h 1)))
+ (setf (TreeNode.right res) (makeTree (- h 1)))
+ (setf (TreeNode.val res) h)
+ res)))
+
+ ; Allocates approximately size megabytes of trees and stores
+ ; them into a global array.
+
+ (define (init)
+ ; Each tree will be about a megabyte.
+ (let ((ntrees (quotient (* size MEG) treeSize)))
+ (set! trees (make-vector ntrees null))
+ (println "Allocating " ntrees " trees.")
+ (println " (" (* ntrees treeSize) " bytes)")
+ (do ((i 0 (+ i 1)))
+ ((>= i ntrees))
+ (vector-set! trees i (makeTree treeHeight))
+ (doYoungGenAlloc (* promoteRate ntrees treeSize) words/dead))
+ (println " (" nodes " nodes)")))
+
+ ; Confirms that all trees are balanced and have the correct height.
+
+ (define (checkTrees)
+ (let ((ntrees (vector-length trees)))
+ (do ((i 0 (+ i 1)))
+ ((>= i ntrees))
+ (let* ((t (vector-ref trees i))
+ (h1 (height t))
+ (h2 (shortestPath t)))
+ (if (or (not (= h1 treeHeight))
+ (not (= h2 treeHeight)))
+ (println "*****BUG: " h1 " " h2))))))
+
+ ; Called only by replaceTree (below) and by itself.
+
+ (define (replaceTreeWork full partial dir)
+ (let ((canGoLeft (and (not (eq? (TreeNode.left full) null))
+ (> (TreeNode.val (TreeNode.left full))
+ (TreeNode.val partial))))
+ (canGoRight (and (not (eq? (TreeNode.right full) null))
+ (> (TreeNode.val (TreeNode.right full))
+ (TreeNode.val partial)))))
+ (cond ((and canGoLeft canGoRight)
+ (if dir
+ (replaceTreeWork (TreeNode.left full)
+ partial
+ (not dir))
+ (replaceTreeWork (TreeNode.right full)
+ partial
+ (not dir))))
+ ((and (not canGoLeft) (not canGoRight))
+ (if dir
+ (setf (TreeNode.left full) partial)
+ (setf (TreeNode.right full) partial)))
+ ((not canGoLeft)
+ (setf (TreeNode.left full) partial))
+ (else
+ (setf (TreeNode.right full) partial)))))
+
+ ; Given a balanced tree full and a smaller balanced tree partial,
+ ; replaces an appropriate subtree of full by partial, taking care
+ ; to preserve the shape of the full tree.
+
+ (define (replaceTree full partial)
+ (let ((dir (zero? (modulo (TreeNode.val partial) 2))))
+ (set! actuallyMut (+ actuallyMut 1))
+ (replaceTreeWork full partial dir)))
+
+ ; Allocates approximately n bytes of long-lived storage,
+ ; replacing oldest existing long-lived storage.
+
+ (define (oldGenAlloc n)
+ (let ((full (quotient n treeSize))
+ (partial (modulo n treeSize)))
+ ;(println "In oldGenAlloc, doing "
+ ; full
+ ; " full trees and one partial tree of size "
+ ; partial)
+ (do ((i 0 (+ i 1)))
+ ((>= i full))
+ (vector-set! trees where (makeTree treeHeight))
+ (set! where
+ (modulo (+ where 1) (vector-length trees))))
+ (let loop ((partial partial))
+ (if (> partial INSIGNIFICANT)
+ (let* ((h (bytesToHeight partial))
+ (newTree (makeTree h)))
+ (replaceTree (vector-ref trees where) newTree)
+ (set! where
+ (modulo (+ where 1) (vector-length trees)))
+ (loop (- partial (heightToBytes h))))))))
+
+ ; Interchanges two randomly selected subtrees (of same size and depth).
+
+ (define (oldGenSwapSubtrees)
+ ; Randomly pick:
+ ; * two tree indices
+ ; * A depth
+ ; * A path to that depth.
+ (let* ((index1 (rnd (vector-length trees)))
+ (index2 (rnd (vector-length trees)))
+ (depth (rnd treeHeight))
+ (path (rnd pathBits))
+ (tn1 (vector-ref trees index1))
+ (tn2 (vector-ref trees index2)))
+ (do ((i 0 (+ i 1)))
+ ((>= i depth))
+ (if (even? path)
+ (begin (set! tn1 (TreeNode.left tn1))
+ (set! tn2 (TreeNode.left tn2)))
+ (begin (set! tn1 (TreeNode.right tn1))
+ (set! tn2 (TreeNode.right tn2))))
+ (set! path (quotient path 2)))
+ (if (even? path)
+ (let ((tmp (TreeNode.left tn1)))
+ (setf (TreeNode.left tn1) (TreeNode.left tn2))
+ (setf (TreeNode.left tn2) tmp))
+ (let ((tmp (TreeNode.right tn1)))
+ (setf (TreeNode.right tn1) (TreeNode.right tn2))
+ (setf (TreeNode.right tn2) tmp)))
+ (set! actuallyMut (+ actuallyMut 2))))
+
+ ; Update "n" old-generation pointers.
+
+ (define (oldGenMut n)
+ (do ((i 0 (+ i 1)))
+ ((>= i (quotient n 2)))
+ (oldGenSwapSubtrees)))
+
+ ; Does the amount of mutator work appropriate for n bytes of young-gen
+ ; garbage allocation.
+
+ (define (doMutWork n)
+ (let ((limit (quotient (* workUnits n) 10)))
+ (do ((k 0 (+ k 1))
+ (sum 0 (+ sum 1)))
+ ((>= k limit)
+ ; We don't want dead code elimination to eliminate this loop.
+ (set! mutatorSum (+ mutatorSum sum))))))
+
+ ; Allocate n bytes of young-gen garbage, in units of "nwords"
+ ; words.
+
+ (define (doYoungGenAlloc n nwords)
+ (let ((nbytes (* nwords bytes/word)))
+ (do ((allocated 0 (+ allocated nbytes)))
+ ((>= allocated n)
+ (set! youngBytes (+ youngBytes allocated)))
+ (set! aexport (make-vector nwords 0)))))
+
+ ; Allocate "n" bytes of young-gen data; and do the
+ ; corresponding amount of old-gen allocation and pointer
+ ; mutation.
+
+ ; oldGenAlloc may perform some mutations, so this code
+ ; takes those mutations into account.
+
+ (define (doStep n)
+ (let ((mutations actuallyMut))
+ (doYoungGenAlloc n words/dead)
+ (doMutWork n)
+ ; Now do old-gen allocation
+ (oldGenAlloc (quotient n promoteRate))
+ (oldGenMut (max 0 (- (+ mutations ptrMutRate) actuallyMut)))))
+
+ (println size " megabytes")
+ (println workUnits " work units per step.")
+ (println "promotion ratio is 1:" promoteRate)
+ (println "pointer mutation rate is " ptrMutRate)
+ (println steps " steps")
+
+ (init)
+ (checkTrees)
+ (set! youngBytes 0)
+ (set! nodes 0)
+
+ (println "Initialization complete...")
+
+ (run-benchmark "GCOld"
+ 1
+ (lambda ()
+ (lambda ()
+ (do ((step 0 (+ step 1)))
+ ((>= step steps))
+ (doStep MEG))))
+ (lambda (result) #t))
+
+ (checkTrees)
+
+ (println "Allocated " steps " Mb of young gen garbage")
+ (println " (actually allocated "
+ (round2 (/ youngBytes MEG))
+ " megabytes)")
+ (println "Promoted " (round2 (/ steps promoteRate)) " Mb")
+ (println " (actually promoted "
+ (round2 (/ (* nodes bytes/node) MEG))
+ " megabytes)")
+ (if (not (zero? ptrMutRate))
+ (println "Mutated " actuallyMut " pointers"))
+
+ ; This output serves mainly to discourage optimization.
+
+ (+ mutatorSum (vector-length aexport))))
+
+(define (gcold-benchmark . args)
+ (define gcold-iters 1)
+
+ (GCOld 25 0 10 10 gcold-iters))
+(let loop ((i 10000000))
+ (and (> i 0)
+ (loop (1- i))))
+
+;;; Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+;;;
+;;; 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, 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 software; see the file COPYING.LESSER. If
+;;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(use-modules (ice-9 rdelim)
+ (ice-9 popen)
+ (ice-9 regex)
+ (ice-9 format)
+ (ice-9 pretty-print)
+ (srfi srfi-1)
+ (srfi srfi-37))
+
+
+;;;
+;;; Running Guile.
+;;;
+
+(define (run-reference-guile env bench-dir profile-opts bench)
+ "Run the ``mainstream'' Guile, i.e., Guile 1.9 with its own GC."
+ (open-input-pipe (string-append
+ env " "
+ bench-dir "/gc-profile.scm " profile-opts
+ " \"" bench "\"")))
+
+(define (run-bdwgc-guile env bench-dir profile-opts options bench)
+ "Run the Guile port to the Boehm-Demers-Weiser GC (BDW-GC)."
+ (let ((fsd (assoc-ref options 'free-space-divisor)))
+ (open-input-pipe (string-append env " "
+ "GC_FREE_SPACE_DIVISOR="
+ (number->string fsd)
+
+ (if (or (assoc-ref options 'incremental?)
+ (assoc-ref options 'generational?))
+ " GC_ENABLE_INCREMENTAL=yes"
+ "")
+ (if (assoc-ref options 'generational?)
+ " GC_PAUSE_TIME_TARGET=999999"
+ "")
+ (if (assoc-ref options 'parallel?)
+ "" ;; let it choose the number of procs
+ " GC_MARKERS=1")
+ " "
+ bench-dir "/gc-profile.scm " profile-opts
+ " \"" bench "\""))))
+
+
+;;;
+;;; Extracting performance results.
+;;;
+
+(define (grep regexp input)
+ "Read line by line from the @var{input} port and return all matches for
+@var{regexp}."
+ (let ((regexp (if (string? regexp) (make-regexp regexp) regexp)))
+ (with-input-from-port input
+ (lambda ()
+ (let loop ((line (read-line))
+ (result '()))
+ (format #t "> ~A~%" line)
+ (if (eof-object? line)
+ (reverse result)
+ (cond ((regexp-exec regexp line)
+ =>
+ (lambda (match)
+ (loop (read-line)
+ (cons match result))))
+ (else
+ (loop (read-line) result)))))))))
+
+(define (parse-result benchmark-output)
+ (let ((result (grep "^(execution time|heap size):[[:blank:]]+([0-9.]+)"
+ benchmark-output)))
+ (fold (lambda (match result)
+ (cond ((equal? (match:substring match 1) "execution time")
+ (cons (cons 'execution-time
+ (string->number (match:substring match 2)))
+ result))
+ ((equal? (match:substring match 1) "heap size")
+ (cons (cons 'heap-size
+ (string->number (match:substring match 2)))
+ result))
+ (else
+ result)))
+ '()
+ result)))
+
+(define (pretty-print-result benchmark reference bdwgc)
+ (define ref-heap (assoc-ref reference 'heap-size))
+ (define ref-time (assoc-ref reference 'execution-time))
+
+ (define (distance x1 y1 x2 y2)
+ ;; Return the distance between (X1,Y1) and (X2,Y2). Y is the heap size,
+ ;; in MiB and X is the execution time in seconds.
+ (let ((y1 (/ y1 (expt 2 20)))
+ (y2 (/ y2 (expt 2 20))))
+ (sqrt (+ (expt (- y1 y2) 2)
+ (expt (- x1 x2) 2)))))
+
+ (define (score time heap)
+ ;; Return a score lower than +1.0. The score is positive if the
+ ;; distance to the origin of (TIME,HEAP) is smaller than that of
+ ;; (REF-TIME,REF-HEAP), negative otherwise.
+
+ ;; heap ^ .
+ ;; size | . worse
+ ;; | . [-]
+ ;; | .
+ ;; | . . . .ref. . . .
+ ;; | .
+ ;; | [+] .
+ ;; | better .
+ ;; 0 +-------------------->
+ ;; exec. time
+
+ (let ((ref-dist (distance ref-time ref-heap 0 0))
+ (dist (distance time heap 0 0)))
+ (/ (- ref-dist dist) ref-dist)))
+
+ (define (score-string time heap)
+ ;; Return a string denoting a bar to illustrate the score of (TIME,HEAP)
+ ;; relative to (REF-TIME,REF-HEAP).
+ (define %max-width 15)
+
+ (let ((s (score time heap)))
+ (make-string (inexact->exact (round (* (if (< s 0.0) (- s) s)
+ %max-width)))
+ (if (< s 0.0)
+ #\-
+ #\+))))
+
+ (define (print-line name result ref?)
+ (let ((name (string-pad-right name 23))
+ (time (assoc-ref result 'execution-time))
+ (heap (assoc-ref result 'heap-size)))
+ (format #t "~a ~6,2f (~,2fx) ~7,3f (~,2fx)~A~%"
+ name
+ (/ heap (expt 2.0 20)) (/ heap ref-heap 1.0)
+ time (/ time ref-time 1.0)
+ (if (not ref?)
+ (string-append " "
+ (score-string time heap))
+ ""))))
+
+ (format #t "benchmark: `~a'~%" benchmark)
+ (format #t " heap size (MiB) execution time (s.)~%")
+ (print-line "Guile" reference #t)
+ (for-each (lambda (bdwgc)
+ (let ((name (format #f "BDW-GC, FSD=~a~a"
+ (assoc-ref bdwgc 'free-space-divisor)
+ (cond ((assoc-ref bdwgc 'incremental?)
+ " incr.")
+ ((assoc-ref bdwgc 'generational?)
+ " gene.")
+ ((assoc-ref bdwgc 'parallel?)
+ " paral.")
+ (else "")))))
+ (print-line name bdwgc #f)))
+ bdwgc))
+
+(define (print-raw-result benchmark reference bdwgc)
+ (pretty-print `(,benchmark
+ (reference . ,reference)
+ (bdw-gc . ,bdwgc))))
+
+
+
+;;;
+;;; Option processing.
+;;;
+
+(define %options
+ (list (option '(#\h "help") #f #f
+ (lambda args
+ (show-help)
+ (exit 0)))
+ (option '(#\r "reference") #t #f
+ (lambda (opt name arg result)
+ (alist-cons 'reference-environment arg
+ (alist-delete 'reference-environment result
+ eq?))))
+ (option '(#\b "bdw-gc") #t #f
+ (lambda (opt name arg result)
+ (alist-cons 'bdwgc-environment arg
+ (alist-delete 'bdwgc-environment result
+ eq?))))
+ (option '(#\d "benchmark-dir") #t #f
+ (lambda (opt name arg result)
+ (alist-cons 'benchmark-directory arg
+ (alist-delete 'benchmark-directory result
+ eq?))))
+ (option '(#\p "profile-options") #t #f
+ (lambda (opt name arg result)
+ (let ((opts (assoc-ref result 'profile-options)))
+ (alist-cons 'profile-options
+ (string-append opts " " arg)
+ (alist-delete 'profile-options result
+ eq?)))))
+ (option '(#\l "log-file") #t #f
+ (lambda (opt name arg result)
+ (alist-cons 'log-port (open-output-file arg)
+ (alist-delete 'log-port result
+ eq?))))
+ (option '("raw") #f #f
+ (lambda (opt name arg result)
+ (alist-cons 'printer print-raw-result
+ (alist-delete 'printer result eq?))))
+ (option '("load-results") #f #f
+ (lambda (opt name arg result)
+ (alist-cons 'load-results? #t result)))))
+
+(define %default-options
+ `((reference-environment . "GUILE=guile")
+ (benchmark-directory . "./gc-benchmarks")
+ (log-port . ,(current-output-port))
+ (profile-options . "")
+ (input . ())
+ (printer . ,pretty-print-result)))
+
+(define (show-help)
+ (format #t "Usage: run-benchmark [OPTIONS] BENCHMARKS...
+Run BENCHMARKS (a list of Scheme files) and display a performance
+comparison of standard Guile (1.9) and the BDW-GC-based Guile.
+
+ -h, --help Show this help message
+
+ -r, --reference=ENV
+ -b, --bdw-gc=ENV
+ Use ENV as the environment necessary to run the
+ \"reference\" Guile (1.9) or the BDW-GC-based Guile,
+ respectively. At a minimum, ENV should define the
+ `GUILE' environment variable. For example:
+
+ --reference='GUILE=/foo/bar/guile LD_LIBRARY_PATH=/foo'
+
+ -p, --profile-options=OPTS
+ Pass OPTS as additional options for `gc-profile.scm'.
+ -l, --log-file=FILE
+ Save output to FILE instead of the standard output.
+
+ --raw Write benchmark results in raw (s-exp) format.
+ --load-results
+ Load raw (s-exp) results instead of actually running
+ the benchmarks.
+
+ -d, --benchmark-dir=DIR
+ Use DIR as the GC benchmark directory where `gc-profile.scm'
+ lives (it is automatically determined by default).
+
+Report bugs to <bug-guile@gnu.org>.~%"))
+
+(define (parse-args args)
+ (define (leave fmt . args)
+ (apply format (current-error-port) (string-append fmt "~%") args)
+ (exit 1))
+
+ (args-fold args %options
+ (lambda (opt name arg result)
+ (leave "~A: unrecognized option" opt))
+ (lambda (file result)
+ (let ((files (or (assoc-ref result 'input) '())))
+ (alist-cons 'input (cons file files)
+ (alist-delete 'input result eq?))))
+ %default-options))
+
+
+;;;
+;;; The main program.
+;;;
+
+(define (main . args)
+ (let* ((args (parse-args args))
+ (benchmark-files (assoc-ref args 'input)))
+
+ (let* ((log (assoc-ref args 'log-port))
+ (bench-dir (assoc-ref args 'benchmark-directory))
+ (ref-env (assoc-ref args 'reference-environment))
+ (bdwgc-env (or (assoc-ref args 'bdwgc-environment)
+ (string-append "GUILE=" bench-dir
+ "/../meta/guile")))
+ (prof-opts (assoc-ref args 'profile-options))
+ (print (assoc-ref args 'printer)))
+ (define (run benchmark)
+ (let ((ref (parse-result (run-reference-guile ref-env
+ bench-dir
+ prof-opts
+ benchmark)))
+ (bdwgc (map (lambda (fsd incremental?
+ generational? parallel?)
+ (let ((opts
+ (list
+ (cons 'free-space-divisor fsd)
+ (cons 'incremental? incremental?)
+ (cons 'generational? generational?)
+ (cons 'parallel? parallel?))))
+ (append opts
+ (parse-result
+ (run-bdwgc-guile bdwgc-env
+ bench-dir
+ prof-opts
+ opts
+ benchmark)))))
+ '( 3 6 9 3 3)
+ '(#f #f #f #t #f) ;; incremental
+ '(#f #f #f #f #t) ;; generational
+ '(#f #f #f #f #f)))) ;; parallel
+ `(,benchmark
+ (reference . ,ref)
+ (bdw-gc . ,bdwgc))))
+
+ (define (load-results file)
+ (with-input-from-file file
+ (lambda ()
+ (let loop ((results '()) (o (read)))
+ (if (eof-object? o)
+ (reverse results)
+ (loop (cons o results)
+ (read)))))))
+
+ (for-each (lambda (result)
+ (let ((benchmark (car result))
+ (ref (assoc-ref (cdr result) 'reference))
+ (bdwgc (assoc-ref (cdr result) 'bdw-gc)))
+ (with-output-to-port log
+ (lambda ()
+ (print benchmark ref bdwgc)
+ (newline)
+ (force-output)))))
+ (if (assoc-ref args 'load-results?)
+ (append-map load-results benchmark-files)
+ (map run benchmark-files))))))
+;;; From from http://www.ccs.neu.edu/home/will/Twobit/KVW/string.txt .
+; string test
+; (try 100000)
+
+(define s "abcdef")
+
+(define (grow)
+ (set! s (string-append "123" s "456" s "789"))
+ (set! s (string-append
+ (substring s (quotient (string-length s) 2) (string-length s))
+ (substring s 0 (+ 1 (quotient (string-length s) 2)))))
+ s)
+
+(define (trial n)
+ (do ((i 0 (+ i 1)))
+ ((> (string-length s) n) (string-length s))
+ (grow)))
+
+(define (try n)
+ (do ((i 0 (+ i 1)))
+ ((>= i 10) (string-length s))
+ (set! s "abcdef")
+ (trial n)))
+
+(try 50000000);;;; readline.scm --- support functions for command-line editing
+;;;;
+;;;; Copyright (C) 1997, 1999, 2000, 2001, 2002, 2006, 2009, 2010, 2011, 2014 Free Software Foundation, Inc.
+;;;;
+;;;; 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, 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 software; see the file COPYING. If not, write to
+;;;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;;;; Boston, MA 02110-1301 USA
+;;;;
+;;;; Contributed by Daniel Risacher <risacher@worldnet.att.net>.
+;;;; Extensions based upon code by
+;;;; Andrew Archibald <aarchiba@undergrad.math.uwaterloo.ca>.
+
+
+
+(define-module (ice-9 readline)
+ #\use-module (ice-9 session)
+ #\use-module (ice-9 regex)
+ #\use-module (ice-9 buffered-input)
+ #\no-backtrace
+ #\export (filename-completion-function
+ add-history
+ read-history
+ write-history
+ clear-history))
+
+
+
+;;; Dynamically link the glue code for accessing the readline library,
+;;; but only when it isn't already present.
+
+(if (not (provided? 'readline))
+ (load-extension "libguilereadline-v-18" "scm_init_readline"))
+
+(if (not (provided? 'readline))
+ (scm-error 'misc-error
+ #f
+ "readline is not provided in this Guile installation"
+ '()
+ '()))
+
+
+
+;;; Run-time options
+
+(export
+ readline-options
+ readline-enable
+ readline-disable)
+(export-syntax
+ readline-set!)
+
+(define-option-interface
+ (readline-options-interface
+ (readline-options readline-enable readline-disable)
+ (readline-set!)))
+
+
+
+;;; MDJ 980513 <djurfeldt@nada.kth.se>:
+;;; There should probably be low-level support instead of this code.
+
+;;; Dirk:FIXME:: If the-readline-port, input-port or output-port are closed,
+;;; guile will enter an endless loop or crash.
+
+(define-once new-input-prompt "")
+(define-once continuation-prompt "")
+(define-once input-port (current-input-port))
+(define-once output-port (current-output-port))
+(define-once read-hook #f)
+
+(define (make-readline-port)
+ (let ((history-buffer #f))
+ (make-line-buffered-input-port (lambda (continuation?)
+ ;; When starting a new read, add
+ ;; the previously read expression
+ ;; to the history.
+ (if (and (not continuation?)
+ history-buffer)
+ (begin
+ (add-history history-buffer)
+ (set! history-buffer #f)))
+ ;; Set up prompts and read a line.
+ (let* ((prompt (if continuation?
+ continuation-prompt
+ new-input-prompt))
+ (str (%readline (if (string? prompt)
+ prompt
+ (prompt))
+ input-port
+ output-port
+ read-hook)))
+ (or (eof-object? str)
+ (string=? str "")
+ (set! history-buffer
+ (if history-buffer
+ (string-append history-buffer
+ "\n"
+ str)
+ str)))
+ str)))))
+
+;;; We only create one readline port. There's no point in having
+;;; more, since they would all share the tty and history ---
+;;; everything except the prompt. And don't forget the
+;;; compile/load/run phase distinctions. Also, the readline library
+;;; isn't reentrant.
+(define-once the-readline-port #f)
+
+(define-once history-variable "GUILE_HISTORY")
+(define-once history-file
+ (string-append (or (getenv "HOME") ".") "/.guile_history"))
+
+(define-public readline-port
+ (let ((do (lambda (r/w)
+ (if (memq 'history-file (readline-options-interface))
+ (r/w (or (getenv history-variable)
+ history-file))))))
+ (lambda ()
+ (if (not the-readline-port)
+ (begin
+ (do read-history)
+ (set! the-readline-port (make-readline-port))
+ (add-hook! exit-hook (lambda ()
+ (do write-history)
+ (clear-history)))))
+ the-readline-port)))
+
+;;; The user might try to use readline in his programs. It then
+;;; becomes very uncomfortable that the current-input-port is the
+;;; readline port...
+;;;
+;;; Here, we detect this situation and replace it with the
+;;; underlying port.
+;;;
+;;; %readline is the low-level readline procedure.
+
+(define-public (readline . args)
+ (let ((prompt new-input-prompt)
+ (inp input-port))
+ (cond ((not (null? args))
+ (set! prompt (car args))
+ (set! args (cdr args))
+ (cond ((not (null? args))
+ (set! inp (car args))
+ (set! args (cdr args))))))
+ (apply %readline
+ prompt
+ (if (eq? inp the-readline-port)
+ input-port
+ inp)
+ args)))
+
+(define-public (set-readline-prompt! p . rest)
+ (set! new-input-prompt p)
+ (if (not (null? rest))
+ (set! continuation-prompt (car rest))))
+
+(define-public (set-readline-input-port! p)
+ (cond ((or (not (file-port? p)) (not (input-port? p)))
+ (scm-error 'wrong-type-arg "set-readline-input-port!"
+ "Not a file input port: ~S" (list p) #f))
+ ((port-closed? p)
+ (scm-error 'misc-error "set-readline-input-port!"
+ "Port not open: ~S" (list p) #f))
+ (else
+ (set! input-port p))))
+
+(define-public (set-readline-output-port! p)
+ (cond ((or (not (file-port? p)) (not (output-port? p)))
+ (scm-error 'wrong-type-arg "set-readline-input-port!"
+ "Not a file output port: ~S" (list p) #f))
+ ((port-closed? p)
+ (scm-error 'misc-error "set-readline-output-port!"
+ "Port not open: ~S" (list p) #f))
+ (else
+ (set! output-port p))))
+
+(define-public (set-readline-read-hook! h)
+ (set! read-hook h))
+
+(define-public apropos-completion-function
+ (let ((completions '()))
+ (lambda (text cont?)
+ (if (not cont?)
+ (set! completions
+ (map symbol->string
+ (apropos-internal
+ (string-append "^" (regexp-quote text))))))
+ (if (null? completions)
+ #f
+ (let ((retval (car completions)))
+ (begin (set! completions (cdr completions))
+ retval))))))
+
+(if (provided? 'regex)
+ (set! *readline-completion-function* apropos-completion-function))
+
+(define-public (with-readline-completion-function completer thunk)
+ "With @var{completer} as readline completion function, call @var{thunk}."
+ (let ((old-completer *readline-completion-function*))
+ (dynamic-wind
+ (lambda ()
+ (set! *readline-completion-function* completer))
+ thunk
+ (lambda ()
+ (set! *readline-completion-function* old-completer)))))
+
+(define-once readline-repl-reader
+ (let ((boot-9-repl-reader repl-reader))
+ (lambda* (repl-prompt #\optional (reader (fluid-ref current-reader)))
+ (let ((port (current-input-port)))
+ (if (eq? port (readline-port))
+ (let ((outer-new-input-prompt new-input-prompt)
+ (outer-continuation-prompt continuation-prompt)
+ (outer-read-hook read-hook))
+ (dynamic-wind
+ (lambda ()
+ (set-buffered-input-continuation?! port #f)
+ (set-readline-prompt! repl-prompt "... ")
+ (set-readline-read-hook! (lambda ()
+ (run-hook before-read-hook))))
+ (lambda () ((or reader read) port))
+ (lambda ()
+ (set-readline-prompt! outer-new-input-prompt
+ outer-continuation-prompt)
+ (set-readline-read-hook! outer-read-hook))))
+ (boot-9-repl-reader repl-prompt reader))))))
+
+(define-public (activate-readline)
+ (if (isatty? (current-input-port))
+ (begin
+ (set-current-input-port (readline-port))
+ (set! repl-reader readline-repl-reader)
+ (set! (using-readline?) #t))))
+
+(define-public (make-completion-function strings)
+ "Construct and return a completion function for a list of strings.
+The returned function is suitable for passing to
+@code{with-readline-completion-function. The argument @var{strings}
+should be a list of strings, where each string is one of the possible
+completions."
+ (letrec ((strs '())
+ (regexp #f)
+ (completer (lambda (text continue?)
+ (if continue?
+ (if (null? strs)
+ #f
+ (let ((str (car strs)))
+ (set! strs (cdr strs))
+ (if (string-match regexp str)
+ str
+ (completer text #t))))
+ (begin
+ (set! strs strings)
+ (set! regexp
+ (string-append "^" (regexp-quote text)))
+ (completer text #t))))))
+ completer))
+;;; GDB debugging support for Guile.
+;;;
+;;; Copyright 2014, 2015 Free Software Foundation, Inc.
+;;;
+;;; 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 <http://www.gnu.org/licenses/>.
+
+(define-module (guile-gdb)
+ #\use-module (system base types)
+ #\use-module ((gdb) #\hide (symbol?))
+ #\use-module (gdb printing)
+ #\use-module (srfi srfi-11)
+ #\use-module (ice-9 match)
+ #\export (%gdb-memory-backend
+ display-vm-frames))
+
+;;; Commentary:
+;;;
+;;; This file defines GDB extensions to pretty-print 'SCM' objects, and
+;;; to walk Guile's virtual machine stack.
+;;;
+;;; This file is installed under a name that follows the convention that
+;;; allows GDB to auto-load it anytime the user is debugging libguile
+;;; (info "(gdb) objfile-gdbdotext file").
+;;;
+;;; Code:
+
+(define (type-name-from-descriptor descriptor-array type-number)
+ "Return the name of the type TYPE-NUMBER as seen in DESCRIPTOR-ARRAY, or #f
+if the information is not available."
+ (let ((descriptors (lookup-global-symbol descriptor-array)))
+ (and descriptors
+ (let ((code (type-code (symbol-type descriptors))))
+ (or (= TYPE_CODE_ARRAY code)
+ (= TYPE_CODE_PTR code)))
+ (let* ((type-descr (value-subscript (symbol-value descriptors)
+ type-number))
+ (name (value-field type-descr "name")))
+ (value->string name)))))
+
+(define (scm-value->integer value)
+ "Return the integer value of VALUE, which is assumed to be a GDB value
+corresponding to an 'SCM' object."
+ (let ((type (type-strip-typedefs (value-type value))))
+ (cond ((= (type-code type) TYPE_CODE_UNION)
+ ;; SCM_DEBUG_TYPING_STRICTNESS = 2
+ (value->integer (value-field (value-field value "n")
+ "n")))
+ (else
+ ;; SCM_DEBUG_TYPING_STRICTNESS = 1
+ (value->integer value)))))
+
+(define %gdb-memory-backend
+ ;; The GDB back-end to access the inferior's memory.
+ (let ((void* (type-pointer (lookup-type "void"))))
+ (define (dereference-word address)
+ ;; Return the word at ADDRESS.
+ (value->integer
+ (value-dereference (value-cast (make-value address)
+ (type-pointer void*)))))
+
+ (define (open address size)
+ ;; Return a port to the SIZE bytes starting at ADDRESS.
+ (if size
+ (open-memory #\start address #\size size)
+ (open-memory #\start address)))
+
+ (define (type-name kind number)
+ ;; Return the type name of KIND type NUMBER.
+ (type-name-from-descriptor (case kind
+ ((smob) "scm_smobs")
+ ((port) "scm_ptobs"))
+ number))
+
+ (memory-backend dereference-word open type-name)))
+
+
+;;;
+;;; GDB pretty-printer registration.
+;;;
+
+(define (make-scm-pretty-printer-worker obj)
+ (define (list->iterator list)
+ (make-iterator list list
+ (let ((n 0))
+ (lambda (iter)
+ (match (iterator-progress iter)
+ (() (end-of-iteration))
+ ((elt . list)
+ (set-iterator-progress! iter list)
+ (let ((name (format #f "[~a]" n)))
+ (set! n (1+ n))
+ (cons name (object->string elt)))))))))
+ (cond
+ ((string? obj)
+ (make-pretty-printer-worker
+ "string" ; display hint
+ (lambda (printer) obj)
+ #f))
+ ((and (array? obj)
+ (match (array-shape obj)
+ (((0 _)) #t)
+ (_ #f)))
+ (make-pretty-printer-worker
+ "array" ; display hint
+ (lambda (printer)
+ (let ((tag (array-type obj)))
+ (case tag
+ ((#t) "#<vector>")
+ ((b) "#<bitvector>")
+ (else (format #f "#<~avector>" tag)))))
+ (lambda (printer)
+ (list->iterator (array->list obj)))))
+ ((inferior-struct? obj)
+ (make-pretty-printer-worker
+ "array" ; display hint
+ (lambda (printer)
+ (format #f "#<struct ~a>" (inferior-struct-name obj)))
+ (lambda (printer)
+ (list->iterator (inferior-struct-fields obj)))))
+ (else
+ (make-pretty-printer-worker
+ #f ; display hint
+ (lambda (printer)
+ (object->string obj))
+ #f))))
+
+(define %scm-pretty-printer
+ (make-pretty-printer
+ "SCM"
+ (lambda (pp value)
+ (let ((name (type-name (value-type value))))
+ (and (and name (string=? name "SCM"))
+ (make-scm-pretty-printer-worker
+ (scm->object (scm-value->integer value) %gdb-memory-backend)))))))
+
+(define* (register-pretty-printer #\optional objfile)
+ (prepend-pretty-printer! objfile %scm-pretty-printer))
+
+(register-pretty-printer)
+
+
+;;;
+;;; VM stack walking.
+;;;
+
+(define (find-vm-engine-frame)
+ "Return the bottom-most frame containing a call to the VM engine."
+ (define (vm-engine-frame? frame)
+ (let ((sym (frame-function frame)))
+ (and sym
+ (member (symbol-name sym)
+ '("vm_debug_engine" "vm_regular_engine")))))
+
+ (let loop ((frame (newest-frame)))
+ (and frame
+ (if (vm-engine-frame? frame)
+ frame
+ (loop (frame-older frame))))))
+
+(define (vm-stack-pointer)
+ "Return the current value of the VM stack pointer or #f."
+ (let ((frame (find-vm-engine-frame)))
+ (and frame
+ (frame-read-var frame "sp"))))
+
+(define (vm-frame-pointer)
+ "Return the current value of the VM frame pointer or #f."
+ (let ((frame (find-vm-engine-frame)))
+ (and frame
+ (frame-read-var frame "fp"))))
+
+(define* (display-vm-frames #\optional (port (current-output-port)))
+ "Display the VM frames on PORT."
+ (define (display-objects start end)
+ ;; Display all the objects (arguments and local variables) located
+ ;; between START and END.
+ (let loop ((number 0)
+ (address start))
+ (when (and (> start 0) (<= address end))
+ (let ((object (dereference-word %gdb-memory-backend address)))
+ ;; TODO: Push onto GDB's value history.
+ (format port " slot ~a -> ~s~%"
+ number (scm->object object %gdb-memory-backend)))
+ (loop (+ 1 number) (+ address %word-size)))))
+
+ (let loop ((number 0)
+ (sp (value->integer (vm-stack-pointer)))
+ (fp (value->integer (vm-frame-pointer))))
+ (unless (zero? fp)
+ (let-values (((ra mvra link proc)
+ (vm-frame fp %gdb-memory-backend)))
+ (format port "#~a ~s~%" number (scm->object proc %gdb-memory-backend))
+ (display-objects fp sp)
+ (loop (+ 1 number) (- fp (* 5 %word-size)) link)))))
+
+;; See libguile/frames.h.
+(define* (vm-frame fp #\optional (backend %gdb-memory-backend))
+ "Return the components of the stack frame at FP."
+ (let ((caller (dereference-word backend (- fp %word-size)))
+ (ra (dereference-word backend (- fp (* 2 %word-size))))
+ (mvra (dereference-word backend (- fp (* 3 %word-size))))
+ (link (dereference-word backend (- fp (* 4 %word-size)))))
+ (values ra mvra link caller)))
+
+;;; libguile-2.0-gdb.scm ends here
+;;;; and-let-star.scm --- and-let* syntactic form (SRFI-2) for Guile
+;;;;
+;;;; Copyright (C) 1999, 2001, 2004, 2006, 2013,
+;;;; 2015 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (ice-9 and-let-star)
+ \:export-syntax (and-let*))
+
+(define-syntax %and-let*
+ (lambda (form)
+ (syntax-case form ()
+
+ ;; Handle zero-clauses special-case.
+ ((_ orig-form () . body)
+ #'(begin #t . body))
+
+ ;; Reduce clauses down to one regardless of body.
+ ((_ orig-form ((var expr) rest . rest*) . body)
+ (identifier? #'var)
+ #'(let ((var expr))
+ (and var (%and-let* orig-form (rest . rest*) . body))))
+ ((_ orig-form ((expr) rest . rest*) . body)
+ #'(and expr (%and-let* orig-form (rest . rest*) . body)))
+ ((_ orig-form (var rest . rest*) . body)
+ (identifier? #'var)
+ #'(and var (%and-let* orig-form (rest . rest*) . body)))
+
+ ;; Handle 1-clause cases without a body.
+ ((_ orig-form ((var expr)))
+ (identifier? #'var)
+ #'expr)
+ ((_ orig-form ((expr)))
+ #'expr)
+ ((_ orig-form (var))
+ (identifier? #'var)
+ #'var)
+
+ ;; Handle 1-clause cases with a body.
+ ((_ orig-form ((var expr)) . body)
+ (identifier? #'var)
+ #'(let ((var expr))
+ (and var (begin . body))))
+ ((_ orig-form ((expr)) . body)
+ #'(and expr (begin . body)))
+ ((_ orig-form (var) . body)
+ (identifier? #'var)
+ #'(and var (begin . body)))
+
+ ;; Handle bad clauses.
+ ((_ orig-form (bad-clause . rest) . body)
+ (syntax-violation 'and-let* "Bad clause" #'orig-form #'bad-clause)))))
+
+(define-syntax and-let*
+ (lambda (form)
+ (syntax-case form ()
+ ((_ (c ...) body ...)
+ #`(%and-let* #,form (c ...) body ...)))))
+
+(cond-expand-provide (current-module) '(srfi-2))
+;;; installed-scm-file
+
+;;;; Copyright (C) 1999, 2001, 2004, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+(define (array-shape a)
+ (map (lambda (ind) (if (number? ind) (list 0 (+ -1 ind)) ind))
+ (array-dimensions a)))
+;;;; binary-ports.scm --- Binary IO on ports
+
+;;;; Copyright (C) 2009, 2010, 2011, 2013 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Ludovic Courtès <ludo@gnu.org>
+
+;;; Commentary:
+;;;
+;;; The I/O port API of the R6RS is provided by this module. In many areas
+;;; it complements or refines Guile's own historical port API. For instance,
+;;; it allows for binary I/O with bytevectors.
+;;;
+;;; Code:
+
+(define-module (ice-9 binary-ports)
+ #\use-module (rnrs bytevectors)
+ #\export (eof-object
+ open-bytevector-input-port
+ make-custom-binary-input-port
+ get-u8
+ lookahead-u8
+ get-bytevector-n
+ get-bytevector-n!
+ get-bytevector-some
+ get-bytevector-all
+ get-string-n!
+ put-u8
+ put-bytevector
+ unget-bytevector
+ open-bytevector-output-port
+ make-custom-binary-output-port))
+
+;; Note that this extension also defines %make-transcoded-port, which is
+;; not exported but is used by (rnrs io ports).
+
+(load-extension (string-append "libguile-" (effective-version))
+ "scm_init_r6rs_ports")
+;;; -*- mode: scheme; coding: utf-8; -*-
+
+;;;; Copyright (C) 1995-2014, 2016 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+
+;;; Commentary:
+
+;;; This file is the first thing loaded into Guile. It adds many mundane
+;;; definitions and a few that are interesting.
+;;;
+;;; The module system (hence the hierarchical namespace) are defined in this
+;;; file.
+;;;
+
+;;; Code:
+
+
+
+;; Before compiling, make sure any symbols are resolved in the (guile)
+;; module, the primary location of those symbols, rather than in
+;; (guile-user), the default module that we compile in.
+
+(eval-when (compile)
+ (set-current-module (resolve-module '(guile))))
+
+;; Prevent this file being loaded more than once in a session. Just
+;; doesn't make sense!
+(if (current-module)
+ (error "re-loading ice-9/boot-9.scm not allowed"))
+
+
+
+;;; {Error handling}
+;;;
+
+;; Define delimited continuation operators, and implement catch and throw in
+;; terms of them.
+
+(define make-prompt-tag
+ (lambda* (#\optional (stem "prompt"))
+ (gensym stem)))
+
+(define default-prompt-tag
+ ;; not sure if we should expose this to the user as a fluid
+ (let ((%default-prompt-tag (make-prompt-tag)))
+ (lambda ()
+ %default-prompt-tag)))
+
+(define (call-with-prompt tag thunk handler)
+ (@prompt tag (thunk) handler))
+(define (abort-to-prompt tag . args)
+ (@abort tag args))
+
+
+;; Define catch and with-throw-handler, using some common helper routines and a
+;; shared fluid. Hide the helpers in a lexical contour.
+
+(define with-throw-handler #f)
+(let ()
+ (define (default-exception-handler k . args)
+ (cond
+ ((eq? k 'quit)
+ (primitive-exit (cond
+ ((not (pair? args)) 0)
+ ((integer? (car args)) (car args))
+ ((not (car args)) 1)
+ (else 0))))
+ (else
+ (format (current-error-port) "guile: uncaught throw to ~a: ~a\n" k args)
+ (primitive-exit 1))))
+
+ (define %running-exception-handlers (make-fluid '()))
+ (define %exception-handler (make-fluid default-exception-handler))
+
+ (define (default-throw-handler prompt-tag catch-k)
+ (let ((prev (fluid-ref %exception-handler)))
+ (lambda (thrown-k . args)
+ (if (or (eq? thrown-k catch-k) (eqv? catch-k #t))
+ (apply abort-to-prompt prompt-tag thrown-k args)
+ (apply prev thrown-k args)))))
+
+ (define (custom-throw-handler prompt-tag catch-k pre)
+ (let ((prev (fluid-ref %exception-handler)))
+ (lambda (thrown-k . args)
+ (if (or (eq? thrown-k catch-k) (eqv? catch-k #t))
+ (let ((running (fluid-ref %running-exception-handlers)))
+ (with-fluids ((%running-exception-handlers (cons pre running)))
+ (if (not (memq pre running))
+ (apply pre thrown-k args))
+ ;; fall through
+ (if prompt-tag
+ (apply abort-to-prompt prompt-tag thrown-k args)
+ (apply prev thrown-k args))))
+ (apply prev thrown-k args)))))
+
+ (set! catch
+ (lambda* (k thunk handler #\optional pre-unwind-handler)
+ "Invoke @var{thunk} in the dynamic context of @var{handler} for
+exceptions matching @var{key}. If thunk throws to the symbol
+@var{key}, then @var{handler} is invoked this way:
+@lisp
+ (handler key args ...)
+@end lisp
+
+@var{key} is a symbol or @code{#t}.
+
+@var{thunk} takes no arguments. If @var{thunk} returns
+normally, that is the return value of @code{catch}.
+
+Handler is invoked outside the scope of its own @code{catch}.
+If @var{handler} again throws to the same key, a new handler
+from further up the call chain is invoked.
+
+If the key is @code{#t}, then a throw to @emph{any} symbol will
+match this call to @code{catch}.
+
+If a @var{pre-unwind-handler} is given and @var{thunk} throws
+an exception that matches @var{key}, Guile calls the
+@var{pre-unwind-handler} before unwinding the dynamic state and
+invoking the main @var{handler}. @var{pre-unwind-handler} should
+be a procedure with the same signature as @var{handler}, that
+is @code{(lambda (key . args))}. It is typically used to save
+the stack at the point where the exception occurred, but can also
+query other parts of the dynamic state at that point, such as
+fluid values.
+
+A @var{pre-unwind-handler} can exit either normally or non-locally.
+If it exits normally, Guile unwinds the stack and dynamic context
+and then calls the normal (third argument) handler. If it exits
+non-locally, that exit determines the continuation."
+ (if (not (or (symbol? k) (eqv? k #t)))
+ (scm-error 'wrong-type-arg "catch"
+ "Wrong type argument in position ~a: ~a"
+ (list 1 k) (list k)))
+ (let ((tag (make-prompt-tag "catch")))
+ (call-with-prompt
+ tag
+ (lambda ()
+ (with-fluids
+ ((%exception-handler
+ (if pre-unwind-handler
+ (custom-throw-handler tag k pre-unwind-handler)
+ (default-throw-handler tag k))))
+ (thunk)))
+ (lambda (cont k . args)
+ (apply handler k args))))))
+
+ (set! with-throw-handler
+ (lambda (k thunk pre-unwind-handler)
+ "Add @var{handler} to the dynamic context as a throw handler
+for key @var{k}, then invoke @var{thunk}."
+ (if (not (or (symbol? k) (eqv? k #t)))
+ (scm-error 'wrong-type-arg "with-throw-handler"
+ "Wrong type argument in position ~a: ~a"
+ (list 1 k) (list k)))
+ (with-fluids ((%exception-handler
+ (custom-throw-handler #f k pre-unwind-handler)))
+ (thunk))))
+
+ (set! throw
+ (lambda (key . args)
+ "Invoke the catch form matching @var{key}, passing @var{args} to the
+@var{handler}.
+
+@var{key} is a symbol. It will match catches of the same symbol or of @code{#t}.
+
+If there is no handler at all, Guile prints an error and then exits."
+ (if (not (symbol? key))
+ ((fluid-ref %exception-handler) 'wrong-type-arg "throw"
+ "Wrong type argument in position ~a: ~a" (list 1 key) (list key))
+ (apply (fluid-ref %exception-handler) key args)))))
+
+
+
+;;; Boot versions of `map' and `for-each', enough to get the expander
+;;; running, and get the "map" used in eval.scm for with-fluids to work.
+;;;
+(define map
+ (case-lambda
+ ((f l)
+ (let map1 ((l l))
+ (if (null? l)
+ '()
+ (cons (f (car l)) (map1 (cdr l))))))
+ ((f l1 l2)
+ (let map2 ((l1 l1) (l2 l2))
+ (if (null? l1)
+ '()
+ (cons (f (car l1) (car l2))
+ (map2 (cdr l1) (cdr l2))))))
+ ((f l1 . rest)
+ (let lp ((l1 l1) (rest rest))
+ (if (null? l1)
+ '()
+ (cons (apply f (car l1) (map car rest))
+ (lp (cdr l1) (map cdr rest))))))))
+
+(define for-each
+ (case-lambda
+ ((f l)
+ (let for-each1 ((l l))
+ (if (pair? l)
+ (begin
+ (f (car l))
+ (for-each1 (cdr l))))))
+ ((f l1 l2)
+ (let for-each2 ((l1 l1) (l2 l2))
+ (if (pair? l1)
+ (begin
+ (f (car l1) (car l2))
+ (for-each2 (cdr l1) (cdr l2))))))
+ ((f l1 . rest)
+ (let lp ((l1 l1) (rest rest))
+ (if (pair? l1)
+ (begin
+ (apply f (car l1) (map car rest))
+ (lp (cdr l1) (map cdr rest))))))))
+
+
+
+;;; {R4RS compliance}
+;;;
+
+(primitive-load-path "ice-9/r4rs")
+
+
+
+;;; {Simple Debugging Tools}
+;;;
+
+;; peek takes any number of arguments, writes them to the
+;; current ouput port, and returns the last argument.
+;; It is handy to wrap around an expression to look at
+;; a value each time is evaluated, e.g.:
+;;
+;; (+ 10 (troublesome-fn))
+;; => (+ 10 (pk 'troublesome-fn-returned (troublesome-fn)))
+;;
+
+(define (peek . stuff)
+ (newline)
+ (display ";;; ")
+ (write stuff)
+ (newline)
+ (car (last-pair stuff)))
+
+(define pk peek)
+
+(define (warn . stuff)
+ (with-output-to-port (current-warning-port)
+ (lambda ()
+ (newline)
+ (display ";;; WARNING ")
+ (display stuff)
+ (newline)
+ (car (last-pair stuff)))))
+
+
+
+;;; {Features}
+;;;
+
+(define (provide sym)
+ (if (not (memq sym *features*))
+ (set! *features* (cons sym *features*))))
+
+;; Return #t iff FEATURE is available to this Guile interpreter. In SLIB,
+;; provided? also checks to see if the module is available. We should do that
+;; too, but don't.
+
+(define (provided? feature)
+ (and (memq feature *features*) #t))
+
+
+
+;;; {Structs}
+;;;
+
+(define (make-struct/no-tail vtable . args)
+ (apply make-struct vtable 0 args))
+
+
+
+;; Temporary definition used in the include-from-path expansion;
+;; replaced later.
+
+(define (absolute-file-name? file-name)
+ #t)
+
+;;; {and-map and or-map}
+;;;
+;;; (and-map fn lst) is like (and (fn (car lst)) (fn (cadr lst)) (fn...) ...)
+;;; (or-map fn lst) is like (or (fn (car lst)) (fn (cadr lst)) (fn...) ...)
+;;;
+
+;; and-map f l
+;;
+;; Apply f to successive elements of l until exhaustion or f returns #f.
+;; If returning early, return #f. Otherwise, return the last value returned
+;; by f. If f has never been called because l is empty, return #t.
+;;
+(define (and-map f lst)
+ (let loop ((result #t)
+ (l lst))
+ (and result
+ (or (and (null? l)
+ result)
+ (loop (f (car l)) (cdr l))))))
+
+;; or-map f l
+;;
+;; Apply f to successive elements of l until exhaustion or while f returns #f.
+;; If returning early, return the return value of f.
+;;
+(define (or-map f lst)
+ (let loop ((result #f)
+ (l lst))
+ (or result
+ (and (not (null? l))
+ (loop (f (car l)) (cdr l))))))
+
+
+
+;; let format alias simple-format until the more complete version is loaded
+
+(define format simple-format)
+
+;; this is scheme wrapping the C code so the final pred call is a tail call,
+;; per SRFI-13 spec
+(define string-any
+ (lambda* (char_pred s #\optional (start 0) (end (string-length s)))
+ (if (and (procedure? char_pred)
+ (> end start)
+ (<= end (string-length s))) ;; let c-code handle range error
+ (or (string-any-c-code char_pred s start (1- end))
+ (char_pred (string-ref s (1- end))))
+ (string-any-c-code char_pred s start end))))
+
+;; this is scheme wrapping the C code so the final pred call is a tail call,
+;; per SRFI-13 spec
+(define string-every
+ (lambda* (char_pred s #\optional (start 0) (end (string-length s)))
+ (if (and (procedure? char_pred)
+ (> end start)
+ (<= end (string-length s))) ;; let c-code handle range error
+ (and (string-every-c-code char_pred s start (1- end))
+ (char_pred (string-ref s (1- end))))
+ (string-every-c-code char_pred s start end))))
+
+;; A variant of string-fill! that we keep for compatability
+;;
+(define (substring-fill! str start end fill)
+ (string-fill! str fill start end))
+
+
+
+;; Define a minimal stub of the module API for psyntax, before modules
+;; have booted.
+(define (module-name x)
+ '(guile))
+(define (module-add! module sym var)
+ (hashq-set! (%get-pre-modules-obarray) sym var))
+(define (module-define! module sym val)
+ (let ((v (hashq-ref (%get-pre-modules-obarray) sym)))
+ (if v
+ (variable-set! v val)
+ (module-add! (current-module) sym (make-variable val)))))
+(define (module-ref module sym)
+ (let ((v (module-variable module sym)))
+ (if v (variable-ref v) (error "badness!" (pk module) (pk sym)))))
+(define module-generate-unique-id!
+ (let ((next-id 0))
+ (lambda (m)
+ (let ((i next-id))
+ (set! next-id (+ i 1))
+ i))))
+(define module-gensym gensym)
+(define (resolve-module . args)
+ #f)
+
+;; API provided by psyntax
+(define syntax-violation #f)
+(define datum->syntax #f)
+(define syntax->datum #f)
+(define syntax-source #f)
+(define identifier? #f)
+(define generate-temporaries #f)
+(define bound-identifier=? #f)
+(define free-identifier=? #f)
+
+;; $sc-dispatch is an implementation detail of psyntax. It is used by
+;; expanded macros, to dispatch an input against a set of patterns.
+(define $sc-dispatch #f)
+
+;; Load it up!
+(primitive-load-path "ice-9/psyntax-pp")
+;; The binding for `macroexpand' has now been overridden, making psyntax the
+;; expander now.
+
+(define-syntax and
+ (syntax-rules ()
+ ((_) #t)
+ ((_ x) x)
+ ;; Avoid ellipsis, which would lead to quadratic expansion time.
+ ((_ x . y) (if x (and . y) #f))))
+
+(define-syntax or
+ (syntax-rules ()
+ ((_) #f)
+ ((_ x) x)
+ ;; Avoid ellipsis, which would lead to quadratic expansion time.
+ ((_ x . y) (let ((t x)) (if t t (or . y))))))
+
+(include-from-path "ice-9/quasisyntax")
+
+(define-syntax-rule (when test stmt stmt* ...)
+ (if test (begin stmt stmt* ...)))
+
+(define-syntax-rule (unless test stmt stmt* ...)
+ (if (not test) (begin stmt stmt* ...)))
+
+(define-syntax cond
+ (lambda (whole-expr)
+ (define (fold f seed xs)
+ (let loop ((xs xs) (seed seed))
+ (if (null? xs) seed
+ (loop (cdr xs) (f (car xs) seed)))))
+ (define (reverse-map f xs)
+ (fold (lambda (x seed) (cons (f x) seed))
+ '() xs))
+ (syntax-case whole-expr ()
+ ((_ clause clauses ...)
+ #`(begin
+ #,@(fold (lambda (clause-builder tail)
+ (clause-builder tail))
+ #'()
+ (reverse-map
+ (lambda (clause)
+ (define* (bad-clause #\optional (msg "invalid clause"))
+ (syntax-violation 'cond msg whole-expr clause))
+ (syntax-case clause (=> else)
+ ((else e e* ...)
+ (lambda (tail)
+ (if (null? tail)
+ #'((begin e e* ...))
+ (bad-clause "else must be the last clause"))))
+ ((else . _) (bad-clause))
+ ((test => receiver)
+ (lambda (tail)
+ #`((let ((t test))
+ (if t
+ (receiver t)
+ #,@tail)))))
+ ((test => receiver ...)
+ (bad-clause "wrong number of receiver expressions"))
+ ((generator guard => receiver)
+ (lambda (tail)
+ #`((call-with-values (lambda () generator)
+ (lambda vals
+ (if (apply guard vals)
+ (apply receiver vals)
+ #,@tail))))))
+ ((generator guard => receiver ...)
+ (bad-clause "wrong number of receiver expressions"))
+ ((test)
+ (lambda (tail)
+ #`((let ((t test))
+ (if t t #,@tail)))))
+ ((test e e* ...)
+ (lambda (tail)
+ #`((if test
+ (begin e e* ...)
+ #,@tail))))
+ (_ (bad-clause))))
+ #'(clause clauses ...))))))))
+
+(define-syntax case
+ (lambda (whole-expr)
+ (define (fold f seed xs)
+ (let loop ((xs xs) (seed seed))
+ (if (null? xs) seed
+ (loop (cdr xs) (f (car xs) seed)))))
+ (define (fold2 f a b xs)
+ (let loop ((xs xs) (a a) (b b))
+ (if (null? xs) (values a b)
+ (call-with-values
+ (lambda () (f (car xs) a b))
+ (lambda (a b)
+ (loop (cdr xs) a b))))))
+ (define (reverse-map-with-seed f seed xs)
+ (fold2 (lambda (x ys seed)
+ (call-with-values
+ (lambda () (f x seed))
+ (lambda (y seed)
+ (values (cons y ys) seed))))
+ '() seed xs))
+ (syntax-case whole-expr ()
+ ((_ expr clause clauses ...)
+ (with-syntax ((key #'key))
+ #`(let ((key expr))
+ #,@(fold
+ (lambda (clause-builder tail)
+ (clause-builder tail))
+ #'()
+ (reverse-map-with-seed
+ (lambda (clause seen)
+ (define* (bad-clause #\optional (msg "invalid clause"))
+ (syntax-violation 'case msg whole-expr clause))
+ (syntax-case clause ()
+ ((test . rest)
+ (with-syntax
+ ((clause-expr
+ (syntax-case #'rest (=>)
+ ((=> receiver) #'(receiver key))
+ ((=> receiver ...)
+ (bad-clause
+ "wrong number of receiver expressions"))
+ ((e e* ...) #'(begin e e* ...))
+ (_ (bad-clause)))))
+ (syntax-case #'test (else)
+ ((datums ...)
+ (let ((seen
+ (fold
+ (lambda (datum seen)
+ (define (warn-datum type)
+ ((@ (system base message)
+ warning)
+ type
+ (append (source-properties datum)
+ (source-properties
+ (syntax->datum #'test)))
+ datum
+ (syntax->datum clause)
+ (syntax->datum whole-expr)))
+ (when (memv datum seen)
+ (warn-datum 'duplicate-case-datum))
+ (when (or (pair? datum) (array? datum))
+ (warn-datum 'bad-case-datum))
+ (cons datum seen))
+ seen
+ (map syntax->datum #'(datums ...)))))
+ (values (lambda (tail)
+ #`((if (memv key '(datums ...))
+ clause-expr
+ #,@tail)))
+ seen)))
+ (else (values (lambda (tail)
+ (if (null? tail)
+ #'(clause-expr)
+ (bad-clause
+ "else must be the last clause")))
+ seen))
+ (_ (bad-clause)))))
+ (_ (bad-clause))))
+ '() #'(clause clauses ...)))))))))
+
+(define-syntax do
+ (syntax-rules ()
+ ((do ((var init step ...) ...)
+ (test expr ...)
+ command ...)
+ (letrec
+ ((loop
+ (lambda (var ...)
+ (if test
+ (begin
+ (if #f #f)
+ expr ...)
+ (begin
+ command
+ ...
+ (loop (do "step" var step ...)
+ ...))))))
+ (loop init ...)))
+ ((do "step" x)
+ x)
+ ((do "step" x y)
+ y)))
+
+;; XXX FIXME: When 'call-with-values' is fixed to no longer do automatic
+;; truncation of values (in 2.2 ?), then this hack can be removed.
+(define (%define-values-arity-error)
+ (throw 'wrong-number-of-args
+ #f
+ "define-values: wrong number of return values returned by expression"
+ '()
+ #f))
+
+(define-syntax define-values
+ (lambda (orig-form)
+ (syntax-case orig-form ()
+ ((_ () expr)
+ ;; XXX Work around the lack of hygienic top-level identifiers
+ (with-syntax (((dummy) (generate-temporaries '(dummy))))
+ #`(define dummy
+ (call-with-values (lambda () expr)
+ (case-lambda
+ (() #f)
+ (_ (%define-values-arity-error)))))))
+ ((_ (var) expr)
+ (identifier? #'var)
+ #`(define var
+ (call-with-values (lambda () expr)
+ (case-lambda
+ ((v) v)
+ (_ (%define-values-arity-error))))))
+ ((_ (var0 ... varn) expr)
+ (and-map identifier? #'(var0 ... varn))
+ ;; XXX Work around the lack of hygienic toplevel identifiers
+ (with-syntax (((dummy) (generate-temporaries '(dummy))))
+ #`(begin
+ ;; Avoid mutating the user-visible variables
+ (define dummy
+ (call-with-values (lambda () expr)
+ (case-lambda
+ ((var0 ... varn)
+ (list var0 ... varn))
+ (_ (%define-values-arity-error)))))
+ (define var0
+ (let ((v (car dummy)))
+ (set! dummy (cdr dummy))
+ v))
+ ...
+ (define varn
+ (let ((v (car dummy)))
+ (set! dummy #f) ; blackhole dummy
+ v)))))
+ ((_ var expr)
+ (identifier? #'var)
+ #'(define var
+ (call-with-values (lambda () expr)
+ list)))
+ ((_ (var0 ... . varn) expr)
+ (and-map identifier? #'(var0 ... varn))
+ ;; XXX Work around the lack of hygienic toplevel identifiers
+ (with-syntax (((dummy) (generate-temporaries '(dummy))))
+ #`(begin
+ ;; Avoid mutating the user-visible variables
+ (define dummy
+ (call-with-values (lambda () expr)
+ (case-lambda
+ ((var0 ... . varn)
+ (list var0 ... varn))
+ (_ (%define-values-arity-error)))))
+ (define var0
+ (let ((v (car dummy)))
+ (set! dummy (cdr dummy))
+ v))
+ ...
+ (define varn
+ (let ((v (car dummy)))
+ (set! dummy #f) ; blackhole dummy
+ v))))))))
+
+(define-syntax-rule (delay exp)
+ (make-promise (lambda () exp)))
+
+(define-syntax current-source-location
+ (lambda (x)
+ (syntax-case x ()
+ ((_)
+ (with-syntax ((s (datum->syntax x (syntax-source x))))
+ #''s)))))
+
+;; We provide this accessor out of convenience. current-line and
+;; current-column aren't so interesting, because they distort what they
+;; are measuring; better to use syntax-source from a macro.
+;;
+(define-syntax current-filename
+ (lambda (x)
+ "A macro that expands to the current filename: the filename that
+the (current-filename) form appears in. Expands to #f if this
+information is unavailable."
+ (false-if-exception
+ (canonicalize-path (assq-ref (syntax-source x) 'filename)))))
+
+(define-syntax-rule (define-once sym val)
+ (define sym
+ (if (module-locally-bound? (current-module) 'sym) sym val)))
+
+;;; The real versions of `map' and `for-each', with cycle detection, and
+;;; that use reverse! instead of recursion in the case of `map'.
+;;;
+(define map
+ (case-lambda
+ ((f l)
+ (let map1 ((hare l) (tortoise l) (move? #f) (out '()))
+ (if (pair? hare)
+ (if move?
+ (if (eq? tortoise hare)
+ (scm-error 'wrong-type-arg "map" "Circular list: ~S"
+ (list l) #f)
+ (map1 (cdr hare) (cdr tortoise) #f
+ (cons (f (car hare)) out)))
+ (map1 (cdr hare) tortoise #t
+ (cons (f (car hare)) out)))
+ (if (null? hare)
+ (reverse! out)
+ (scm-error 'wrong-type-arg "map" "Not a list: ~S"
+ (list l) #f)))))
+
+ ((f l1 l2)
+ (let map2 ((h1 l1) (h2 l2) (t1 l1) (t2 l2) (move? #f) (out '()))
+ (cond
+ ((pair? h1)
+ (cond
+ ((not (pair? h2))
+ (scm-error 'wrong-type-arg "map"
+ (if (list? h2)
+ "List of wrong length: ~S"
+ "Not a list: ~S")
+ (list l2) #f))
+ ((not move?)
+ (map2 (cdr h1) (cdr h2) t1 t2 #t
+ (cons (f (car h1) (car h2)) out)))
+ ((eq? t1 h1)
+ (scm-error 'wrong-type-arg "map" "Circular list: ~S"
+ (list l1) #f))
+ ((eq? t2 h2)
+ (scm-error 'wrong-type-arg "map" "Circular list: ~S"
+ (list l2) #f))
+ (else
+ (map2 (cdr h1) (cdr h2) (cdr t1) (cdr t2) #f
+ (cons (f (car h1) (car h2)) out)))))
+
+ ((and (null? h1) (null? h2))
+ (reverse! out))
+
+ ((null? h1)
+ (scm-error 'wrong-type-arg "map"
+ (if (list? h2)
+ "List of wrong length: ~S"
+ "Not a list: ~S")
+ (list l2) #f))
+ (else
+ (scm-error 'wrong-type-arg "map"
+ "Not a list: ~S"
+ (list l1) #f)))))
+
+ ((f l1 . rest)
+ (let ((len (length l1)))
+ (let mapn ((rest rest))
+ (or (null? rest)
+ (if (= (length (car rest)) len)
+ (mapn (cdr rest))
+ (scm-error 'wrong-type-arg "map" "List of wrong length: ~S"
+ (list (car rest)) #f)))))
+ (let mapn ((l1 l1) (rest rest) (out '()))
+ (if (null? l1)
+ (reverse! out)
+ (mapn (cdr l1) (map cdr rest)
+ (cons (apply f (car l1) (map car rest)) out)))))))
+
+(define map-in-order map)
+
+(define for-each
+ (case-lambda
+ ((f l)
+ (let for-each1 ((hare l) (tortoise l))
+ (if (pair? hare)
+ (begin
+ (f (car hare))
+ (let ((hare (cdr hare)))
+ (if (pair? hare)
+ (begin
+ (when (eq? tortoise hare)
+ (scm-error 'wrong-type-arg "for-each" "Circular list: ~S"
+ (list l) #f))
+ (f (car hare))
+ (for-each1 (cdr hare) (cdr tortoise))))))
+ (if (not (null? hare))
+ (scm-error 'wrong-type-arg "for-each" "Not a list: ~S"
+ (list l) #f)))))
+
+ ((f l1 l2)
+ (let for-each2 ((h1 l1) (h2 l2) (t1 l1) (t2 l2) (move? #f))
+ (cond
+ ((and (pair? h1) (pair? h2))
+ (cond
+ ((not move?)
+ (f (car h1) (car h2))
+ (for-each2 (cdr h1) (cdr h2) t1 t2 #t))
+ ((eq? t1 h1)
+ (scm-error 'wrong-type-arg "for-each" "Circular list: ~S"
+ (list l1) #f))
+ ((eq? t2 h2)
+ (scm-error 'wrong-type-arg "for-each" "Circular list: ~S"
+ (list l2) #f))
+ (else
+ (f (car h1) (car h2))
+ (for-each2 (cdr h1) (cdr h2) (cdr t1) (cdr t2) #f))))
+
+ ((if (null? h1)
+ (or (null? h2) (pair? h2))
+ (and (pair? h1) (null? h2)))
+ (if #f #f))
+
+ ((list? h1)
+ (scm-error 'wrong-type-arg "for-each" "Unexpected tail: ~S"
+ (list h2) #f))
+ (else
+ (scm-error 'wrong-type-arg "for-each" "Unexpected tail: ~S"
+ (list h1) #f)))))
+
+ ((f l1 . rest)
+ (let ((len (length l1)))
+ (let for-eachn ((rest rest))
+ (or (null? rest)
+ (if (= (length (car rest)) len)
+ (for-eachn (cdr rest))
+ (scm-error 'wrong-type-arg "for-each" "List of wrong length: ~S"
+ (list (car rest)) #f)))))
+
+ (let for-eachn ((l1 l1) (rest rest))
+ (if (pair? l1)
+ (begin
+ (apply f (car l1) (map car rest))
+ (for-eachn (cdr l1) (map cdr rest))))))))
+
+
+
+
+;;;
+;;; Enhanced file opening procedures
+;;;
+
+(define* (open-input-file
+ file #\key (binary #f) (encoding #f) (guess-encoding #f))
+ "Takes a string naming an existing file and returns an input port
+capable of delivering characters from the file. If the file
+cannot be opened, an error is signalled."
+ (open-file file (if binary "rb" "r")
+ #\encoding encoding
+ #\guess-encoding guess-encoding))
+
+(define* (open-output-file file #\key (binary #f) (encoding #f))
+ "Takes a string naming an output file to be created and returns an
+output port capable of writing characters to a new file by that
+name. If the file cannot be opened, an error is signalled. If a
+file with the given name already exists, the effect is unspecified."
+ (open-file file (if binary "wb" "w")
+ #\encoding encoding))
+
+(define* (call-with-input-file
+ file proc #\key (binary #f) (encoding #f) (guess-encoding #f))
+ "PROC should be a procedure of one argument, and FILE should be a
+string naming a file. The file must
+already exist. These procedures call PROC
+with one argument: the port obtained by opening the named file for
+input or output. If the file cannot be opened, an error is
+signalled. If the procedure returns, then the port is closed
+automatically and the values yielded by the procedure are returned.
+If the procedure does not return, then the port will not be closed
+automatically unless it is possible to prove that the port will
+never again be used for a read or write operation."
+ (let ((p (open-input-file file
+ #\binary binary
+ #\encoding encoding
+ #\guess-encoding guess-encoding)))
+ (call-with-values
+ (lambda () (proc p))
+ (lambda vals
+ (close-input-port p)
+ (apply values vals)))))
+
+(define* (call-with-output-file file proc #\key (binary #f) (encoding #f))
+ "PROC should be a procedure of one argument, and FILE should be a
+string naming a file. The behaviour is unspecified if the file
+already exists. These procedures call PROC
+with one argument: the port obtained by opening the named file for
+input or output. If the file cannot be opened, an error is
+signalled. If the procedure returns, then the port is closed
+automatically and the values yielded by the procedure are returned.
+If the procedure does not return, then the port will not be closed
+automatically unless it is possible to prove that the port will
+never again be used for a read or write operation."
+ (let ((p (open-output-file file #\binary binary #\encoding encoding)))
+ (call-with-values
+ (lambda () (proc p))
+ (lambda vals
+ (close-output-port p)
+ (apply values vals)))))
+
+(define* (with-input-from-file
+ file thunk #\key (binary #f) (encoding #f) (guess-encoding #f))
+ "THUNK must be a procedure of no arguments, and FILE must be a
+string naming a file. The file must already exist. The file is opened for
+input, an input port connected to it is made
+the default value returned by `current-input-port',
+and the THUNK is called with no arguments.
+When the THUNK returns, the port is closed and the previous
+default is restored. Returns the values yielded by THUNK. If an
+escape procedure is used to escape from the continuation of these
+procedures, their behavior is implementation dependent."
+ (call-with-input-file file
+ (lambda (p) (with-input-from-port p thunk))
+ #\binary binary
+ #\encoding encoding
+ #\guess-encoding guess-encoding))
+
+(define* (with-output-to-file file thunk #\key (binary #f) (encoding #f))
+ "THUNK must be a procedure of no arguments, and FILE must be a
+string naming a file. The effect is unspecified if the file already exists.
+The file is opened for output, an output port connected to it is made
+the default value returned by `current-output-port',
+and the THUNK is called with no arguments.
+When the THUNK returns, the port is closed and the previous
+default is restored. Returns the values yielded by THUNK. If an
+escape procedure is used to escape from the continuation of these
+procedures, their behavior is implementation dependent."
+ (call-with-output-file file
+ (lambda (p) (with-output-to-port p thunk))
+ #\binary binary
+ #\encoding encoding))
+
+(define* (with-error-to-file file thunk #\key (binary #f) (encoding #f))
+ "THUNK must be a procedure of no arguments, and FILE must be a
+string naming a file. The effect is unspecified if the file already exists.
+The file is opened for output, an output port connected to it is made
+the default value returned by `current-error-port',
+and the THUNK is called with no arguments.
+When the THUNK returns, the port is closed and the previous
+default is restored. Returns the values yielded by THUNK. If an
+escape procedure is used to escape from the continuation of these
+procedures, their behavior is implementation dependent."
+ (call-with-output-file file
+ (lambda (p) (with-error-to-port p thunk))
+ #\binary binary
+ #\encoding encoding))
+
+
+
+;;;
+;;; Extensible exception printing.
+;;;
+
+(define set-exception-printer! #f)
+;; There is already a definition of print-exception from backtrace.c
+;; that we will override.
+
+(let ((exception-printers '()))
+ (define (print-location frame port)
+ (let ((source (and=> frame frame-source)))
+ ;; source := (addr . (filename . (line . column)))
+ (if source
+ (let ((filename (or (cadr source) "<unnamed port>"))
+ (line (caddr source))
+ (col (cdddr source)))
+ (format port "~a:~a:~a: " filename (1+ line) col))
+ (format port "ERROR: "))))
+
+ (set! set-exception-printer!
+ (lambda (key proc)
+ (set! exception-printers (acons key proc exception-printers))))
+
+ (set! print-exception
+ (lambda (port frame key args)
+ (define (default-printer)
+ (format port "Throw to key `~a' with args `~s'." key args))
+
+ (if frame
+ (let ((proc (frame-procedure frame)))
+ (print-location frame port)
+ (format port "In procedure ~a:\n"
+ (or (false-if-exception (procedure-name proc))
+ proc))))
+
+ (print-location frame port)
+ (catch #t
+ (lambda ()
+ (let ((printer (assq-ref exception-printers key)))
+ (if printer
+ (printer port key args default-printer)
+ (default-printer))))
+ (lambda (k . args)
+ (format port "Error while printing exception.")))
+ (newline port)
+ (force-output port))))
+
+;;;
+;;; Printers for those keys thrown by Guile.
+;;;
+(let ()
+ (define (scm-error-printer port key args default-printer)
+ ;; Abuse case-lambda as a pattern matcher, given that we don't have
+ ;; ice-9 match at this point.
+ (apply (case-lambda
+ ((subr msg args . rest)
+ (if subr
+ (format port "In procedure ~a: " subr))
+ (apply format port msg (or args '())))
+ (_ (default-printer)))
+ args))
+
+ (define (syntax-error-printer port key args default-printer)
+ (apply (case-lambda
+ ((who what where form subform . extra)
+ (format port "Syntax error:\n")
+ (if where
+ (let ((file (or (assq-ref where 'filename) "unknown file"))
+ (line (and=> (assq-ref where 'line) 1+))
+ (col (assq-ref where 'column)))
+ (format port "~a:~a:~a: " file line col))
+ (format port "unknown location: "))
+ (if who
+ (format port "~a: " who))
+ (format port "~a" what)
+ (if subform
+ (format port " in subform ~s of ~s" subform form)
+ (if form
+ (format port " in form ~s" form))))
+ (_ (default-printer)))
+ args))
+
+ (define (keyword-error-printer port key args default-printer)
+ (let ((message (cadr args))
+ (faulty (car (cadddr args)))) ; I won't do it again, I promise.
+ (format port "~a: ~s" message faulty)))
+
+ (define (getaddrinfo-error-printer port key args default-printer)
+ (format port "In procedure getaddrinfo: ~a" (gai-strerror (car args))))
+
+ (set-exception-printer! 'goops-error scm-error-printer)
+ (set-exception-printer! 'host-not-found scm-error-printer)
+ (set-exception-printer! 'keyword-argument-error keyword-error-printer)
+ (set-exception-printer! 'misc-error scm-error-printer)
+ (set-exception-printer! 'no-data scm-error-printer)
+ (set-exception-printer! 'no-recovery scm-error-printer)
+ (set-exception-printer! 'null-pointer-error scm-error-printer)
+ (set-exception-printer! 'out-of-range scm-error-printer)
+ (set-exception-printer! 'program-error scm-error-printer)
+ (set-exception-printer! 'read-error scm-error-printer)
+ (set-exception-printer! 'regular-expression-syntax scm-error-printer)
+ (set-exception-printer! 'signal scm-error-printer)
+ (set-exception-printer! 'stack-overflow scm-error-printer)
+ (set-exception-printer! 'system-error scm-error-printer)
+ (set-exception-printer! 'try-again scm-error-printer)
+ (set-exception-printer! 'unbound-variable scm-error-printer)
+ (set-exception-printer! 'wrong-number-of-args scm-error-printer)
+ (set-exception-printer! 'wrong-type-arg scm-error-printer)
+
+ (set-exception-printer! 'syntax-error syntax-error-printer)
+
+ (set-exception-printer! 'getaddrinfo-error getaddrinfo-error-printer))
+
+
+
+
+;;; {Defmacros}
+;;;
+
+(define-syntax define-macro
+ (lambda (x)
+ "Define a defmacro."
+ (syntax-case x ()
+ ((_ (macro . args) doc body1 body ...)
+ (string? (syntax->datum #'doc))
+ #'(define-macro macro doc (lambda args body1 body ...)))
+ ((_ (macro . args) body ...)
+ #'(define-macro macro #f (lambda args body ...)))
+ ((_ macro transformer)
+ #'(define-macro macro #f transformer))
+ ((_ macro doc transformer)
+ (or (string? (syntax->datum #'doc))
+ (not (syntax->datum #'doc)))
+ #'(define-syntax macro
+ (lambda (y)
+ doc
+ #((macro-type . defmacro)
+ (defmacro-args args))
+ (syntax-case y ()
+ ((_ . args)
+ (let ((v (syntax->datum #'args)))
+ (datum->syntax y (apply transformer v)))))))))))
+
+(define-syntax defmacro
+ (lambda (x)
+ "Define a defmacro, with the old lispy defun syntax."
+ (syntax-case x ()
+ ((_ macro args doc body1 body ...)
+ (string? (syntax->datum #'doc))
+ #'(define-macro macro doc (lambda args body1 body ...)))
+ ((_ macro args body ...)
+ #'(define-macro macro #f (lambda args body ...))))))
+
+(provide 'defmacro)
+
+
+
+;;; {Deprecation}
+;;;
+
+(define-syntax begin-deprecated
+ (lambda (x)
+ (syntax-case x ()
+ ((_ form form* ...)
+ (if (include-deprecated-features)
+ #'(begin form form* ...)
+ #'(begin))))))
+
+
+
+;;; {Trivial Functions}
+;;;
+
+(define (identity x) x)
+
+(define (compose proc . rest)
+ "Compose PROC with the procedures in REST, such that the last one in
+REST is applied first and PROC last, and return the resulting procedure.
+The given procedures must have compatible arity."
+ (if (null? rest)
+ proc
+ (let ((g (apply compose rest)))
+ (lambda args
+ (call-with-values (lambda () (apply g args)) proc)))))
+
+(define (negate proc)
+ "Return a procedure with the same arity as PROC that returns the `not'
+of PROC's result."
+ (lambda args
+ (not (apply proc args))))
+
+(define (const value)
+ "Return a procedure that accepts any number of arguments and returns
+VALUE."
+ (lambda _
+ value))
+
+(define (and=> value procedure)
+ "When VALUE is #f, return #f. Otherwise, return (PROC VALUE)."
+ (and value (procedure value)))
+
+(define call/cc call-with-current-continuation)
+
+(define-syntax false-if-exception
+ (syntax-rules ()
+ ((false-if-exception expr)
+ (catch #t
+ (lambda () expr)
+ (lambda args #f)))
+ ((false-if-exception expr #\warning template arg ...)
+ (catch #t
+ (lambda () expr)
+ (lambda (key . args)
+ (for-each (lambda (s)
+ (if (not (string-null? s))
+ (format (current-warning-port) ";;; ~a\n" s)))
+ (string-split
+ (call-with-output-string
+ (lambda (port)
+ (format port template arg ...)
+ (print-exception port #f key args)))
+ #\newline))
+ #f)))))
+
+
+
+;;; {General Properties}
+;;;
+
+;; Properties are a lispy way to associate random info with random objects.
+;; Traditionally properties are implemented as an alist or a plist actually
+;; pertaining to the object in question.
+;;
+;; These "object properties" have the advantage that they can be associated with
+;; any object, even if the object has no plist. Object properties are good when
+;; you are extending pre-existing objects in unexpected ways. They also present
+;; a pleasing, uniform procedure-with-setter interface. But if you have a data
+;; type that always has properties, it's often still best to store those
+;; properties within the object itself.
+
+(define (make-object-property)
+ (define-syntax-rule (with-mutex lock exp)
+ (dynamic-wind (lambda () (lock-mutex lock))
+ (lambda () exp)
+ (lambda () (unlock-mutex lock))))
+ (let ((prop (make-weak-key-hash-table))
+ (lock (make-mutex)))
+ (make-procedure-with-setter
+ (lambda (obj) (with-mutex lock (hashq-ref prop obj)))
+ (lambda (obj val) (with-mutex lock (hashq-set! prop obj val))))))
+
+
+
+
+;;; {Symbol Properties}
+;;;
+
+;;; Symbol properties are something you see in old Lisp code. In most current
+;;; Guile code, symbols are not used as a data structure -- they are used as
+;;; keys into other data structures.
+
+(define (symbol-property sym prop)
+ (let ((pair (assoc prop (symbol-pref sym))))
+ (and pair (cdr pair))))
+
+(define (set-symbol-property! sym prop val)
+ (let ((pair (assoc prop (symbol-pref sym))))
+ (if pair
+ (set-cdr! pair val)
+ (symbol-pset! sym (acons prop val (symbol-pref sym))))))
+
+(define (symbol-property-remove! sym prop)
+ (let ((pair (assoc prop (symbol-pref sym))))
+ (if pair
+ (symbol-pset! sym (delq! pair (symbol-pref sym))))))
+
+
+
+;;; {Arrays}
+;;;
+
+(define (array-shape a)
+ (map (lambda (ind) (if (number? ind) (list 0 (+ -1 ind)) ind))
+ (array-dimensions a)))
+
+
+
+;;; {Keywords}
+;;;
+
+;;; It's much better if you can use lambda* / define*, of course.
+
+(define (kw-arg-ref args kw)
+ (let ((rem (member kw args)))
+ (and rem (pair? (cdr rem)) (cadr rem))))
+
+
+
+;;; {Structs}
+;;;
+
+(define (struct-layout s)
+ (struct-ref (struct-vtable s) vtable-index-layout))
+
+
+
+;;; {Records}
+;;;
+
+;; Printing records: by default, records are printed as
+;;
+;; #<type-name field1: val1 field2: val2 ...>
+;;
+;; You can change that by giving a custom printing function to
+;; MAKE-RECORD-TYPE (after the list of field symbols). This function
+;; will be called like
+;;
+;; (<printer> object port)
+;;
+;; It should print OBJECT to PORT.
+
+(define (inherit-print-state old-port new-port)
+ (if (get-print-state old-port)
+ (port-with-print-state new-port (get-print-state old-port))
+ new-port))
+
+;; 0: type-name, 1: fields, 2: constructor
+(define record-type-vtable
+ (let ((s (make-vtable (string-append standard-vtable-fields "prprpw")
+ (lambda (s p)
+ (display "#<record-type " p)
+ (display (record-type-name s) p)
+ (display ">" p)))))
+ (set-struct-vtable-name! s 'record-type)
+ s))
+
+(define (record-type? obj)
+ (and (struct? obj) (eq? record-type-vtable (struct-vtable obj))))
+
+(define* (make-record-type type-name fields #\optional printer)
+ ;; Pre-generate constructors for nfields < 20.
+ (define-syntax make-constructor
+ (lambda (x)
+ (define *max-static-argument-count* 20)
+ (define (make-formals n)
+ (let lp ((i 0))
+ (if (< i n)
+ (cons (datum->syntax
+ x
+ (string->symbol
+ (string (integer->char (+ (char->integer #\a) i)))))
+ (lp (1+ i)))
+ '())))
+ (syntax-case x ()
+ ((_ rtd exp) (not (identifier? #'exp))
+ #'(let ((n exp))
+ (make-constructor rtd n)))
+ ((_ rtd nfields)
+ #`(case nfields
+ #,@(let lp ((n 0))
+ (if (< n *max-static-argument-count*)
+ (cons (with-syntax (((formal ...) (make-formals n))
+ (n n))
+ #'((n)
+ (lambda (formal ...)
+ (make-struct rtd 0 formal ...))))
+ (lp (1+ n)))
+ '()))
+ (else
+ (lambda args
+ (if (= (length args) nfields)
+ (apply make-struct rtd 0 args)
+ (scm-error 'wrong-number-of-args
+ (format #f "make-~a" type-name)
+ "Wrong number of arguments" '() #f)))))))))
+
+ (define (default-record-printer s p)
+ (display "#<" p)
+ (display (record-type-name (record-type-descriptor s)) p)
+ (let loop ((fields (record-type-fields (record-type-descriptor s)))
+ (off 0))
+ (cond
+ ((not (null? fields))
+ (display " " p)
+ (display (car fields) p)
+ (display ": " p)
+ (display (struct-ref s off) p)
+ (loop (cdr fields) (+ 1 off)))))
+ (display ">" p))
+
+ (let ((rtd (make-struct record-type-vtable 0
+ (make-struct-layout
+ (apply string-append
+ (map (lambda (f) "pw") fields)))
+ (or printer default-record-printer)
+ type-name
+ (copy-tree fields))))
+ (struct-set! rtd (+ vtable-offset-user 2)
+ (make-constructor rtd (length fields)))
+ ;; Temporary solution: Associate a name to the record type descriptor
+ ;; so that the object system can create a wrapper class for it.
+ (set-struct-vtable-name! rtd (if (symbol? type-name)
+ type-name
+ (string->symbol type-name)))
+ rtd))
+
+(define (record-type-name obj)
+ (if (record-type? obj)
+ (struct-ref obj vtable-offset-user)
+ (error 'not-a-record-type obj)))
+
+(define (record-type-fields obj)
+ (if (record-type? obj)
+ (struct-ref obj (+ 1 vtable-offset-user))
+ (error 'not-a-record-type obj)))
+
+(define* (record-constructor rtd #\optional field-names)
+ (if (not field-names)
+ (struct-ref rtd (+ 2 vtable-offset-user))
+ (primitive-eval
+ `(lambda ,field-names
+ (make-struct ',rtd 0 ,@(map (lambda (f)
+ (if (memq f field-names)
+ f
+ #f))
+ (record-type-fields rtd)))))))
+
+(define (record-predicate rtd)
+ (lambda (obj) (and (struct? obj) (eq? rtd (struct-vtable obj)))))
+
+(define (%record-type-error rtd obj) ;; private helper
+ (or (eq? rtd (record-type-descriptor obj))
+ (scm-error 'wrong-type-arg "%record-type-check"
+ "Wrong type record (want `~S'): ~S"
+ (list (record-type-name rtd) obj)
+ #f)))
+
+(define (record-accessor rtd field-name)
+ (let ((pos (list-index (record-type-fields rtd) field-name)))
+ (if (not pos)
+ (error 'no-such-field field-name))
+ (lambda (obj)
+ (if (eq? (struct-vtable obj) rtd)
+ (struct-ref obj pos)
+ (%record-type-error rtd obj)))))
+
+(define (record-modifier rtd field-name)
+ (let ((pos (list-index (record-type-fields rtd) field-name)))
+ (if (not pos)
+ (error 'no-such-field field-name))
+ (lambda (obj val)
+ (if (eq? (struct-vtable obj) rtd)
+ (struct-set! obj pos val)
+ (%record-type-error rtd obj)))))
+
+(define (record? obj)
+ (and (struct? obj) (record-type? (struct-vtable obj))))
+
+(define (record-type-descriptor obj)
+ (if (struct? obj)
+ (struct-vtable obj)
+ (error 'not-a-record obj)))
+
+(provide 'record)
+
+
+
+;;; {Booleans}
+;;;
+
+(define (->bool x) (not (not x)))
+
+
+
+;;; {Symbols}
+;;;
+
+(define (symbol-append . args)
+ (string->symbol (apply string-append (map symbol->string args))))
+
+(define (list->symbol . args)
+ (string->symbol (apply list->string args)))
+
+(define (symbol . args)
+ (string->symbol (apply string args)))
+
+
+
+;;; {Lists}
+;;;
+
+(define (list-index l k)
+ (let loop ((n 0)
+ (l l))
+ (and (not (null? l))
+ (if (eq? (car l) k)
+ n
+ (loop (+ n 1) (cdr l))))))
+
+
+
+;; Load `posix.scm' even when not (provided? 'posix) so that we get the
+;; `stat' accessors.
+(primitive-load-path "ice-9/posix")
+
+(if (provided? 'socket)
+ (primitive-load-path "ice-9/networking"))
+
+;; For reference, Emacs file-exists-p uses stat in this same way.
+(define file-exists?
+ (if (provided? 'posix)
+ (lambda (str)
+ (->bool (stat str #f)))
+ (lambda (str)
+ (let ((port (catch 'system-error (lambda () (open-file str OPEN_READ))
+ (lambda args #f))))
+ (if port (begin (close-port port) #t)
+ #f)))))
+
+(define file-is-directory?
+ (if (provided? 'posix)
+ (lambda (str)
+ (eq? (stat:type (stat str)) 'directory))
+ (lambda (str)
+ (let ((port (catch 'system-error
+ (lambda () (open-file (string-append str "/.")
+ OPEN_READ))
+ (lambda args #f))))
+ (if port (begin (close-port port) #t)
+ #f)))))
+
+(define (system-error-errno args)
+ (if (eq? (car args) 'system-error)
+ (car (list-ref args 4))
+ #f))
+
+
+
+;;; {Error Handling}
+;;;
+
+(define error
+ (case-lambda
+ (()
+ (scm-error 'misc-error #f "?" #f #f))
+ ((message . args)
+ (let ((msg (string-join (cons "~A" (make-list (length args) "~S")))))
+ (scm-error 'misc-error #f msg (cons message args) #f)))))
+
+
+
+;;; {Time Structures}
+;;;
+
+(define (tm:sec obj) (vector-ref obj 0))
+(define (tm:min obj) (vector-ref obj 1))
+(define (tm:hour obj) (vector-ref obj 2))
+(define (tm:mday obj) (vector-ref obj 3))
+(define (tm:mon obj) (vector-ref obj 4))
+(define (tm:year obj) (vector-ref obj 5))
+(define (tm:wday obj) (vector-ref obj 6))
+(define (tm:yday obj) (vector-ref obj 7))
+(define (tm:isdst obj) (vector-ref obj 8))
+(define (tm:gmtoff obj) (vector-ref obj 9))
+(define (tm:zone obj) (vector-ref obj 10))
+
+(define (set-tm:sec obj val) (vector-set! obj 0 val))
+(define (set-tm:min obj val) (vector-set! obj 1 val))
+(define (set-tm:hour obj val) (vector-set! obj 2 val))
+(define (set-tm:mday obj val) (vector-set! obj 3 val))
+(define (set-tm:mon obj val) (vector-set! obj 4 val))
+(define (set-tm:year obj val) (vector-set! obj 5 val))
+(define (set-tm:wday obj val) (vector-set! obj 6 val))
+(define (set-tm:yday obj val) (vector-set! obj 7 val))
+(define (set-tm:isdst obj val) (vector-set! obj 8 val))
+(define (set-tm:gmtoff obj val) (vector-set! obj 9 val))
+(define (set-tm:zone obj val) (vector-set! obj 10 val))
+
+(define (tms:clock obj) (vector-ref obj 0))
+(define (tms:utime obj) (vector-ref obj 1))
+(define (tms:stime obj) (vector-ref obj 2))
+(define (tms:cutime obj) (vector-ref obj 3))
+(define (tms:cstime obj) (vector-ref obj 4))
+
+
+
+;;; {File Descriptors and Ports}
+;;;
+
+(define file-position ftell)
+(define* (file-set-position port offset #\optional (whence SEEK_SET))
+ (seek port offset whence))
+
+(define (move->fdes fd/port fd)
+ (cond ((integer? fd/port)
+ (dup->fdes fd/port fd)
+ (close fd/port)
+ fd)
+ (else
+ (primitive-move->fdes fd/port fd)
+ (set-port-revealed! fd/port 1)
+ fd/port)))
+
+(define (release-port-handle port)
+ (let ((revealed (port-revealed port)))
+ (if (> revealed 0)
+ (set-port-revealed! port (- revealed 1)))))
+
+(define dup->port
+ (case-lambda
+ ((port/fd mode)
+ (fdopen (dup->fdes port/fd) mode))
+ ((port/fd mode new-fd)
+ (let ((port (fdopen (dup->fdes port/fd new-fd) mode)))
+ (set-port-revealed! port 1)
+ port))))
+
+(define dup->inport
+ (case-lambda
+ ((port/fd)
+ (dup->port port/fd "r"))
+ ((port/fd new-fd)
+ (dup->port port/fd "r" new-fd))))
+
+(define dup->outport
+ (case-lambda
+ ((port/fd)
+ (dup->port port/fd "w"))
+ ((port/fd new-fd)
+ (dup->port port/fd "w" new-fd))))
+
+(define dup
+ (case-lambda
+ ((port/fd)
+ (if (integer? port/fd)
+ (dup->fdes port/fd)
+ (dup->port port/fd (port-mode port/fd))))
+ ((port/fd new-fd)
+ (if (integer? port/fd)
+ (dup->fdes port/fd new-fd)
+ (dup->port port/fd (port-mode port/fd) new-fd)))))
+
+(define (duplicate-port port modes)
+ (dup->port port modes))
+
+(define (fdes->inport fdes)
+ (let loop ((rest-ports (fdes->ports fdes)))
+ (cond ((null? rest-ports)
+ (let ((result (fdopen fdes "r")))
+ (set-port-revealed! result 1)
+ result))
+ ((input-port? (car rest-ports))
+ (set-port-revealed! (car rest-ports)
+ (+ (port-revealed (car rest-ports)) 1))
+ (car rest-ports))
+ (else
+ (loop (cdr rest-ports))))))
+
+(define (fdes->outport fdes)
+ (let loop ((rest-ports (fdes->ports fdes)))
+ (cond ((null? rest-ports)
+ (let ((result (fdopen fdes "w")))
+ (set-port-revealed! result 1)
+ result))
+ ((output-port? (car rest-ports))
+ (set-port-revealed! (car rest-ports)
+ (+ (port-revealed (car rest-ports)) 1))
+ (car rest-ports))
+ (else
+ (loop (cdr rest-ports))))))
+
+(define (port->fdes port)
+ (set-port-revealed! port (+ (port-revealed port) 1))
+ (fileno port))
+
+(define (setenv name value)
+ (if value
+ (putenv (string-append name "=" value))
+ (putenv name)))
+
+(define (unsetenv name)
+ "Remove the entry for NAME from the environment."
+ (putenv name))
+
+
+
+;;; {Load Paths}
+;;;
+
+(let-syntax ((compile-time-case
+ (lambda (stx)
+ (syntax-case stx ()
+ ((_ exp clauses ...)
+ (let ((val (primitive-eval (syntax->datum #'exp))))
+ (let next-clause ((clauses #'(clauses ...)))
+ (syntax-case clauses (else)
+ (()
+ (syntax-violation 'compile-time-case
+ "all clauses failed to match" stx))
+ (((else form ...))
+ #'(begin form ...))
+ ((((k ...) form ...) clauses ...)
+ (if (memv val (syntax->datum #'(k ...)))
+ #'(begin form ...)
+ (next-clause #'(clauses ...))))))))))))
+ ;; emacs: (put 'compile-time-case 'scheme-indent-function 1)
+ (compile-time-case (system-file-name-convention)
+ ((posix)
+ (define (file-name-separator? c)
+ (char=? c #\/))
+
+ (define file-name-separator-string "/")
+
+ (define (absolute-file-name? file-name)
+ (string-prefix? "/" file-name)))
+
+ ((windows)
+ (define (file-name-separator? c)
+ (or (char=? c #\/)
+ (char=? c #\\)))
+
+ (define file-name-separator-string "/")
+
+ (define (absolute-file-name? file-name)
+ (define (file-name-separator-at-index? idx)
+ (and (> (string-length file-name) idx)
+ (file-name-separator? (string-ref file-name idx))))
+ (define (unc-file-name?)
+ ;; Universal Naming Convention (UNC) file-names start with \\,
+ ;; and are always absolute. See:
+ ;; http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#fully_qualified_vs._relative_paths
+ (and (file-name-separator-at-index? 0)
+ (file-name-separator-at-index? 1)))
+ (define (has-drive-specifier?)
+ (and (>= (string-length file-name) 2)
+ (let ((drive (string-ref file-name 0)))
+ (or (char<=? #\a drive #\z)
+ (char<=? #\A drive #\Z)))
+ (eqv? (string-ref file-name 1) #\:)))
+ (or (unc-file-name?)
+ (if (has-drive-specifier?)
+ (file-name-separator-at-index? 2)
+ (file-name-separator-at-index? 0)))))))
+
+(define (in-vicinity vicinity file)
+ (let ((tail (let ((len (string-length vicinity)))
+ (if (zero? len)
+ #f
+ (string-ref vicinity (- len 1))))))
+ (string-append vicinity
+ (if (or (not tail) (file-name-separator? tail))
+ ""
+ file-name-separator-string)
+ file)))
+
+
+
+;;; {Help for scm_shell}
+;;;
+;;; The argument-processing code used by Guile-based shells generates
+;;; Scheme code based on the argument list. This page contains help
+;;; functions for the code it generates.
+;;;
+
+(define (command-line) (program-arguments))
+
+;; This is mostly for the internal use of the code generated by
+;; scm_compile_shell_switches.
+
+(define (load-user-init)
+ (let* ((home (or (getenv "HOME")
+ (false-if-exception (passwd:dir (getpwuid (getuid))))
+ file-name-separator-string)) ;; fallback for cygwin etc.
+ (init-file (in-vicinity home ".guile")))
+ (if (file-exists? init-file)
+ (primitive-load init-file))))
+
+
+
+;;; {The interpreter stack}
+;;;
+
+;; %stacks defined in stacks.c
+(define (%start-stack tag thunk)
+ (let ((prompt-tag (make-prompt-tag "start-stack")))
+ (call-with-prompt
+ prompt-tag
+ (lambda ()
+ (with-fluids ((%stacks (acons tag prompt-tag
+ (or (fluid-ref %stacks) '()))))
+ (thunk)))
+ (lambda (k . args)
+ (%start-stack tag (lambda () (apply k args)))))))
+
+(define-syntax-rule (start-stack tag exp)
+ (%start-stack tag (lambda () exp)))
+
+
+
+;;; {Loading by paths}
+;;;
+
+;;; Load a Scheme source file named NAME, searching for it in the
+;;; directories listed in %load-path, and applying each of the file
+;;; name extensions listed in %load-extensions.
+(define (load-from-path name)
+ (start-stack 'load-stack
+ (primitive-load-path name)))
+
+(define-syntax-rule (add-to-load-path elt)
+ "Add ELT to Guile's load path, at compile-time and at run-time."
+ (eval-when (expand load eval)
+ (set! %load-path (cons elt (delete elt %load-path)))))
+
+(define %load-verbosely #f)
+(define (assert-load-verbosity v) (set! %load-verbosely v))
+
+(define (%load-announce file)
+ (if %load-verbosely
+ (with-output-to-port (current-warning-port)
+ (lambda ()
+ (display ";;; ")
+ (display "loading ")
+ (display file)
+ (newline)
+ (force-output)))))
+
+(set! %load-hook %load-announce)
+
+
+
+;;; {Reader Extensions}
+;;;
+;;; Reader code for various "#c" forms.
+;;;
+
+(define read-eval? (make-fluid #f))
+(read-hash-extend #\.
+ (lambda (c port)
+ (if (fluid-ref read-eval?)
+ (eval (read port) (interaction-environment))
+ (error
+ "#. read expansion found and read-eval? is #f."))))
+
+
+
+;;; {Low Level Modules}
+;;;
+;;; These are the low level data structures for modules.
+;;;
+;;; Every module object is of the type 'module-type', which is a record
+;;; consisting of the following members:
+;;;
+;;; - eval-closure: A deprecated field, to be removed in Guile 2.2.
+;;;
+;;; - obarray: a hash table that maps symbols to variable objects. In this
+;;; hash table, the definitions are found that are local to the module (that
+;;; is, not imported from other modules). When looking up bindings in the
+;;; module, this hash table is searched first.
+;;;
+;;; - binder: either #f or a function taking a module and a symbol argument.
+;;; If it is a function it is called after the obarray has been
+;;; unsuccessfully searched for a binding. It then can provide bindings
+;;; that would otherwise not be found locally in the module.
+;;;
+;;; - uses: a list of modules from which non-local bindings can be inherited.
+;;; These modules are the third place queried for bindings after the obarray
+;;; has been unsuccessfully searched and the binder function did not deliver
+;;; a result either.
+;;;
+;;; - transformer: either #f or a function taking a scheme expression as
+;;; delivered by read. If it is a function, it will be called to perform
+;;; syntax transformations (e. g. makro expansion) on the given scheme
+;;; expression. The output of the transformer function will then be passed
+;;; to Guile's internal memoizer. This means that the output must be valid
+;;; scheme code. The only exception is, that the output may make use of the
+;;; syntax extensions provided to identify the modules that a binding
+;;; belongs to.
+;;;
+;;; - name: the name of the module. This is used for all kinds of printing
+;;; outputs. In certain places the module name also serves as a way of
+;;; identification. When adding a module to the uses list of another
+;;; module, it is made sure that the new uses list will not contain two
+;;; modules of the same name.
+;;;
+;;; - kind: classification of the kind of module. The value is (currently?)
+;;; only used for printing. It has no influence on how a module is treated.
+;;; Currently the following values are used when setting the module kind:
+;;; 'module, 'directory, 'interface, 'custom-interface. If no explicit kind
+;;; is set, it defaults to 'module.
+;;;
+;;; - duplicates-handlers: a list of procedures that get called to make a
+;;; choice between two duplicate bindings when name clashes occur. See the
+;;; `duplicate-handlers' global variable below.
+;;;
+;;; - observers: a list of procedures that get called when the module is
+;;; modified.
+;;;
+;;; - weak-observers: a weak-key hash table of procedures that get called
+;;; when the module is modified. See `module-observe-weak' for details.
+;;;
+;;; In addition, the module may (must?) contain a binding for
+;;; `%module-public-interface'. This variable should be bound to a module
+;;; representing the exported interface of a module. See the
+;;; `module-public-interface' and `module-export!' procedures.
+;;;
+;;; !!! warning: The interface to lazy binder procedures is going
+;;; to be changed in an incompatible way to permit all the basic
+;;; module ops to be virtualized.
+;;;
+;;; (make-module size use-list lazy-binding-proc) => module
+;;; module-{obarray,uses,binder}[|-set!]
+;;; (module? obj) => [#t|#f]
+;;; (module-locally-bound? module symbol) => [#t|#f]
+;;; (module-bound? module symbol) => [#t|#f]
+;;; (module-symbol-locally-interned? module symbol) => [#t|#f]
+;;; (module-symbol-interned? module symbol) => [#t|#f]
+;;; (module-local-variable module symbol) => [#<variable ...> | #f]
+;;; (module-variable module symbol) => [#<variable ...> | #f]
+;;; (module-symbol-binding module symbol opt-value)
+;;; => [ <obj> | opt-value | an error occurs ]
+;;; (module-make-local-var! module symbol) => #<variable...>
+;;; (module-add! module symbol var) => unspecified
+;;; (module-remove! module symbol) => unspecified
+;;; (module-for-each proc module) => unspecified
+;;; (make-scm-module) => module ; a lazy copy of the symhash module
+;;; (set-current-module module) => unspecified
+;;; (current-module) => #<module...>
+;;;
+;;;
+
+
+
+;;; {Printing Modules}
+;;;
+
+;; This is how modules are printed. You can re-define it.
+(define (%print-module mod port)
+ (display "#<" port)
+ (display (or (module-kind mod) "module") port)
+ (display " " port)
+ (display (module-name mod) port)
+ (display " " port)
+ (display (number->string (object-address mod) 16) port)
+ (display ">" port))
+
+(letrec-syntax
+ ;; Locally extend the syntax to allow record accessors to be defined at
+ ;; compile-time. Cache the rtd locally to the constructor, the getters and
+ ;; the setters, in order to allow for redefinition of the record type; not
+ ;; relevant in the case of modules, but perhaps if we make this public, it
+ ;; could matter.
+
+ ((define-record-type
+ (lambda (x)
+ (define (make-id scope . fragments)
+ (datum->syntax #'scope
+ (apply symbol-append
+ (map (lambda (x)
+ (if (symbol? x) x (syntax->datum x)))
+ fragments))))
+
+ (define (getter rtd type-name field slot)
+ #`(define #,(make-id rtd type-name '- field)
+ (let ((rtd #,rtd))
+ (lambda (#,type-name)
+ (if (eq? (struct-vtable #,type-name) rtd)
+ (struct-ref #,type-name #,slot)
+ (%record-type-error rtd #,type-name))))))
+
+ (define (setter rtd type-name field slot)
+ #`(define #,(make-id rtd 'set- type-name '- field '!)
+ (let ((rtd #,rtd))
+ (lambda (#,type-name val)
+ (if (eq? (struct-vtable #,type-name) rtd)
+ (struct-set! #,type-name #,slot val)
+ (%record-type-error rtd #,type-name))))))
+
+ (define (accessors rtd type-name fields n exp)
+ (syntax-case fields ()
+ (() exp)
+ (((field #\no-accessors) field* ...) (identifier? #'field)
+ (accessors rtd type-name #'(field* ...) (1+ n)
+ exp))
+ (((field #\no-setter) field* ...) (identifier? #'field)
+ (accessors rtd type-name #'(field* ...) (1+ n)
+ #`(begin #,exp
+ #,(getter rtd type-name #'field n))))
+ (((field #\no-getter) field* ...) (identifier? #'field)
+ (accessors rtd type-name #'(field* ...) (1+ n)
+ #`(begin #,exp
+ #,(setter rtd type-name #'field n))))
+ ((field field* ...) (identifier? #'field)
+ (accessors rtd type-name #'(field* ...) (1+ n)
+ #`(begin #,exp
+ #,(getter rtd type-name #'field n)
+ #,(setter rtd type-name #'field n))))))
+
+ (define (predicate rtd type-name fields exp)
+ (accessors
+ rtd type-name fields 0
+ #`(begin
+ #,exp
+ (define (#,(make-id rtd type-name '?) obj)
+ (and (struct? obj) (eq? (struct-vtable obj) #,rtd))))))
+
+ (define (field-list fields)
+ (syntax-case fields ()
+ (() '())
+ (((f . opts) . rest) (identifier? #'f)
+ (cons #'f (field-list #'rest)))
+ ((f . rest) (identifier? #'f)
+ (cons #'f (field-list #'rest)))))
+
+ (define (constructor rtd type-name fields exp)
+ (let ((ctor (make-id rtd type-name '-constructor))
+ (args (field-list fields)))
+ (predicate rtd type-name fields
+ #`(begin #,exp
+ (define #,ctor
+ (let ((rtd #,rtd))
+ (lambda #,args
+ (make-struct rtd 0 #,@args))))
+ (struct-set! #,rtd (+ vtable-offset-user 2)
+ #,ctor)))))
+
+ (define (type type-name printer fields)
+ (define (make-layout)
+ (let lp ((fields fields) (slots '()))
+ (syntax-case fields ()
+ (() (datum->syntax #'here
+ (make-struct-layout
+ (apply string-append slots))))
+ ((_ . rest) (lp #'rest (cons "pw" slots))))))
+
+ (let ((rtd (make-id type-name type-name '-type)))
+ (constructor rtd type-name fields
+ #`(begin
+ (define #,rtd
+ (make-struct record-type-vtable 0
+ '#,(make-layout)
+ #,printer
+ '#,type-name
+ '#,(field-list fields)))
+ (set-struct-vtable-name! #,rtd '#,type-name)))))
+
+ (syntax-case x ()
+ ((_ type-name printer (field ...))
+ (type #'type-name #'printer #'(field ...)))))))
+
+ ;; module-type
+ ;;
+ ;; A module is characterized by an obarray in which local symbols
+ ;; are interned, a list of modules, "uses", from which non-local
+ ;; bindings can be inherited, and an optional lazy-binder which
+ ;; is a (CLOSURE module symbol) which, as a last resort, can provide
+ ;; bindings that would otherwise not be found locally in the module.
+ ;;
+ ;; NOTE: If you change the set of fields or their order, you also need to
+ ;; change the constants in libguile/modules.h.
+ ;;
+ ;; NOTE: The getter `module-transfomer' is defined libguile/modules.c.
+ ;; NOTE: The getter `module-name' is defined later, due to boot reasons.
+ ;; NOTE: The getter `module-public-interface' is used in libguile/modules.c.
+ ;;
+ (define-record-type module
+ (lambda (obj port) (%print-module obj port))
+ (obarray
+ uses
+ binder
+ eval-closure
+ (transformer #\no-getter)
+ (name #\no-getter)
+ kind
+ duplicates-handlers
+ (import-obarray #\no-setter)
+ observers
+ (weak-observers #\no-setter)
+ version
+ submodules
+ submodule-binder
+ public-interface
+ filename
+ next-unique-id)))
+
+
+;; make-module &opt size uses binder
+;;
+;; Create a new module, perhaps with a particular size of obarray,
+;; initial uses list, or binding procedure.
+;;
+(define* (make-module #\optional (size 31) (uses '()) (binder #f))
+ (define %default-import-size
+ ;; Typical number of imported bindings actually used by a module.
+ 600)
+
+ (if (not (integer? size))
+ (error "Illegal size to make-module." size))
+ (if (not (and (list? uses)
+ (and-map module? uses)))
+ (error "Incorrect use list." uses))
+ (if (and binder (not (procedure? binder)))
+ (error
+ "Lazy-binder expected to be a procedure or #f." binder))
+
+ (module-constructor (make-hash-table size)
+ uses binder #f macroexpand
+ #f #f #f
+ (make-hash-table %default-import-size)
+ '()
+ (make-weak-key-hash-table 31) #f
+ (make-hash-table 7) #f #f #f 0))
+
+
+
+
+;;; {Observer protocol}
+;;;
+
+(define (module-observe module proc)
+ (set-module-observers! module (cons proc (module-observers module)))
+ (cons module proc))
+
+(define* (module-observe-weak module observer-id #\optional (proc observer-id))
+ ;; Register PROC as an observer of MODULE under name OBSERVER-ID (which can
+ ;; be any Scheme object). PROC is invoked and passed MODULE any time
+ ;; MODULE is modified. PROC gets unregistered when OBSERVER-ID gets GC'd
+ ;; (thus, it is never unregistered if OBSERVER-ID is an immediate value,
+ ;; for instance).
+
+ ;; The two-argument version is kept for backward compatibility: when called
+ ;; with two arguments, the observer gets unregistered when closure PROC
+ ;; gets GC'd (making it impossible to use an anonymous lambda for PROC).
+ (hashq-set! (module-weak-observers module) observer-id proc))
+
+(define (module-unobserve token)
+ (let ((module (car token))
+ (id (cdr token)))
+ (if (integer? id)
+ (hash-remove! (module-weak-observers module) id)
+ (set-module-observers! module (delq1! id (module-observers module)))))
+ *unspecified*)
+
+(define module-defer-observers #f)
+(define module-defer-observers-mutex (make-mutex 'recursive))
+(define module-defer-observers-table (make-hash-table))
+
+(define (module-modified m)
+ (if module-defer-observers
+ (hash-set! module-defer-observers-table m #t)
+ (module-call-observers m)))
+
+;;; This function can be used to delay calls to observers so that they
+;;; can be called once only in the face of massive updating of modules.
+;;;
+(define (call-with-deferred-observers thunk)
+ (dynamic-wind
+ (lambda ()
+ (lock-mutex module-defer-observers-mutex)
+ (set! module-defer-observers #t))
+ thunk
+ (lambda ()
+ (set! module-defer-observers #f)
+ (hash-for-each (lambda (m dummy)
+ (module-call-observers m))
+ module-defer-observers-table)
+ (hash-clear! module-defer-observers-table)
+ (unlock-mutex module-defer-observers-mutex))))
+
+(define (module-call-observers m)
+ (for-each (lambda (proc) (proc m)) (module-observers m))
+
+ ;; We assume that weak observers don't (un)register themselves as they are
+ ;; called since this would preclude proper iteration over the hash table
+ ;; elements.
+ (hash-for-each (lambda (id proc) (proc m)) (module-weak-observers m)))
+
+
+
+;;; {Module Searching in General}
+;;;
+;;; We sometimes want to look for properties of a symbol
+;;; just within the obarray of one module. If the property
+;;; holds, then it is said to hold ``locally'' as in, ``The symbol
+;;; DISPLAY is locally rebound in the module `safe-guile'.''
+;;;
+;;;
+;;; Other times, we want to test for a symbol property in the obarray
+;;; of M and, if it is not found there, try each of the modules in the
+;;; uses list of M. This is the normal way of testing for some
+;;; property, so we state these properties without qualification as
+;;; in: ``The symbol 'fnord is interned in module M because it is
+;;; interned locally in module M2 which is a member of the uses list
+;;; of M.''
+;;;
+
+;; module-search fn m
+;;
+;; return the first non-#f result of FN applied to M and then to
+;; the modules in the uses of m, and so on recursively. If all applications
+;; return #f, then so does this function.
+;;
+(define (module-search fn m v)
+ (define (loop pos)
+ (and (pair? pos)
+ (or (module-search fn (car pos) v)
+ (loop (cdr pos)))))
+ (or (fn m v)
+ (loop (module-uses m))))
+
+
+;;; {Is a symbol bound in a module?}
+;;;
+;;; Symbol S in Module M is bound if S is interned in M and if the binding
+;;; of S in M has been set to some well-defined value.
+;;;
+
+;; module-locally-bound? module symbol
+;;
+;; Is a symbol bound (interned and defined) locally in a given module?
+;;
+(define (module-locally-bound? m v)
+ (let ((var (module-local-variable m v)))
+ (and var
+ (variable-bound? var))))
+
+;; module-bound? module symbol
+;;
+;; Is a symbol bound (interned and defined) anywhere in a given module
+;; or its uses?
+;;
+(define (module-bound? m v)
+ (let ((var (module-variable m v)))
+ (and var
+ (variable-bound? var))))
+
+;;; {Is a symbol interned in a module?}
+;;;
+;;; Symbol S in Module M is interned if S occurs in
+;;; of S in M has been set to some well-defined value.
+;;;
+;;; It is possible to intern a symbol in a module without providing
+;;; an initial binding for the corresponding variable. This is done
+;;; with:
+;;; (module-add! module symbol (make-undefined-variable))
+;;;
+;;; In that case, the symbol is interned in the module, but not
+;;; bound there. The unbound symbol shadows any binding for that
+;;; symbol that might otherwise be inherited from a member of the uses list.
+;;;
+
+(define (module-obarray-get-handle ob key)
+ ((if (symbol? key) hashq-get-handle hash-get-handle) ob key))
+
+(define (module-obarray-ref ob key)
+ ((if (symbol? key) hashq-ref hash-ref) ob key))
+
+(define (module-obarray-set! ob key val)
+ ((if (symbol? key) hashq-set! hash-set!) ob key val))
+
+(define (module-obarray-remove! ob key)
+ ((if (symbol? key) hashq-remove! hash-remove!) ob key))
+
+;; module-symbol-locally-interned? module symbol
+;;
+;; is a symbol interned (not neccessarily defined) locally in a given module
+;; or its uses? Interned symbols shadow inherited bindings even if
+;; they are not themselves bound to a defined value.
+;;
+(define (module-symbol-locally-interned? m v)
+ (not (not (module-obarray-get-handle (module-obarray m) v))))
+
+;; module-symbol-interned? module symbol
+;;
+;; is a symbol interned (not neccessarily defined) anywhere in a given module
+;; or its uses? Interned symbols shadow inherited bindings even if
+;; they are not themselves bound to a defined value.
+;;
+(define (module-symbol-interned? m v)
+ (module-search module-symbol-locally-interned? m v))
+
+
+;;; {Mapping modules x symbols --> variables}
+;;;
+
+;; module-local-variable module symbol
+;; return the local variable associated with a MODULE and SYMBOL.
+;;
+;;; This function is very important. It is the only function that can
+;;; return a variable from a module other than the mutators that store
+;;; new variables in modules. Therefore, this function is the location
+;;; of the "lazy binder" hack.
+;;;
+;;; If symbol is defined in MODULE, and if the definition binds symbol
+;;; to a variable, return that variable object.
+;;;
+;;; If the symbols is not found at first, but the module has a lazy binder,
+;;; then try the binder.
+;;;
+;;; If the symbol is not found at all, return #f.
+;;;
+;;; (This is now written in C, see `modules.c'.)
+;;;
+
+;;; {Mapping modules x symbols --> bindings}
+;;;
+;;; These are similar to the mapping to variables, except that the
+;;; variable is dereferenced.
+;;;
+
+;; module-symbol-binding module symbol opt-value
+;;
+;; return the binding of a variable specified by name within
+;; a given module, signalling an error if the variable is unbound.
+;; If the OPT-VALUE is passed, then instead of signalling an error,
+;; return OPT-VALUE.
+;;
+(define (module-symbol-local-binding m v . opt-val)
+ (let ((var (module-local-variable m v)))
+ (if (and var (variable-bound? var))
+ (variable-ref var)
+ (if (not (null? opt-val))
+ (car opt-val)
+ (error "Locally unbound variable." v)))))
+
+;; module-symbol-binding module symbol opt-value
+;;
+;; return the binding of a variable specified by name within
+;; a given module, signalling an error if the variable is unbound.
+;; If the OPT-VALUE is passed, then instead of signalling an error,
+;; return OPT-VALUE.
+;;
+(define (module-symbol-binding m v . opt-val)
+ (let ((var (module-variable m v)))
+ (if (and var (variable-bound? var))
+ (variable-ref var)
+ (if (not (null? opt-val))
+ (car opt-val)
+ (error "Unbound variable." v)))))
+
+
+
+
+;;; {Adding Variables to Modules}
+;;;
+
+;; module-make-local-var! module symbol
+;;
+;; ensure a variable for V in the local namespace of M.
+;; If no variable was already there, then create a new and uninitialzied
+;; variable.
+;;
+;; This function is used in modules.c.
+;;
+(define (module-make-local-var! m v)
+ (or (let ((b (module-obarray-ref (module-obarray m) v)))
+ (and (variable? b)
+ (begin
+ ;; Mark as modified since this function is called when
+ ;; the standard eval closure defines a binding
+ (module-modified m)
+ b)))
+
+ ;; Create a new local variable.
+ (let ((local-var (make-undefined-variable)))
+ (module-add! m v local-var)
+ local-var)))
+
+;; module-ensure-local-variable! module symbol
+;;
+;; Ensure that there is a local variable in MODULE for SYMBOL. If
+;; there is no binding for SYMBOL, create a new uninitialized
+;; variable. Return the local variable.
+;;
+(define (module-ensure-local-variable! module symbol)
+ (or (module-local-variable module symbol)
+ (let ((var (make-undefined-variable)))
+ (module-add! module symbol var)
+ var)))
+
+;; module-add! module symbol var
+;;
+;; ensure a particular variable for V in the local namespace of M.
+;;
+(define (module-add! m v var)
+ (if (not (variable? var))
+ (error "Bad variable to module-add!" var))
+ (if (not (symbol? v))
+ (error "Bad symbol to module-add!" v))
+ (module-obarray-set! (module-obarray m) v var)
+ (module-modified m))
+
+;; module-remove!
+;;
+;; make sure that a symbol is undefined in the local namespace of M.
+;;
+(define (module-remove! m v)
+ (module-obarray-remove! (module-obarray m) v)
+ (module-modified m))
+
+(define (module-clear! m)
+ (hash-clear! (module-obarray m))
+ (module-modified m))
+
+;; MODULE-FOR-EACH -- exported
+;;
+;; Call PROC on each symbol in MODULE, with arguments of (SYMBOL VARIABLE).
+;;
+(define (module-for-each proc module)
+ (hash-for-each proc (module-obarray module)))
+
+(define (module-map proc module)
+ (hash-map->list proc (module-obarray module)))
+
+;; Submodules
+;;
+;; Modules exist in a separate namespace from values, because you generally do
+;; not want the name of a submodule, which you might not even use, to collide
+;; with local variables that happen to be named the same as the submodule.
+;;
+(define (module-ref-submodule module name)
+ (or (hashq-ref (module-submodules module) name)
+ (and (module-submodule-binder module)
+ ((module-submodule-binder module) module name))))
+
+(define (module-define-submodule! module name submodule)
+ (hashq-set! (module-submodules module) name submodule))
+
+;; It used to be, however, that module names were also present in the
+;; value namespace. When we enable deprecated code, we preserve this
+;; legacy behavior.
+;;
+;; These shims are defined here instead of in deprecated.scm because we
+;; need their definitions before loading other modules.
+;;
+(begin-deprecated
+ (define (module-ref-submodule module name)
+ (or (hashq-ref (module-submodules module) name)
+ (and (module-submodule-binder module)
+ ((module-submodule-binder module) module name))
+ (let ((var (module-local-variable module name)))
+ (and var (variable-bound? var) (module? (variable-ref var))
+ (begin
+ (warn "module" module "not in submodules table")
+ (variable-ref var))))))
+
+ (define (module-define-submodule! module name submodule)
+ (let ((var (module-local-variable module name)))
+ (if (and var
+ (or (not (variable-bound? var))
+ (not (module? (variable-ref var)))))
+ (warn "defining module" module ": not overriding local definition" var)
+ (module-define! module name submodule)))
+ (hashq-set! (module-submodules module) name submodule)))
+
+
+
+;;; {Module-based Loading}
+;;;
+
+(define (save-module-excursion thunk)
+ (let ((inner-module (current-module))
+ (outer-module #f))
+ (dynamic-wind (lambda ()
+ (set! outer-module (current-module))
+ (set-current-module inner-module)
+ (set! inner-module #f))
+ thunk
+ (lambda ()
+ (set! inner-module (current-module))
+ (set-current-module outer-module)
+ (set! outer-module #f)))))
+
+
+
+;;; {MODULE-REF -- exported}
+;;;
+
+;; Returns the value of a variable called NAME in MODULE or any of its
+;; used modules. If there is no such variable, then if the optional third
+;; argument DEFAULT is present, it is returned; otherwise an error is signaled.
+;;
+(define (module-ref module name . rest)
+ (let ((variable (module-variable module name)))
+ (if (and variable (variable-bound? variable))
+ (variable-ref variable)
+ (if (null? rest)
+ (error "No variable named" name 'in module)
+ (car rest) ; default value
+ ))))
+
+;; MODULE-SET! -- exported
+;;
+;; Sets the variable called NAME in MODULE (or in a module that MODULE uses)
+;; to VALUE; if there is no such variable, an error is signaled.
+;;
+(define (module-set! module name value)
+ (let ((variable (module-variable module name)))
+ (if variable
+ (variable-set! variable value)
+ (error "No variable named" name 'in module))))
+
+;; MODULE-DEFINE! -- exported
+;;
+;; Sets the variable called NAME in MODULE to VALUE; if there is no such
+;; variable, it is added first.
+;;
+(define (module-define! module name value)
+ (let ((variable (module-local-variable module name)))
+ (if variable
+ (begin
+ (variable-set! variable value)
+ (module-modified module))
+ (let ((variable (make-variable value)))
+ (module-add! module name variable)))))
+
+;; MODULE-DEFINED? -- exported
+;;
+;; Return #t iff NAME is defined in MODULE (or in a module that MODULE
+;; uses)
+;;
+(define (module-defined? module name)
+ (let ((variable (module-variable module name)))
+ (and variable (variable-bound? variable))))
+
+;; MODULE-USE! module interface
+;;
+;; Add INTERFACE to the list of interfaces used by MODULE.
+;;
+(define (module-use! module interface)
+ (if (not (or (eq? module interface)
+ (memq interface (module-uses module))))
+ (begin
+ ;; Newly used modules must be appended rather than consed, so that
+ ;; `module-variable' traverses the use list starting from the first
+ ;; used module.
+ (set-module-uses! module (append (module-uses module)
+ (list interface)))
+ (hash-clear! (module-import-obarray module))
+ (module-modified module))))
+
+;; MODULE-USE-INTERFACES! module interfaces
+;;
+;; Same as MODULE-USE!, but only notifies module observers after all
+;; interfaces are added to the inports list.
+;;
+(define (module-use-interfaces! module interfaces)
+ (let* ((cur (module-uses module))
+ (new (let lp ((in interfaces) (out '()))
+ (if (null? in)
+ (reverse out)
+ (lp (cdr in)
+ (let ((iface (car in)))
+ (if (or (memq iface cur) (memq iface out))
+ out
+ (cons iface out))))))))
+ (set-module-uses! module (append cur new))
+ (hash-clear! (module-import-obarray module))
+ (module-modified module)))
+
+
+
+;;; {Recursive Namespaces}
+;;;
+;;; A hierarchical namespace emerges if we consider some module to be
+;;; root, and submodules of that module to be nested namespaces.
+;;;
+;;; The routines here manage variable names in hierarchical namespace.
+;;; Each variable name is a list of elements, looked up in successively nested
+;;; modules.
+;;;
+;;; (nested-ref some-root-module '(foo bar baz))
+;;; => <value of a variable named baz in the submodule bar of
+;;; the submodule foo of some-root-module>
+;;;
+;;;
+;;; There are:
+;;;
+;;; ;; a-root is a module
+;;; ;; name is a list of symbols
+;;;
+;;; nested-ref a-root name
+;;; nested-set! a-root name val
+;;; nested-define! a-root name val
+;;; nested-remove! a-root name
+;;;
+;;; These functions manipulate values in namespaces. For referencing the
+;;; namespaces themselves, use the following:
+;;;
+;;; nested-ref-module a-root name
+;;; nested-define-module! a-root name mod
+;;;
+;;; (current-module) is a natural choice for a root so for convenience there are
+;;; also:
+;;;
+;;; local-ref name == nested-ref (current-module) name
+;;; local-set! name val == nested-set! (current-module) name val
+;;; local-define name val == nested-define! (current-module) name val
+;;; local-remove name == nested-remove! (current-module) name
+;;; local-ref-module name == nested-ref-module (current-module) name
+;;; local-define-module! name m == nested-define-module! (current-module) name m
+;;;
+
+
+(define (nested-ref root names)
+ (if (null? names)
+ root
+ (let loop ((cur root)
+ (head (car names))
+ (tail (cdr names)))
+ (if (null? tail)
+ (module-ref cur head #f)
+ (let ((cur (module-ref-submodule cur head)))
+ (and cur
+ (loop cur (car tail) (cdr tail))))))))
+
+(define (nested-set! root names val)
+ (let loop ((cur root)
+ (head (car names))
+ (tail (cdr names)))
+ (if (null? tail)
+ (module-set! cur head val)
+ (let ((cur (module-ref-submodule cur head)))
+ (if (not cur)
+ (error "failed to resolve module" names)
+ (loop cur (car tail) (cdr tail)))))))
+
+(define (nested-define! root names val)
+ (let loop ((cur root)
+ (head (car names))
+ (tail (cdr names)))
+ (if (null? tail)
+ (module-define! cur head val)
+ (let ((cur (module-ref-submodule cur head)))
+ (if (not cur)
+ (error "failed to resolve module" names)
+ (loop cur (car tail) (cdr tail)))))))
+
+(define (nested-remove! root names)
+ (let loop ((cur root)
+ (head (car names))
+ (tail (cdr names)))
+ (if (null? tail)
+ (module-remove! cur head)
+ (let ((cur (module-ref-submodule cur head)))
+ (if (not cur)
+ (error "failed to resolve module" names)
+ (loop cur (car tail) (cdr tail)))))))
+
+
+(define (nested-ref-module root names)
+ (let loop ((cur root)
+ (names names))
+ (if (null? names)
+ cur
+ (let ((cur (module-ref-submodule cur (car names))))
+ (and cur
+ (loop cur (cdr names)))))))
+
+(define (nested-define-module! root names module)
+ (if (null? names)
+ (error "can't redefine root module" root module)
+ (let loop ((cur root)
+ (head (car names))
+ (tail (cdr names)))
+ (if (null? tail)
+ (module-define-submodule! cur head module)
+ (let ((cur (or (module-ref-submodule cur head)
+ (let ((m (make-module 31)))
+ (set-module-kind! m 'directory)
+ (set-module-name! m (append (module-name cur)
+ (list head)))
+ (module-define-submodule! cur head m)
+ m))))
+ (loop cur (car tail) (cdr tail)))))))
+
+
+(define (local-ref names)
+ (nested-ref (current-module) names))
+
+(define (local-set! names val)
+ (nested-set! (current-module) names val))
+
+(define (local-define names val)
+ (nested-define! (current-module) names val))
+
+(define (local-remove names)
+ (nested-remove! (current-module) names))
+
+(define (local-ref-module names)
+ (nested-ref-module (current-module) names))
+
+(define (local-define-module names mod)
+ (nested-define-module! (current-module) names mod))
+
+
+
+
+
+;;; {The (guile) module}
+;;;
+;;; The standard module, which has the core Guile bindings. Also called the
+;;; "root module", as it is imported by many other modules, but it is not
+;;; necessarily the root of anything; and indeed, the module named '() might be
+;;; better thought of as a root.
+;;;
+
+;; The root module uses the pre-modules-obarray as its obarray. This
+;; special obarray accumulates all bindings that have been established
+;; before the module system is fully booted.
+;;
+;; (The obarray continues to be used by code that has been closed over
+;; before the module system has been booted.)
+;;
+(define the-root-module
+ (let ((m (make-module 0)))
+ (set-module-obarray! m (%get-pre-modules-obarray))
+ (set-module-name! m '(guile))
+
+ ;; Inherit next-unique-id from preliminary stub of
+ ;; %module-get-next-unique-id! defined above.
+ (set-module-next-unique-id! m (module-generate-unique-id! #f))
+
+ m))
+
+;; The root interface is a module that uses the same obarray as the
+;; root module. It does not allow new definitions, tho.
+;;
+(define the-scm-module
+ (let ((m (make-module 0)))
+ (set-module-obarray! m (%get-pre-modules-obarray))
+ (set-module-name! m '(guile))
+ (set-module-kind! m 'interface)
+
+ ;; In Guile 1.8 and earlier M was its own public interface.
+ (set-module-public-interface! m m)
+
+ m))
+
+(set-module-public-interface! the-root-module the-scm-module)
+
+
+
+;; Now that we have a root module, even though modules aren't fully booted,
+;; expand the definition of resolve-module.
+;;
+(define (resolve-module name . args)
+ (if (equal? name '(guile))
+ the-root-module
+ (error "unexpected module to resolve during module boot" name)))
+
+(define (module-generate-unique-id! m)
+ (let ((i (module-next-unique-id m)))
+ (set-module-next-unique-id! m (+ i 1))
+ i))
+
+;; Cheat. These bindings are needed by modules.c, but we don't want
+;; to move their real definition here because that would be unnatural.
+;;
+(define define-module* #f)
+(define process-use-modules #f)
+(define module-export! #f)
+(define default-duplicate-binding-procedures #f)
+
+;; This boots the module system. All bindings needed by modules.c
+;; must have been defined by now.
+;;
+(set-current-module the-root-module)
+
+
+
+
+;; Now that modules are booted, give module-name its final definition.
+;;
+(define module-name
+ (let ((accessor (record-accessor module-type 'name)))
+ (lambda (mod)
+ (or (accessor mod)
+ (let ((name (list (gensym))))
+ ;; Name MOD and bind it in the module root so that it's visible to
+ ;; `resolve-module'. This is important as `psyntax' stores module
+ ;; names and relies on being able to `resolve-module' them.
+ (set-module-name! mod name)
+ (nested-define-module! (resolve-module '() #f) name mod)
+ (accessor mod))))))
+
+(define* (module-gensym #\optional (id " mg") (m (current-module)))
+ "Return a fresh symbol in the context of module M, based on ID (a
+string or symbol). As long as M is a valid module, this procedure is
+deterministic."
+ (define (->string number)
+ (number->string number 16))
+
+ (if m
+ (string->symbol
+ (string-append id "-"
+ (->string (hash (module-name m) most-positive-fixnum))
+ "-"
+ (->string (module-generate-unique-id! m))))
+ (gensym id)))
+
+(define (make-modules-in module name)
+ (or (nested-ref-module module name)
+ (let ((m (make-module 31)))
+ (set-module-kind! m 'directory)
+ (set-module-name! m (append (module-name module) name))
+ (nested-define-module! module name m)
+ m)))
+
+(define (beautify-user-module! module)
+ (let ((interface (module-public-interface module)))
+ (if (or (not interface)
+ (eq? interface module))
+ (let ((interface (make-module 31)))
+ (set-module-name! interface (module-name module))
+ (set-module-version! interface (module-version module))
+ (set-module-kind! interface 'interface)
+ (set-module-public-interface! module interface))))
+ (if (and (not (memq the-scm-module (module-uses module)))
+ (not (eq? module the-root-module)))
+ ;; Import the default set of bindings (from the SCM module) in MODULE.
+ (module-use! module the-scm-module)))
+
+(define (version-matches? version-ref target)
+ (define (sub-versions-match? v-refs t)
+ (define (sub-version-matches? v-ref t)
+ (let ((matches? (lambda (v) (sub-version-matches? v t))))
+ (cond
+ ((number? v-ref) (eqv? v-ref t))
+ ((list? v-ref)
+ (case (car v-ref)
+ ((>=) (>= t (cadr v-ref)))
+ ((<=) (<= t (cadr v-ref)))
+ ((and) (and-map matches? (cdr v-ref)))
+ ((or) (or-map matches? (cdr v-ref)))
+ ((not) (not (matches? (cadr v-ref))))
+ (else (error "Invalid sub-version reference" v-ref))))
+ (else (error "Invalid sub-version reference" v-ref)))))
+ (or (null? v-refs)
+ (and (not (null? t))
+ (sub-version-matches? (car v-refs) (car t))
+ (sub-versions-match? (cdr v-refs) (cdr t)))))
+
+ (let ((matches? (lambda (v) (version-matches? v target))))
+ (or (null? version-ref)
+ (case (car version-ref)
+ ((and) (and-map matches? (cdr version-ref)))
+ ((or) (or-map matches? (cdr version-ref)))
+ ((not) (not (matches? (cadr version-ref))))
+ (else (sub-versions-match? version-ref target))))))
+
+(define (make-fresh-user-module)
+ (let ((m (make-module)))
+ (beautify-user-module! m)
+ m))
+
+;; NOTE: This binding is used in libguile/modules.c.
+;;
+(define resolve-module
+ (let ((root (make-module)))
+ (set-module-name! root '())
+ ;; Define the-root-module as '(guile).
+ (module-define-submodule! root 'guile the-root-module)
+
+ (lambda* (name #\optional (autoload #t) (version #f) #\key (ensure #t))
+ (let ((already (nested-ref-module root name)))
+ (cond
+ ((and already
+ (or (not autoload) (module-public-interface already)))
+ ;; A hit, a palpable hit.
+ (if (and version
+ (not (version-matches? version (module-version already))))
+ (error "incompatible module version already loaded" name))
+ already)
+ (autoload
+ ;; Try to autoload the module, and recurse.
+ (try-load-module name version)
+ (resolve-module name #f #\ensure ensure))
+ (else
+ ;; No module found (or if one was, it had no public interface), and
+ ;; we're not autoloading. Make an empty module if #\ensure is true.
+ (or already
+ (and ensure
+ (make-modules-in root name)))))))))
+
+
+(define (try-load-module name version)
+ (try-module-autoload name version))
+
+(define (reload-module m)
+ "Revisit the source file corresponding to the module @var{m}."
+ (let ((f (module-filename m)))
+ (if f
+ (save-module-excursion
+ (lambda ()
+ ;; Re-set the initial environment, as in try-module-autoload.
+ (set-current-module (make-fresh-user-module))
+ (primitive-load-path f)
+ m))
+ ;; Though we could guess, we *should* know it.
+ (error "unknown file name for module" m))))
+
+(define (purify-module! module)
+ "Removes bindings in MODULE which are inherited from the (guile) module."
+ (let ((use-list (module-uses module)))
+ (if (and (pair? use-list)
+ (eq? (car (last-pair use-list)) the-scm-module))
+ (set-module-uses! module (reverse (cdr (reverse use-list)))))))
+
+;; Return a module that is an interface to the module designated by
+;; NAME.
+;;
+;; `resolve-interface' takes four keyword arguments:
+;;
+;; #\select SELECTION
+;;
+;; SELECTION is a list of binding-specs to be imported; A binding-spec
+;; is either a symbol or a pair of symbols (ORIG . SEEN), where ORIG
+;; is the name in the used module and SEEN is the name in the using
+;; module. Note that SEEN is also passed through RENAMER, below. The
+;; default is to select all bindings. If you specify no selection but
+;; a renamer, only the bindings that already exist in the used module
+;; are made available in the interface. Bindings that are added later
+;; are not picked up.
+;;
+;; #\hide BINDINGS
+;;
+;; BINDINGS is a list of bindings which should not be imported.
+;;
+;; #\prefix PREFIX
+;;
+;; PREFIX is a symbol that will be appended to each exported name.
+;; The default is to not perform any renaming.
+;;
+;; #\renamer RENAMER
+;;
+;; RENAMER is a procedure that takes a symbol and returns its new
+;; name. The default is not perform any renaming.
+;;
+;; Signal "no code for module" error if module name is not resolvable
+;; or its public interface is not available. Signal "no binding"
+;; error if selected binding does not exist in the used module.
+;;
+(define* (resolve-interface name #\key
+ (select #f)
+ (hide '())
+ (prefix #f)
+ (renamer (if prefix
+ (symbol-prefix-proc prefix)
+ identity))
+ version)
+ (let* ((module (resolve-module name #t version #\ensure #f))
+ (public-i (and module (module-public-interface module))))
+ (unless public-i
+ (error "no code for module" name))
+ (if (and (not select) (null? hide) (eq? renamer identity))
+ public-i
+ (let ((selection (or select (module-map (lambda (sym var) sym)
+ public-i)))
+ (custom-i (make-module 31)))
+ (set-module-kind! custom-i 'custom-interface)
+ (set-module-name! custom-i name)
+ ;; XXX - should use a lazy binder so that changes to the
+ ;; used module are picked up automatically.
+ (for-each (lambda (bspec)
+ (let* ((direct? (symbol? bspec))
+ (orig (if direct? bspec (car bspec)))
+ (seen (if direct? bspec (cdr bspec)))
+ (var (or (module-local-variable public-i orig)
+ (module-local-variable module orig)
+ (error
+ ;; fixme: format manually for now
+ (simple-format
+ #f "no binding `~A' in module ~A"
+ orig name)))))
+ (if (memq orig hide)
+ (set! hide (delq! orig hide))
+ (module-add! custom-i
+ (renamer seen)
+ var))))
+ selection)
+ ;; Check that we are not hiding bindings which don't exist
+ (for-each (lambda (binding)
+ (if (not (module-local-variable public-i binding))
+ (error
+ (simple-format
+ #f "no binding `~A' to hide in module ~A"
+ binding name))))
+ hide)
+ custom-i))))
+
+(define (symbol-prefix-proc prefix)
+ (lambda (symbol)
+ (symbol-append prefix symbol)))
+
+;; This function is called from "modules.c". If you change it, be
+;; sure to update "modules.c" as well.
+
+(define* (define-module* name
+ #\key filename pure version (duplicates '())
+ (imports '()) (exports '()) (replacements '())
+ (re-exports '()) (autoloads '()) transformer)
+ (define (list-of pred l)
+ (or (null? l)
+ (and (pair? l) (pred (car l)) (list-of pred (cdr l)))))
+ (define (valid-export? x)
+ (or (symbol? x) (and (pair? x) (symbol? (car x)) (symbol? (cdr x)))))
+ (define (valid-autoload? x)
+ (and (pair? x) (list-of symbol? (car x)) (list-of symbol? (cdr x))))
+
+ (define (resolve-imports imports)
+ (define (resolve-import import-spec)
+ (if (list? import-spec)
+ (apply resolve-interface import-spec)
+ (error "unexpected use-module specification" import-spec)))
+ (let lp ((imports imports) (out '()))
+ (cond
+ ((null? imports) (reverse! out))
+ ((pair? imports)
+ (lp (cdr imports)
+ (cons (resolve-import (car imports)) out)))
+ (else (error "unexpected tail of imports list" imports)))))
+
+ ;; We could add a #\no-check arg, set by the define-module macro, if
+ ;; these checks are taking too much time.
+ ;;
+ (let ((module (resolve-module name #f)))
+ (beautify-user-module! module)
+ (if filename
+ (set-module-filename! module filename))
+ (if pure
+ (purify-module! module))
+ (if version
+ (begin
+ (if (not (list-of integer? version))
+ (error "expected list of integers for version"))
+ (set-module-version! module version)
+ (set-module-version! (module-public-interface module) version)))
+ (let ((imports (resolve-imports imports)))
+ (call-with-deferred-observers
+ (lambda ()
+ (if (pair? imports)
+ (module-use-interfaces! module imports))
+ (if (list-of valid-export? exports)
+ (if (pair? exports)
+ (module-export! module exports))
+ (error "expected exports to be a list of symbols or symbol pairs"))
+ (if (list-of valid-export? replacements)
+ (if (pair? replacements)
+ (module-replace! module replacements))
+ (error "expected replacements to be a list of symbols or symbol pairs"))
+ (if (list-of valid-export? re-exports)
+ (if (pair? re-exports)
+ (module-re-export! module re-exports))
+ (error "expected re-exports to be a list of symbols or symbol pairs"))
+ ;; FIXME
+ (if (not (null? autoloads))
+ (apply module-autoload! module autoloads))
+ ;; Wait until modules have been loaded to resolve duplicates
+ ;; handlers.
+ (if (pair? duplicates)
+ (let ((handlers (lookup-duplicates-handlers duplicates)))
+ (set-module-duplicates-handlers! module handlers))))))
+
+ (if transformer
+ (if (and (pair? transformer) (list-of symbol? transformer))
+ (let ((iface (resolve-interface transformer))
+ (sym (car (last-pair transformer))))
+ (set-module-transformer! module (module-ref iface sym)))
+ (error "expected transformer to be a module name" transformer)))
+
+ (run-hook module-defined-hook module)
+ module))
+
+;; `module-defined-hook' is a hook that is run whenever a new module
+;; is defined. Its members are called with one argument, the new
+;; module.
+(define module-defined-hook (make-hook 1))
+
+
+
+;;; {Autoload}
+;;;
+
+(define (make-autoload-interface module name bindings)
+ (let ((b (lambda (a sym definep)
+ (false-if-exception
+ (and (memq sym bindings)
+ (let ((i (module-public-interface (resolve-module name))))
+ (if (not i)
+ (error "missing interface for module" name))
+ (let ((autoload (memq a (module-uses module))))
+ ;; Replace autoload-interface with actual interface if
+ ;; that has not happened yet.
+ (if (pair? autoload)
+ (set-car! autoload i)))
+ (module-local-variable i sym)))
+ #\warning "Failed to autoload ~a in ~a:\n" sym name))))
+ (module-constructor (make-hash-table 0) '() b #f #f name 'autoload #f
+ (make-hash-table 0) '() (make-weak-value-hash-table 31) #f
+ (make-hash-table 0) #f #f #f 0)))
+
+(define (module-autoload! module . args)
+ "Have @var{module} automatically load the module named @var{name} when one
+of the symbols listed in @var{bindings} is looked up. @var{args} should be a
+list of module-name/binding-list pairs, e.g., as in @code{(module-autoload!
+module '(ice-9 q) '(make-q q-length))}."
+ (let loop ((args args))
+ (cond ((null? args)
+ #t)
+ ((null? (cdr args))
+ (error "invalid name+binding autoload list" args))
+ (else
+ (let ((name (car args))
+ (bindings (cadr args)))
+ (module-use! module (make-autoload-interface module
+ name bindings))
+ (loop (cddr args)))))))
+
+
+
+
+;;; {Autoloading modules}
+;;;
+
+;;; XXX FIXME autoloads-in-progress and autoloads-done
+;;; are not handled in a thread-safe way.
+
+(define autoloads-in-progress '())
+
+;; This function is called from scm_load_scheme_module in
+;; "deprecated.c". Please do not change its interface.
+;;
+(define* (try-module-autoload module-name #\optional version)
+ "Try to load a module of the given name. If it is not found, return
+#f. Otherwise return #t. May raise an exception if a file is found,
+but it fails to load."
+ (let* ((reverse-name (reverse module-name))
+ (name (symbol->string (car reverse-name)))
+ (dir-hint-module-name (reverse (cdr reverse-name)))
+ (dir-hint (apply string-append
+ (map (lambda (elt)
+ (string-append (symbol->string elt)
+ file-name-separator-string))
+ dir-hint-module-name))))
+ (resolve-module dir-hint-module-name #f)
+ (and (not (autoload-done-or-in-progress? dir-hint name))
+ (let ((didit #f))
+ (dynamic-wind
+ (lambda () (autoload-in-progress! dir-hint name))
+ (lambda ()
+ (with-fluids ((current-reader #f))
+ (save-module-excursion
+ (lambda ()
+ (define (call/ec proc)
+ (let ((tag (make-prompt-tag)))
+ (call-with-prompt
+ tag
+ (lambda ()
+ (proc (lambda () (abort-to-prompt tag))))
+ (lambda (k) (values)))))
+ ;; The initial environment when loading a module is a fresh
+ ;; user module.
+ (set-current-module (make-fresh-user-module))
+ ;; Here we could allow some other search strategy (other than
+ ;; primitive-load-path), for example using versions encoded
+ ;; into the file system -- but then we would have to figure
+ ;; out how to locate the compiled file, do auto-compilation,
+ ;; etc. Punt for now, and don't use versions when locating
+ ;; the file.
+ (call/ec
+ (lambda (abort)
+ (primitive-load-path (in-vicinity dir-hint name)
+ abort)
+ (set! didit #t)))))))
+ (lambda () (set-autoloaded! dir-hint name didit)))
+ didit))))
+
+
+
+;;; {Dynamic linking of modules}
+;;;
+
+(define autoloads-done '((guile . guile)))
+
+(define (autoload-done-or-in-progress? p m)
+ (let ((n (cons p m)))
+ (->bool (or (member n autoloads-done)
+ (member n autoloads-in-progress)))))
+
+(define (autoload-done! p m)
+ (let ((n (cons p m)))
+ (set! autoloads-in-progress
+ (delete! n autoloads-in-progress))
+ (or (member n autoloads-done)
+ (set! autoloads-done (cons n autoloads-done)))))
+
+(define (autoload-in-progress! p m)
+ (let ((n (cons p m)))
+ (set! autoloads-done
+ (delete! n autoloads-done))
+ (set! autoloads-in-progress (cons n autoloads-in-progress))))
+
+(define (set-autoloaded! p m done?)
+ (if done?
+ (autoload-done! p m)
+ (let ((n (cons p m)))
+ (set! autoloads-done (delete! n autoloads-done))
+ (set! autoloads-in-progress (delete! n autoloads-in-progress)))))
+
+
+
+;;; {Run-time options}
+;;;
+
+(define-syntax define-option-interface
+ (syntax-rules ()
+ ((_ (interface (options enable disable) (option-set!)))
+ (begin
+ (define options
+ (case-lambda
+ (() (interface))
+ ((arg)
+ (if (list? arg)
+ (begin (interface arg) (interface))
+ (for-each
+ (lambda (option)
+ (apply (lambda (name value documentation)
+ (display name)
+ (let ((len (string-length (symbol->string name))))
+ (when (< len 16)
+ (display #\tab)
+ (when (< len 8)
+ (display #\tab))))
+ (display #\tab)
+ (display value)
+ (display #\tab)
+ (display documentation)
+ (newline))
+ option))
+ (interface #t))))))
+ (define (enable . flags)
+ (interface (append flags (interface)))
+ (interface))
+ (define (disable . flags)
+ (let ((options (interface)))
+ (for-each (lambda (flag) (set! options (delq! flag options)))
+ flags)
+ (interface options)
+ (interface)))
+ (define-syntax-rule (option-set! opt val)
+ (eval-when (expand load eval)
+ (options (append (options) (list 'opt val)))))))))
+
+(define-option-interface
+ (debug-options-interface
+ (debug-options debug-enable debug-disable)
+ (debug-set!)))
+
+(define-option-interface
+ (read-options-interface
+ (read-options read-enable read-disable)
+ (read-set!)))
+
+(define-option-interface
+ (print-options-interface
+ (print-options print-enable print-disable)
+ (print-set!)))
+
+
+
+;;; {The Unspecified Value}
+;;;
+;;; Currently Guile represents unspecified values via one particular value,
+;;; which may be obtained by evaluating (if #f #f). It would be nice in the
+;;; future if we could replace this with a return of 0 values, though.
+;;;
+
+(define-syntax *unspecified*
+ (identifier-syntax (if #f #f)))
+
+(define (unspecified? v) (eq? v *unspecified*))
+
+
+
+
+;;; {Parameters}
+;;;
+
+(define <parameter>
+ ;; Three fields: the procedure itself, the fluid, and the converter.
+ (make-struct <applicable-struct-vtable> 0 'pwprpr))
+(set-struct-vtable-name! <parameter> '<parameter>)
+
+(define* (make-parameter init #\optional (conv (lambda (x) x)))
+ "Make a new parameter.
+
+A parameter is a dynamically bound value, accessed through a procedure.
+To access the current value, apply the procedure with no arguments:
+
+ (define p (make-parameter 10))
+ (p) => 10
+
+To provide a new value for the parameter in a dynamic extent, use
+`parameterize':
+
+ (parameterize ((p 20))
+ (p)) => 20
+ (p) => 10
+
+The value outside of the dynamic extent of the body is unaffected. To
+update the current value, apply it to one argument:
+
+ (p 20) => 10
+ (p) => 20
+
+As you can see, the call that updates a parameter returns its previous
+value.
+
+All values for the parameter are first run through the CONV procedure,
+including INIT, the initial value. The default CONV procedure is the
+identity procedure. CONV is commonly used to ensure some set of
+invariants on the values that a parameter may have."
+ (let ((fluid (make-fluid (conv init))))
+ (make-struct <parameter> 0
+ (case-lambda
+ (() (fluid-ref fluid))
+ ((x) (let ((prev (fluid-ref fluid)))
+ (fluid-set! fluid (conv x))
+ prev)))
+ fluid conv)))
+
+(define* (fluid->parameter fluid #\optional (conv (lambda (x) x)))
+ "Make a parameter that wraps a fluid.
+
+The value of the parameter will be the same as the value of the fluid.
+If the parameter is rebound in some dynamic extent, perhaps via
+`parameterize', the new value will be run through the optional CONV
+procedure, as with any parameter. Note that unlike `make-parameter',
+CONV is not applied to the initial value."
+ (make-struct <parameter> 0
+ (case-lambda
+ (() (fluid-ref fluid))
+ ((x) (let ((prev (fluid-ref fluid)))
+ (fluid-set! fluid (conv x))
+ prev)))
+ fluid conv))
+
+(define (parameter? x)
+ (and (struct? x) (eq? (struct-vtable x) <parameter>)))
+
+(define (parameter-fluid p)
+ (if (parameter? p)
+ (struct-ref p 1)
+ (scm-error 'wrong-type-arg "parameter-fluid"
+ "Not a parameter: ~S" (list p) #f)))
+
+(define (parameter-converter p)
+ (if (parameter? p)
+ (struct-ref p 2)
+ (scm-error 'wrong-type-arg "parameter-fluid"
+ "Not a parameter: ~S" (list p) #f)))
+
+(define-syntax parameterize
+ (lambda (x)
+ (syntax-case x ()
+ ((_ ((param value) ...) body body* ...)
+ (with-syntax (((p ...) (generate-temporaries #'(param ...))))
+ #'(let ((p param) ...)
+ (if (not (parameter? p))
+ (scm-error 'wrong-type-arg "parameterize"
+ "Not a parameter: ~S" (list p) #f))
+ ...
+ (with-fluids (((struct-ref p 1) ((struct-ref p 2) value))
+ ...)
+ body body* ...)))))))
+
+
+;;;
+;;; Current ports as parameters.
+;;;
+
+(let ()
+ (define-syntax-rule (port-parameterize! binding fluid predicate msg)
+ (begin
+ (set! binding (fluid->parameter (module-ref (current-module) 'fluid)
+ (lambda (x)
+ (if (predicate x) x
+ (error msg x)))))
+ (module-remove! (current-module) 'fluid)))
+
+ (port-parameterize! current-input-port %current-input-port-fluid
+ input-port? "expected an input port")
+ (port-parameterize! current-output-port %current-output-port-fluid
+ output-port? "expected an output port")
+ (port-parameterize! current-error-port %current-error-port-fluid
+ output-port? "expected an output port")
+ (port-parameterize! current-warning-port %current-warning-port-fluid
+ output-port? "expected an output port"))
+
+
+
+;;;
+;;; Languages.
+;;;
+
+;; The language can be a symbolic name or a <language> object from
+;; (system base language).
+;;
+(define current-language (make-parameter 'scheme))
+
+
+
+
+;;; {Running Repls}
+;;;
+
+(define *repl-stack* (make-fluid '()))
+
+;; Programs can call `batch-mode?' to see if they are running as part of a
+;; script or if they are running interactively. REPL implementations ensure that
+;; `batch-mode?' returns #f during their extent.
+;;
+(define (batch-mode?)
+ (null? (fluid-ref *repl-stack*)))
+
+;; Programs can re-enter batch mode, for example after a fork, by calling
+;; `ensure-batch-mode!'. It's not a great interface, though; it would be better
+;; to abort to the outermost prompt, and call a thunk there.
+;;
+(define (ensure-batch-mode!)
+ (set! batch-mode? (lambda () #t)))
+
+(define (quit . args)
+ (apply throw 'quit args))
+
+(define exit quit)
+
+(define (gc-run-time)
+ (cdr (assq 'gc-time-taken (gc-stats))))
+
+(define abort-hook (make-hook))
+(define before-error-hook (make-hook))
+(define after-error-hook (make-hook))
+(define before-backtrace-hook (make-hook))
+(define after-backtrace-hook (make-hook))
+
+(define before-read-hook (make-hook))
+(define after-read-hook (make-hook))
+(define before-eval-hook (make-hook 1))
+(define after-eval-hook (make-hook 1))
+(define before-print-hook (make-hook 1))
+(define after-print-hook (make-hook 1))
+
+;;; This hook is run at the very end of an interactive session.
+;;;
+(define exit-hook (make-hook))
+
+;;; The default repl-reader function. We may override this if we've
+;;; the readline library.
+(define repl-reader
+ (lambda* (prompt #\optional (reader (fluid-ref current-reader)))
+ (if (not (char-ready?))
+ (begin
+ (display (if (string? prompt) prompt (prompt)))
+ ;; An interesting situation. The printer resets the column to
+ ;; 0 by printing a newline, but we then advance it by printing
+ ;; the prompt. However the port-column of the output port
+ ;; does not typically correspond with the actual column on the
+ ;; screen, because the input is echoed back! Since the
+ ;; input is line-buffered and thus ends with a newline, the
+ ;; output will really start on column zero. So, here we zero
+ ;; it out. See bug 9664.
+ ;;
+ ;; Note that for similar reasons, the output-line will not
+ ;; reflect the actual line on the screen. But given the
+ ;; possibility of multiline input, the fix is not as
+ ;; straightforward, so we don't bother.
+ ;;
+ ;; Also note that the readline implementation papers over
+ ;; these concerns, because it's readline itself printing the
+ ;; prompt, and not Guile.
+ (set-port-column! (current-output-port) 0)))
+ (force-output)
+ (run-hook before-read-hook)
+ ((or reader read) (current-input-port))))
+
+
+
+
+;;; {IOTA functions: generating lists of numbers}
+;;;
+
+(define (iota n)
+ (let loop ((count (1- n)) (result '()))
+ (if (< count 0) result
+ (loop (1- count) (cons count result)))))
+
+
+
+;;; {While}
+;;;
+;;; with `continue' and `break'.
+;;;
+
+;; The inliner will remove the prompts at compile-time if it finds that
+;; `continue' or `break' are not used.
+;;
+(define-syntax while
+ (lambda (x)
+ (syntax-case x ()
+ ((while cond body ...)
+ #`(let ((break-tag (make-prompt-tag "break"))
+ (continue-tag (make-prompt-tag "continue")))
+ (call-with-prompt
+ break-tag
+ (lambda ()
+ (define-syntax #,(datum->syntax #'while 'break)
+ (lambda (x)
+ (syntax-case x ()
+ ((_ arg (... ...))
+ #'(abort-to-prompt break-tag arg (... ...)))
+ (_
+ #'(lambda args
+ (apply abort-to-prompt break-tag args))))))
+ (let lp ()
+ (call-with-prompt
+ continue-tag
+ (lambda ()
+ (define-syntax #,(datum->syntax #'while 'continue)
+ (lambda (x)
+ (syntax-case x ()
+ ((_)
+ #'(abort-to-prompt continue-tag))
+ ((_ . args)
+ (syntax-violation 'continue "too many arguments" x))
+ (_
+ #'(lambda ()
+ (abort-to-prompt continue-tag))))))
+ (do () ((not cond) #f) body ...))
+ (lambda (k) (lp)))))
+ (lambda (k . args)
+ (if (null? args)
+ #t
+ (apply values args)))))))))
+
+
+
+
+;;; {Module System Macros}
+;;;
+
+;; Return a list of expressions that evaluate to the appropriate
+;; arguments for resolve-interface according to SPEC.
+
+(eval-when (expand)
+ (if (memq 'prefix (read-options))
+ (error "boot-9 must be compiled with #:kw, not :kw")))
+
+(define (keyword-like-symbol->keyword sym)
+ (symbol->keyword (string->symbol (substring (symbol->string sym) 1))))
+
+(define-syntax define-module
+ (lambda (x)
+ (define (keyword-like? stx)
+ (let ((dat (syntax->datum stx)))
+ (and (symbol? dat)
+ (eqv? (string-ref (symbol->string dat) 0) #\:))))
+ (define (->keyword sym)
+ (symbol->keyword (string->symbol (substring (symbol->string sym) 1))))
+
+ (define (parse-iface args)
+ (let loop ((in args) (out '()))
+ (syntax-case in ()
+ (() (reverse! out))
+ ;; The user wanted #\foo, but wrote :foo. Fix it.
+ ((sym . in) (keyword-like? #'sym)
+ (loop #`(#,(->keyword (syntax->datum #'sym)) . in) out))
+ ((kw . in) (not (keyword? (syntax->datum #'kw)))
+ (syntax-violation 'define-module "expected keyword arg" x #'kw))
+ ((#\renamer renamer . in)
+ (loop #'in (cons* #',renamer #\renamer out)))
+ ((kw val . in)
+ (loop #'in (cons* #'val #'kw out))))))
+
+ (define (parse args imp exp rex rep aut)
+ ;; Just quote everything except #\use-module and #\use-syntax. We
+ ;; need to know about all arguments regardless since we want to turn
+ ;; symbols that look like keywords into real keywords, and the
+ ;; keyword args in a define-module form are not regular
+ ;; (i.e. no-backtrace doesn't take a value).
+ (syntax-case args ()
+ (()
+ (let ((imp (if (null? imp) '() #`(#\imports `#,imp)))
+ (exp (if (null? exp) '() #`(#\exports '#,exp)))
+ (rex (if (null? rex) '() #`(#\re-exports '#,rex)))
+ (rep (if (null? rep) '() #`(#\replacements '#,rep)))
+ (aut (if (null? aut) '() #`(#\autoloads '#,aut))))
+ #`(#,@imp #,@exp #,@rex #,@rep #,@aut)))
+ ;; The user wanted #\foo, but wrote :foo. Fix it.
+ ((sym . args) (keyword-like? #'sym)
+ (parse #`(#,(->keyword (syntax->datum #'sym)) . args)
+ imp exp rex rep aut))
+ ((kw . args) (not (keyword? (syntax->datum #'kw)))
+ (syntax-violation 'define-module "expected keyword arg" x #'kw))
+ ((#\no-backtrace . args)
+ ;; Ignore this one.
+ (parse #'args imp exp rex rep aut))
+ ((#\pure . args)
+ #`(#\pure #t . #,(parse #'args imp exp rex rep aut)))
+ ((kw)
+ (syntax-violation 'define-module "keyword arg without value" x #'kw))
+ ((#\version (v ...) . args)
+ #`(#\version '(v ...) . #,(parse #'args imp exp rex rep aut)))
+ ((#\duplicates (d ...) . args)
+ #`(#\duplicates '(d ...) . #,(parse #'args imp exp rex rep aut)))
+ ((#\filename f . args)
+ #`(#\filename 'f . #,(parse #'args imp exp rex rep aut)))
+ ((#\use-module (name name* ...) . args)
+ (and (and-map symbol? (syntax->datum #'(name name* ...))))
+ (parse #'args #`(#,@imp ((name name* ...))) exp rex rep aut))
+ ((#\use-syntax (name name* ...) . args)
+ (and (and-map symbol? (syntax->datum #'(name name* ...))))
+ #`(#\transformer '(name name* ...)
+ . #,(parse #'args #`(#,@imp ((name name* ...))) exp rex rep aut)))
+ ((#\use-module ((name name* ...) arg ...) . args)
+ (and (and-map symbol? (syntax->datum #'(name name* ...))))
+ (parse #'args
+ #`(#,@imp ((name name* ...) #,@(parse-iface #'(arg ...))))
+ exp rex rep aut))
+ ((#\export (ex ...) . args)
+ (parse #'args imp #`(#,@exp ex ...) rex rep aut))
+ ((#\export-syntax (ex ...) . args)
+ (parse #'args imp #`(#,@exp ex ...) rex rep aut))
+ ((#\re-export (re ...) . args)
+ (parse #'args imp exp #`(#,@rex re ...) rep aut))
+ ((#\re-export-syntax (re ...) . args)
+ (parse #'args imp exp #`(#,@rex re ...) rep aut))
+ ((#\replace (r ...) . args)
+ (parse #'args imp exp rex #`(#,@rep r ...) aut))
+ ((#\replace-syntax (r ...) . args)
+ (parse #'args imp exp rex #`(#,@rep r ...) aut))
+ ((#\autoload name bindings . args)
+ (parse #'args imp exp rex rep #`(#,@aut name bindings)))
+ ((kw val . args)
+ (syntax-violation 'define-module "unknown keyword or bad argument"
+ #'kw #'val))))
+
+ (syntax-case x ()
+ ((_ (name name* ...) arg ...)
+ (and-map symbol? (syntax->datum #'(name name* ...)))
+ (with-syntax (((quoted-arg ...)
+ (parse #'(arg ...) '() '() '() '() '()))
+ ;; Ideally the filename is either a string or #f;
+ ;; this hack is to work around a case in which
+ ;; port-filename returns a symbol (`socket') for
+ ;; sockets.
+ (filename (let ((f (assq-ref (or (syntax-source x) '())
+ 'filename)))
+ (and (string? f) f))))
+ #'(eval-when (expand load eval)
+ (let ((m (define-module* '(name name* ...)
+ #\filename filename quoted-arg ...)))
+ (set-current-module m)
+ m)))))))
+
+;; The guts of the use-modules macro. Add the interfaces of the named
+;; modules to the use-list of the current module, in order.
+
+;; This function is called by "modules.c". If you change it, be sure
+;; to change scm_c_use_module as well.
+
+(define (process-use-modules module-interface-args)
+ (let ((interfaces (map (lambda (mif-args)
+ (or (apply resolve-interface mif-args)
+ (error "no such module" mif-args)))
+ module-interface-args)))
+ (call-with-deferred-observers
+ (lambda ()
+ (module-use-interfaces! (current-module) interfaces)))))
+
+(define-syntax use-modules
+ (lambda (x)
+ (define (keyword-like? stx)
+ (let ((dat (syntax->datum stx)))
+ (and (symbol? dat)
+ (eqv? (string-ref (symbol->string dat) 0) #\:))))
+ (define (->keyword sym)
+ (symbol->keyword (string->symbol (substring (symbol->string sym) 1))))
+
+ (define (quotify-iface args)
+ (let loop ((in args) (out '()))
+ (syntax-case in ()
+ (() (reverse! out))
+ ;; The user wanted #\foo, but wrote :foo. Fix it.
+ ((sym . in) (keyword-like? #'sym)
+ (loop #`(#,(->keyword (syntax->datum #'sym)) . in) out))
+ ((kw . in) (not (keyword? (syntax->datum #'kw)))
+ (syntax-violation 'define-module "expected keyword arg" x #'kw))
+ ((#\renamer renamer . in)
+ (loop #'in (cons* #'renamer #\renamer out)))
+ ((kw val . in)
+ (loop #'in (cons* #''val #'kw out))))))
+
+ (define (quotify specs)
+ (let lp ((in specs) (out '()))
+ (syntax-case in ()
+ (() (reverse out))
+ (((name name* ...) . in)
+ (and-map symbol? (syntax->datum #'(name name* ...)))
+ (lp #'in (cons #''((name name* ...)) out)))
+ ((((name name* ...) arg ...) . in)
+ (and-map symbol? (syntax->datum #'(name name* ...)))
+ (with-syntax (((quoted-arg ...) (quotify-iface #'(arg ...))))
+ (lp #'in (cons #`(list '(name name* ...) quoted-arg ...)
+ out)))))))
+
+ (syntax-case x ()
+ ((_ spec ...)
+ (with-syntax (((quoted-args ...) (quotify #'(spec ...))))
+ #'(eval-when (expand load eval)
+ (process-use-modules (list quoted-args ...))
+ *unspecified*))))))
+
+(define-syntax-rule (use-syntax spec ...)
+ (begin
+ (eval-when (expand load eval)
+ (issue-deprecation-warning
+ "`use-syntax' is deprecated. Please contact guile-devel for more info."))
+ (use-modules spec ...)))
+
+(include-from-path "ice-9/r6rs-libraries")
+
+(define-syntax-rule (define-private foo bar)
+ (define foo bar))
+
+(define-syntax define-public
+ (syntax-rules ()
+ ((_ (name . args) . body)
+ (begin
+ (define (name . args) . body)
+ (export name)))
+ ((_ name val)
+ (begin
+ (define name val)
+ (export name)))))
+
+(define-syntax-rule (defmacro-public name args body ...)
+ (begin
+ (defmacro name args body ...)
+ (export-syntax name)))
+
+;; And now for the most important macro.
+(define-syntax-rule (lumbum formals body ...)
+ (lambda formals body ...))
+
+
+;; Export a local variable
+
+;; This function is called from "modules.c". If you change it, be
+;; sure to update "modules.c" as well.
+
+(define (module-export! m names)
+ (let ((public-i (module-public-interface m)))
+ (for-each (lambda (name)
+ (let* ((internal-name (if (pair? name) (car name) name))
+ (external-name (if (pair? name) (cdr name) name))
+ (var (module-ensure-local-variable! m internal-name)))
+ (module-add! public-i external-name var)))
+ names)))
+
+(define (module-replace! m names)
+ (let ((public-i (module-public-interface m)))
+ (for-each (lambda (name)
+ (let* ((internal-name (if (pair? name) (car name) name))
+ (external-name (if (pair? name) (cdr name) name))
+ (var (module-ensure-local-variable! m internal-name)))
+ ;; FIXME: use a bit on variables instead of object
+ ;; properties.
+ (set-object-property! var 'replace #t)
+ (module-add! public-i external-name var)))
+ names)))
+
+;; Export all local variables from a module
+;;
+(define (module-export-all! mod)
+ (define (fresh-interface!)
+ (let ((iface (make-module)))
+ (set-module-name! iface (module-name mod))
+ (set-module-version! iface (module-version mod))
+ (set-module-kind! iface 'interface)
+ (set-module-public-interface! mod iface)
+ iface))
+ (let ((iface (or (module-public-interface mod)
+ (fresh-interface!))))
+ (set-module-obarray! iface (module-obarray mod))))
+
+;; Re-export a imported variable
+;;
+(define (module-re-export! m names)
+ (let ((public-i (module-public-interface m)))
+ (for-each (lambda (name)
+ (let* ((internal-name (if (pair? name) (car name) name))
+ (external-name (if (pair? name) (cdr name) name))
+ (var (module-variable m internal-name)))
+ (cond ((not var)
+ (error "Undefined variable:" internal-name))
+ ((eq? var (module-local-variable m internal-name))
+ (error "re-exporting local variable:" internal-name))
+ (else
+ (module-add! public-i external-name var)))))
+ names)))
+
+(define-syntax-rule (export name ...)
+ (eval-when (expand load eval)
+ (call-with-deferred-observers
+ (lambda ()
+ (module-export! (current-module) '(name ...))))))
+
+(define-syntax-rule (re-export name ...)
+ (eval-when (expand load eval)
+ (call-with-deferred-observers
+ (lambda ()
+ (module-re-export! (current-module) '(name ...))))))
+
+(define-syntax-rule (export! name ...)
+ (eval-when (expand load eval)
+ (call-with-deferred-observers
+ (lambda ()
+ (module-replace! (current-module) '(name ...))))))
+
+(define-syntax-rule (export-syntax name ...)
+ (export name ...))
+
+(define-syntax-rule (re-export-syntax name ...)
+ (re-export name ...))
+
+
+
+;;; {Parameters}
+;;;
+
+(define* (make-mutable-parameter init #\optional (converter identity))
+ (let ((fluid (make-fluid (converter init))))
+ (case-lambda
+ (() (fluid-ref fluid))
+ ((val) (fluid-set! fluid (converter val))))))
+
+
+
+
+;;; {Handling of duplicate imported bindings}
+;;;
+
+;; Duplicate handlers take the following arguments:
+;;
+;; module importing module
+;; name conflicting name
+;; int1 old interface where name occurs
+;; val1 value of binding in old interface
+;; int2 new interface where name occurs
+;; val2 value of binding in new interface
+;; var previous resolution or #f
+;; val value of previous resolution
+;;
+;; A duplicate handler can take three alternative actions:
+;;
+;; 1. return #f => leave responsibility to next handler
+;; 2. exit with an error
+;; 3. return a variable resolving the conflict
+;;
+
+(define duplicate-handlers
+ (let ((m (make-module 7)))
+
+ (define (check module name int1 val1 int2 val2 var val)
+ (scm-error 'misc-error
+ #f
+ "~A: `~A' imported from both ~A and ~A"
+ (list (module-name module)
+ name
+ (module-name int1)
+ (module-name int2))
+ #f))
+
+ (define (warn module name int1 val1 int2 val2 var val)
+ (format (current-warning-port)
+ "WARNING: ~A: `~A' imported from both ~A and ~A\n"
+ (module-name module)
+ name
+ (module-name int1)
+ (module-name int2))
+ #f)
+
+ (define (replace module name int1 val1 int2 val2 var val)
+ (let ((old (or (and var (object-property var 'replace) var)
+ (module-variable int1 name)))
+ (new (module-variable int2 name)))
+ (if (object-property old 'replace)
+ (and (or (eq? old new)
+ (not (object-property new 'replace)))
+ old)
+ (and (object-property new 'replace)
+ new))))
+
+ (define (warn-override-core module name int1 val1 int2 val2 var val)
+ (and (eq? int1 the-scm-module)
+ (begin
+ (format (current-warning-port)
+ "WARNING: ~A: imported module ~A overrides core binding `~A'\n"
+ (module-name module)
+ (module-name int2)
+ name)
+ (module-local-variable int2 name))))
+
+ (define (first module name int1 val1 int2 val2 var val)
+ (or var (module-local-variable int1 name)))
+
+ (define (last module name int1 val1 int2 val2 var val)
+ (module-local-variable int2 name))
+
+ (define (noop module name int1 val1 int2 val2 var val)
+ #f)
+
+ (set-module-name! m 'duplicate-handlers)
+ (set-module-kind! m 'interface)
+ (module-define! m 'check check)
+ (module-define! m 'warn warn)
+ (module-define! m 'replace replace)
+ (module-define! m 'warn-override-core warn-override-core)
+ (module-define! m 'first first)
+ (module-define! m 'last last)
+ (module-define! m 'merge-generics noop)
+ (module-define! m 'merge-accessors noop)
+ m))
+
+(define (lookup-duplicates-handlers handler-names)
+ (and handler-names
+ (map (lambda (handler-name)
+ (or (module-symbol-local-binding
+ duplicate-handlers handler-name #f)
+ (error "invalid duplicate handler name:"
+ handler-name)))
+ (if (list? handler-names)
+ handler-names
+ (list handler-names)))))
+
+(define default-duplicate-binding-procedures
+ (make-mutable-parameter #f))
+
+(define default-duplicate-binding-handler
+ (make-mutable-parameter '(replace warn-override-core warn last)
+ (lambda (handler-names)
+ (default-duplicate-binding-procedures
+ (lookup-duplicates-handlers handler-names))
+ handler-names)))
+
+
+
+;;; {`load'.}
+;;;
+;;; Load is tricky when combined with relative file names, compilation,
+;;; and the file system. If a file name is relative, what is it
+;;; relative to? The name of the source file at the time it was
+;;; compiled? The name of the compiled file? What if both or either
+;;; were installed? And how do you get that information? Tricky, I
+;;; say.
+;;;
+;;; To get around all of this, we're going to do something nasty, and
+;;; turn `load' into a macro. That way it can know the name of the
+;;; source file with respect to which it was invoked, so it can resolve
+;;; relative file names with respect to the original source file.
+;;;
+;;; There is an exception, and that is that if the source file was in
+;;; the load path when it was compiled, instead of looking up against
+;;; the absolute source location, we load-from-path against the relative
+;;; source location.
+;;;
+
+(define %auto-compilation-options
+ ;; Default `compile-file' option when auto-compiling.
+ '(#\warnings (unbound-variable arity-mismatch format
+ duplicate-case-datum bad-case-datum)))
+
+(define* (load-in-vicinity dir file-name #\optional reader)
+ "Load source file FILE-NAME in vicinity of directory DIR. Use a
+pre-compiled version of FILE-NAME when available, and auto-compile one
+when none is available, reading FILE-NAME with READER."
+
+ ;; The auto-compilation code will residualize a .go file in the cache
+ ;; dir: by default, $HOME/.cache/guile/2.0/ccache/PATH.go. This
+ ;; function determines the PATH to use as a key into the compilation
+ ;; cache.
+ (define (canonical->suffix canon)
+ (cond
+ ((and (not (string-null? canon))
+ (file-name-separator? (string-ref canon 0)))
+ canon)
+ ((and (eq? (system-file-name-convention) 'windows)
+ (absolute-file-name? canon))
+ ;; An absolute file name that doesn't start with a separator
+ ;; starts with a drive component. Transform the drive component
+ ;; to a file name element: c:\foo -> \c\foo.
+ (string-append file-name-separator-string
+ (substring canon 0 1)
+ (substring canon 2)))
+ (else canon)))
+
+ (define compiled-extension
+ ;; File name extension of compiled files.
+ (cond ((or (null? %load-compiled-extensions)
+ (string-null? (car %load-compiled-extensions)))
+ (warn "invalid %load-compiled-extensions"
+ %load-compiled-extensions)
+ ".go")
+ (else (car %load-compiled-extensions))))
+
+ (define (more-recent? stat1 stat2)
+ ;; Return #t when STAT1 has an mtime greater than that of STAT2.
+ (or (> (stat:mtime stat1) (stat:mtime stat2))
+ (and (= (stat:mtime stat1) (stat:mtime stat2))
+ (>= (stat:mtimensec stat1)
+ (stat:mtimensec stat2)))))
+
+ (define (fallback-file-name canon-file-name)
+ ;; Return the in-cache compiled file name for source file
+ ;; CANON-FILE-NAME.
+
+ ;; FIXME: would probably be better just to append
+ ;; SHA1(canon-file-name) to the %compile-fallback-path, to avoid
+ ;; deep directory stats.
+ (and %compile-fallback-path
+ (string-append %compile-fallback-path
+ (canonical->suffix canon-file-name)
+ compiled-extension)))
+
+ (define (compile file)
+ ;; Compile source FILE, lazily loading the compiler.
+ ((module-ref (resolve-interface '(system base compile))
+ 'compile-file)
+ file
+ #\opts %auto-compilation-options
+ #\env (current-module)))
+
+ (define (load-thunk-from-file file)
+ (let ((objcode (resolve-interface '(system vm objcode)))
+ (program (resolve-interface '(system vm program))))
+ ((module-ref program 'make-program)
+ ((module-ref objcode 'load-objcode) file))))
+
+ ;; Returns a thunk loaded from the .go file corresponding to `name'.
+ ;; Does not search load paths, only the fallback path. If the .go
+ ;; file is missing or out of date, and auto-compilation is enabled,
+ ;; will try auto-compilation, just as primitive-load-path does
+ ;; internally. primitive-load is unaffected. Returns #f if
+ ;; auto-compilation failed or was disabled.
+ ;;
+ ;; NB: Unless we need to compile the file, this function should not
+ ;; cause (system base compile) to be loaded up. For that reason
+ ;; compiled-file-name partially duplicates functionality from (system
+ ;; base compile).
+
+ (define (fresh-compiled-thunk name scmstat go-file-name)
+ ;; Return GO-FILE-NAME after making sure that it contains a freshly
+ ;; compiled version of source file NAME with stat SCMSTAT; return #f
+ ;; on failure.
+ (false-if-exception
+ (let ((gostat (and (not %fresh-auto-compile)
+ (stat go-file-name #f))))
+ (if (and gostat (more-recent? gostat scmstat))
+ (load-thunk-from-file go-file-name)
+ (begin
+ (when gostat
+ (format (current-warning-port)
+ ";;; note: source file ~a\n;;; newer than compiled ~a\n"
+ name go-file-name))
+ (cond
+ (%load-should-auto-compile
+ (%warn-auto-compilation-enabled)
+ (format (current-warning-port) ";;; compiling ~a\n" name)
+ (let ((cfn (compile name)))
+ (format (current-warning-port) ";;; compiled ~a\n" cfn)
+ (load-thunk-from-file cfn)))
+ (else #f)))))
+ #\warning "WARNING: compilation of ~a failed:\n" name))
+
+ (define (sans-extension file)
+ (let ((dot (string-rindex file #\.)))
+ (if dot
+ (substring file 0 dot)
+ file)))
+
+ (define (load-absolute abs-file-name)
+ ;; Load from ABS-FILE-NAME, using a compiled file or auto-compiling
+ ;; if needed.
+ (define scmstat
+ (false-if-exception
+ (stat abs-file-name)
+ #\warning "Stat of ~a failed:\n" abs-file-name))
+
+ (define (pre-compiled)
+ (or-map
+ (lambda (dir)
+ (or-map
+ (lambda (ext)
+ (let ((candidate (string-append (in-vicinity dir file-name) ext)))
+ (let ((gostat (stat candidate #f)))
+ (and gostat
+ (more-recent? gostat scmstat)
+ (false-if-exception
+ (load-thunk-from-file candidate)
+ #\warning "WARNING: failed to load compiled file ~a:\n"
+ candidate)))))
+ %load-compiled-extensions))
+ %load-compiled-path))
+
+ (define (fallback)
+ (and=> (false-if-exception (canonicalize-path abs-file-name))
+ (lambda (canon)
+ (and=> (fallback-file-name canon)
+ (lambda (go-file-name)
+ (fresh-compiled-thunk abs-file-name
+ scmstat
+ go-file-name))))))
+
+ (let ((compiled (and scmstat (or (pre-compiled) (fallback)))))
+ (if compiled
+ (begin
+ (if %load-hook
+ (%load-hook abs-file-name))
+ (compiled))
+ (start-stack 'load-stack
+ (primitive-load abs-file-name)))))
+
+ (save-module-excursion
+ (lambda ()
+ (with-fluids ((current-reader reader)
+ (%file-port-name-canonicalization 'relative))
+ (cond
+ ((absolute-file-name? file-name)
+ (load-absolute file-name))
+ ((absolute-file-name? dir)
+ (load-absolute (in-vicinity dir file-name)))
+ (else
+ (load-from-path (in-vicinity dir file-name))))))))
+
+(define-syntax load
+ (make-variable-transformer
+ (lambda (x)
+ (let* ((src (syntax-source x))
+ (file (and src (assq-ref src 'filename)))
+ (dir (and (string? file) (dirname file))))
+ (syntax-case x ()
+ ((_ arg ...)
+ #`(load-in-vicinity #,(or dir #'(getcwd)) arg ...))
+ (id
+ (identifier? #'id)
+ #`(lambda args
+ (apply load-in-vicinity #,(or dir #'(getcwd)) args))))))))
+
+
+
+;;; {`cond-expand' for SRFI-0 support.}
+;;;
+;;; This syntactic form expands into different commands or
+;;; definitions, depending on the features provided by the Scheme
+;;; implementation.
+;;;
+;;; Syntax:
+;;;
+;;; <cond-expand>
+;;; --> (cond-expand <cond-expand-clause>+)
+;;; | (cond-expand <cond-expand-clause>* (else <command-or-definition>))
+;;; <cond-expand-clause>
+;;; --> (<feature-requirement> <command-or-definition>*)
+;;; <feature-requirement>
+;;; --> <feature-identifier>
+;;; | (and <feature-requirement>*)
+;;; | (or <feature-requirement>*)
+;;; | (not <feature-requirement>)
+;;; <feature-identifier>
+;;; --> <a symbol which is the name or alias of a SRFI>
+;;;
+;;; Additionally, this implementation provides the
+;;; <feature-identifier>s `guile' and `r5rs', so that programs can
+;;; determine the implementation type and the supported standard.
+;;;
+;;; Remember to update the features list when adding more SRFIs.
+;;;
+
+(define %cond-expand-features
+ ;; This should contain only features that are present in core Guile,
+ ;; before loading any modules. Modular features are handled by
+ ;; placing 'cond-expand-provide' in the relevant module.
+ '(guile
+ guile-2
+ r5rs
+ srfi-0 ;; cond-expand itself
+ srfi-4 ;; homogeneous numeric vectors
+ ;; We omit srfi-6 because the 'open-input-string' etc in Guile
+ ;; core are not conformant with SRFI-6; they expose details
+ ;; of the binary I/O model and may fail to support some characters.
+ srfi-13 ;; string library
+ srfi-14 ;; character sets
+ srfi-16 ;; case-lambda
+ srfi-23 ;; `error` procedure
+ srfi-30 ;; nested multi-line comments
+ srfi-39 ;; parameterize
+ srfi-46 ;; basic syntax-rules extensions
+ srfi-55 ;; require-extension
+ srfi-61 ;; general cond clause
+ srfi-62 ;; s-expression comments
+ srfi-87 ;; => in case clauses
+ srfi-105 ;; curly infix expressions
+ ))
+
+;; This table maps module public interfaces to the list of features.
+;;
+(define %cond-expand-table (make-hash-table 31))
+
+;; Add one or more features to the `cond-expand' feature list of the
+;; module `module'.
+;;
+(define (cond-expand-provide module features)
+ (let ((mod (module-public-interface module)))
+ (and mod
+ (hashq-set! %cond-expand-table mod
+ (append (hashq-ref %cond-expand-table mod '())
+ features)))))
+
+(define-syntax cond-expand
+ (lambda (x)
+ (define (module-has-feature? mod sym)
+ (or-map (lambda (mod)
+ (memq sym (hashq-ref %cond-expand-table mod '())))
+ (module-uses mod)))
+
+ (define (condition-matches? condition)
+ (syntax-case condition (and or not)
+ ((and c ...)
+ (and-map condition-matches? #'(c ...)))
+ ((or c ...)
+ (or-map condition-matches? #'(c ...)))
+ ((not c)
+ (if (condition-matches? #'c) #f #t))
+ (c
+ (identifier? #'c)
+ (let ((sym (syntax->datum #'c)))
+ (if (memq sym %cond-expand-features)
+ #t
+ (module-has-feature? (current-module) sym))))))
+
+ (define (match clauses alternate)
+ (syntax-case clauses ()
+ (((condition form ...) . rest)
+ (if (condition-matches? #'condition)
+ #'(begin form ...)
+ (match #'rest alternate)))
+ (() (alternate))))
+
+ (syntax-case x (else)
+ ((_ clause ... (else form ...))
+ (match #'(clause ...)
+ (lambda ()
+ #'(begin form ...))))
+ ((_ clause ...)
+ (match #'(clause ...)
+ (lambda ()
+ (syntax-violation 'cond-expand "unfulfilled cond-expand" x)))))))
+
+;; This procedure gets called from the startup code with a list of
+;; numbers, which are the numbers of the SRFIs to be loaded on startup.
+;;
+(define (use-srfis srfis)
+ (process-use-modules
+ (map (lambda (num)
+ (list (list 'srfi (string->symbol
+ (string-append "srfi-" (number->string num))))))
+ srfis)))
+
+
+
+;;; srfi-55: require-extension
+;;;
+
+(define-syntax require-extension
+ (lambda (x)
+ (syntax-case x (srfi)
+ ((_ (srfi n ...))
+ (and-map integer? (syntax->datum #'(n ...)))
+ (with-syntax
+ (((srfi-n ...)
+ (map (lambda (n)
+ (datum->syntax x (symbol-append 'srfi- n)))
+ (map string->symbol
+ (map number->string (syntax->datum #'(n ...)))))))
+ #'(use-modules (srfi srfi-n) ...)))
+ ((_ (type arg ...))
+ (identifier? #'type)
+ (syntax-violation 'require-extension "Not a recognized extension type"
+ x)))))
+
+
+;;; Defining transparently inlinable procedures
+;;;
+
+(define-syntax define-inlinable
+ ;; Define a macro and a procedure such that direct calls are inlined, via
+ ;; the macro expansion, whereas references in non-call contexts refer to
+ ;; the procedure. Inspired by the `define-integrable' macro by Dybvig et al.
+ (lambda (x)
+ ;; Use a space in the prefix to avoid potential -Wunused-toplevel
+ ;; warning
+ (define prefix (string->symbol "% "))
+ (define (make-procedure-name name)
+ (datum->syntax name
+ (symbol-append prefix (syntax->datum name)
+ '-procedure)))
+
+ (syntax-case x ()
+ ((_ (name formals ...) body ...)
+ (identifier? #'name)
+ (with-syntax ((proc-name (make-procedure-name #'name))
+ ((args ...) (generate-temporaries #'(formals ...))))
+ #`(begin
+ (define (proc-name formals ...)
+ (syntax-parameterize ((name (identifier-syntax proc-name)))
+ body ...))
+ (define-syntax-parameter name
+ (lambda (x)
+ (syntax-case x ()
+ ((_ args ...)
+ #'((syntax-parameterize ((name (identifier-syntax proc-name)))
+ (lambda (formals ...)
+ body ...))
+ args ...))
+ ((_ a (... ...))
+ (syntax-violation 'name "Wrong number of arguments" x))
+ (_
+ (identifier? x)
+ #'proc-name))))))))))
+
+
+
+(define using-readline?
+ (let ((using-readline? (make-fluid)))
+ (make-procedure-with-setter
+ (lambda () (fluid-ref using-readline?))
+ (lambda (v) (fluid-set! using-readline? v)))))
+
+
+
+;;; {Deprecated stuff}
+;;;
+
+(begin-deprecated
+ (module-use! the-scm-module (resolve-interface '(ice-9 deprecated))))
+
+
+
+;;; SRFI-4 in the default environment. FIXME: we should figure out how
+;;; to deprecate this.
+;;;
+
+;; FIXME:
+(module-use! the-scm-module (resolve-interface '(srfi srfi-4)))
+
+
+
+;;; A few identifiers that need to be defined in this file are really
+;;; internal implementation details. We shove them off into internal
+;;; modules, removing them from the (guile) module.
+;;;
+
+(define-module (system syntax))
+
+(let ()
+ (define (steal-bindings! from to ids)
+ (for-each
+ (lambda (sym)
+ (let ((v (module-local-variable from sym)))
+ (module-remove! from sym)
+ (module-add! to sym v)))
+ ids)
+ (module-export! to ids))
+
+ (steal-bindings! the-root-module (resolve-module '(system syntax))
+ '(syntax-local-binding
+ syntax-module
+ syntax-locally-bound-identifiers
+ syntax-session-id)))
+
+
+
+
+;;; Place the user in the guile-user module.
+;;;
+
+;; Set filename to #f to prevent reload.
+(define-module (guile-user)
+ #\autoload (system base compile) (compile compile-file)
+ #\filename #f)
+
+;; Remain in the `(guile)' module at compilation-time so that the
+;; `-Wunused-toplevel' warning works as expected.
+(eval-when (compile) (set-current-module the-root-module))
+
+;;; boot-9.scm ends here
+;;;; buffered-input.scm --- construct a port from a buffered input reader
+;;;;
+;;;; Copyright (C) 2001, 2006, 2010 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (ice-9 buffered-input)
+ #\export (make-buffered-input-port
+ make-line-buffered-input-port
+ set-buffered-input-continuation?!))
+
+;; @code{buffered-input-continuation?} is a property of the ports
+;; created by @code{make-line-buffered-input-port} that stores the
+;; read continuation flag for each such port.
+(define buffered-input-continuation? (make-object-property))
+
+(define (set-buffered-input-continuation?! port val)
+ "Set the read continuation flag for @var{port} to @var{val}.
+
+See @code{make-buffered-input-port} for the meaning and use of this
+flag."
+ (set! (buffered-input-continuation? port) val))
+
+(define (make-buffered-input-port reader)
+ "Construct a line-buffered input port from the specified @var{reader}.
+@var{reader} should be a procedure of one argument that somehow reads
+a chunk of input and returns it as a string.
+
+The port created by @code{make-buffered-input-port} does @emph{not}
+interpolate any additional characters between the strings returned by
+@var{reader}.
+
+@var{reader} should take a boolean @var{continuation?} argument.
+@var{continuation?} indicates whether @var{reader} is being called to
+start a logically new read operation (in which case
+@var{continuation?} is @code{#f}) or to continue a read operation for
+which some input has already been read (in which case
+@var{continuation?} is @code{#t}). Some @var{reader} implementations
+use the @var{continuation?} argument to determine what prompt to
+display to the user.
+
+The new/continuation distinction is largely an application-level
+concept: @code{set-buffered-input-continuation?!} allows an
+application to specify when a read operation is considered to be new.
+But note that if there is non-whitespace data already buffered in the
+port when a new read operation starts, this data will be read before
+the first call to @var{reader}, and so @var{reader} will be called
+with @var{continuation?} set to @code{#t}."
+ (let ((read-string "")
+ (string-index 0))
+ (letrec ((get-character
+ (lambda ()
+ (if (< string-index (string-length read-string))
+ ;; Read a char.
+ (let ((res (string-ref read-string string-index)))
+ (set! string-index (+ 1 string-index))
+ (if (not (char-whitespace? res))
+ (set! (buffered-input-continuation? port) #t))
+ res)
+ ;; Fill the buffer.
+ (let ((x (reader (buffered-input-continuation? port))))
+ (cond
+ ((eof-object? x)
+ ;; Don't buffer the EOF object.
+ x)
+ (else
+ (set! read-string x)
+ (set! string-index 0)
+ (get-character)))))))
+ (input-waiting
+ (lambda ()
+ (- (string-length read-string) string-index)))
+ (port #f))
+ (set! port (make-soft-port (vector #f #f #f get-character #f input-waiting) "r"))
+ (set! (buffered-input-continuation? port) #f)
+ port)))
+
+(define (make-line-buffered-input-port reader)
+ "Construct a line-buffered input port from the specified @var{reader}.
+@var{reader} should be a procedure of one argument that somehow reads
+a line of input and returns it as a string @emph{without} the
+terminating newline character.
+
+The port created by @code{make-line-buffered-input-port} automatically
+interpolates a newline character after each string returned by
+@var{reader}.
+
+@var{reader} should take a boolean @var{continuation?} argument. For
+the meaning and use of this argument, see
+@code{make-buffered-input-port}."
+ (make-buffered-input-port (lambda (continuation?)
+ (let ((str (reader continuation?)))
+ (if (eof-object? str)
+ str
+ (string-append str "\n"))))))
+
+;;; buffered-input.scm ends here
+;;;; calling.scm --- Calling Conventions
+;;;;
+;;;; Copyright (C) 1995, 1996, 1997, 2000, 2001, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+(define-module (ice-9 calling)
+ \:export-syntax (with-excursion-function
+ with-getter-and-setter
+ with-getter
+ with-delegating-getter-and-setter
+ with-excursion-getter-and-setter
+ with-configuration-getter-and-setter
+ with-delegating-configuration-getter-and-setter
+ let-with-configuration-getter-and-setter))
+
+;;;;
+;;;
+;;; This file contains a number of macros that support
+;;; common calling conventions.
+
+;;;
+;;; with-excursion-function <vars> proc
+;;; <vars> is an unevaluated list of names that are bound in the caller.
+;;; proc is a procedure, called:
+;;; (proc excursion)
+;;;
+;;; excursion is a procedure isolates all changes to <vars>
+;;; in the dynamic scope of the call to proc. In other words,
+;;; the values of <vars> are saved when proc is entered, and when
+;;; proc returns, those values are restored. Values are also restored
+;;; entering and leaving the call to proc non-locally, such as using
+;;; call-with-current-continuation, error, or throw.
+;;;
+(defmacro with-excursion-function (vars proc)
+ `(,proc ,(excursion-function-syntax vars)))
+
+
+
+;;; with-getter-and-setter <vars> proc
+;;; <vars> is an unevaluated list of names that are bound in the caller.
+;;; proc is a procedure, called:
+;;; (proc getter setter)
+;;;
+;;; getter and setter are procedures used to access
+;;; or modify <vars>.
+;;;
+;;; setter, called with keywords arguments, modifies the named
+;;; values. If "foo" and "bar" are among <vars>, then:
+;;;
+;;; (setter :foo 1 :bar 2)
+;;; == (set! foo 1 bar 2)
+;;;
+;;; getter, called with just keywords, returns
+;;; a list of the corresponding values. For example,
+;;; if "foo" and "bar" are among the <vars>, then
+;;;
+;;; (getter :foo :bar)
+;;; => (<value-of-foo> <value-of-bar>)
+;;;
+;;; getter, called with no arguments, returns a list of all accepted
+;;; keywords and the corresponding values. If "foo" and "bar" are
+;;; the *only* <vars>, then:
+;;;
+;;; (getter)
+;;; => (\:foo <value-of-bar> :bar <value-of-foo>)
+;;;
+;;; The unusual calling sequence of a getter supports too handy
+;;; idioms:
+;;;
+;;; (apply setter (getter)) ;; save and restore
+;;;
+;;; (apply-to-args (getter :foo :bar) ;; fetch and bind
+;;; (lambda (foo bar) ....))
+;;;
+;;; ;; [ "apply-to-args" is just like two-argument "apply" except that it
+;;; ;; takes its arguments in a different order.
+;;;
+;;;
+(defmacro with-getter-and-setter (vars proc)
+ `(,proc ,@ (getter-and-setter-syntax vars)))
+
+;;; with-getter vars proc
+;;; A short-hand for a call to with-getter-and-setter.
+;;; The procedure is called:
+;;; (proc getter)
+;;;
+(defmacro with-getter (vars proc)
+ `(,proc ,(car (getter-and-setter-syntax vars))))
+
+
+;;; with-delegating-getter-and-setter <vars> get-delegate set-delegate proc
+;;; Compose getters and setters.
+;;;
+;;; <vars> is an unevaluated list of names that are bound in the caller.
+;;;
+;;; get-delegate is called by the new getter to extend the set of
+;;; gettable variables beyond just <vars>
+;;; set-delegate is called by the new setter to extend the set of
+;;; gettable variables beyond just <vars>
+;;;
+;;; proc is a procedure that is called
+;;; (proc getter setter)
+;;;
+(defmacro with-delegating-getter-and-setter (vars get-delegate set-delegate proc)
+ `(,proc ,@ (delegating-getter-and-setter-syntax vars get-delegate set-delegate)))
+
+
+;;; with-excursion-getter-and-setter <vars> proc
+;;; <vars> is an unevaluated list of names that are bound in the caller.
+;;; proc is called:
+;;;
+;;; (proc excursion getter setter)
+;;;
+;;; See also:
+;;; with-getter-and-setter
+;;; with-excursion-function
+;;;
+(defmacro with-excursion-getter-and-setter (vars proc)
+ `(,proc ,(excursion-function-syntax vars)
+ ,@ (getter-and-setter-syntax vars)))
+
+
+(define (excursion-function-syntax vars)
+ (let ((saved-value-names (map gensym vars))
+ (tmp-var-name (gensym "temp"))
+ (swap-fn-name (gensym "swap"))
+ (thunk-name (gensym "thunk")))
+ `(lambda (,thunk-name)
+ (letrec ((,tmp-var-name #f)
+ (,swap-fn-name
+ (lambda () ,@ (map (lambda (n sn)
+ `(begin (set! ,tmp-var-name ,n)
+ (set! ,n ,sn)
+ (set! ,sn ,tmp-var-name)))
+ vars saved-value-names)))
+ ,@ (map (lambda (sn n) `(,sn ,n)) saved-value-names vars))
+ (dynamic-wind
+ ,swap-fn-name
+ ,thunk-name
+ ,swap-fn-name)))))
+
+
+(define (getter-and-setter-syntax vars)
+ (let ((args-name (gensym "args"))
+ (an-arg-name (gensym "an-arg"))
+ (new-val-name (gensym "new-value"))
+ (loop-name (gensym "loop"))
+ (kws (map symbol->keyword vars)))
+ (list `(lambda ,args-name
+ (let ,loop-name ((,args-name ,args-name))
+ (if (null? ,args-name)
+ ,(if (null? kws)
+ ''()
+ `(let ((all-vals (,loop-name ',kws)))
+ (let ,loop-name ((vals all-vals)
+ (kws ',kws))
+ (if (null? vals)
+ '()
+ `(,(car kws) ,(car vals) ,@(,loop-name (cdr vals) (cdr kws)))))))
+ (map (lambda (,an-arg-name)
+ (case ,an-arg-name
+ ,@ (append
+ (map (lambda (kw v) `((,kw) ,v)) kws vars)
+ `((else (throw 'bad-get-option ,an-arg-name))))))
+ ,args-name))))
+
+ `(lambda ,args-name
+ (let ,loop-name ((,args-name ,args-name))
+ (or (null? ,args-name)
+ (null? (cdr ,args-name))
+ (let ((,an-arg-name (car ,args-name))
+ (,new-val-name (cadr ,args-name)))
+ (case ,an-arg-name
+ ,@ (append
+ (map (lambda (kw v) `((,kw) (set! ,v ,new-val-name))) kws vars)
+ `((else (throw 'bad-set-option ,an-arg-name)))))
+ (,loop-name (cddr ,args-name)))))))))
+
+(define (delegating-getter-and-setter-syntax vars get-delegate set-delegate)
+ (let ((args-name (gensym "args"))
+ (an-arg-name (gensym "an-arg"))
+ (new-val-name (gensym "new-value"))
+ (loop-name (gensym "loop"))
+ (kws (map symbol->keyword vars)))
+ (list `(lambda ,args-name
+ (let ,loop-name ((,args-name ,args-name))
+ (if (null? ,args-name)
+ (append!
+ ,(if (null? kws)
+ ''()
+ `(let ((all-vals (,loop-name ',kws)))
+ (let ,loop-name ((vals all-vals)
+ (kws ',kws))
+ (if (null? vals)
+ '()
+ `(,(car kws) ,(car vals) ,@(,loop-name (cdr vals) (cdr kws)))))))
+ (,get-delegate))
+ (map (lambda (,an-arg-name)
+ (case ,an-arg-name
+ ,@ (append
+ (map (lambda (kw v) `((,kw) ,v)) kws vars)
+ `((else (car (,get-delegate ,an-arg-name)))))))
+ ,args-name))))
+
+ `(lambda ,args-name
+ (let ,loop-name ((,args-name ,args-name))
+ (or (null? ,args-name)
+ (null? (cdr ,args-name))
+ (let ((,an-arg-name (car ,args-name))
+ (,new-val-name (cadr ,args-name)))
+ (case ,an-arg-name
+ ,@ (append
+ (map (lambda (kw v) `((,kw) (set! ,v ,new-val-name))) kws vars)
+ `((else (,set-delegate ,an-arg-name ,new-val-name)))))
+ (,loop-name (cddr ,args-name)))))))))
+
+
+
+
+;;; with-configuration-getter-and-setter <vars-etc> proc
+;;;
+;;; Create a getter and setter that can trigger arbitrary computation.
+;;;
+;;; <vars-etc> is a list of variable specifiers, explained below.
+;;; proc is called:
+;;;
+;;; (proc getter setter)
+;;;
+;;; Each element of the <vars-etc> list is of the form:
+;;;
+;;; (<var> getter-hook setter-hook)
+;;;
+;;; Both hook elements are evaluated; the variable name is not.
+;;; Either hook may be #f or procedure.
+;;;
+;;; A getter hook is a thunk that returns a value for the corresponding
+;;; variable. If omitted (#f is passed), the binding of <var> is
+;;; returned.
+;;;
+;;; A setter hook is a procedure of one argument that accepts a new value
+;;; for the corresponding variable. If omitted, the binding of <var>
+;;; is simply set using set!.
+;;;
+(defmacro with-configuration-getter-and-setter (vars-etc proc)
+ `((lambda (simpler-get simpler-set body-proc)
+ (with-delegating-getter-and-setter ()
+ simpler-get simpler-set body-proc))
+
+ (lambda (kw)
+ (case kw
+ ,@(map (lambda (v) `((,(symbol->keyword (car v)))
+ ,(cond
+ ((cadr v) => list)
+ (else `(list ,(car v))))))
+ vars-etc)))
+
+ (lambda (kw new-val)
+ (case kw
+ ,@(map (lambda (v) `((,(symbol->keyword (car v)))
+ ,(cond
+ ((caddr v) => (lambda (proc) `(,proc new-val)))
+ (else `(set! ,(car v) new-val)))))
+ vars-etc)))
+
+ ,proc))
+
+(defmacro with-delegating-configuration-getter-and-setter (vars-etc delegate-get delegate-set proc)
+ `((lambda (simpler-get simpler-set body-proc)
+ (with-delegating-getter-and-setter ()
+ simpler-get simpler-set body-proc))
+
+ (lambda (kw)
+ (case kw
+ ,@(append! (map (lambda (v) `((,(symbol->keyword (car v)))
+ ,(cond
+ ((cadr v) => list)
+ (else `(list ,(car v))))))
+ vars-etc)
+ `((else (,delegate-get kw))))))
+
+ (lambda (kw new-val)
+ (case kw
+ ,@(append! (map (lambda (v) `((,(symbol->keyword (car v)))
+ ,(cond
+ ((caddr v) => (lambda (proc) `(,proc new-val)))
+ (else `(set! ,(car v) new-val)))))
+ vars-etc)
+ `((else (,delegate-set kw new-val))))))
+
+ ,proc))
+
+
+;;; let-configuration-getter-and-setter <vars-etc> proc
+;;;
+;;; This procedure is like with-configuration-getter-and-setter (q.v.)
+;;; except that each element of <vars-etc> is:
+;;;
+;;; (<var> initial-value getter-hook setter-hook)
+;;;
+;;; Unlike with-configuration-getter-and-setter, let-configuration-getter-and-setter
+;;; introduces bindings for the variables named in <vars-etc>.
+;;; It is short-hand for:
+;;;
+;;; (let ((<var1> initial-value-1)
+;;; (<var2> initial-value-2)
+;;; ...)
+;;; (with-configuration-getter-and-setter ((<var1> v1-get v1-set) ...) proc))
+;;;
+(defmacro let-with-configuration-getter-and-setter (vars-etc proc)
+ `(let ,(map (lambda (v) `(,(car v) ,(cadr v))) vars-etc)
+ (with-configuration-getter-and-setter ,(map (lambda (v) `(,(car v) ,(caddr v) ,(cadddr v))) vars-etc)
+ ,proc)))
+;;; Guile object channel
+
+;; Copyright (C) 2001, 2006, 2009, 2010 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; Now you can use Guile's modules in Emacs Lisp like this:
+;;
+;; (guile-import current-module)
+;; (guile-import module-ref)
+;;
+;; (setq assq (module-ref (current-module) 'assq))
+;; => ("<guile>" %%1%% . "#<primitive-procedure assq>")
+;;
+;; (guile-use-modules (ice-9 documentation))
+;;
+;; (object-documentation assq)
+;; =>
+;; " - primitive: assq key alist
+;; - primitive: assv key alist
+;; - primitive: assoc key alist
+;; Fetches the entry in ALIST that is associated with KEY. To decide
+;; whether the argument KEY matches a particular entry in ALIST,
+;; `assq' compares keys with `eq?', `assv' uses `eqv?' and `assoc'
+;; uses `equal?'. If KEY cannot be found in ALIST (according to
+;; whichever equality predicate is in use), then `#f' is returned.
+;; These functions return the entire alist entry found (i.e. both the
+;; key and the value)."
+;;
+;; Probably we can use GTK in Emacs Lisp. Can anybody try it?
+;;
+;; I have also implemented Guile Scheme mode and Scheme Interaction mode.
+;; Just put the following lines in your ~/.emacs:
+;;
+;; (require 'guile-scheme)
+;; (setq initial-major-mode 'scheme-interaction-mode)
+;;
+;; Currently, the following commands are available:
+;;
+;; M-TAB guile-scheme-complete-symbol
+;; M-C-x guile-scheme-eval-define
+;; C-x C-e guile-scheme-eval-last-sexp
+;; C-c C-b guile-scheme-eval-buffer
+;; C-c C-r guile-scheme-eval-region
+;; C-c : guile-scheme-eval-expression
+;;
+;; I'll write more commands soon, or if you want to hack, please take
+;; a look at the following files:
+;;
+;; guile-core/ice-9/channel.scm ;; object channel
+;; guile-core/emacs/guile.el ;; object adapter
+;; guile-core/emacs/guile-emacs.scm ;; Guile <-> Emacs channels
+;; guile-core/emacs/guile-scheme.el ;; Guile Scheme mode
+;;
+;; As always, there are more than one bugs ;)
+
+;;; Code:
+
+(define-module (ice-9 channel)
+ \:export (make-object-channel
+ channel-open
+ channel-print-value
+ channel-print-token))
+
+;;;
+;;; Channel type
+;;;
+
+(define channel-type
+ (make-record-type 'channel '(stdin stdout printer token-module)))
+
+(define make-channel (record-constructor channel-type))
+
+(define (make-object-channel printer)
+ (make-channel (current-input-port)
+ (current-output-port)
+ printer
+ (make-module)))
+
+(define channel-stdin (record-accessor channel-type 'stdin))
+(define channel-stdout (record-accessor channel-type 'stdout))
+(define channel-printer (record-accessor channel-type 'printer))
+(define channel-token-module (record-accessor channel-type 'token-module))
+
+;;;
+;;; Channel
+;;;
+
+(define (channel-open ch)
+ (let ((stdin (channel-stdin ch))
+ (stdout (channel-stdout ch))
+ (printer (channel-printer ch))
+ (token-module (channel-token-module ch)))
+ (let loop ()
+ (catch #t
+ (lambda ()
+ (channel:prompt stdout)
+ (let ((cmd (read stdin)))
+ (if (eof-object? cmd)
+ (throw 'quit)
+ (case cmd
+ ((eval)
+ (module-use! (current-module) token-module)
+ (printer ch (eval (read stdin) (current-module))))
+ ((destroy)
+ (let ((token (read stdin)))
+ (if (module-defined? token-module token)
+ (module-remove! token-module token)
+ (channel:error stdout "Invalid token: ~S" token))))
+ ((quit)
+ (throw 'quit))
+ (else
+ (channel:error stdout "Unknown command: ~S" cmd)))))
+ (loop))
+ (lambda (key . args)
+ (case key
+ ((quit) (throw 'quit))
+ (else
+ (format stdout "exception = ~S\n"
+ (list key (apply format #f (cadr args) (caddr args))))
+ (loop))))))))
+
+(define (channel-print-value ch val)
+ (format (channel-stdout ch) "value = ~S\n" val))
+
+(define (channel-print-token ch val)
+ (let* ((token (symbol-append (gensym "%%") '%%))
+ (pair (cons token (object->string val))))
+ (format (channel-stdout ch) "token = ~S\n" pair)
+ (module-define! (channel-token-module ch) token val)))
+
+(define (channel:prompt port)
+ (display "channel> " port)
+ (force-output port))
+
+(define (channel:error port msg . args)
+ (display "ERROR: " port)
+ (apply format port msg args)
+ (newline port))
+
+;;;
+;;; Guile 1.4 compatibility
+;;;
+
+(define guile:eval eval)
+(define eval
+ (if (= (car (procedure-minimum-arity guile:eval)) 1)
+ (lambda (x e) (guile:eval x e))
+ guile:eval))
+
+(define object->string
+ (if (defined? 'object->string)
+ object->string
+ (lambda (x) (format #f "~S" x))))
+
+;;; channel.scm ends here
+;;; Parsing Guile's command-line
+
+;;; Copyright (C) 1994-1998, 2000-2016 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+;;;
+;;; Please be careful not to load up other modules in this file, unless
+;;; they are explicitly requested. Loading modules currently imposes a
+;;; speed penalty of a few stats, an mmap, and some allocation, which
+;;; can range from 1 to 20ms, depending on the state of your disk cache.
+;;; Since `compile-shell-switches' is called even for the most transient
+;;; of command-line programs, we need to keep it lean.
+;;;
+;;; Generally speaking, the goal is for Guile to boot and execute simple
+;;; expressions like "1" within 20ms or less, measured using system time
+;;; from the time of the `guile' invocation to exit.
+;;;
+
+(define-module (ice-9 command-line)
+ #\autoload (system vm vm) (set-default-vm-engine! set-vm-engine! the-vm)
+ #\export (compile-shell-switches
+ version-etc
+ *GPLv3+*
+ *LGPLv3+*
+ emit-bug-reporting-address))
+
+;; An initial stab at i18n.
+(define _ gettext)
+
+(define *GPLv3+*
+ (_ "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law."))
+
+(define *LGPLv3+*
+ (_ "License LGPLv3+: GNU LGPL 3 or later <http://gnu.org/licenses/lgpl.html>.
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law."))
+
+;; Display the --version information in the
+;; standard way: command and package names, package version, followed
+;; by a short license notice and a list of up to 10 author names.
+;; If COMMAND_NAME is NULL, the PACKAGE is asumed to be the name of
+;; the program. The formats are therefore:
+;; PACKAGE VERSION
+;; or
+;; COMMAND_NAME (PACKAGE) VERSION.
+;;
+;; Based on the version-etc gnulib module.
+;;
+(define* (version-etc package version #\key
+ (port (current-output-port))
+ ;; FIXME: authors
+ (copyright-year 2016)
+ (copyright-holder "Free Software Foundation, Inc.")
+ (copyright (format #f "Copyright (C) ~a ~a"
+ copyright-year copyright-holder))
+ (license *GPLv3+*)
+ command-name
+ packager packager-version)
+ (if command-name
+ (format port "~a (~a) ~a\n" command-name package version)
+ (format port "~a ~a\n" package version))
+
+ (if packager
+ (if packager-version
+ (format port (_ "Packaged by ~a (~a)\n") packager packager-version)
+ (format port (_ "Packaged by ~a\n") packager)))
+
+ (display copyright port)
+ (newline port)
+ (newline port)
+ (display license port)
+ (newline port))
+
+
+;; Display the usual `Report bugs to' stanza.
+;;
+(define* (emit-bug-reporting-address package bug-address #\key
+ (port (current-output-port))
+ (url (string-append
+ "http://www.gnu.org/software/"
+ package
+ "/"))
+ packager packager-bug-address)
+ (format port (_ "\nReport bugs to: ~a\n") bug-address)
+ (if (and packager packager-bug-address)
+ (format port (_ "Report ~a bugs to: ~a\n") packager packager-bug-address))
+ (format port (_ "~a home page: <~a>\n") package url)
+ (format port
+ (_ "General help using GNU software: <http://www.gnu.org/gethelp/>\n")))
+
+(define *usage*
+ (_ "Evaluate code with Guile, interactively or from a script.
+
+ [-s] FILE load source code from FILE, and exit
+ -c EXPR evalute expression EXPR, and exit
+ -- stop scanning arguments; run interactively
+
+The above switches stop argument processing, and pass all
+remaining arguments as the value of (command-line).
+If FILE begins with `-' the -s switch is mandatory.
+
+ -L DIRECTORY add DIRECTORY to the front of the module load path
+ -C DIRECTORY like -L, but for compiled files
+ -x EXTENSION add EXTENSION to the front of the load extensions
+ -l FILE load source code from FILE
+ -e FUNCTION after reading script, apply FUNCTION to
+ command line arguments
+ --language=LANG change language; default: scheme
+ -ds do -s script at this point
+ --debug start with the \"debugging\" VM engine
+ --no-debug start with the normal VM engine (backtraces but
+ no breakpoints); default is --debug for interactive
+ use, but not for `-s' and `-c'.
+ --auto-compile compile source files automatically
+ --fresh-auto-compile invalidate auto-compilation cache
+ --no-auto-compile disable automatic source file compilation;
+ default is to enable auto-compilation of source
+ files.
+ --listen[=P] listen on a local port or a path for REPL clients;
+ if P is not given, the default is local port 37146
+ -q inhibit loading of user init file
+ --use-srfi=LS load SRFI modules for the SRFIs in LS,
+ which is a list of numbers like \"2,13,14\"
+ -h, --help display this help and exit
+ -v, --version display version information and exit
+ \\ read arguments from following script lines"))
+
+
+(define* (shell-usage name fatal? #\optional fmt . args)
+ (let ((port (if fatal?
+ (current-error-port)
+ (current-output-port))))
+ (when fmt
+ (apply format port fmt args)
+ (newline port))
+
+ (format port (_ "Usage: ~a [OPTION]... [FILE]...\n") name)
+ (display *usage* port)
+ (newline port)
+
+ (emit-bug-reporting-address
+ "GNU Guile" "bug-guile@gnu.org"
+ #\port port
+ #\url "http://www.gnu.org/software/guile/"
+ #\packager (assq-ref %guile-build-info 'packager)
+ #\packager-bug-address
+ (assq-ref %guile-build-info 'packager-bug-address))
+
+ (if fatal?
+ (exit 1))))
+
+;; Try to avoid loading (ice-9 eval-string) and (system base compile) if
+;; possible.
+(define (eval-string/lang str)
+ (case (current-language)
+ ((scheme)
+ (call-with-input-string
+ str
+ (lambda (port)
+ (let lp ()
+ (let ((exp (read port)))
+ (if (not (eof-object? exp))
+ (begin
+ (eval exp (current-module))
+ (lp))))))))
+ (else
+ ((module-ref (resolve-module '(ice-9 eval-string)) 'eval-string) str))))
+
+(define (load/lang f)
+ (case (current-language)
+ ((scheme)
+ (load-in-vicinity (getcwd) f))
+ (else
+ ((module-ref (resolve-module '(system base compile)) 'compile-file)
+ f #\to 'value))))
+
+(define* (compile-shell-switches args #\optional (usage-name "guile"))
+ (let ((arg0 "guile")
+ (script-cell #f)
+ (entry-point #f)
+ (user-load-path '())
+ (user-load-compiled-path '())
+ (user-extensions '())
+ (interactive? #t)
+ (inhibit-user-init? #f)
+ (turn-on-debugging? #f)
+ (turn-off-debugging? #f))
+
+ (define (error fmt . args)
+ (apply shell-usage usage-name #t
+ (string-append "error: " fmt "~%") args))
+
+ (define (parse args out)
+ (cond
+ ((null? args)
+ (finish args out))
+ (else
+ (let ((arg (car args))
+ (args (cdr args)))
+ (cond
+ ((not (string-prefix? "-" arg)) ; foo
+ ;; If we specified the -ds option, script-cell is the cdr of
+ ;; an expression like (load #f). We replace the car (i.e.,
+ ;; the #f) with the script name.
+ (set! arg0 arg)
+ (set! interactive? #f)
+ (if script-cell
+ (begin
+ (set-car! script-cell arg0)
+ (finish args out))
+ (finish args
+ (cons `((@@ (ice-9 command-line) load/lang) ,arg0)
+ out))))
+
+ ((string=? arg "-s") ; foo
+ (if (null? args)
+ (error "missing argument to `-s' switch"))
+ (set! arg0 (car args))
+ (set! interactive? #f)
+ (if script-cell
+ (begin
+ (set-car! script-cell arg0)
+ (finish (cdr args) out))
+ (finish (cdr args)
+ (cons `((@@ (ice-9 command-line) load/lang) ,arg0)
+ out))))
+
+ ((string=? arg "-c") ; evaluate expr
+ (if (null? args)
+ (error "missing argument to `-c' switch"))
+ (set! interactive? #f)
+ (finish (cdr args)
+ (cons `((@@ (ice-9 command-line) eval-string/lang)
+ ,(car args))
+ out)))
+
+ ((string=? arg "--") ; end args go interactive
+ (finish args out))
+
+ ((string=? arg "-l") ; load a file
+ (if (null? args)
+ (error "missing argument to `-l' switch"))
+ (parse (cdr args)
+ (cons `((@@ (ice-9 command-line) load/lang) ,(car args))
+ out)))
+
+ ((string=? arg "-L") ; add to %load-path
+ (if (null? args)
+ (error "missing argument to `-L' switch"))
+ (set! user-load-path (cons (car args) user-load-path))
+ (parse (cdr args)
+ out))
+
+ ((string=? arg "-C") ; add to %load-compiled-path
+ (if (null? args)
+ (error "missing argument to `-C' switch"))
+ (set! user-load-compiled-path
+ (cons (car args) user-load-compiled-path))
+ (parse (cdr args)
+ out))
+
+ ((string=? arg "-x") ; add to %load-extensions
+ (if (null? args)
+ (error "missing argument to `-x' switch"))
+ (set! user-extensions (cons (car args) user-extensions))
+ (parse (cdr args)
+ out))
+
+ ((string=? arg "-e") ; entry point
+ (if (null? args)
+ (error "missing argument to `-e' switch"))
+ (let* ((port (open-input-string (car args)))
+ (arg1 (read port))
+ (arg2 (read port)))
+ ;; Recognize syntax of certain versions of guile 1.4 and
+ ;; transform to (@ MODULE-NAME FUNC).
+ (set! entry-point
+ (cond
+ ((not (eof-object? arg2))
+ `(@ ,arg1 ,arg2))
+ ((and (pair? arg1)
+ (not (memq (car arg1) '(@ @@)))
+ (and-map symbol? arg1))
+ `(@ ,arg1 main))
+ (else
+ arg1))))
+ (parse (cdr args)
+ out))
+
+ ((string-prefix? "--language=" arg) ; language
+ (parse args
+ (cons `(current-language
+ ',(string->symbol
+ (substring arg (string-length "--language="))))
+ out)))
+
+ ((string=? "--language" arg) ; language
+ (when (null? args)
+ (error "missing argument to `--language' option"))
+ (parse (cdr args)
+ (cons `(current-language ',(string->symbol (car args)))
+ out)))
+
+ ((string=? arg "-ds") ; do script here
+ ;; We put a dummy "load" expression, and let the -s put the
+ ;; filename in.
+ (when script-cell
+ (error "the -ds switch may only be specified once"))
+ (set! script-cell (list #f))
+ (parse args
+ (acons '(@@ (ice-9 command-line) load/lang)
+ script-cell
+ out)))
+
+ ((string=? arg "--debug")
+ (set! turn-on-debugging? #t)
+ (set! turn-off-debugging? #f)
+ (parse args out))
+
+ ((string=? arg "--no-debug")
+ (set! turn-off-debugging? #t)
+ (set! turn-on-debugging? #f)
+ (parse args out))
+
+ ;; Do auto-compile on/off now, because the form itself might
+ ;; need this decision.
+ ((string=? arg "--auto-compile")
+ (set! %load-should-auto-compile #t)
+ (parse args out))
+
+ ((string=? arg "--fresh-auto-compile")
+ (set! %load-should-auto-compile #t)
+ (set! %fresh-auto-compile #t)
+ (parse args out))
+
+ ((string=? arg "--no-auto-compile")
+ (set! %load-should-auto-compile #f)
+ (parse args out))
+
+ ((string=? arg "-q") ; don't load user init
+ (set! inhibit-user-init? #t)
+ (parse args out))
+
+ ((string-prefix? "--use-srfi=" arg)
+ (let ((srfis (map (lambda (x)
+ (let ((n (string->number x)))
+ (if (and n (exact? n) (integer? n) (>= n 0))
+ n
+ (error "invalid SRFI specification"))))
+ (string-split (substring arg 11) #\,))))
+ (if (null? srfis)
+ (error "invalid SRFI specification"))
+ (parse args
+ (cons `(use-srfis ',srfis) out))))
+
+ ((string=? arg "--listen") ; start a repl server
+ (parse args
+ (cons '((@@ (system repl server) spawn-server)) out)))
+
+ ((string-prefix? "--listen=" arg) ; start a repl server
+ (parse
+ args
+ (cons
+ (let ((where (substring arg 9)))
+ (cond
+ ((string->number where) ; --listen=PORT
+ => (lambda (port)
+ (if (and (integer? port) (exact? port) (>= port 0))
+ `((@@ (system repl server) spawn-server)
+ ((@@ (system repl server) make-tcp-server-socket) #\port ,port))
+ (error "invalid port for --listen"))))
+ ((string-prefix? "/" where) ; --listen=/PATH/TO/SOCKET
+ `((@@ (system repl server) spawn-server)
+ ((@@ (system repl server) make-unix-domain-server-socket) #\path ,where)))
+ (else
+ (error "unknown argument to --listen"))))
+ out)))
+
+ ((or (string=? arg "-h") (string=? arg "--help"))
+ (shell-usage usage-name #f)
+ (exit 0))
+
+ ((or (string=? arg "-v") (string=? arg "--version"))
+ (version-etc "GNU Guile" (version)
+ #\license *LGPLv3+*
+ #\command-name "guile"
+ #\packager (assq-ref %guile-build-info 'packager)
+ #\packager-version
+ (assq-ref %guile-build-info 'packager-version))
+ (exit 0))
+
+ (else
+ (error "unrecognized switch ~a" arg)))))))
+
+ (define (finish args out)
+ ;; Check to make sure the -ds got a -s.
+ (when (and script-cell (not (car script-cell)))
+ (error "the `-ds' switch requires the use of `-s' as well"))
+
+ ;; Make any remaining arguments available to the
+ ;; script/command/whatever.
+ (set-program-arguments (cons arg0 args))
+
+ ;; If debugging was requested, or we are interactive and debugging
+ ;; was not explicitly turned off, use the debug engine.
+ (if (or turn-on-debugging?
+ (and interactive? (not turn-off-debugging?)))
+ (begin
+ (set-default-vm-engine! 'debug)
+ (set-vm-engine! (the-vm) 'debug)))
+
+ ;; Return this value.
+ `(;; It would be nice not to load up (ice-9 control), but the
+ ;; default-prompt-handler is nontrivial.
+ (@ (ice-9 control) %)
+ (begin
+ ;; If we didn't end with a -c or a -s and didn't supply a -q, load
+ ;; the user's customization file.
+ ,@(if (and interactive? (not inhibit-user-init?))
+ '((load-user-init))
+ '())
+
+ ;; Use-specified extensions.
+ ,@(map (lambda (ext)
+ `(set! %load-extensions (cons ,ext %load-extensions)))
+ user-extensions)
+
+ ;; Add the user-specified load paths here, so they won't be in
+ ;; effect during the loading of the user's customization file.
+ ,@(map (lambda (path)
+ `(set! %load-path (cons ,path %load-path)))
+ user-load-path)
+ ,@(map (lambda (path)
+ `(set! %load-compiled-path
+ (cons ,path %load-compiled-path)))
+ user-load-compiled-path)
+
+ ;; Put accumulated actions in their correct order.
+ ,@(reverse! out)
+
+ ;; Handle the `-e' switch, if it was specified.
+ ,@(if entry-point
+ `((,entry-point (command-line)))
+ '())
+ ,(if interactive?
+ ;; If we didn't end with a -c or a -s, start the
+ ;; repl.
+ '((@ (ice-9 top-repl) top-repl))
+ ;; Otherwise, after doing all the other actions
+ ;; prescribed by the command line, quit.
+ '(quit)))))
+
+ (if (pair? args)
+ (begin
+ (set! arg0 (car args))
+ (let ((slash (string-rindex arg0 #\/)))
+ (set! usage-name
+ (if slash (substring arg0 (1+ slash)) arg0)))
+ (parse (cdr args) '()))
+ (parse args '()))))
+;;;; common-list.scm --- COMMON LISP list functions for Scheme
+;;;;
+;;;; Copyright (C) 1995, 1996, 1997, 2001, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+
+;; These procedures are exported:
+;; (adjoin e l)
+;; (union l1 l2)
+;; (intersection l1 l2)
+;; (set-difference l1 l2)
+;; (reduce-init p init l)
+;; (reduce p l)
+;; (some pred l . rest)
+;; (every pred l . rest)
+;; (notany pred . ls)
+;; (notevery pred . ls)
+;; (count-if pred l)
+;; (find-if pred l)
+;; (member-if pred l)
+;; (remove-if pred l)
+;; (remove-if-not pred l)
+;; (delete-if! pred l)
+;; (delete-if-not! pred l)
+;; (butlast lst n)
+;; (and? . args)
+;; (or? . args)
+;; (has-duplicates? lst)
+;; (pick p l)
+;; (pick-mappings p l)
+;; (uniq l)
+;;
+;; See docstrings for each procedure for more info. See also module
+;; `(srfi srfi-1)' for a complete list handling library.
+
+;;; Code:
+
+(define-module (ice-9 common-list)
+ \:export (adjoin union intersection set-difference reduce-init reduce
+ some every notany notevery count-if find-if member-if remove-if
+ remove-if-not delete-if! delete-if-not! butlast and? or?
+ has-duplicates? pick pick-mappings uniq))
+
+;;"comlist.scm" Implementation of COMMON LISP list functions for Scheme
+; Copyright (C) 1991, 1993, 1995 Aubrey Jaffer.
+;
+;Permission to copy this software, to redistribute it, 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 warrantee 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.
+
+(define (adjoin e l)
+ "Return list L, possibly with element E added if it is not already in L."
+ (if (memq e l) l (cons e l)))
+
+(define (union l1 l2)
+ "Return a new list that is the union of L1 and L2.
+Elements that occur in both lists occur only once in
+the result list."
+ (cond ((null? l1) l2)
+ ((null? l2) l1)
+ (else (union (cdr l1) (adjoin (car l1) l2)))))
+
+(define (intersection l1 l2)
+ "Return a new list that is the intersection of L1 and L2.
+Only elements that occur in both lists occur in the result list."
+ (if (null? l2) l2
+ (let loop ((l1 l1) (result '()))
+ (cond ((null? l1) (reverse! result))
+ ((memv (car l1) l2) (loop (cdr l1) (cons (car l1) result)))
+ (else (loop (cdr l1) result))))))
+
+(define (set-difference l1 l2)
+ "Return elements from list L1 that are not in list L2."
+ (let loop ((l1 l1) (result '()))
+ (cond ((null? l1) (reverse! result))
+ ((memv (car l1) l2) (loop (cdr l1) result))
+ (else (loop (cdr l1) (cons (car l1) result))))))
+
+(define (reduce-init p init l)
+ "Same as `reduce' except it implicitly inserts INIT at the start of L."
+ (if (null? l)
+ init
+ (reduce-init p (p init (car l)) (cdr l))))
+
+(define (reduce p l)
+ "Combine all the elements of sequence L using a binary operation P.
+The combination is left-associative. For example, using +, one can
+add up all the elements. `reduce' allows you to apply a function which
+accepts only two arguments to more than 2 objects. Functional
+programmers usually refer to this as foldl."
+ (cond ((null? l) l)
+ ((null? (cdr l)) (car l))
+ (else (reduce-init p (car l) (cdr l)))))
+
+(define (some pred l . rest)
+ "PRED is a boolean function of as many arguments as there are list
+arguments to `some', i.e., L plus any optional arguments. PRED is
+applied to successive elements of the list arguments in order. As soon
+as one of these applications returns a true value, return that value.
+If no application returns a true value, return #f.
+All the lists should have the same length."
+ (cond ((null? rest)
+ (let mapf ((l l))
+ (and (not (null? l))
+ (or (pred (car l)) (mapf (cdr l))))))
+ (else (let mapf ((l l) (rest rest))
+ (and (not (null? l))
+ (or (apply pred (car l) (map car rest))
+ (mapf (cdr l) (map cdr rest))))))))
+
+(define (every pred l . rest)
+ "Return #t iff every application of PRED to L, etc., returns #t.
+Analogous to `some' except it returns #t if every application of
+PRED is #t and #f otherwise."
+ (cond ((null? rest)
+ (let mapf ((l l))
+ (or (null? l)
+ (and (pred (car l)) (mapf (cdr l))))))
+ (else (let mapf ((l l) (rest rest))
+ (or (null? l)
+ (and (apply pred (car l) (map car rest))
+ (mapf (cdr l) (map cdr rest))))))))
+
+(define (notany pred . ls)
+ "Return #t iff every application of PRED to L, etc., returns #f.
+Analogous to some but returns #t if no application of PRED returns a
+true value or #f as soon as any one does."
+ (not (apply some pred ls)))
+
+(define (notevery pred . ls)
+ "Return #t iff there is an application of PRED to L, etc., that returns #f.
+Analogous to some but returns #t as soon as an application of PRED returns #f,
+or #f otherwise."
+ (not (apply every pred ls)))
+
+(define (count-if pred l)
+ "Return the number of elements in L for which (PRED element) returns true."
+ (let loop ((n 0) (l l))
+ (cond ((null? l) n)
+ ((pred (car l)) (loop (+ n 1) (cdr l)))
+ (else (loop n (cdr l))))))
+
+(define (find-if pred l)
+ "Search for the first element in L for which (PRED element) returns true.
+If found, return that element, otherwise return #f."
+ (cond ((null? l) #f)
+ ((pred (car l)) (car l))
+ (else (find-if pred (cdr l)))))
+
+(define (member-if pred l)
+ "Return the first sublist of L for whose car PRED is true."
+ (cond ((null? l) #f)
+ ((pred (car l)) l)
+ (else (member-if pred (cdr l)))))
+
+(define (remove-if pred l)
+ "Remove all elements from L where (PRED element) is true.
+Return everything that's left."
+ (let loop ((l l) (result '()))
+ (cond ((null? l) (reverse! result))
+ ((pred (car l)) (loop (cdr l) result))
+ (else (loop (cdr l) (cons (car l) result))))))
+
+(define (remove-if-not pred l)
+ "Remove all elements from L where (PRED element) is #f.
+Return everything that's left."
+ (let loop ((l l) (result '()))
+ (cond ((null? l) (reverse! result))
+ ((not (pred (car l))) (loop (cdr l) result))
+ (else (loop (cdr l) (cons (car l) result))))))
+
+(define (delete-if! pred l)
+ "Destructive version of `remove-if'."
+ (let delete-if ((l l))
+ (cond ((null? l) '())
+ ((pred (car l)) (delete-if (cdr l)))
+ (else
+ (set-cdr! l (delete-if (cdr l)))
+ l))))
+
+(define (delete-if-not! pred l)
+ "Destructive version of `remove-if-not'."
+ (let delete-if-not ((l l))
+ (cond ((null? l) '())
+ ((not (pred (car l))) (delete-if-not (cdr l)))
+ (else
+ (set-cdr! l (delete-if-not (cdr l)))
+ l))))
+
+(define (butlast lst n)
+ "Return all but the last N elements of LST."
+ (letrec ((l (- (length lst) n))
+ (bl (lambda (lst n)
+ (cond ((null? lst) lst)
+ ((positive? n)
+ (cons (car lst) (bl (cdr lst) (+ -1 n))))
+ (else '())))))
+ (bl lst (if (negative? n)
+ (error "negative argument to butlast" n)
+ l))))
+
+(define (and? . args)
+ "Return #t iff all of ARGS are true."
+ (cond ((null? args) #t)
+ ((car args) (apply and? (cdr args)))
+ (else #f)))
+
+(define (or? . args)
+ "Return #t iff any of ARGS is true."
+ (cond ((null? args) #f)
+ ((car args) #t)
+ (else (apply or? (cdr args)))))
+
+(define (has-duplicates? lst)
+ "Return #t iff 2 members of LST are equal?, else #f."
+ (cond ((null? lst) #f)
+ ((member (car lst) (cdr lst)) #t)
+ (else (has-duplicates? (cdr lst)))))
+
+(define (pick p l)
+ "Apply P to each element of L, returning a list of elts
+for which P returns a non-#f value."
+ (let loop ((s '())
+ (l l))
+ (cond
+ ((null? l) s)
+ ((p (car l)) (loop (cons (car l) s) (cdr l)))
+ (else (loop s (cdr l))))))
+
+(define (pick-mappings p l)
+ "Apply P to each element of L, returning a list of the
+non-#f return values of P."
+ (let loop ((s '())
+ (l l))
+ (cond
+ ((null? l) s)
+ ((p (car l)) => (lambda (mapping) (loop (cons mapping s) (cdr l))))
+ (else (loop s (cdr l))))))
+
+(define (uniq l)
+ "Return a list containing elements of L, with duplicates removed."
+ (let loop ((acc '())
+ (l l))
+ (if (null? l)
+ (reverse! acc)
+ (loop (if (memq (car l) acc)
+ acc
+ (cons (car l) acc))
+ (cdr l)))))
+
+;;; common-list.scm ends here
+;;; -*- mode: scheme; coding: utf-8; -*-
+;;;
+;;; Copyright (C) 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(use-modules (language tree-il)
+ (language tree-il primitives)
+ (language tree-il canonicalize)
+ (srfi srfi-1)
+ (ice-9 pretty-print)
+ (system syntax))
+
+;; Minimize a syntax-object such that it can no longer be used as the
+;; first argument to 'datum->syntax', but is otherwise equivalent.
+(define (squeeze-syntax-object! syn)
+ (define (ensure-list x) (if (vector? x) (vector->list x) x))
+ (let ((x (vector-ref syn 1))
+ (wrap (vector-ref syn 2))
+ (mod (vector-ref syn 3)))
+ (let ((marks (car wrap))
+ (subst (cdr wrap)))
+ (define (set-wrap! marks subst)
+ (vector-set! syn 2 (cons marks subst)))
+ (cond
+ ((symbol? x)
+ (let loop ((marks marks) (subst subst))
+ (cond
+ ((null? subst) (set-wrap! marks subst) syn)
+ ((eq? 'shift (car subst)) (loop (cdr marks) (cdr subst)))
+ ((find (lambda (entry) (and (eq? x (car entry))
+ (equal? marks (cadr entry))))
+ (apply map list (map ensure-list
+ (cdr (vector->list (car subst))))))
+ => (lambda (entry)
+ (set-wrap! marks
+ (list (list->vector
+ (cons 'ribcage
+ (map vector entry)))))
+ syn))
+ (else (loop marks (cdr subst))))))
+ ((or (pair? x) (vector? x))
+ syn)
+ (else x)))))
+
+(define (squeeze-constant! x)
+ (define (syntax-object? x)
+ (and (vector? x)
+ (= 4 (vector-length x))
+ (eq? 'syntax-object (vector-ref x 0))))
+ (cond ((syntax-object? x)
+ (squeeze-syntax-object! x))
+ ((pair? x)
+ (set-car! x (squeeze-constant! (car x)))
+ (set-cdr! x (squeeze-constant! (cdr x)))
+ x)
+ ((vector? x)
+ (for-each (lambda (i)
+ (vector-set! x i (squeeze-constant! (vector-ref x i))))
+ (iota (vector-length x)))
+ x)
+ (else x)))
+
+(define (squeeze-tree-il! x)
+ (post-order! (lambda (x)
+ (if (const? x)
+ (set! (const-exp x)
+ (squeeze-constant! (const-exp x))))
+ #f)
+ x))
+
+;; Avoid gratuitous churn in psyntax-pp.scm due to the marks and labels
+;; changing session identifiers.
+(set! syntax-session-id (lambda () "*"))
+
+(let ((source (list-ref (command-line) 1))
+ (target (list-ref (command-line) 2)))
+ (let ((in (open-input-file source))
+ (out (open-output-file (string-append target ".tmp"))))
+ (write '(eval-when (compile) (set-current-module (resolve-module '(guile))))
+ out)
+ (newline out)
+ (let loop ((x (read in)))
+ (if (eof-object? x)
+ (begin
+ (close-port out)
+ (close-port in))
+ (begin
+ (pretty-print (tree-il->scheme
+ (squeeze-tree-il!
+ (canonicalize!
+ (resolve-primitives!
+ (macroexpand x 'c '(compile load eval))
+ (current-module))))
+ (current-module)
+ (list #\avoid-lambda? #f
+ #\use-case? #f
+ #\strip-numeric-suffixes? #t
+ #\use-derived-syntax?
+ (and (pair? x)
+ (eq? 'let (car x)))))
+ out #\width 120 #\max-expr-width 70)
+ (newline out)
+ (loop (read in))))))
+ (system (format #f "mv -f ~s.tmp ~s" target target)))
+;;; Beyond call/cc
+
+;; Copyright (C) 2010, 2011, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (ice-9 control)
+ #\re-export (call-with-prompt abort-to-prompt
+ default-prompt-tag make-prompt-tag)
+ #\export (% abort shift reset shift* reset*
+ call-with-escape-continuation call/ec
+ let-escape-continuation let/ec))
+
+(define (abort . args)
+ (apply abort-to-prompt (default-prompt-tag) args))
+
+(define-syntax %
+ (syntax-rules ()
+ ((_ expr)
+ (call-with-prompt (default-prompt-tag)
+ (lambda () expr)
+ default-prompt-handler))
+ ((_ expr handler)
+ (call-with-prompt (default-prompt-tag)
+ (lambda () expr)
+ handler))
+ ((_ tag expr handler)
+ (call-with-prompt tag
+ (lambda () expr)
+ handler))))
+
+;; Each prompt tag has a type -- an expected set of arguments, and an unwritten
+;; contract of what its handler will do on an abort. In the case of the default
+;; prompt tag, we could choose to return values, exit nonlocally, or punt to the
+;; user.
+;;
+;; We choose the latter, by requiring that the user return one value, a
+;; procedure, to an abort to the prompt tag. That argument is then invoked with
+;; the continuation as an argument, within a reinstated default prompt. In this
+;; way the return value(s) from a default prompt are under the user's control.
+(define (default-prompt-handler k proc)
+ (% (default-prompt-tag)
+ (proc k)
+ default-prompt-handler))
+
+;; Kindly provided by Wolfgang J Moeller <wjm@heenes.com>, modelled
+;; after the ones by Oleg Kiselyov in
+;; http://okmij.org/ftp/Scheme/delim-control-n.scm, which are in the
+;; public domain, as noted at the top of http://okmij.org/ftp/.
+;;
+(define-syntax-rule (reset . body)
+ (call-with-prompt (default-prompt-tag)
+ (lambda () . body)
+ (lambda (cont f) (f cont))))
+
+(define-syntax-rule (shift var . body)
+ (abort-to-prompt (default-prompt-tag)
+ (lambda (cont)
+ ((lambda (var) (reset . body))
+ (lambda vals (reset (apply cont vals)))))))
+
+(define (reset* thunk)
+ (reset (thunk)))
+
+(define (shift* fc)
+ (shift c (fc c)))
+
+(define (call-with-escape-continuation proc)
+ "Call PROC with an escape continuation."
+ (let ((tag (list 'call/ec)))
+ (call-with-prompt tag
+ (lambda ()
+ (proc (lambda args
+ (apply abort-to-prompt tag args))))
+ (lambda (_ . args)
+ (apply values args)))))
+
+(define call/ec call-with-escape-continuation)
+
+(define-syntax-rule (let-escape-continuation k body ...)
+ "Bind K to an escape continuation within the lexical extent of BODY."
+ (let ((tag (list 'let/ec)))
+ (call-with-prompt tag
+ (lambda ()
+ (let ((k (lambda args
+ (apply abort-to-prompt tag args))))
+ body ...))
+ (lambda (_ . results)
+ (apply values results)))))
+
+(define-syntax-rule (let/ec k body ...)
+ (let-escape-continuation k body ...))
+;;; Copyright (C) 2010, 2013 Free Software Foundation, Inc.
+;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (ice-9 curried-definitions)
+ #\replace ((cdefine . define)
+ (cdefine* . define*)
+ define-public
+ define*-public))
+
+(define-syntax cdefine
+ (syntax-rules ()
+ ((_ (head . rest) body body* ...)
+ (cdefine head
+ (lambda rest body body* ...)))
+ ((_ name val)
+ (define name val))))
+
+(define-syntax cdefine*
+ (syntax-rules ()
+ ((_ (head . rest) body body* ...)
+ (cdefine* head
+ (lambda* rest body body* ...)))
+ ((_ name val)
+ (define* name val))))
+
+(define-syntax define-public
+ (syntax-rules ()
+ ((_ (head . rest) body body* ...)
+ (define-public head
+ (lambda rest body body* ...)))
+ ((_ name val)
+ (begin
+ (define name val)
+ (export name)))))
+
+(define-syntax define*-public
+ (syntax-rules ()
+ ((_ (head . rest) body body* ...)
+ (define*-public head
+ (lambda* rest body body* ...)))
+ ((_ name val)
+ (begin
+ (define* name val)
+ (export name)))))
+;;;; Copyright (C) 1996, 1997, 1998, 1999, 2001, 2006, 2010 Free Software Foundation
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+;;;; The author can be reached at djurfeldt@nada.kth.se
+;;;; Mikael Djurfeldt, SANS/NADA KTH, 10044 STOCKHOLM, SWEDEN
+;;;;
+
+
+(define-module (ice-9 debug))
+
+(issue-deprecation-warning
+ "(ice-9 debug) is deprecated. Use (system vm trace) for tracing.")
+;;;; Copyright (C) 2003, 2005, 2006, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+(define-module (ice-9 deprecated)
+ #\export (substring-move-left! substring-move-right!
+ dynamic-maybe-call dynamic-maybe-link
+ try-module-linked try-module-dynamic-link
+ list* feature? eval-case unmemoize-expr
+ $asinh
+ $acosh
+ $atanh
+ $sqrt
+ $abs
+ $exp
+ $expt
+ $log
+ $sin
+ $cos
+ $tan
+ $asin
+ $acos
+ $atan
+ $sinh
+ $cosh
+ $tanh
+ closure?
+ %nil
+ @bind
+ bad-throw
+ error-catching-loop
+ error-catching-repl
+ scm-style-repl
+ apply-to-args
+ has-suffix?
+ scheme-file-suffix
+ get-option
+ for-next-option
+ display-usage-report
+ transform-usage-lambda
+ collect
+ assert-repl-silence
+ assert-repl-print-unspecified
+ assert-repl-verbosity
+ set-repl-prompt!
+ set-batch-mode?!
+ repl
+ pre-unwind-handler-dispatch
+ default-pre-unwind-handler
+ handle-system-error
+ stack-saved?
+ the-last-stack
+ save-stack
+ named-module-use!
+ top-repl
+ turn-on-debugging
+ read-hash-procedures
+ process-define-module
+ fluid-let-syntax
+ set-system-module!
+ char-code-limit
+ generalized-vector?
+ generalized-vector-length
+ generalized-vector-ref
+ generalized-vector-set!
+ generalized-vector->list))
+
+
+;;;; Deprecated definitions.
+
+(define substring-move-left!
+ (lambda args
+ (issue-deprecation-warning
+ "`substring-move-left!' is deprecated. Use `substring-move!' instead.")
+ (apply substring-move! args)))
+(define substring-move-right!
+ (lambda args
+ (issue-deprecation-warning
+ "`substring-move-right!' is deprecated. Use `substring-move!' instead.")
+ (apply substring-move! args)))
+
+
+
+;; This method of dynamically linking Guile Extensions is deprecated.
+;; Use `load-extension' explicitly from Scheme code instead.
+
+(define (split-c-module-name str)
+ (let loop ((rev '())
+ (start 0)
+ (pos 0)
+ (end (string-length str)))
+ (cond
+ ((= pos end)
+ (reverse (cons (string->symbol (substring str start pos)) rev)))
+ ((eq? (string-ref str pos) #\space)
+ (loop (cons (string->symbol (substring str start pos)) rev)
+ (+ pos 1)
+ (+ pos 1)
+ end))
+ (else
+ (loop rev start (+ pos 1) end)))))
+
+(define (convert-c-registered-modules dynobj)
+ (let ((res (map (lambda (c)
+ (list (split-c-module-name (car c)) (cdr c) dynobj))
+ (c-registered-modules))))
+ (c-clear-registered-modules)
+ res))
+
+(define registered-modules '())
+
+(define (register-modules dynobj)
+ (set! registered-modules
+ (append! (convert-c-registered-modules dynobj)
+ registered-modules)))
+
+(define (warn-autoload-deprecation modname)
+ (issue-deprecation-warning
+ "Autoloading of compiled code modules is deprecated."
+ "Write a Scheme file instead that uses `load-extension'.")
+ (issue-deprecation-warning
+ (simple-format #f "(You just autoloaded module ~S.)" modname)))
+
+(define (init-dynamic-module modname)
+ ;; Register any linked modules which have been registered on the C level
+ (register-modules #f)
+ (or-map (lambda (modinfo)
+ (if (equal? (car modinfo) modname)
+ (begin
+ (warn-autoload-deprecation modname)
+ (set! registered-modules (delq! modinfo registered-modules))
+ (let ((mod (resolve-module modname #f)))
+ (save-module-excursion
+ (lambda ()
+ (set-current-module mod)
+ (set-module-public-interface! mod mod)
+ (dynamic-call (cadr modinfo) (caddr modinfo))
+ ))
+ #t))
+ #f))
+ registered-modules))
+
+(define (dynamic-maybe-call name dynobj)
+ (issue-deprecation-warning
+ "`dynamic-maybe-call' is deprecated. "
+ "Wrap `dynamic-call' in a `false-if-exception' yourself.")
+ (false-if-exception (dynamic-call name dynobj)))
+
+
+(define (dynamic-maybe-link filename)
+ (issue-deprecation-warning
+ "`dynamic-maybe-link' is deprecated. "
+ "Wrap `dynamic-link' in a `false-if-exception' yourself.")
+ (false-if-exception (dynamic-link filename)))
+
+(define (find-and-link-dynamic-module module-name)
+ (define (make-init-name mod-name)
+ (string-append "scm_init"
+ (list->string (map (lambda (c)
+ (if (or (char-alphabetic? c)
+ (char-numeric? c))
+ c
+ #\_))
+ (string->list mod-name)))
+ "_module"))
+
+ ;; Put the subdirectory for this module in the car of SUBDIR-AND-LIBNAME,
+ ;; and the `libname' (the name of the module prepended by `lib') in the cdr
+ ;; field. For example, if MODULE-NAME is the list (inet tcp-ip udp), then
+ ;; SUBDIR-AND-LIBNAME will be the pair ("inet/tcp-ip" . "libudp").
+ (let ((subdir-and-libname
+ (let loop ((dirs "")
+ (syms module-name))
+ (if (null? (cdr syms))
+ (cons dirs (string-append "lib" (symbol->string (car syms))))
+ (loop (string-append dirs (symbol->string (car syms)) "/")
+ (cdr syms)))))
+ (init (make-init-name (apply string-append
+ (map (lambda (s)
+ (string-append "_"
+ (symbol->string s)))
+ module-name)))))
+ (let ((subdir (car subdir-and-libname))
+ (libname (cdr subdir-and-libname)))
+
+ ;; Now look in each dir in %LOAD-PATH for `subdir/libfoo.la'. If that
+ ;; file exists, fetch the dlname from that file and attempt to link
+ ;; against it. If `subdir/libfoo.la' does not exist, or does not seem
+ ;; to name any shared library, look for `subdir/libfoo.so' instead and
+ ;; link against that.
+ (let check-dirs ((dir-list %load-path))
+ (if (null? dir-list)
+ #f
+ (let* ((dir (in-vicinity (car dir-list) subdir))
+ (sharlib-full
+ (or (try-using-libtool-name dir libname)
+ (try-using-sharlib-name dir libname))))
+ (if (and sharlib-full (file-exists? sharlib-full))
+ (link-dynamic-module sharlib-full init)
+ (check-dirs (cdr dir-list)))))))))
+
+(define (try-using-libtool-name libdir libname)
+ (let ((libtool-filename (in-vicinity libdir
+ (string-append libname ".la"))))
+ (and (file-exists? libtool-filename)
+ libtool-filename)))
+
+(define (try-using-sharlib-name libdir libname)
+ (in-vicinity libdir (string-append libname ".so")))
+
+(define (link-dynamic-module filename initname)
+ ;; Register any linked modules which have been registered on the C level
+ (register-modules #f)
+ (let ((dynobj (dynamic-link filename)))
+ (dynamic-call initname dynobj)
+ (register-modules dynobj)))
+
+(define (try-module-linked module-name)
+ (issue-deprecation-warning
+ "`try-module-linked' is deprecated."
+ "See the manual for how more on C extensions.")
+ (init-dynamic-module module-name))
+
+(define (try-module-dynamic-link module-name)
+ (issue-deprecation-warning
+ "`try-module-dynamic-link' is deprecated."
+ "See the manual for how more on C extensions.")
+ (and (find-and-link-dynamic-module module-name)
+ (init-dynamic-module module-name)))
+
+
+(define (list* . args)
+ (issue-deprecation-warning "'list*' is deprecated. Use 'cons*' instead.")
+ (apply cons* args))
+
+(define (feature? sym)
+ (issue-deprecation-warning
+ "`feature?' is deprecated. Use `provided?' instead.")
+ (provided? sym))
+
+(define-macro (eval-case . clauses)
+ (issue-deprecation-warning
+ "`eval-case' is deprecated. Use `eval-when' instead.")
+ ;; Practically speaking, eval-case only had load-toplevel and else as
+ ;; conditions.
+ (cond
+ ((assoc-ref clauses '(load-toplevel))
+ => (lambda (exps)
+ ;; the *unspecified so that non-toplevel definitions will be
+ ;; caught
+ `(begin *unspecified* . ,exps)))
+ ((assoc-ref clauses 'else)
+ => (lambda (exps)
+ `(begin *unspecified* . ,exps)))
+ (else
+ `(begin))))
+
+;; The strange prototype system for uniform arrays has been
+;; deprecated.
+(read-hash-extend
+ #\y
+ (lambda (c port)
+ (issue-deprecation-warning
+ "The `#y' bytevector syntax is deprecated. Use `#s8' instead.")
+ (let ((x (read port)))
+ (cond
+ ((list? x) (list->s8vector x))
+ (else (error "#y needs to be followed by a list" x))))))
+
+(define (unmemoize-expr . args)
+ (issue-deprecation-warning
+ "`unmemoize-expr' is deprecated. Use `unmemoize-expression' instead.")
+ (apply unmemoize-expression args))
+
+(define ($asinh z)
+ (issue-deprecation-warning
+ "`$asinh' is deprecated. Use `asinh' instead.")
+ (asinh z))
+(define ($acosh z)
+ (issue-deprecation-warning
+ "`$acosh' is deprecated. Use `acosh' instead.")
+ (acosh z))
+(define ($atanh z)
+ (issue-deprecation-warning
+ "`$atanh' is deprecated. Use `atanh' instead.")
+ (atanh z))
+(define ($sqrt z)
+ (issue-deprecation-warning
+ "`$sqrt' is deprecated. Use `sqrt' instead.")
+ (sqrt z))
+(define ($abs z)
+ (issue-deprecation-warning
+ "`$abs' is deprecated. Use `abs' instead.")
+ (abs z))
+(define ($exp z)
+ (issue-deprecation-warning
+ "`$exp' is deprecated. Use `exp' instead.")
+ (exp z))
+(define ($expt z1 z2)
+ (issue-deprecation-warning
+ "`$expt' is deprecated. Use `expt' instead.")
+ (expt z1 z2))
+(define ($log z)
+ (issue-deprecation-warning
+ "`$log' is deprecated. Use `log' instead.")
+ (log z))
+(define ($sin z)
+ (issue-deprecation-warning
+ "`$sin' is deprecated. Use `sin' instead.")
+ (sin z))
+(define ($cos z)
+ (issue-deprecation-warning
+ "`$cos' is deprecated. Use `cos' instead.")
+ (cos z))
+(define ($tan z)
+ (issue-deprecation-warning
+ "`$tan' is deprecated. Use `tan' instead.")
+ (tan z))
+(define ($asin z)
+ (issue-deprecation-warning
+ "`$asin' is deprecated. Use `asin' instead.")
+ (asin z))
+(define ($acos z)
+ (issue-deprecation-warning
+ "`$acos' is deprecated. Use `acos' instead.")
+ (acos z))
+(define ($atan z)
+ (issue-deprecation-warning
+ "`$atan' is deprecated. Use `atan' instead.")
+ (atan z))
+(define ($sinh z)
+ (issue-deprecation-warning
+ "`$sinh' is deprecated. Use `sinh' instead.")
+ (sinh z))
+(define ($cosh z)
+ (issue-deprecation-warning
+ "`$cosh' is deprecated. Use `cosh' instead.")
+ (cosh z))
+(define ($tanh z)
+ (issue-deprecation-warning
+ "`$tanh' is deprecated. Use `tanh' instead.")
+ (tanh z))
+
+(define (closure? x)
+ (issue-deprecation-warning
+ "`closure?' is deprecated. Use `procedure?' instead.")
+ (procedure? x))
+
+(define %nil #nil)
+
+;;; @bind is used by the old elisp code as a dynamic scoping mechanism.
+;;; Please let the Guile developers know if you are using this macro.
+;;;
+(define-syntax @bind
+ (lambda (x)
+ (define (bound-member id ids)
+ (cond ((null? ids) #f)
+ ((bound-identifier=? id (car ids)) #t)
+ ((bound-member (car ids) (cdr ids)))))
+
+ (issue-deprecation-warning
+ "`@bind' is deprecated. Use `with-fluids' instead.")
+
+ (syntax-case x ()
+ ((_ () b0 b1 ...)
+ #'(let () b0 b1 ...))
+ ((_ ((id val) ...) b0 b1 ...)
+ (and-map identifier? #'(id ...))
+ (if (let lp ((ids #'(id ...)))
+ (cond ((null? ids) #f)
+ ((bound-member (car ids) (cdr ids)) #t)
+ (else (lp (cdr ids)))))
+ (syntax-violation '@bind "duplicate bound identifier" x)
+ (with-syntax (((old-v ...) (generate-temporaries #'(id ...)))
+ ((v ...) (generate-temporaries #'(id ...))))
+ #'(let ((old-v id) ...
+ (v val) ...)
+ (dynamic-wind
+ (lambda ()
+ (set! id v) ...)
+ (lambda () b0 b1 ...)
+ (lambda ()
+ (set! id old-v) ...)))))))))
+
+;; There are deprecated definitions for module-ref-submodule and
+;; module-define-submodule! in boot-9.scm.
+
+;; Define (%app) and (%app modules), and have (app) alias (%app). This
+;; side-effects the-root-module, both to the submodules table and (through
+;; module-define-submodule! above) the obarray.
+;;
+(let ((%app (make-module 31)))
+ (set-module-name! %app '(%app))
+ (module-define-submodule! the-root-module '%app %app)
+ (module-define-submodule! the-root-module 'app %app)
+ (module-define-submodule! %app 'modules (resolve-module '() #f)))
+
+;; Allow code that poked %module-public-interface to keep on working.
+;;
+(set! module-public-interface
+ (let ((getter module-public-interface))
+ (lambda (mod)
+ (or (getter mod)
+ (cond
+ ((and=> (module-local-variable mod '%module-public-interface)
+ variable-ref)
+ => (lambda (iface)
+ (issue-deprecation-warning
+"Setting a module's public interface via munging %module-public-interface is
+deprecated. Use set-module-public-interface! instead.")
+ (set-module-public-interface! mod iface)
+ iface))
+ (else #f))))))
+
+(set! set-module-public-interface!
+ (let ((setter set-module-public-interface!))
+ (lambda (mod iface)
+ (setter mod iface)
+ (module-define! mod '%module-public-interface iface))))
+
+(define (bad-throw key . args)
+ (issue-deprecation-warning
+ "`bad-throw' in the default environment is deprecated.
+Find it in the `(ice-9 scm-style-repl)' module instead.")
+ (apply (@ (ice-9 scm-style-repl) bad-throw) key args))
+
+(define (error-catching-loop thunk)
+ (issue-deprecation-warning
+ "`error-catching-loop' in the default environment is deprecated.
+Find it in the `(ice-9 scm-style-repl)' module instead.")
+ ((@ (ice-9 scm-style-repl) error-catching-loop) thunk))
+
+(define (error-catching-repl r e p)
+ (issue-deprecation-warning
+ "`error-catching-repl' in the default environment is deprecated.
+Find it in the `(ice-9 scm-style-repl)' module instead.")
+ ((@ (ice-9 scm-style-repl) error-catching-repl) r e p))
+
+(define (scm-style-repl)
+ (issue-deprecation-warning
+ "`scm-style-repl' in the default environment is deprecated.
+Find it in the `(ice-9 scm-style-repl)' module instead, or
+better yet, use the repl from `(system repl repl)'.")
+ ((@ (ice-9 scm-style-repl) scm-style-repl)))
+
+
+;;; Apply-to-args had the following comment attached to it in boot-9, but it's
+;;; wrong-headed: in the mentioned case, a point should either be a record or
+;;; multiple values.
+;;;
+;;; apply-to-args is functionally redundant with apply and, worse,
+;;; is less general than apply since it only takes two arguments.
+;;;
+;;; On the other hand, apply-to-args is a syntacticly convenient way to
+;;; perform binding in many circumstances when the "let" family of
+;;; of forms don't cut it. E.g.:
+;;;
+;;; (apply-to-args (return-3d-mouse-coords)
+;;; (lambda (x y z)
+;;; ...))
+;;;
+
+(define (apply-to-args args fn)
+ (issue-deprecation-warning
+ "`apply-to-args' is deprecated. Include a local copy in your program.")
+ (apply fn args))
+
+(define (has-suffix? str suffix)
+ (issue-deprecation-warning
+ "`has-suffix?' is deprecated. Use `string-suffix?' instead (args reversed).")
+ (string-suffix? suffix str))
+
+(define scheme-file-suffix
+ (lambda ()
+ (issue-deprecation-warning
+ "`scheme-file-suffix' is deprecated. Use `%load-extensions' instead.")
+ ".scm"))
+
+
+
+;;; {Command Line Options}
+;;;
+
+(define (get-option argv kw-opts kw-args return)
+ (issue-deprecation-warning
+ "`get-option' is deprecated. Use `(ice-9 getopt-long)' instead.")
+ (cond
+ ((null? argv)
+ (return #f #f argv))
+
+ ((or (not (eq? #\- (string-ref (car argv) 0)))
+ (eq? (string-length (car argv)) 1))
+ (return 'normal-arg (car argv) (cdr argv)))
+
+ ((eq? #\- (string-ref (car argv) 1))
+ (let* ((kw-arg-pos (or (string-index (car argv) #\=)
+ (string-length (car argv))))
+ (kw (symbol->keyword (substring (car argv) 2 kw-arg-pos)))
+ (kw-opt? (member kw kw-opts))
+ (kw-arg? (member kw kw-args))
+ (arg (or (and (not (eq? kw-arg-pos (string-length (car argv))))
+ (substring (car argv)
+ (+ kw-arg-pos 1)
+ (string-length (car argv))))
+ (and kw-arg?
+ (begin (set! argv (cdr argv)) (car argv))))))
+ (if (or kw-opt? kw-arg?)
+ (return kw arg (cdr argv))
+ (return 'usage-error kw (cdr argv)))))
+
+ (else
+ (let* ((char (substring (car argv) 1 2))
+ (kw (symbol->keyword char)))
+ (cond
+
+ ((member kw kw-opts)
+ (let* ((rest-car (substring (car argv) 2 (string-length (car argv))))
+ (new-argv (if (= 0 (string-length rest-car))
+ (cdr argv)
+ (cons (string-append "-" rest-car) (cdr argv)))))
+ (return kw #f new-argv)))
+
+ ((member kw kw-args)
+ (let* ((rest-car (substring (car argv) 2 (string-length (car argv))))
+ (arg (if (= 0 (string-length rest-car))
+ (cadr argv)
+ rest-car))
+ (new-argv (if (= 0 (string-length rest-car))
+ (cddr argv)
+ (cdr argv))))
+ (return kw arg new-argv)))
+
+ (else (return 'usage-error kw argv)))))))
+
+(define (for-next-option proc argv kw-opts kw-args)
+ (issue-deprecation-warning
+ "`for-next-option' is deprecated. Use `(ice-9 getopt-long)' instead.")
+ (let loop ((argv argv))
+ (get-option argv kw-opts kw-args
+ (lambda (opt opt-arg argv)
+ (and opt (proc opt opt-arg argv loop))))))
+
+(define (display-usage-report kw-desc)
+ (issue-deprecation-warning
+ "`display-usage-report' is deprecated. Use `(ice-9 getopt-long)' instead.")
+ (for-each
+ (lambda (kw)
+ (or (eq? (car kw) #t)
+ (eq? (car kw) 'else)
+ (let* ((opt-desc kw)
+ (help (cadr opt-desc))
+ (opts (car opt-desc))
+ (opts-proper (if (string? (car opts)) (cdr opts) opts))
+ (arg-name (if (string? (car opts))
+ (string-append "<" (car opts) ">")
+ ""))
+ (left-part (string-append
+ (with-output-to-string
+ (lambda ()
+ (map (lambda (x) (display (keyword->symbol x)) (display " "))
+ opts-proper)))
+ arg-name))
+ (middle-part (if (and (< (string-length left-part) 30)
+ (< (string-length help) 40))
+ (make-string (- 30 (string-length left-part)) #\space)
+ "\n\t")))
+ (display left-part)
+ (display middle-part)
+ (display help)
+ (newline))))
+ kw-desc))
+
+(define (transform-usage-lambda cases)
+ (issue-deprecation-warning
+ "`display-usage-report' is deprecated. Use `(ice-9 getopt-long)' instead.")
+ (let* ((raw-usage (delq! 'else (map car cases)))
+ (usage-sans-specials (map (lambda (x)
+ (or (and (not (list? x)) x)
+ (and (symbol? (car x)) #t)
+ (and (boolean? (car x)) #t)
+ x))
+ raw-usage))
+ (usage-desc (delq! #t usage-sans-specials))
+ (kw-desc (map car usage-desc))
+ (kw-opts (apply append (map (lambda (x) (and (not (string? (car x))) x)) kw-desc)))
+ (kw-args (apply append (map (lambda (x) (and (string? (car x)) (cdr x))) kw-desc)))
+ (transmogrified-cases (map (lambda (case)
+ (cons (let ((opts (car case)))
+ (if (or (boolean? opts) (eq? 'else opts))
+ opts
+ (cond
+ ((symbol? (car opts)) opts)
+ ((boolean? (car opts)) opts)
+ ((string? (caar opts)) (cdar opts))
+ (else (car opts)))))
+ (cdr case)))
+ cases)))
+ `(let ((%display-usage (lambda () (display-usage-report ',usage-desc))))
+ (lambda (%argv)
+ (let %next-arg ((%argv %argv))
+ (get-option %argv
+ ',kw-opts
+ ',kw-args
+ (lambda (%opt %arg %new-argv)
+ (case %opt
+ ,@ transmogrified-cases))))))))
+
+
+
+;;; {collect}
+;;;
+;;; Similar to `begin' but returns a list of the results of all constituent
+;;; forms instead of the result of the last form.
+;;;
+
+(define-syntax collect
+ (lambda (x)
+ (issue-deprecation-warning
+ "`collect' is deprecated. Define it yourself.")
+ (syntax-case x ()
+ ((_) #''())
+ ((_ x x* ...)
+ #'(let ((val x))
+ (cons val (collect x* ...)))))))
+
+
+
+
+(define (assert-repl-silence v)
+ (issue-deprecation-warning
+ "`assert-repl-silence' has moved to `(ice-9 scm-style-repl)'.")
+ ((@ (ice-9 scm-style-repl) assert-repl-silence) v))
+
+(define (assert-repl-print-unspecified v)
+ (issue-deprecation-warning
+ "`assert-repl-print-unspecified' has moved to `(ice-9 scm-style-repl)'.")
+ ((@ (ice-9 scm-style-repl) assert-repl-print-unspecified) v))
+
+(define (assert-repl-verbosity v)
+ (issue-deprecation-warning
+ "`assert-repl-verbosity' has moved to `(ice-9 scm-style-repl)'.")
+ ((@ (ice-9 scm-style-repl) assert-repl-verbosity) v))
+
+(define (set-repl-prompt! v)
+ (issue-deprecation-warning
+ "`set-repl-prompt!' is deprecated. Use `repl-default-prompt-set!' from
+the `(system repl common)' module.")
+ ;; Avoid @, as when bootstrapping it will cause the (system repl common)
+ ;; module to be loaded at expansion time, which eventually loads srfi-1, but
+ ;; that fails due to an unbuilt supporting lib... grrrrrrrrr.
+ ((module-ref (resolve-interface '(system repl common))
+ 'repl-default-prompt-set!)
+ v))
+
+(define (set-batch-mode?! arg)
+ (cond
+ (arg
+ (issue-deprecation-warning
+ "`set-batch-mode?!' is deprecated. Use `ensure-batch-mode!' instead.")
+ (ensure-batch-mode!))
+ (else
+ (issue-deprecation-warning
+ "`set-batch-mode?!' with an argument of `#f' is deprecated. Use the
+`*repl-stack*' fluid instead.")
+ #t)))
+
+(define (repl read evaler print)
+ (issue-deprecation-warning
+ "`repl' is deprecated. Define it yourself.")
+ (let loop ((source (read (current-input-port))))
+ (print (evaler source))
+ (loop (read (current-input-port)))))
+
+(define (pre-unwind-handler-dispatch key . args)
+ (issue-deprecation-warning
+ "`pre-unwind-handler-dispatch' is deprecated. Use
+`default-pre-unwind-handler' from `(ice-9 scm-style-repl)' directly.")
+ (apply (@ (ice-9 scm-style-repl) default-pre-unwind-handler) key args))
+
+(define (default-pre-unwind-handler key . args)
+ (issue-deprecation-warning
+ "`default-pre-unwind-handler' is deprecated. Use it from
+`(ice-9 scm-style-repl)' if you need it.")
+ (apply (@ (ice-9 scm-style-repl) default-pre-unwind-handler) key args))
+
+(define (handle-system-error key . args)
+ (issue-deprecation-warning
+ "`handle-system-error' is deprecated. Use it from
+`(ice-9 scm-style-repl)' if you need it.")
+ (apply (@ (ice-9 scm-style-repl) handle-system-error) key args))
+
+(define-syntax stack-saved?
+ (make-variable-transformer
+ (lambda (x)
+ (issue-deprecation-warning
+ "`stack-saved?' is deprecated. Use it from
+`(ice-9 save-stack)' if you need it.")
+ (syntax-case x (set!)
+ ((set! id val)
+ (identifier? #'id)
+ #'(set! (@ (ice-9 save-stack) stack-saved?) val))
+ (id
+ (identifier? #'id)
+ #'(@ (ice-9 save-stack) stack-saved?))))))
+
+(define-syntax the-last-stack
+ (lambda (x)
+ (issue-deprecation-warning
+ "`the-last-stack' is deprecated. Use it from `(ice-9 save-stack)'
+if you need it.")
+ (syntax-case x ()
+ (id
+ (identifier? #'id)
+ #'(@ (ice-9 save-stack) the-last-stack)))))
+
+(define (save-stack . args)
+ (issue-deprecation-warning
+ "`save-stack' is deprecated. Use it from `(ice-9 save-stack)' if you need
+it.")
+ (apply (@ (ice-9 save-stack) save-stack) args))
+
+(define (named-module-use! user usee)
+ (issue-deprecation-warning
+ "`named-module-use!' is deprecated. Define it yourself if you need it.")
+ (module-use! (resolve-module user) (resolve-interface usee)))
+
+(define (top-repl)
+ (issue-deprecation-warning
+ "`top-repl' has moved to the `(ice-9 top-repl)' module.")
+ ((module-ref (resolve-module '(ice-9 top-repl)) 'top-repl)))
+
+(set! debug-enable
+ (let ((debug-enable debug-enable))
+ (lambda opts
+ (if (memq 'debug opts)
+ (begin
+ (issue-deprecation-warning
+ "`(debug-enable 'debug)' is obsolete and has no effect."
+ "Remove it from your code.")
+ (apply debug-enable (delq 'debug opts)))
+ (apply debug-enable opts)))))
+
+(define (turn-on-debugging)
+ (issue-deprecation-warning
+ "`(turn-on-debugging)' is obsolete and usually has no effect."
+ "Debugging capabilities are present by default.")
+ (debug-enable 'backtrace)
+ (read-enable 'positions))
+
+(define (read-hash-procedures-warning)
+ (issue-deprecation-warning
+ "`read-hash-procedures' is deprecated."
+ "Use the fluid `%read-hash-procedures' instead."))
+
+(define-syntax read-hash-procedures
+ (identifier-syntax
+ (_
+ (begin (read-hash-procedures-warning)
+ (fluid-ref %read-hash-procedures)))
+ ((set! _ expr)
+ (begin (read-hash-procedures-warning)
+ (fluid-set! %read-hash-procedures expr)))))
+
+(define (process-define-module args)
+ (define (missing kw)
+ (error "missing argument to define-module keyword" kw))
+ (define (unrecognized arg)
+ (error "unrecognized define-module argument" arg))
+
+ (issue-deprecation-warning
+ "`process-define-module' is deprecated. Use `define-module*' instead.")
+
+ (let ((name (car args))
+ (filename #f)
+ (pure? #f)
+ (version #f)
+ (system? #f)
+ (duplicates '())
+ (transformer #f))
+ (let loop ((kws (cdr args))
+ (imports '())
+ (exports '())
+ (re-exports '())
+ (replacements '())
+ (autoloads '()))
+ (if (null? kws)
+ (define-module* name
+ #\filename filename #\pure pure? #\version version
+ #\duplicates duplicates #\transformer transformer
+ #\imports (reverse! imports)
+ #\exports exports
+ #\re-exports re-exports
+ #\replacements replacements
+ #\autoloads autoloads)
+ (case (car kws)
+ ((#\use-module #\use-syntax)
+ (or (pair? (cdr kws))
+ (missing (car kws)))
+ (cond
+ ((equal? (cadr kws) '(ice-9 syncase))
+ (issue-deprecation-warning
+ "(ice-9 syncase) is deprecated. Support for syntax-case is now in Guile core.")
+ (loop (cddr kws)
+ imports exports re-exports replacements autoloads))
+ (else
+ (let ((iface-spec (cadr kws)))
+ (if (eq? (car kws) #\use-syntax)
+ (set! transformer iface-spec))
+ (loop (cddr kws)
+ (cons iface-spec imports) exports re-exports
+ replacements autoloads)))))
+ ((#\autoload)
+ (or (and (pair? (cdr kws)) (pair? (cddr kws)))
+ (missing (car kws)))
+ (let ((name (cadr kws))
+ (bindings (caddr kws)))
+ (loop (cdddr kws)
+ imports exports re-exports
+ replacements (cons* name bindings autoloads))))
+ ((#\no-backtrace)
+ ;; FIXME: deprecate?
+ (set! system? #t)
+ (loop (cdr kws)
+ imports exports re-exports replacements autoloads))
+ ((#\pure)
+ (set! pure? #t)
+ (loop (cdr kws)
+ imports exports re-exports replacements autoloads))
+ ((#\version)
+ (or (pair? (cdr kws))
+ (missing (car kws)))
+ (set! version (cadr kws))
+ (loop (cddr kws)
+ imports exports re-exports replacements autoloads))
+ ((#\duplicates)
+ (if (not (pair? (cdr kws)))
+ (missing (car kws)))
+ (set! duplicates (cadr kws))
+ (loop (cddr kws)
+ imports exports re-exports replacements autoloads))
+ ((#\export #\export-syntax)
+ (or (pair? (cdr kws))
+ (missing (car kws)))
+ (loop (cddr kws)
+ imports (append exports (cadr kws)) re-exports
+ replacements autoloads))
+ ((#\re-export #\re-export-syntax)
+ (or (pair? (cdr kws))
+ (missing (car kws)))
+ (loop (cddr kws)
+ imports exports (append re-exports (cadr kws))
+ replacements autoloads))
+ ((#\replace #\replace-syntax)
+ (or (pair? (cdr kws))
+ (missing (car kws)))
+ (loop (cddr kws)
+ imports exports re-exports
+ (append replacements (cadr kws)) autoloads))
+ ((#\filename)
+ (or (pair? (cdr kws))
+ (missing (car kws)))
+ (set! filename (cadr kws))
+ (loop (cddr kws)
+ imports exports re-exports replacements autoloads))
+ (else
+ (unrecognized kws)))))))
+
+(define-syntax fluid-let-syntax
+ (lambda (x)
+ (issue-deprecation-warning
+ "`fluid-let-syntax' is deprecated. Use syntax parameters instead.")
+ (syntax-case x ()
+ ((_ ((k v) ...) body0 body ...)
+ #'(syntax-parameterize ((k v) ...)
+ body0 body ...)))))
+
+(define (close-io-port port)
+ (issue-deprecation-warning
+ "`close-io-port' is deprecated. Use `close-port' instead.")
+ (close-port port))
+
+(define (set-system-module! m s)
+ (issue-deprecation-warning
+ "`set-system-module!' is deprecated. There is no need to use it.")
+ (set-procedure-property! (module-eval-closure m) 'system-module s))
+
+(set! module-eval-closure
+ (lambda (m)
+ (issue-deprecation-warning
+ "`module-eval-closure' is deprecated. Use module-variable or module-define! instead.")
+ (standard-eval-closure m)))
+
+;; Legacy definition. We can't make it identifier-syntax yet though,
+;; because compiled code might rely on it.
+(define char-code-limit 256)
+;;;; Copyright (C) 2000,2001, 2002, 2003, 2006, 2009, 2010 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+
+;; * This module exports:
+;;
+;; file-commentary -- a procedure that returns a file's "commentary"
+;;
+;; documentation-files -- a search-list of files using the Guile
+;; Documentation Format Version 2.
+;;
+;; search-documentation-files -- a procedure that takes NAME (a symbol)
+;; and searches `documentation-files' for
+;; associated documentation. optional
+;; arg FILES is a list of filenames to use
+;; instead of `documentation-files'.
+;;
+;; object-documentation -- a procedure that returns its arg's docstring
+;;
+;; * Guile Documentation Format
+;;
+;; Here is the complete and authoritative documentation for the Guile
+;; Documentation Format Version 2:
+;;
+;; HEADER
+;; ^LPROC1
+;; DOCUMENTATION1
+;;
+;; ^LPROC2
+;; DOCUMENTATION2
+;;
+;; ^L...
+;;
+;; The HEADER is completely ignored. The "^L" are formfeeds. PROC1, PROC2
+;; and so on are symbols that name the element documented. DOCUMENTATION1,
+;; DOCUMENTATION2 and so on are the related documentation, w/o any further
+;; formatting. Note that there are two newlines before the next formfeed;
+;; these are discarded when the documentation is read in.
+;;
+;; (Version 1, corresponding to guile-1.4 and prior, is documented as being
+;; not documented anywhere except by this embarrassingly circular comment.)
+;;
+;; * File Commentary
+;;
+;; A file's commentary is the body of text found between comments
+;; ;;; Commentary:
+;; and
+;; ;;; Code:
+;; both of which must be at the beginning of the line. In the result string,
+;; semicolons at the beginning of each line are discarded.
+;;
+;; You can specify to `file-commentary' alternate begin and end strings, and
+;; scrub procedure. Use #t to get default values. For example:
+;;
+;; (file-commentary "documentation.scm")
+;; You should see this text!
+;;
+;; (file-commentary "documentation.scm" "^;;; Code:" "ends here$")
+;; You should see the rest of this file.
+;;
+;; (file-commentary "documentation.scm" #t #t string-upcase)
+;; You should see this text very loudly (note semicolons untouched).
+
+;;; Code:
+
+(define-module (ice-9 documentation)
+ \:use-module (ice-9 rdelim)
+ \:export (file-commentary
+ documentation-files search-documentation-files
+ object-documentation)
+ \:autoload (ice-9 regex) (match:suffix)
+ \:no-backtrace)
+
+
+;;
+;; commentary extraction
+;;
+
+(define (file-commentary filename . cust) ; (IN-LINE-RE AFTER-LINE-RE SCRUB)
+
+ ;; These are constants but are not at the top level because the repl in
+ ;; boot-9.scm loads session.scm which in turn loads this file, and we want
+ ;; that to work even even when regexps are not available (ie. make-regexp
+ ;; doesn't exist), as for instance is the case on mingw.
+ ;;
+ (define default-in-line-re (make-regexp "^;;; Commentary:"))
+ (define default-after-line-re (make-regexp "^;;; Code:"))
+ (define default-scrub (let ((dirt (make-regexp "^;+")))
+ (lambda (line)
+ (let ((m (regexp-exec dirt line)))
+ (if m (match:suffix m) line)))))
+
+ ;; fixme: might be cleaner to use optargs here...
+ (let ((in-line-re (if (> 1 (length cust))
+ default-in-line-re
+ (let ((v (car cust)))
+ (cond ((regexp? v) v)
+ ((string? v) (make-regexp v))
+ (else default-in-line-re)))))
+ (after-line-re (if (> 2 (length cust))
+ default-after-line-re
+ (let ((v (cadr cust)))
+ (cond ((regexp? v) v)
+ ((string? v) (make-regexp v))
+ (else default-after-line-re)))))
+ (scrub (if (> 3 (length cust))
+ default-scrub
+ (let ((v (caddr cust)))
+ (cond ((procedure? v) v)
+ (else default-scrub))))))
+ (call-with-input-file filename
+ (lambda (port)
+ (let loop ((line (read-delimited "\n" port))
+ (doc "")
+ (parse-state 'before))
+ (if (or (eof-object? line) (eq? 'after parse-state))
+ doc
+ (let ((new-state
+ (cond ((regexp-exec in-line-re line) 'in)
+ ((regexp-exec after-line-re line) 'after)
+ (else parse-state))))
+ (if (eq? 'after new-state)
+ doc
+ (loop (read-delimited "\n" port)
+ (if (and (eq? 'in new-state) (eq? 'in parse-state))
+ (string-append doc (scrub line) "\n")
+ doc)
+ new-state)))))))))
+
+
+
+;;
+;; documentation-files is the list of places to look for documentation
+;;
+(define documentation-files
+ (map (lambda (vicinity)
+ (in-vicinity (vicinity) "guile-procedures.txt"))
+ (list %library-dir
+ %package-data-dir
+ %site-dir
+ (lambda () "."))))
+
+(define entry-delimiter "\f")
+
+(define (find-documentation-in-file name file)
+ (and (file-exists? file)
+ (call-with-input-file file
+ (lambda (port)
+ (let ((name (symbol->string name)))
+ (let ((len (string-length name)))
+ (read-delimited entry-delimiter port) ;skip to first entry
+ (let loop ((entry (read-delimited entry-delimiter port)))
+ (cond ((eof-object? entry) #f)
+ ;; match?
+ ((and ;; large enough?
+ (>= (string-length entry) len)
+ ;; matching name?
+ (string=? (substring entry 0 len) name)
+ ;; terminated?
+ (memq (string-ref entry len) '(#\newline)))
+ ;; cut away name tag and extra surrounding newlines
+ (substring entry (+ len 2) (- (string-length entry) 2)))
+ (else (loop (read-delimited entry-delimiter port)))))))))))
+
+(define (search-documentation-files name . files)
+ (or-map (lambda (file)
+ (find-documentation-in-file name file))
+ (cond ((null? files) documentation-files)
+ (else files))))
+
+(define (object-documentation object)
+ "Return the docstring for OBJECT.
+OBJECT can be a procedure, macro or any object that has its
+`documentation' property set."
+ (or (and (procedure? object)
+ (procedure-documentation object))
+ (object-property object 'documentation)
+ (and (macro? object)
+ (object-documentation (macro-transformer object)))
+ (and (procedure? object)
+ (procedure-name object)
+ (let ((docstring (search-documentation-files
+ (procedure-name object))))
+ (if docstring
+ (set-procedure-property! object 'documentation docstring))
+ docstring))))
+
+;;; documentation.scm ends here
+;;; Evaluating code from users
+
+;;; Copyright (C) 2011, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (ice-9 eval-string)
+ #\use-module (system base compile)
+ #\use-module (system base language)
+ #\use-module (system vm program)
+ #\replace (eval-string))
+
+(define (ensure-language x)
+ (if (language? x)
+ x
+ (lookup-language x)))
+
+(define* (read-and-eval port #\key (lang (current-language)))
+ (parameterize ((current-language (ensure-language lang)))
+ (define (read)
+ ((language-reader (current-language)) port (current-module)))
+ (define (eval exp)
+ ((language-evaluator (current-language)) exp (current-module)))
+
+ (let ((exp (read)))
+ (if (eof-object? exp)
+ ;; The behavior of read-and-compile and of the old
+ ;; eval-string.
+ *unspecified*
+ (let lp ((exp exp))
+ (call-with-values
+ (lambda () (eval exp))
+ (lambda vals
+ (let ((next (read)))
+ (cond
+ ((eof-object? next)
+ (apply values vals))
+ (else
+ (lp next)))))))))))
+
+(define* (eval-string str #\key
+ (module (current-module))
+ (file #f)
+ (line #f)
+ (column #f)
+ (lang (current-language))
+ (compile? #f))
+ (define (maybe-with-module module thunk)
+ (if module
+ (save-module-excursion
+ (lambda ()
+ (set-current-module module)
+ (thunk)))
+ (thunk)))
+
+ (let ((lang (ensure-language lang)))
+ (call-with-input-string
+ str
+ (lambda (port)
+ (maybe-with-module
+ module
+ (lambda ()
+ (if module
+ (set-current-module module))
+ (if file
+ (set-port-filename! port file))
+ (if line
+ (set-port-line! port line))
+ (if column
+ (set-port-column! port line))
+
+ (if (or compile? (not (language-evaluator lang)))
+ ((make-program (read-and-compile port #\from lang #\to 'objcode)))
+ (read-and-eval port #\lang lang))))))))
+;;; -*- mode: scheme; coding: utf-8; -*-
+
+;;;; Copyright (C) 2009, 2010, 2012, 2013 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+
+;;; Commentary:
+
+;;; Scheme eval, written in Scheme.
+;;;
+;;; Expressions are first expanded, by the syntax expander (i.e.
+;;; psyntax), then memoized into internal forms. The evaluator itself
+;;; only operates on the internal forms ("memoized expressions").
+;;;
+;;; Environments are represented as linked lists of the form (VAL ... .
+;;; MOD). If MOD is #f, it means the environment was captured before
+;;; modules were booted. If MOD is the literal value '(), we are
+;;; evaluating at the top level, and so should track changes to the
+;;; current module.
+;;;
+;;; Evaluate this in Emacs to make code indentation work right:
+;;;
+;;; (put 'memoized-expression-case 'scheme-indent-function 1)
+;;;
+
+;;; Code:
+
+
+
+(eval-when (compile)
+ (define-syntax capture-env
+ (syntax-rules ()
+ ((_ (exp ...))
+ (let ((env (exp ...)))
+ (capture-env env)))
+ ((_ env)
+ (if (null? env)
+ (current-module)
+ (if (not env)
+ ;; the and current-module checks that modules are booted,
+ ;; and thus the-root-module is defined
+ (and (current-module) the-root-module)
+ env)))))
+
+ ;; Fast case for procedures with fixed arities.
+ (define-syntax make-fixed-closure
+ (lambda (x)
+ (define *max-static-argument-count* 8)
+ (define (make-formals n)
+ (map (lambda (i)
+ (datum->syntax
+ x
+ (string->symbol
+ (string (integer->char (+ (char->integer #\a) i))))))
+ (iota n)))
+ (syntax-case x ()
+ ((_ eval nreq body env) (not (identifier? #'env))
+ #'(let ((e env))
+ (make-fixed-closure eval nreq body e)))
+ ((_ eval nreq body env)
+ #`(case nreq
+ #,@(map (lambda (nreq)
+ (let ((formals (make-formals nreq)))
+ #`((#,nreq)
+ (lambda (#,@formals)
+ (eval body
+ (cons* #,@(reverse formals) env))))))
+ (iota *max-static-argument-count*))
+ (else
+ #,(let ((formals (make-formals *max-static-argument-count*)))
+ #`(lambda (#,@formals . more)
+ (let lp ((new-env (cons* #,@(reverse formals) env))
+ (nreq (- nreq #,*max-static-argument-count*))
+ (args more))
+ (if (zero? nreq)
+ (eval body
+ (if (null? args)
+ new-env
+ (scm-error 'wrong-number-of-args
+ "eval" "Wrong number of arguments"
+ '() #f)))
+ (if (null? args)
+ (scm-error 'wrong-number-of-args
+ "eval" "Wrong number of arguments"
+ '() #f)
+ (lp (cons (car args) new-env)
+ (1- nreq)
+ (cdr args)))))))))))))
+
+ (define-syntax call
+ (lambda (x)
+ (define *max-static-call-count* 4)
+ (syntax-case x ()
+ ((_ eval proc nargs args env) (identifier? #'env)
+ #`(case nargs
+ #,@(map (lambda (nargs)
+ #`((#,nargs)
+ (proc
+ #,@(map
+ (lambda (n)
+ (let lp ((n n) (args #'args))
+ (if (zero? n)
+ #`(eval (car #,args) env)
+ (lp (1- n) #`(cdr #,args)))))
+ (iota nargs)))))
+ (iota *max-static-call-count*))
+ (else
+ (apply proc
+ #,@(map
+ (lambda (n)
+ (let lp ((n n) (args #'args))
+ (if (zero? n)
+ #`(eval (car #,args) env)
+ (lp (1- n) #`(cdr #,args)))))
+ (iota *max-static-call-count*))
+ (let lp ((exps #,(let lp ((n *max-static-call-count*)
+ (args #'args))
+ (if (zero? n)
+ args
+ (lp (1- n) #`(cdr #,args)))))
+ (args '()))
+ (if (null? exps)
+ (reverse args)
+ (lp (cdr exps)
+ (cons (eval (car exps) env) args)))))))))))
+
+ ;; This macro could be more straightforward if the compiler had better
+ ;; copy propagation. As it is we do some copy propagation by hand.
+ (define-syntax mx-bind
+ (lambda (x)
+ (syntax-case x ()
+ ((_ data () body)
+ #'body)
+ ((_ data (a . b) body) (and (identifier? #'a) (identifier? #'b))
+ #'(let ((a (car data))
+ (b (cdr data)))
+ body))
+ ((_ data (a . b) body) (identifier? #'a)
+ #'(let ((a (car data))
+ (xb (cdr data)))
+ (mx-bind xb b body)))
+ ((_ data (a . b) body)
+ #'(let ((xa (car data))
+ (xb (cdr data)))
+ (mx-bind xa a (mx-bind xb b body))))
+ ((_ data v body) (identifier? #'v)
+ #'(let ((v data))
+ body)))))
+
+ ;; The resulting nested if statements will be an O(n) dispatch. Once
+ ;; we compile `case' effectively, this situation will improve.
+ (define-syntax mx-match
+ (lambda (x)
+ (syntax-case x (quote)
+ ((_ mx data tag)
+ #'(error "what" mx))
+ ((_ mx data tag (('type pat) body) c* ...)
+ #`(if (eqv? tag #,(or (memoized-typecode (syntax->datum #'type))
+ (error "not a typecode" #'type)))
+ (mx-bind data pat body)
+ (mx-match mx data tag c* ...))))))
+
+ (define-syntax memoized-expression-case
+ (lambda (x)
+ (syntax-case x ()
+ ((_ mx c ...)
+ #'(let ((tag (memoized-expression-typecode mx))
+ (data (memoized-expression-data mx)))
+ (mx-match mx data tag c ...)))))))
+
+
+;;;
+;;; On 18 Feb 2010, I did a profile of how often the various memoized expression
+;;; types occur when getting to a prompt on a fresh build. Here are the numbers
+;;; I got:
+;;;
+;;; lexical-ref: 32933054
+;;; call: 20281547
+;;; toplevel-ref: 13228724
+;;; if: 9156156
+;;; quote: 6610137
+;;; let: 2619707
+;;; lambda: 1010921
+;;; begin: 948945
+;;; lexical-set: 509862
+;;; call-with-values: 139668
+;;; apply: 49402
+;;; module-ref: 14468
+;;; define: 1259
+;;; toplevel-set: 328
+;;; dynwind: 162
+;;; with-fluids: 0
+;;; call/cc: 0
+;;; module-set: 0
+;;;
+;;; So until we compile `case' into a computed goto, we'll order the clauses in
+;;; `eval' in this order, to put the most frequent cases first.
+;;;
+
+(define primitive-eval
+ (let ()
+ ;; We pre-generate procedures with fixed arities, up to some number of
+ ;; arguments; see make-fixed-closure above.
+
+ ;; A unique marker for unbound keywords.
+ (define unbound-arg (list 'unbound-arg))
+
+ ;; Procedures with rest, optional, or keyword arguments, potentially with
+ ;; multiple arities, as with case-lambda.
+ (define (make-general-closure env body nreq rest? nopt kw inits alt)
+ (define alt-proc
+ (and alt ; (body docstring nreq ...)
+ (let* ((body (car alt))
+ (spec (cddr alt))
+ (nreq (car spec))
+ (rest (if (null? (cdr spec)) #f (cadr spec)))
+ (tail (and (pair? (cdr spec)) (pair? (cddr spec)) (cddr spec)))
+ (nopt (if tail (car tail) 0))
+ (kw (and tail (cadr tail)))
+ (inits (if tail (caddr tail) '()))
+ (alt (and tail (cadddr tail))))
+ (make-general-closure env body nreq rest nopt kw inits alt))))
+ (define (set-procedure-arity! proc)
+ (let lp ((alt alt) (nreq nreq) (nopt nopt) (rest? rest?))
+ (if (not alt)
+ (begin
+ (set-procedure-property! proc 'arglist
+ (list nreq
+ nopt
+ (if kw (cdr kw) '())
+ (and kw (car kw))
+ (and rest? '_)))
+ (set-procedure-minimum-arity! proc nreq nopt rest?))
+ (let* ((spec (cddr alt))
+ (nreq* (car spec))
+ (rest?* (if (null? (cdr spec)) #f (cadr spec)))
+ (tail (and (pair? (cdr spec)) (pair? (cddr spec)) (cddr spec)))
+ (nopt* (if tail (car tail) 0))
+ (alt* (and tail (cadddr tail))))
+ (if (or (< nreq* nreq)
+ (and (= nreq* nreq)
+ (if rest?
+ (and rest?* (> nopt* nopt))
+ (or rest?* (> nopt* nopt)))))
+ (lp alt* nreq* nopt* rest?*)
+ (lp alt* nreq nopt rest?)))))
+ proc)
+ (set-procedure-arity!
+ (lambda %args
+ (let lp ((env env)
+ (nreq* nreq)
+ (args %args))
+ (if (> nreq* 0)
+ ;; First, bind required arguments.
+ (if (null? args)
+ (if alt
+ (apply alt-proc %args)
+ (scm-error 'wrong-number-of-args
+ "eval" "Wrong number of arguments"
+ '() #f))
+ (lp (cons (car args) env)
+ (1- nreq*)
+ (cdr args)))
+ ;; Move on to optional arguments.
+ (if (not kw)
+ ;; Without keywords, bind optionals from arguments.
+ (let lp ((env env)
+ (nopt nopt)
+ (args args)
+ (inits inits))
+ (if (zero? nopt)
+ (if rest?
+ (eval body (cons args env))
+ (if (null? args)
+ (eval body env)
+ (if alt
+ (apply alt-proc %args)
+ (scm-error 'wrong-number-of-args
+ "eval" "Wrong number of arguments"
+ '() #f))))
+ (if (null? args)
+ (lp (cons (eval (car inits) env) env)
+ (1- nopt) args (cdr inits))
+ (lp (cons (car args) env)
+ (1- nopt) (cdr args) (cdr inits)))))
+ (let lp ((env env)
+ (nopt* nopt)
+ (args args)
+ (inits inits))
+ (cond
+ ;; With keywords, we stop binding optionals at the
+ ;; first keyword.
+ ((> nopt* 0)
+ (if (or (null? args) (keyword? (car args)))
+ (lp (cons (eval (car inits) env) env)
+ (1- nopt*) args (cdr inits))
+ (lp (cons (car args) env)
+ (1- nopt*) (cdr args) (cdr inits))))
+ ;; Finished with optionals.
+ ((and alt (pair? args) (not (keyword? (car args)))
+ (not rest?))
+ ;; Too many positional args, no #\rest arg,
+ ;; and we have an alternate.
+ (apply alt-proc %args))
+ (else
+ (let* ((aok (car kw))
+ (kw (cdr kw))
+ (kw-base (+ nopt nreq (if rest? 1 0)))
+ (imax (let lp ((imax (1- kw-base)) (kw kw))
+ (if (null? kw)
+ imax
+ (lp (max (cdar kw) imax)
+ (cdr kw)))))
+ ;; Fill in kwargs with "undefined" vals.
+ (env (let lp ((i kw-base)
+ ;; Also, here we bind the rest
+ ;; arg, if any.
+ (env (if rest?
+ (cons args env)
+ env)))
+ (if (<= i imax)
+ (lp (1+ i) (cons unbound-arg env))
+ env))))
+ ;; Now scan args for keywords.
+ (let lp ((args args))
+ (cond
+ ((pair? args)
+ (cond
+ ((keyword? (car args))
+ (let ((k (car args))
+ (args (cdr args)))
+ (cond
+ ((assq k kw)
+ => (lambda (kw-pair)
+ ;; Found a known keyword; set its value.
+ (if (pair? args)
+ (let ((v (car args))
+ (args (cdr args)))
+ (list-set! env
+ (- imax (cdr kw-pair))
+ v)
+ (lp args))
+ (scm-error 'keyword-argument-error
+ "eval"
+ "Keyword argument has no value"
+ '() (list k)))))
+ ;; Otherwise unknown keyword.
+ (aok
+ (lp (if (pair? args) (cdr args) args)))
+ (else
+ (scm-error 'keyword-argument-error
+ "eval" "Unrecognized keyword"
+ '() (list k))))))
+ (rest?
+ ;; Be lenient parsing rest args.
+ (lp (cdr args)))
+ (else
+ (scm-error 'keyword-argument-error
+ "eval" "Invalid keyword"
+ '() (list (car args))))))
+ (else
+ ;; Finished parsing keywords. Fill in
+ ;; uninitialized kwargs by evalling init
+ ;; expressions in their appropriate
+ ;; environment.
+ (let lp ((i (- imax kw-base))
+ (inits inits))
+ (if (pair? inits)
+ (let ((tail (list-tail env i)))
+ (if (eq? (car tail) unbound-arg)
+ (set-car! tail
+ (eval (car inits)
+ (cdr tail))))
+ (lp (1- i) (cdr inits)))
+ ;; Finally, eval the body.
+ (eval body env)))))
+ )))))))))))
+
+ ;; The "engine". EXP is a memoized expression.
+ (define (eval exp env)
+ (memoized-expression-case exp
+ (('lexical-ref n)
+ (list-ref env n))
+
+ (('call (f nargs . args))
+ (let ((proc (eval f env)))
+ (call eval proc nargs args env)))
+
+ (('toplevel-ref var-or-sym)
+ (variable-ref
+ (if (variable? var-or-sym)
+ var-or-sym
+ (memoize-variable-access! exp
+ (capture-env (if (pair? env)
+ (cdr (last-pair env))
+ env))))))
+
+ (('if (test consequent . alternate))
+ (if (eval test env)
+ (eval consequent env)
+ (eval alternate env)))
+
+ (('quote x)
+ x)
+
+ (('let (inits . body))
+ (let lp ((inits inits) (new-env (capture-env env)))
+ (if (null? inits)
+ (eval body new-env)
+ (lp (cdr inits)
+ (cons (eval (car inits) env) new-env)))))
+
+ (('lambda (body docstring nreq . tail))
+ (let ((proc
+ (if (null? tail)
+ (make-fixed-closure eval nreq body (capture-env env))
+ (if (null? (cdr tail))
+ (make-general-closure (capture-env env) body
+ nreq (car tail)
+ 0 #f '() #f)
+ (apply make-general-closure (capture-env env)
+ body nreq tail)))))
+ (when docstring
+ (set-procedure-property! proc 'documentation docstring))
+ proc))
+
+ (('begin (first . rest))
+ (let lp ((first first) (rest rest))
+ (if (null? rest)
+ (eval first env)
+ (begin
+ (eval first env)
+ (lp (car rest) (cdr rest))))))
+
+ (('lexical-set! (n . x))
+ (let ((val (eval x env)))
+ (list-set! env n val)))
+
+ (('call-with-values (producer . consumer))
+ (call-with-values (eval producer env)
+ (eval consumer env)))
+
+ (('apply (f args))
+ (apply (eval f env) (eval args env)))
+
+ (('module-ref var-or-spec)
+ (variable-ref
+ (if (variable? var-or-spec)
+ var-or-spec
+ (memoize-variable-access! exp #f))))
+
+ (('define (name . x))
+ (let ((x (eval x env)))
+ (if (and (procedure? x) (not (procedure-property x 'name)))
+ (set-procedure-property! x 'name name))
+ (define! name x)
+ (if #f #f)))
+
+ (('toplevel-set! (var-or-sym . x))
+ (variable-set!
+ (if (variable? var-or-sym)
+ var-or-sym
+ (memoize-variable-access! exp
+ (capture-env (if (pair? env)
+ (cdr (last-pair env))
+ env))))
+ (eval x env)))
+
+ (('dynwind (in exp . out))
+ (dynamic-wind (eval in env)
+ (lambda () (eval exp env))
+ (eval out env)))
+
+ (('with-fluids (fluids vals . exp))
+ (let* ((fluids (map (lambda (x) (eval x env)) fluids))
+ (vals (map (lambda (x) (eval x env)) vals)))
+ (let lp ((fluids fluids) (vals vals))
+ (if (null? fluids)
+ (eval exp env)
+ (with-fluids (((car fluids) (car vals)))
+ (lp (cdr fluids) (cdr vals)))))))
+
+ (('prompt (tag exp . handler))
+ (@prompt (eval tag env)
+ (eval exp env)
+ (eval handler env)))
+
+ (('call/cc proc)
+ (call/cc (eval proc env)))
+
+ (('module-set! (x . var-or-spec))
+ (variable-set!
+ (if (variable? var-or-spec)
+ var-or-spec
+ (memoize-variable-access! exp #f))
+ (eval x env)))))
+
+ ;; primitive-eval
+ (lambda (exp)
+ "Evaluate @var{exp} in the current module."
+ (eval
+ (memoize-expression
+ (if (macroexpanded? exp)
+ exp
+ ((module-transformer (current-module)) exp)))
+ '()))))
+;;;; Copyright (C) 1996, 1998, 1999, 2001, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+
+;; This module is documented in the Guile Reference Manual.
+;; Briefly, these are exported:
+;; procedures: expect-select, expect-regexec
+;; variables: expect-port, expect-timeout, expect-timeout-proc,
+;; expect-eof-proc, expect-char-proc,
+;; expect-strings-compile-flags, expect-strings-exec-flags,
+;; macros: expect, expect-strings
+
+;;; Code:
+
+(define-module (ice-9 expect)
+ \:use-module (ice-9 regex)
+ \:export-syntax (expect expect-strings)
+ \:export (expect-port expect-timeout expect-timeout-proc
+ expect-eof-proc expect-char-proc expect-strings-compile-flags
+ expect-strings-exec-flags expect-select expect-regexec))
+
+;;; Expect: a macro for selecting actions based on what it reads from a port.
+;;; The idea is from Don Libes' expect based on Tcl.
+;;; This version by Gary Houston incorporating ideas from Aubrey Jaffer.
+
+
+(define expect-port #f)
+(define expect-timeout #f)
+(define expect-timeout-proc #f)
+(define expect-eof-proc #f)
+(define expect-char-proc #f)
+
+;;; expect: each test is a procedure which is applied to the accumulating
+;;; string.
+(defmacro expect clauses
+ (let ((s (gensym))
+ (c (gensym))
+ (port (gensym))
+ (timeout (gensym)))
+ `(let ((,s "")
+ (,port (or expect-port (current-input-port)))
+ ;; when timeout occurs, in floating point seconds.
+ (,timeout (if expect-timeout
+ (let* ((secs-usecs (gettimeofday)))
+ (+ (car secs-usecs)
+ expect-timeout
+ (/ (cdr secs-usecs)
+ 1000000))) ; one million.
+ #f)))
+ (let next-char ()
+ (if (and expect-timeout
+ (not (expect-select ,port ,timeout)))
+ (if expect-timeout-proc
+ (expect-timeout-proc ,s)
+ #f)
+ (let ((,c (read-char ,port)))
+ (if expect-char-proc
+ (expect-char-proc ,c))
+ (if (not (eof-object? ,c))
+ (set! ,s (string-append ,s (string ,c))))
+ (cond
+ ;; this expands to clauses where the car invokes the
+ ;; match proc and the cdr is the return value from expect
+ ;; if the proc matched.
+ ,@(let next-expr ((tests (map car clauses))
+ (exprs (map cdr clauses))
+ (body '()))
+ (cond
+ ((null? tests)
+ (reverse body))
+ (else
+ (next-expr
+ (cdr tests)
+ (cdr exprs)
+ (cons
+ `((,(car tests) ,s (eof-object? ,c))
+ ,@(cond ((null? (car exprs))
+ '())
+ ((eq? (caar exprs) '=>)
+ (if (not (= (length (car exprs))
+ 2))
+ (scm-error 'misc-error
+ "expect"
+ "bad recipient: ~S"
+ (list (car exprs))
+ #f)
+ `((apply ,(cadar exprs)
+ (,(car tests) ,s ,port)))))
+ (else
+ (car exprs))))
+ body)))))
+ ;; if none of the clauses matched the current string.
+ (else (cond ((eof-object? ,c)
+ (if expect-eof-proc
+ (expect-eof-proc ,s)
+ #f))
+ (else
+ (next-char)))))))))))
+
+
+(define expect-strings-compile-flags regexp/newline)
+(define expect-strings-exec-flags regexp/noteol)
+
+;;; the regexec front-end to expect:
+;;; each test must evaluate to a regular expression.
+(defmacro expect-strings clauses
+ `(let ,@(let next-test ((tests (map car clauses))
+ (exprs (map cdr clauses))
+ (defs '())
+ (body '()))
+ (cond ((null? tests)
+ (list (reverse defs) `(expect ,@(reverse body))))
+ (else
+ (let ((rxname (gensym)))
+ (next-test (cdr tests)
+ (cdr exprs)
+ (cons `(,rxname (make-regexp
+ ,(car tests)
+ expect-strings-compile-flags))
+ defs)
+ (cons `((lambda (s eof?)
+ (expect-regexec ,rxname s eof?))
+ ,@(car exprs))
+ body))))))))
+
+;;; simplified select: returns #t if input is waiting or #f if timed out or
+;;; select was interrupted by a signal.
+;;; timeout is an absolute time in floating point seconds.
+(define (expect-select port timeout)
+ (let* ((secs-usecs (gettimeofday))
+ (relative (- timeout
+ (car secs-usecs)
+ (/ (cdr secs-usecs)
+ 1000000)))) ; one million.
+ (and (> relative 0)
+ (pair? (car (select (list port) '() '()
+ relative))))))
+
+;;; match a string against a regexp, returning a list of strings (required
+;;; by the => syntax) or #f. called once each time a character is added
+;;; to s (eof? will be #f), and once when eof is reached (with eof? #t).
+(define (expect-regexec rx s eof?)
+ ;; if expect-strings-exec-flags contains regexp/noteol,
+ ;; remove it for the eof test.
+ (let* ((flags (if (and eof?
+ (logand expect-strings-exec-flags regexp/noteol))
+ (logxor expect-strings-exec-flags regexp/noteol)
+ expect-strings-exec-flags))
+ (match (regexp-exec rx s 0 flags)))
+ (if match
+ (do ((i (- (match:count match) 1) (- i 1))
+ (result '() (cons (match:substring match i) result)))
+ ((< i 0) result))
+ #f)))
+
+;;; expect.scm ends here
+;;;; "format.scm" Common LISP text output formatter for SLIB
+;;; Copyright (C) 2010, 2011, 2012 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;
+
+;;; This code was orignally in the public domain.
+;;;
+;;; Written 1992-1994 by Dirk Lutzebaeck (lutzeb@cs.tu-berlin.de).
+;;;
+;;; Authors of the version from SLIB (< 1.4) were Ken Dickey and Aubrey
+;;; Jaffer.
+;;;
+;;; Assimilated into Guile May 1999.
+;;;
+;;; Please don't bother the original authors with bug reports, though;
+;;; send them to bug-guile@gnu.org.
+;;;
+
+(define-module (ice-9 format)
+ #\autoload (ice-9 pretty-print) (pretty-print truncated-print)
+ #\autoload (ice-9 i18n) (%global-locale number->locale-string)
+ #\replace (format))
+
+(define format:version "3.0")
+
+(define (format destination format-string . format-args)
+ (if (not (string? format-string))
+ (error "format: expected a string for format string" format-string))
+
+ (let* ((port
+ (cond
+ ((not destination)
+ ;; Use a Unicode-capable output string port.
+ (with-fluids ((%default-port-encoding "UTF-8"))
+ (open-output-string)))
+ ((boolean? destination) (current-output-port)) ; boolean but not false
+ ((output-port? destination) destination)
+ ((number? destination)
+ (issue-deprecation-warning
+ "Passing a number to format as the port is deprecated."
+ "Pass (current-error-port) instead.")
+ (current-error-port))
+ (else
+ (error "format: bad destination `~a'" destination))))
+
+ (output-col (or (port-column port) 0))
+
+ (flush-output? #f))
+
+ (define format:case-conversion #f)
+ (define format:pos 0) ; curr. format string parsing position
+ (define format:arg-pos 0) ; curr. format argument position
+ ; this is global for error presentation
+
+ ;; format string and char output routines on port
+
+ (define (format:out-str str)
+ (if format:case-conversion
+ (display (format:case-conversion str) port)
+ (display str port))
+ (set! output-col
+ (+ output-col (string-length str))))
+
+ (define (format:out-char ch)
+ (if format:case-conversion
+ (display (format:case-conversion (string ch))
+ port)
+ (write-char ch port))
+ (set! output-col
+ (if (char=? ch #\newline)
+ 0
+ (+ output-col 1))))
+
+ ;;(define (format:out-substr str i n) ; this allocates a new string
+ ;; (display (substring str i n) port)
+ ;; (set! output-col (+ output-col n)))
+
+ (define (format:out-substr str i n)
+ (do ((k i (+ k 1)))
+ ((= k n))
+ (write-char (string-ref str k) port))
+ (set! output-col (+ output-col (- n i))))
+
+ ;;(define (format:out-fill n ch) ; this allocates a new string
+ ;; (format:out-str (make-string n ch)))
+
+ (define (format:out-fill n ch)
+ (do ((i 0 (+ i 1)))
+ ((= i n))
+ (write-char ch port))
+ (set! output-col (+ output-col n)))
+
+ ;; format's user error handler
+
+ (define (format:error . args) ; never returns!
+ (let ((port (current-error-port)))
+ (set! format:error format:intern-error)
+ (if (not (zero? format:arg-pos))
+ (set! format:arg-pos (- format:arg-pos 1)))
+ (format port
+ "~%FORMAT: error with call: (format ~a \"~a<===~a\" ~
+ ~{~a ~}===>~{~a ~})~% "
+ destination
+ (substring format-string 0 format:pos)
+ (substring format-string format:pos
+ (string-length format-string))
+ (list-head format-args format:arg-pos)
+ (list-tail format-args format:arg-pos))
+ (apply format port args)
+ (newline port)
+ (set! format:error format:error-save)
+ (format:abort)))
+
+ (define (format:intern-error . args)
+ ;;if something goes wrong in format:error
+ (display "FORMAT: INTERNAL ERROR IN FORMAT:ERROR!") (newline)
+ (display " destination: ") (write destination) (newline)
+ (display " format string: ") (write format-string) (newline)
+ (display " format args: ") (write format-args) (newline)
+ (display " error args: ") (write args) (newline)
+ (set! format:error format:error-save)
+ (format:abort))
+
+ (define format:error-save format:error)
+
+ (define format:parameter-characters
+ '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\- #\+ #\v #\# #\'))
+
+ (define (format:format-work format-string arglist) ; does the formatting work
+ (letrec
+ ((format-string-len (string-length format-string))
+ (arg-pos 0) ; argument position in arglist
+ (arg-len (length arglist)) ; number of arguments
+ (modifier #f) ; 'colon | 'at | 'colon-at | #f
+ (params '()) ; directive parameter list
+ (param-value-found #f) ; a directive
+ ; parameter value
+ ; found
+ (conditional-nest 0) ; conditional nesting level
+ (clause-pos 0) ; last cond. clause
+ ; beginning char pos
+ (clause-default #f) ; conditional default
+ ; clause string
+ (clauses '()) ; conditional clause
+ ; string list
+ (conditional-type #f) ; reflects the
+ ; contional modifiers
+ (conditional-arg #f) ; argument to apply the conditional
+ (iteration-nest 0) ; iteration nesting level
+ (iteration-pos 0) ; iteration string
+ ; beginning char pos
+ (iteration-type #f) ; reflects the
+ ; iteration modifiers
+ (max-iterations #f) ; maximum number of
+ ; iterations
+ (recursive-pos-save format:pos)
+
+ (next-char ; gets the next char
+ ; from format-string
+ (lambda ()
+ (let ((ch (peek-next-char)))
+ (set! format:pos (+ 1 format:pos))
+ ch)))
+
+ (peek-next-char
+ (lambda ()
+ (if (>= format:pos format-string-len)
+ (format:error "illegal format string")
+ (string-ref format-string format:pos))))
+
+ (one-positive-integer?
+ (lambda (params)
+ (cond
+ ((null? params) #f)
+ ((and (integer? (car params))
+ (>= (car params) 0)
+ (= (length params) 1)) #t)
+ (else
+ (format:error
+ "one positive integer parameter expected")))))
+
+ (next-arg
+ (lambda ()
+ (if (>= arg-pos arg-len)
+ (begin
+ (set! format:arg-pos (+ arg-len 1))
+ (format:error "missing argument(s)")))
+ (add-arg-pos 1)
+ (list-ref arglist (- arg-pos 1))))
+
+ (prev-arg
+ (lambda ()
+ (add-arg-pos -1)
+ (if (negative? arg-pos)
+ (format:error "missing backward argument(s)"))
+ (list-ref arglist arg-pos)))
+
+ (rest-args
+ (lambda ()
+ (let loop ((l arglist) (k arg-pos)) ; list-tail definition
+ (if (= k 0) l (loop (cdr l) (- k 1))))))
+
+ (add-arg-pos
+ (lambda (n)
+ (set! arg-pos (+ n arg-pos))
+ (set! format:arg-pos arg-pos)))
+
+ (anychar-dispatch ; dispatches the format-string
+ (lambda ()
+ (if (>= format:pos format-string-len)
+ arg-pos ; used for ~? continuance
+ (let ((char (next-char)))
+ (cond
+ ((char=? char #\~)
+ (set! modifier #f)
+ (set! params '())
+ (set! param-value-found #f)
+ (tilde-dispatch))
+ (else
+ (if (and (zero? conditional-nest)
+ (zero? iteration-nest))
+ (format:out-char char))
+ (anychar-dispatch)))))))
+
+ (tilde-dispatch
+ (lambda ()
+ (cond
+ ((>= format:pos format-string-len)
+ (format:out-str "~") ; tilde at end of
+ ; string is just
+ ; output
+ arg-pos) ; used for ~?
+ ; continuance
+ ((and (or (zero? conditional-nest)
+ (memv (peek-next-char) ; find conditional
+ ; directives
+ (append '(#\[ #\] #\; #\: #\@ #\^)
+ format:parameter-characters)))
+ (or (zero? iteration-nest)
+ (memv (peek-next-char) ; find iteration
+ ; directives
+ (append '(#\{ #\} #\: #\@ #\^)
+ format:parameter-characters))))
+ (case (char-upcase (next-char))
+
+ ;; format directives
+
+ ((#\A) ; Any -- for humans
+ (set! format:read-proof
+ (memq modifier '(colon colon-at)))
+ (format:out-obj-padded (memq modifier '(at colon-at))
+ (next-arg) #f params)
+ (anychar-dispatch))
+ ((#\S) ; Slashified -- for parsers
+ (set! format:read-proof
+ (memq modifier '(colon colon-at)))
+ (format:out-obj-padded (memq modifier '(at colon-at))
+ (next-arg) #t params)
+ (anychar-dispatch))
+ ((#\D) ; Decimal
+ (format:out-num-padded modifier (next-arg) params 10)
+ (anychar-dispatch))
+ ((#\H) ; Localized number
+ (let* ((num (next-arg))
+ (locale (case modifier
+ ((colon) (next-arg))
+ (else %global-locale)))
+ (argc (length params))
+ (width (format:par params argc 0 #f "width"))
+ (decimals (format:par params argc 1 #t "decimals"))
+ (padchar (integer->char
+ (format:par params argc 2 format:space-ch
+ "padchar")))
+ (str (number->locale-string num decimals
+ locale)))
+ (format:out-str (if (and width
+ (< (string-length str) width))
+ (string-pad str width padchar)
+ str)))
+ (anychar-dispatch))
+ ((#\X) ; Hexadecimal
+ (format:out-num-padded modifier (next-arg) params 16)
+ (anychar-dispatch))
+ ((#\O) ; Octal
+ (format:out-num-padded modifier (next-arg) params 8)
+ (anychar-dispatch))
+ ((#\B) ; Binary
+ (format:out-num-padded modifier (next-arg) params 2)
+ (anychar-dispatch))
+ ((#\R)
+ (if (null? params)
+ (format:out-obj-padded ; Roman, cardinal,
+ ; ordinal numerals
+ #f
+ ((case modifier
+ ((at) format:num->roman)
+ ((colon-at) format:num->old-roman)
+ ((colon) format:num->ordinal)
+ (else format:num->cardinal))
+ (next-arg))
+ #f params)
+ (format:out-num-padded ; any Radix
+ modifier (next-arg) (cdr params) (car params)))
+ (anychar-dispatch))
+ ((#\F) ; Fixed-format floating-point
+ (format:out-fixed modifier (next-arg) params)
+ (anychar-dispatch))
+ ((#\E) ; Exponential floating-point
+ (format:out-expon modifier (next-arg) params)
+ (anychar-dispatch))
+ ((#\G) ; General floating-point
+ (format:out-general modifier (next-arg) params)
+ (anychar-dispatch))
+ ((#\$) ; Dollars floating-point
+ (format:out-dollar modifier (next-arg) params)
+ (anychar-dispatch))
+ ((#\I) ; Complex numbers
+ (let ((z (next-arg)))
+ (if (not (complex? z))
+ (format:error "argument not a complex number"))
+ (format:out-fixed modifier (real-part z) params)
+ (format:out-fixed 'at (imag-part z) params)
+ (format:out-char #\i))
+ (anychar-dispatch))
+ ((#\C) ; Character
+ (let ((ch (if (one-positive-integer? params)
+ (integer->char (car params))
+ (next-arg))))
+ (if (not (char? ch))
+ (format:error "~~c expects a character"))
+ (case modifier
+ ((at)
+ (format:out-str (object->string ch)))
+ ((colon)
+ (let ((c (char->integer ch)))
+ (if (< c 0)
+ (set! c (+ c 256))) ; compensate
+ ; complement
+ ; impl.
+ (cond
+ ((< c #x20) ; assumes that control
+ ; chars are < #x20
+ (format:out-char #\^)
+ (format:out-char
+ (integer->char (+ c #x40))))
+ ((>= c #x7f)
+ (format:out-str "#\\")
+ (format:out-str
+ (number->string c 8)))
+ (else
+ (format:out-char ch)))))
+ (else (format:out-char ch))))
+ (anychar-dispatch))
+ ((#\P) ; Plural
+ (if (memq modifier '(colon colon-at))
+ (prev-arg))
+ (let ((arg (next-arg)))
+ (if (not (number? arg))
+ (format:error "~~p expects a number argument"))
+ (if (= arg 1)
+ (if (memq modifier '(at colon-at))
+ (format:out-char #\y))
+ (if (memq modifier '(at colon-at))
+ (format:out-str "ies")
+ (format:out-char #\s))))
+ (anychar-dispatch))
+ ((#\~) ; Tilde
+ (if (one-positive-integer? params)
+ (format:out-fill (car params) #\~)
+ (format:out-char #\~))
+ (anychar-dispatch))
+ ((#\%) ; Newline
+ (if (one-positive-integer? params)
+ (format:out-fill (car params) #\newline)
+ (format:out-char #\newline))
+ (set! output-col 0)
+ (anychar-dispatch))
+ ((#\&) ; Fresh line
+ (if (one-positive-integer? params)
+ (begin
+ (if (> (car params) 0)
+ (format:out-fill (- (car params)
+ (if (>
+ output-col
+ 0) 0 1))
+ #\newline))
+ (set! output-col 0))
+ (if (> output-col 0)
+ (format:out-char #\newline)))
+ (anychar-dispatch))
+ ((#\_) ; Space character
+ (if (one-positive-integer? params)
+ (format:out-fill (car params) #\space)
+ (format:out-char #\space))
+ (anychar-dispatch))
+ ((#\/) ; Tabulator character
+ (if (one-positive-integer? params)
+ (format:out-fill (car params) #\tab)
+ (format:out-char #\tab))
+ (anychar-dispatch))
+ ((#\|) ; Page seperator
+ (if (one-positive-integer? params)
+ (format:out-fill (car params) #\page)
+ (format:out-char #\page))
+ (set! output-col 0)
+ (anychar-dispatch))
+ ((#\T) ; Tabulate
+ (format:tabulate modifier params)
+ (anychar-dispatch))
+ ((#\Y) ; Structured print
+ (let ((width (if (one-positive-integer? params)
+ (car params)
+ 79)))
+ (case modifier
+ ((at)
+ (format:out-str
+ (call-with-output-string
+ (lambda (p)
+ (truncated-print (next-arg) p
+ #\width width)))))
+ ((colon-at)
+ (format:out-str
+ (call-with-output-string
+ (lambda (p)
+ (truncated-print (next-arg) p
+ #\width
+ (max (- width
+ output-col)
+ 1))))))
+ ((colon)
+ (format:error "illegal modifier in ~~?"))
+ (else
+ (pretty-print (next-arg) port
+ #\width width)
+ (set! output-col 0))))
+ (anychar-dispatch))
+ ((#\? #\K) ; Indirection (is "~K" in T-Scheme)
+ (cond
+ ((memq modifier '(colon colon-at))
+ (format:error "illegal modifier in ~~?"))
+ ((eq? modifier 'at)
+ (let* ((frmt (next-arg))
+ (args (rest-args)))
+ (add-arg-pos (format:format-work frmt args))))
+ (else
+ (let* ((frmt (next-arg))
+ (args (next-arg)))
+ (format:format-work frmt args))))
+ (anychar-dispatch))
+ ((#\!) ; Flush output
+ (set! flush-output? #t)
+ (anychar-dispatch))
+ ((#\newline) ; Continuation lines
+ (if (eq? modifier 'at)
+ (format:out-char #\newline))
+ (if (< format:pos format-string-len)
+ (do ((ch (peek-next-char) (peek-next-char)))
+ ((or (not (char-whitespace? ch))
+ (= format:pos (- format-string-len 1))))
+ (if (eq? modifier 'colon)
+ (format:out-char (next-char))
+ (next-char))))
+ (anychar-dispatch))
+ ((#\*) ; Argument jumping
+ (case modifier
+ ((colon) ; jump backwards
+ (if (one-positive-integer? params)
+ (do ((i 0 (+ i 1)))
+ ((= i (car params)))
+ (prev-arg))
+ (prev-arg)))
+ ((at) ; jump absolute
+ (set! arg-pos (if (one-positive-integer? params)
+ (car params) 0)))
+ ((colon-at)
+ (format:error "illegal modifier `:@' in ~~* directive"))
+ (else ; jump forward
+ (if (one-positive-integer? params)
+ (do ((i 0 (+ i 1)))
+ ((= i (car params)))
+ (next-arg))
+ (next-arg))))
+ (anychar-dispatch))
+ ((#\() ; Case conversion begin
+ (set! format:case-conversion
+ (case modifier
+ ((at) string-capitalize-first)
+ ((colon) string-capitalize)
+ ((colon-at) string-upcase)
+ (else string-downcase)))
+ (anychar-dispatch))
+ ((#\)) ; Case conversion end
+ (if (not format:case-conversion)
+ (format:error "missing ~~("))
+ (set! format:case-conversion #f)
+ (anychar-dispatch))
+ ((#\[) ; Conditional begin
+ (set! conditional-nest (+ conditional-nest 1))
+ (cond
+ ((= conditional-nest 1)
+ (set! clause-pos format:pos)
+ (set! clause-default #f)
+ (set! clauses '())
+ (set! conditional-type
+ (case modifier
+ ((at) 'if-then)
+ ((colon) 'if-else-then)
+ ((colon-at) (format:error "illegal modifier in ~~["))
+ (else 'num-case)))
+ (set! conditional-arg
+ (if (one-positive-integer? params)
+ (car params)
+ (next-arg)))))
+ (anychar-dispatch))
+ ((#\;) ; Conditional separator
+ (if (zero? conditional-nest)
+ (format:error "~~; not in ~~[~~] conditional"))
+ (if (not (null? params))
+ (format:error "no parameter allowed in ~~;"))
+ (if (= conditional-nest 1)
+ (let ((clause-str
+ (cond
+ ((eq? modifier 'colon)
+ (set! clause-default #t)
+ (substring format-string clause-pos
+ (- format:pos 3)))
+ ((memq modifier '(at colon-at))
+ (format:error "illegal modifier in ~~;"))
+ (else
+ (substring format-string clause-pos
+ (- format:pos 2))))))
+ (set! clauses (append clauses (list clause-str)))
+ (set! clause-pos format:pos)))
+ (anychar-dispatch))
+ ((#\]) ; Conditional end
+ (if (zero? conditional-nest) (format:error "missing ~~["))
+ (set! conditional-nest (- conditional-nest 1))
+ (if modifier
+ (format:error "no modifier allowed in ~~]"))
+ (if (not (null? params))
+ (format:error "no parameter allowed in ~~]"))
+ (cond
+ ((zero? conditional-nest)
+ (let ((clause-str (substring format-string clause-pos
+ (- format:pos 2))))
+ (if clause-default
+ (set! clause-default clause-str)
+ (set! clauses (append clauses (list clause-str)))))
+ (case conditional-type
+ ((if-then)
+ (if conditional-arg
+ (format:format-work (car clauses)
+ (list conditional-arg))))
+ ((if-else-then)
+ (add-arg-pos
+ (format:format-work (if conditional-arg
+ (cadr clauses)
+ (car clauses))
+ (rest-args))))
+ ((num-case)
+ (if (or (not (integer? conditional-arg))
+ (< conditional-arg 0))
+ (format:error "argument not a positive integer"))
+ (if (not (and (>= conditional-arg (length clauses))
+ (not clause-default)))
+ (add-arg-pos
+ (format:format-work
+ (if (>= conditional-arg (length clauses))
+ clause-default
+ (list-ref clauses conditional-arg))
+ (rest-args))))))))
+ (anychar-dispatch))
+ ((#\{) ; Iteration begin
+ (set! iteration-nest (+ iteration-nest 1))
+ (cond
+ ((= iteration-nest 1)
+ (set! iteration-pos format:pos)
+ (set! iteration-type
+ (case modifier
+ ((at) 'rest-args)
+ ((colon) 'sublists)
+ ((colon-at) 'rest-sublists)
+ (else 'list)))
+ (set! max-iterations (if (one-positive-integer? params)
+ (car params) #f))))
+ (anychar-dispatch))
+ ((#\}) ; Iteration end
+ (if (zero? iteration-nest) (format:error "missing ~~{"))
+ (set! iteration-nest (- iteration-nest 1))
+ (case modifier
+ ((colon)
+ (if (not max-iterations) (set! max-iterations 1)))
+ ((colon-at at) (format:error "illegal modifier")))
+ (if (not (null? params))
+ (format:error "no parameters allowed in ~~}"))
+ (if (zero? iteration-nest)
+ (let ((iteration-str
+ (substring format-string iteration-pos
+ (- format:pos (if modifier 3 2)))))
+ (if (string=? iteration-str "")
+ (set! iteration-str (next-arg)))
+ (case iteration-type
+ ((list)
+ (let ((args (next-arg))
+ (args-len 0))
+ (if (not (list? args))
+ (format:error "expected a list argument"))
+ (set! args-len (length args))
+ (do ((arg-pos 0 (+ arg-pos
+ (format:format-work
+ iteration-str
+ (list-tail args arg-pos))))
+ (i 0 (+ i 1)))
+ ((or (>= arg-pos args-len)
+ (and max-iterations
+ (>= i max-iterations)))))))
+ ((sublists)
+ (let ((args (next-arg))
+ (args-len 0))
+ (if (not (list? args))
+ (format:error "expected a list argument"))
+ (set! args-len (length args))
+ (do ((arg-pos 0 (+ arg-pos 1)))
+ ((or (>= arg-pos args-len)
+ (and max-iterations
+ (>= arg-pos max-iterations))))
+ (let ((sublist (list-ref args arg-pos)))
+ (if (not (list? sublist))
+ (format:error
+ "expected a list of lists argument"))
+ (format:format-work iteration-str sublist)))))
+ ((rest-args)
+ (let* ((args (rest-args))
+ (args-len (length args))
+ (usedup-args
+ (do ((arg-pos 0 (+ arg-pos
+ (format:format-work
+ iteration-str
+ (list-tail
+ args arg-pos))))
+ (i 0 (+ i 1)))
+ ((or (>= arg-pos args-len)
+ (and max-iterations
+ (>= i max-iterations)))
+ arg-pos))))
+ (add-arg-pos usedup-args)))
+ ((rest-sublists)
+ (let* ((args (rest-args))
+ (args-len (length args))
+ (usedup-args
+ (do ((arg-pos 0 (+ arg-pos 1)))
+ ((or (>= arg-pos args-len)
+ (and max-iterations
+ (>= arg-pos max-iterations)))
+ arg-pos)
+ (let ((sublist (list-ref args arg-pos)))
+ (if (not (list? sublist))
+ (format:error "expected list arguments"))
+ (format:format-work iteration-str sublist)))))
+ (add-arg-pos usedup-args)))
+ (else (format:error "internal error in ~~}")))))
+ (anychar-dispatch))
+ ((#\^) ; Up and out
+ (let* ((continue
+ (cond
+ ((not (null? params))
+ (not
+ (case (length params)
+ ((1) (zero? (car params)))
+ ((2) (= (list-ref params 0) (list-ref params 1)))
+ ((3) (<= (list-ref params 0)
+ (list-ref params 1)
+ (list-ref params 2)))
+ (else (format:error "too much parameters")))))
+ (format:case-conversion ; if conversion stop conversion
+ (set! format:case-conversion string-copy) #t)
+ ((= iteration-nest 1) #t)
+ ((= conditional-nest 1) #t)
+ ((>= arg-pos arg-len)
+ (set! format:pos format-string-len) #f)
+ (else #t))))
+ (if continue
+ (anychar-dispatch))))
+
+ ;; format directive modifiers and parameters
+
+ ((#\@) ; `@' modifier
+ (if (memq modifier '(at colon-at))
+ (format:error "double `@' modifier"))
+ (set! modifier (if (eq? modifier 'colon) 'colon-at 'at))
+ (tilde-dispatch))
+ ((#\:) ; `:' modifier
+ (if (memq modifier '(colon colon-at))
+ (format:error "double `:' modifier"))
+ (set! modifier (if (eq? modifier 'at) 'colon-at 'colon))
+ (tilde-dispatch))
+ ((#\') ; Character parameter
+ (if modifier (format:error "misplaced modifier"))
+ (set! params (append params (list (char->integer (next-char)))))
+ (set! param-value-found #t)
+ (tilde-dispatch))
+ ((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\- #\+) ; num. paramtr
+ (if modifier (format:error "misplaced modifier"))
+ (let ((num-str-beg (- format:pos 1))
+ (num-str-end format:pos))
+ (do ((ch (peek-next-char) (peek-next-char)))
+ ((not (char-numeric? ch)))
+ (next-char)
+ (set! num-str-end (+ 1 num-str-end)))
+ (set! params
+ (append params
+ (list (string->number
+ (substring format-string
+ num-str-beg
+ num-str-end))))))
+ (set! param-value-found #t)
+ (tilde-dispatch))
+ ((#\V) ; Variable parameter from next argum.
+ (if modifier (format:error "misplaced modifier"))
+ (set! params (append params (list (next-arg))))
+ (set! param-value-found #t)
+ (tilde-dispatch))
+ ((#\#) ; Parameter is number of remaining args
+ (if param-value-found (format:error "misplaced '#'"))
+ (if modifier (format:error "misplaced modifier"))
+ (set! params (append params (list (length (rest-args)))))
+ (set! param-value-found #t)
+ (tilde-dispatch))
+ ((#\,) ; Parameter separators
+ (if modifier (format:error "misplaced modifier"))
+ (if (not param-value-found)
+ (set! params (append params '(#f)))) ; append empty paramtr
+ (set! param-value-found #f)
+ (tilde-dispatch))
+ ((#\Q) ; Inquiry messages
+ (if (eq? modifier 'colon)
+ (format:out-str format:version)
+ (let ((nl (string #\newline)))
+ (format:out-str
+ (string-append
+ "SLIB Common LISP format version " format:version nl
+ " (C) copyright 1992-1994 by Dirk Lutzebaeck" nl
+ " please send bug reports to `lutzeb@cs.tu-berlin.de'"
+ nl))))
+ (anychar-dispatch))
+ (else ; Unknown tilde directive
+ (format:error "unknown control character `~c'"
+ (string-ref format-string (- format:pos 1))))))
+ (else (anychar-dispatch)))))) ; in case of conditional
+
+ (set! format:pos 0)
+ (set! format:arg-pos 0)
+ (anychar-dispatch) ; start the formatting
+ (set! format:pos recursive-pos-save)
+ arg-pos)) ; return the position in the arg. list
+
+ ;; when format:read-proof is true, format:obj->str will wrap
+ ;; result strings starting with "#<" in an extra pair of double
+ ;; quotes.
+
+ (define format:read-proof #f)
+
+ ;; format:obj->str returns a R4RS representation as a string of
+ ;; an arbitrary scheme object.
+
+ (define (format:obj->str obj slashify)
+ (let ((res (if slashify
+ (object->string obj)
+ (call-with-output-string (lambda (p) (display obj p))))))
+ (if (and format:read-proof (string-prefix? "#<" res))
+ (object->string res)
+ res)))
+
+ (define format:space-ch (char->integer #\space))
+ (define format:zero-ch (char->integer #\0))
+
+ (define (format:par pars length index default name)
+ (if (> length index)
+ (let ((par (list-ref pars index)))
+ (if par
+ (if name
+ (if (< par 0)
+ (format:error
+ "~s parameter must be a positive integer" name)
+ par)
+ par)
+ default))
+ default))
+
+ (define (format:out-obj-padded pad-left obj slashify pars)
+ (if (null? pars)
+ (format:out-str (format:obj->str obj slashify))
+ (let ((l (length pars)))
+ (let ((mincol (format:par pars l 0 0 "mincol"))
+ (colinc (format:par pars l 1 1 "colinc"))
+ (minpad (format:par pars l 2 0 "minpad"))
+ (padchar (integer->char
+ (format:par pars l 3 format:space-ch #f)))
+ (objstr (format:obj->str obj slashify)))
+ (if (not pad-left)
+ (format:out-str objstr))
+ (do ((objstr-len (string-length objstr))
+ (i minpad (+ i colinc)))
+ ((>= (+ objstr-len i) mincol)
+ (format:out-fill i padchar)))
+ (if pad-left
+ (format:out-str objstr))))))
+
+ (define (format:out-num-padded modifier number pars radix)
+ (if (not (integer? number)) (format:error "argument not an integer"))
+ (let ((numstr (number->string number radix)))
+ (if (and (null? pars) (not modifier))
+ (format:out-str numstr)
+ (let ((l (length pars))
+ (numstr-len (string-length numstr)))
+ (let ((mincol (format:par pars l 0 #f "mincol"))
+ (padchar (integer->char
+ (format:par pars l 1 format:space-ch #f)))
+ (commachar (integer->char
+ (format:par pars l 2 (char->integer #\,) #f)))
+ (commawidth (format:par pars l 3 3 "commawidth")))
+ (if mincol
+ (let ((numlen numstr-len)) ; calc. the output len of number
+ (if (and (memq modifier '(at colon-at)) (>= number 0))
+ (set! numlen (+ numlen 1)))
+ (if (memq modifier '(colon colon-at))
+ (set! numlen (+ (quotient (- numstr-len
+ (if (< number 0) 2 1))
+ commawidth)
+ numlen)))
+ (if (> mincol numlen)
+ (format:out-fill (- mincol numlen) padchar))))
+ (if (and (memq modifier '(at colon-at))
+ (>= number 0))
+ (format:out-char #\+))
+ (if (memq modifier '(colon colon-at)) ; insert comma character
+ (let ((start (remainder numstr-len commawidth))
+ (ns (if (< number 0) 1 0)))
+ (format:out-substr numstr 0 start)
+ (do ((i start (+ i commawidth)))
+ ((>= i numstr-len))
+ (if (> i ns)
+ (format:out-char commachar))
+ (format:out-substr numstr i (+ i commawidth))))
+ (format:out-str numstr)))))))
+
+ (define (format:tabulate modifier pars)
+ (let ((l (length pars)))
+ (let ((colnum (format:par pars l 0 1 "colnum"))
+ (colinc (format:par pars l 1 1 "colinc"))
+ (padch (integer->char (format:par pars l 2 format:space-ch #f))))
+ (case modifier
+ ((colon colon-at)
+ (format:error "unsupported modifier for ~~t"))
+ ((at) ; relative tabulation
+ (format:out-fill
+ (if (= colinc 0)
+ colnum ; colnum = colrel
+ (do ((c 0 (+ c colinc))
+ (col (+ output-col colnum)))
+ ((>= c col)
+ (- c output-col))))
+ padch))
+ (else ; absolute tabulation
+ (format:out-fill
+ (cond
+ ((< output-col colnum)
+ (- colnum output-col))
+ ((= colinc 0)
+ 0)
+ (else
+ (do ((c colnum (+ c colinc)))
+ ((>= c output-col)
+ (- c output-col)))))
+ padch))))))
+
+
+ ;; roman numerals (from dorai@cs.rice.edu).
+
+ (define format:roman-alist
+ '((1000 #\M) (500 #\D) (100 #\C) (50 #\L)
+ (10 #\X) (5 #\V) (1 #\I)))
+
+ (define format:roman-boundary-values
+ '(100 100 10 10 1 1 #f))
+
+ (define (format:num->old-roman n)
+ (if (and (integer? n) (>= n 1))
+ (let loop ((n n)
+ (romans format:roman-alist)
+ (s '()))
+ (if (null? romans) (list->string (reverse s))
+ (let ((roman-val (caar romans))
+ (roman-dgt (cadar romans)))
+ (do ((q (quotient n roman-val) (- q 1))
+ (s s (cons roman-dgt s)))
+ ((= q 0)
+ (loop (remainder n roman-val)
+ (cdr romans) s))))))
+ (format:error "only positive integers can be romanized")))
+
+ (define (format:num->roman n)
+ (if (and (integer? n) (> n 0))
+ (let loop ((n n)
+ (romans format:roman-alist)
+ (boundaries format:roman-boundary-values)
+ (s '()))
+ (if (null? romans)
+ (list->string (reverse s))
+ (let ((roman-val (caar romans))
+ (roman-dgt (cadar romans))
+ (bdry (car boundaries)))
+ (let loop2 ((q (quotient n roman-val))
+ (r (remainder n roman-val))
+ (s s))
+ (if (= q 0)
+ (if (and bdry (>= r (- roman-val bdry)))
+ (loop (remainder r bdry) (cdr romans)
+ (cdr boundaries)
+ (cons roman-dgt
+ (append
+ (cdr (assv bdry romans))
+ s)))
+ (loop r (cdr romans) (cdr boundaries) s))
+ (loop2 (- q 1) r (cons roman-dgt s)))))))
+ (format:error "only positive integers can be romanized")))
+
+ ;; cardinals & ordinals (from dorai@cs.rice.edu)
+
+ (define format:cardinal-ones-list
+ '(#f "one" "two" "three" "four" "five"
+ "six" "seven" "eight" "nine" "ten" "eleven" "twelve" "thirteen"
+ "fourteen" "fifteen" "sixteen" "seventeen" "eighteen"
+ "nineteen"))
+
+ (define format:cardinal-tens-list
+ '(#f #f "twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty"
+ "ninety"))
+
+ (define (format:num->cardinal999 n)
+ ;; this procedure is inspired by the Bruno Haible's CLisp
+ ;; function format-small-cardinal, which converts numbers
+ ;; in the range 1 to 999, and is used for converting each
+ ;; thousand-block in a larger number
+ (let* ((hundreds (quotient n 100))
+ (tens+ones (remainder n 100))
+ (tens (quotient tens+ones 10))
+ (ones (remainder tens+ones 10)))
+ (append
+ (if (> hundreds 0)
+ (append
+ (string->list
+ (list-ref format:cardinal-ones-list hundreds))
+ (string->list" hundred")
+ (if (> tens+ones 0) '(#\space) '()))
+ '())
+ (if (< tens+ones 20)
+ (if (> tens+ones 0)
+ (string->list
+ (list-ref format:cardinal-ones-list tens+ones))
+ '())
+ (append
+ (string->list
+ (list-ref format:cardinal-tens-list tens))
+ (if (> ones 0)
+ (cons #\-
+ (string->list
+ (list-ref format:cardinal-ones-list ones)))
+ '()))))))
+
+ (define format:cardinal-thousand-block-list
+ '("" " thousand" " million" " billion" " trillion" " quadrillion"
+ " quintillion" " sextillion" " septillion" " octillion" " nonillion"
+ " decillion" " undecillion" " duodecillion" " tredecillion"
+ " quattuordecillion" " quindecillion" " sexdecillion" " septendecillion"
+ " octodecillion" " novemdecillion" " vigintillion"))
+
+ (define (format:num->cardinal n)
+ (cond ((not (integer? n))
+ (format:error
+ "only integers can be converted to English cardinals"))
+ ((= n 0) "zero")
+ ((< n 0) (string-append "minus " (format:num->cardinal (- n))))
+ (else
+ (let ((power3-word-limit
+ (length format:cardinal-thousand-block-list)))
+ (let loop ((n n)
+ (power3 0)
+ (s '()))
+ (if (= n 0)
+ (list->string s)
+ (let ((n-before-block (quotient n 1000))
+ (n-after-block (remainder n 1000)))
+ (loop n-before-block
+ (+ power3 1)
+ (if (> n-after-block 0)
+ (append
+ (if (> n-before-block 0)
+ (string->list ", ") '())
+ (format:num->cardinal999 n-after-block)
+ (if (< power3 power3-word-limit)
+ (string->list
+ (list-ref
+ format:cardinal-thousand-block-list
+ power3))
+ (append
+ (string->list " times ten to the ")
+ (string->list
+ (format:num->ordinal
+ (* power3 3)))
+ (string->list " power")))
+ s)
+ s)))))))))
+
+ (define format:ordinal-ones-list
+ '(#f "first" "second" "third" "fourth" "fifth"
+ "sixth" "seventh" "eighth" "ninth" "tenth" "eleventh" "twelfth"
+ "thirteenth" "fourteenth" "fifteenth" "sixteenth" "seventeenth"
+ "eighteenth" "nineteenth"))
+
+ (define format:ordinal-tens-list
+ '(#f #f "twentieth" "thirtieth" "fortieth" "fiftieth" "sixtieth"
+ "seventieth" "eightieth" "ninetieth"))
+
+ (define (format:num->ordinal n)
+ (cond ((not (integer? n))
+ (format:error
+ "only integers can be converted to English ordinals"))
+ ((= n 0) "zeroth")
+ ((< n 0) (string-append "minus " (format:num->ordinal (- n))))
+ (else
+ (let ((hundreds (quotient n 100))
+ (tens+ones (remainder n 100)))
+ (string-append
+ (if (> hundreds 0)
+ (string-append
+ (format:num->cardinal (* hundreds 100))
+ (if (= tens+ones 0) "th" " "))
+ "")
+ (if (= tens+ones 0) ""
+ (if (< tens+ones 20)
+ (list-ref format:ordinal-ones-list tens+ones)
+ (let ((tens (quotient tens+ones 10))
+ (ones (remainder tens+ones 10)))
+ (if (= ones 0)
+ (list-ref format:ordinal-tens-list tens)
+ (string-append
+ (list-ref format:cardinal-tens-list tens)
+ "-"
+ (list-ref format:ordinal-ones-list ones))))
+ )))))))
+
+ ;; format inf and nan.
+
+ (define (format:out-inf-nan number width digits edigits overch padch)
+ ;; inf and nan are always printed exactly as "+inf.0", "-inf.0" or
+ ;; "+nan.0", suitably justified in their field. We insist on
+ ;; printing this exact form so that the numbers can be read back in.
+ (let* ((str (number->string number))
+ (len (string-length str))
+ (dot (string-index str #\.))
+ (digits (+ (or digits 0)
+ (if edigits (+ edigits 2) 0))))
+ (if (and width overch (< width len))
+ (format:out-fill width (integer->char overch))
+ (let* ((leftpad (if width
+ (max (- width (max len (+ dot 1 digits))) 0)
+ 0))
+ (rightpad (if width
+ (max (- width leftpad len) 0)
+ 0))
+ (padch (integer->char (or padch format:space-ch))))
+ (format:out-fill leftpad padch)
+ (format:out-str str)
+ (format:out-fill rightpad padch)))))
+
+ ;; format fixed flonums (~F)
+
+ (define (format:out-fixed modifier number pars)
+ (if (not (or (number? number) (string? number)))
+ (format:error "argument is not a number or a number string"))
+
+ (let ((l (length pars)))
+ (let ((width (format:par pars l 0 #f "width"))
+ (digits (format:par pars l 1 #f "digits"))
+ (scale (format:par pars l 2 0 #f))
+ (overch (format:par pars l 3 #f #f))
+ (padch (format:par pars l 4 format:space-ch #f)))
+
+ (cond
+ ((and (number? number)
+ (or (inf? number) (nan? number)))
+ (format:out-inf-nan number width digits #f overch padch))
+
+ (digits
+ (format:parse-float number #t scale)
+ (if (<= (- format:fn-len format:fn-dot) digits)
+ (format:fn-zfill #f (- digits (- format:fn-len format:fn-dot)))
+ (format:fn-round digits))
+ (if width
+ (let ((numlen (+ format:fn-len 1)))
+ (if (or (not format:fn-pos?) (eq? modifier 'at))
+ (set! numlen (+ numlen 1)))
+ (if (and (= format:fn-dot 0) (> width (+ digits 1)))
+ (set! numlen (+ numlen 1)))
+ (if (< numlen width)
+ (format:out-fill (- width numlen) (integer->char padch)))
+ (if (and overch (> numlen width))
+ (format:out-fill width (integer->char overch))
+ (format:fn-out modifier (> width (+ digits 1)))))
+ (format:fn-out modifier #t)))
+
+ (else
+ (format:parse-float number #t scale)
+ (format:fn-strip)
+ (if width
+ (let ((numlen (+ format:fn-len 1)))
+ (if (or (not format:fn-pos?) (eq? modifier 'at))
+ (set! numlen (+ numlen 1)))
+ (if (= format:fn-dot 0)
+ (set! numlen (+ numlen 1)))
+ (if (< numlen width)
+ (format:out-fill (- width numlen) (integer->char padch)))
+ (if (> numlen width) ; adjust precision if possible
+ (let ((dot-index (- numlen
+ (- format:fn-len format:fn-dot))))
+ (if (> dot-index width)
+ (if overch ; numstr too big for required width
+ (format:out-fill width (integer->char overch))
+ (format:fn-out modifier #t))
+ (begin
+ (format:fn-round (- width dot-index))
+ (format:fn-out modifier #t))))
+ (format:fn-out modifier #t)))
+ (format:fn-out modifier #t)))))))
+
+ ;; format exponential flonums (~E)
+
+ (define (format:out-expon modifier number pars)
+ (if (not (or (number? number) (string? number)))
+ (format:error "argument is not a number"))
+
+ (let ((l (length pars)))
+ (let ((width (format:par pars l 0 #f "width"))
+ (digits (format:par pars l 1 #f "digits"))
+ (edigits (format:par pars l 2 #f "exponent digits"))
+ (scale (format:par pars l 3 1 #f))
+ (overch (format:par pars l 4 #f #f))
+ (padch (format:par pars l 5 format:space-ch #f))
+ (expch (format:par pars l 6 #f #f)))
+
+ (cond
+ ((and (number? number)
+ (or (inf? number) (nan? number)))
+ (format:out-inf-nan number width digits edigits overch padch))
+
+ (digits ; fixed precision
+
+ (let ((digits (if (> scale 0)
+ (if (< scale (+ digits 2))
+ (+ (- digits scale) 1)
+ 0)
+ digits)))
+ (format:parse-float number #f scale)
+ (if (<= (- format:fn-len format:fn-dot) digits)
+ (format:fn-zfill #f (- digits (- format:fn-len format:fn-dot)))
+ (format:fn-round digits))
+ (if width
+ (if (and edigits overch (> format:en-len edigits))
+ (format:out-fill width (integer->char overch))
+ (let ((numlen (+ format:fn-len 3))) ; .E+
+ (if (or (not format:fn-pos?) (eq? modifier 'at))
+ (set! numlen (+ numlen 1)))
+ (if (and (= format:fn-dot 0) (> width (+ digits 1)))
+ (set! numlen (+ numlen 1)))
+ (set! numlen
+ (+ numlen
+ (if (and edigits (>= edigits format:en-len))
+ edigits
+ format:en-len)))
+ (if (< numlen width)
+ (format:out-fill (- width numlen)
+ (integer->char padch)))
+ (if (and overch (> numlen width))
+ (format:out-fill width (integer->char overch))
+ (begin
+ (format:fn-out modifier (> width (- numlen 1)))
+ (format:en-out edigits expch)))))
+ (begin
+ (format:fn-out modifier #t)
+ (format:en-out edigits expch)))))
+
+ (else
+ (format:parse-float number #f scale)
+ (format:fn-strip)
+ (if width
+ (if (and edigits overch (> format:en-len edigits))
+ (format:out-fill width (integer->char overch))
+ (let ((numlen (+ format:fn-len 3))) ; .E+
+ (if (or (not format:fn-pos?) (eq? modifier 'at))
+ (set! numlen (+ numlen 1)))
+ (if (= format:fn-dot 0)
+ (set! numlen (+ numlen 1)))
+ (set! numlen
+ (+ numlen
+ (if (and edigits (>= edigits format:en-len))
+ edigits
+ format:en-len)))
+ (if (< numlen width)
+ (format:out-fill (- width numlen)
+ (integer->char padch)))
+ (if (> numlen width) ; adjust precision if possible
+ (let ((f (- format:fn-len format:fn-dot))) ; fract len
+ (if (> (- numlen f) width)
+ (if overch ; numstr too big for required width
+ (format:out-fill width
+ (integer->char overch))
+ (begin
+ (format:fn-out modifier #t)
+ (format:en-out edigits expch)))
+ (begin
+ (format:fn-round (+ (- f numlen) width))
+ (format:fn-out modifier #t)
+ (format:en-out edigits expch))))
+ (begin
+ (format:fn-out modifier #t)
+ (format:en-out edigits expch)))))
+ (begin
+ (format:fn-out modifier #t)
+ (format:en-out edigits expch))))))))
+
+ ;; format general flonums (~G)
+
+ (define (format:out-general modifier number pars)
+ (if (not (or (number? number) (string? number)))
+ (format:error "argument is not a number or a number string"))
+
+ (let ((l (length pars)))
+ (let ((width (if (> l 0) (list-ref pars 0) #f))
+ (digits (if (> l 1) (list-ref pars 1) #f))
+ (edigits (if (> l 2) (list-ref pars 2) #f))
+ (overch (if (> l 4) (list-ref pars 4) #f))
+ (padch (if (> l 5) (list-ref pars 5) #f)))
+ (cond
+ ((and (number? number)
+ (or (inf? number) (nan? number)))
+ ;; FIXME: this isn't right.
+ (format:out-inf-nan number width digits edigits overch padch))
+ (else
+ (format:parse-float number #t 0)
+ (format:fn-strip)
+ (let* ((ee (if edigits (+ edigits 2) 4)) ; for the following algorithm
+ (ww (if width (- width ee) #f)) ; see Steele's CL book p.395
+ (n (if (= format:fn-dot 0) ; number less than (abs 1.0) ?
+ (- (format:fn-zlead))
+ format:fn-dot))
+ (d (if digits
+ digits
+ (max format:fn-len (min n 7)))) ; q = format:fn-len
+ (dd (- d n)))
+ (if (<= 0 dd d)
+ (begin
+ (format:out-fixed modifier number (list ww dd #f overch padch))
+ (format:out-fill ee #\space)) ;~@T not implemented yet
+ (format:out-expon modifier number pars))))))))
+
+ ;; format dollar flonums (~$)
+
+ (define (format:out-dollar modifier number pars)
+ (if (not (or (number? number) (string? number)))
+ (format:error "argument is not a number or a number string"))
+
+ (let ((l (length pars)))
+ (let ((digits (format:par pars l 0 2 "digits"))
+ (mindig (format:par pars l 1 1 "mindig"))
+ (width (format:par pars l 2 0 "width"))
+ (padch (format:par pars l 3 format:space-ch #f)))
+
+ (cond
+ ((and (number? number)
+ (or (inf? number) (nan? number)))
+ (format:out-inf-nan number width digits #f #f padch))
+
+ (else
+ (format:parse-float number #t 0)
+ (if (<= (- format:fn-len format:fn-dot) digits)
+ (format:fn-zfill #f (- digits (- format:fn-len format:fn-dot)))
+ (format:fn-round digits))
+ (let ((numlen (+ format:fn-len 1)))
+ (if (or (not format:fn-pos?) (memq modifier '(at colon-at)))
+ (set! numlen (+ numlen 1)))
+ (if (and mindig (> mindig format:fn-dot))
+ (set! numlen (+ numlen (- mindig format:fn-dot))))
+ (if (and (= format:fn-dot 0) (not mindig))
+ (set! numlen (+ numlen 1)))
+ (if (< numlen width)
+ (case modifier
+ ((colon)
+ (if (not format:fn-pos?)
+ (format:out-char #\-))
+ (format:out-fill (- width numlen) (integer->char padch)))
+ ((at)
+ (format:out-fill (- width numlen) (integer->char padch))
+ (format:out-char (if format:fn-pos? #\+ #\-)))
+ ((colon-at)
+ (format:out-char (if format:fn-pos? #\+ #\-))
+ (format:out-fill (- width numlen) (integer->char padch)))
+ (else
+ (format:out-fill (- width numlen) (integer->char padch))
+ (if (not format:fn-pos?)
+ (format:out-char #\-))))
+ (if format:fn-pos?
+ (if (memq modifier '(at colon-at)) (format:out-char #\+))
+ (format:out-char #\-))))
+ (if (and mindig (> mindig format:fn-dot))
+ (format:out-fill (- mindig format:fn-dot) #\0))
+ (if (and (= format:fn-dot 0) (not mindig))
+ (format:out-char #\0))
+ (format:out-substr format:fn-str 0 format:fn-dot)
+ (format:out-char #\.)
+ (format:out-substr format:fn-str format:fn-dot format:fn-len))))))
+
+ ; the flonum buffers
+
+ (define format:fn-max 400) ; max. number of number digits
+ (define format:fn-str (make-string format:fn-max)) ; number buffer
+ (define format:fn-len 0) ; digit length of number
+ (define format:fn-dot #f) ; dot position of number
+ (define format:fn-pos? #t) ; number positive?
+ (define format:en-max 10) ; max. number of exponent digits
+ (define format:en-str (make-string format:en-max)) ; exponent buffer
+ (define format:en-len 0) ; digit length of exponent
+ (define format:en-pos? #t) ; exponent positive?
+
+ (define (format:parse-float num fixed? scale)
+ (let ((num-str (if (string? num)
+ num
+ (number->string (exact->inexact num)))))
+ (set! format:fn-pos? #t)
+ (set! format:fn-len 0)
+ (set! format:fn-dot #f)
+ (set! format:en-pos? #t)
+ (set! format:en-len 0)
+ (do ((i 0 (+ i 1))
+ (left-zeros 0)
+ (mantissa? #t)
+ (all-zeros? #t)
+ (num-len (string-length num-str))
+ (c #f)) ; current exam. character in num-str
+ ((= i num-len)
+ (if (not format:fn-dot)
+ (set! format:fn-dot format:fn-len))
+
+ (if all-zeros?
+ (begin
+ (set! left-zeros 0)
+ (set! format:fn-dot 0)
+ (set! format:fn-len 1)))
+
+ ;; now format the parsed values according to format's need
+
+ (if fixed?
+
+ (begin ; fixed format m.nnn or .nnn
+ (if (and (> left-zeros 0) (> format:fn-dot 0))
+ (if (> format:fn-dot left-zeros)
+ (begin ; norm 0{0}nn.mm to nn.mm
+ (format:fn-shiftleft left-zeros)
+ (set! format:fn-dot (- format:fn-dot left-zeros))
+ (set! left-zeros 0))
+ (begin ; normalize 0{0}.nnn to .nnn
+ (format:fn-shiftleft format:fn-dot)
+ (set! left-zeros (- left-zeros format:fn-dot))
+ (set! format:fn-dot 0))))
+ (if (or (not (= scale 0)) (> format:en-len 0))
+ (let ((shift (+ scale (format:en-int))))
+ (cond
+ (all-zeros? #t)
+ ((> (+ format:fn-dot shift) format:fn-len)
+ (format:fn-zfill
+ #f (- shift (- format:fn-len format:fn-dot)))
+ (set! format:fn-dot format:fn-len))
+ ((< (+ format:fn-dot shift) 0)
+ (format:fn-zfill #t (- (- shift) format:fn-dot))
+ (set! format:fn-dot 0))
+ (else
+ (if (> left-zeros 0)
+ (if (<= left-zeros shift) ; shift always > 0 here
+ (format:fn-shiftleft shift) ; shift out 0s
+ (begin
+ (format:fn-shiftleft left-zeros)
+ (set! format:fn-dot (- shift left-zeros))))
+ (set! format:fn-dot (+ format:fn-dot shift))))))))
+
+ (let ((negexp ; expon format m.nnnEee
+ (if (> left-zeros 0)
+ (- left-zeros format:fn-dot -1)
+ (if (= format:fn-dot 0) 1 0))))
+ (if (> left-zeros 0)
+ (begin ; normalize 0{0}.nnn to n.nn
+ (format:fn-shiftleft left-zeros)
+ (set! format:fn-dot 1))
+ (if (= format:fn-dot 0)
+ (set! format:fn-dot 1)))
+ (format:en-set (- (+ (- format:fn-dot scale) (format:en-int))
+ negexp))
+ (cond
+ (all-zeros?
+ (format:en-set 0)
+ (set! format:fn-dot 1))
+ ((< scale 0) ; leading zero
+ (format:fn-zfill #t (- scale))
+ (set! format:fn-dot 0))
+ ((> scale format:fn-dot)
+ (format:fn-zfill #f (- scale format:fn-dot))
+ (set! format:fn-dot scale))
+ (else
+ (set! format:fn-dot scale)))))
+ #t)
+
+ ;; do body
+ (set! c (string-ref num-str i)) ; parse the output of number->string
+ (cond ; which can be any valid number
+ ((char-numeric? c) ; representation of R4RS except
+ (if mantissa? ; complex numbers
+ (begin
+ (if (char=? c #\0)
+ (if all-zeros?
+ (set! left-zeros (+ left-zeros 1)))
+ (begin
+ (set! all-zeros? #f)))
+ (string-set! format:fn-str format:fn-len c)
+ (set! format:fn-len (+ format:fn-len 1)))
+ (begin
+ (string-set! format:en-str format:en-len c)
+ (set! format:en-len (+ format:en-len 1)))))
+ ((or (char=? c #\-) (char=? c #\+))
+ (if mantissa?
+ (set! format:fn-pos? (char=? c #\+))
+ (set! format:en-pos? (char=? c #\+))))
+ ((char=? c #\.)
+ (set! format:fn-dot format:fn-len))
+ ((char=? c #\e)
+ (set! mantissa? #f))
+ ((char=? c #\E)
+ (set! mantissa? #f))
+ ((char-whitespace? c) #t)
+ ((char=? c #\d) #t) ; decimal radix prefix
+ ((char=? c #\#) #t)
+ (else
+ (format:error "illegal character `~c' in number->string" c))))))
+
+ (define (format:en-int) ; convert exponent string to integer
+ (if (= format:en-len 0)
+ 0
+ (do ((i 0 (+ i 1))
+ (n 0))
+ ((= i format:en-len)
+ (if format:en-pos?
+ n
+ (- n)))
+ (set! n (+ (* n 10) (- (char->integer (string-ref format:en-str i))
+ format:zero-ch))))))
+
+ (define (format:en-set en) ; set exponent string number
+ (set! format:en-len 0)
+ (set! format:en-pos? (>= en 0))
+ (let ((en-str (number->string en)))
+ (do ((i 0 (+ i 1))
+ (en-len (string-length en-str))
+ (c #f))
+ ((= i en-len))
+ (set! c (string-ref en-str i))
+ (if (char-numeric? c)
+ (begin
+ (string-set! format:en-str format:en-len c)
+ (set! format:en-len (+ format:en-len 1)))))))
+
+ (define (format:fn-zfill left? n) ; fill current number string with 0s
+ (if (> (+ n format:fn-len) format:fn-max) ; from the left or right
+ (format:error "number is too long to format (enlarge format:fn-max)"))
+ (set! format:fn-len (+ format:fn-len n))
+ (if left?
+ (do ((i format:fn-len (- i 1))) ; fill n 0s to left
+ ((< i 0))
+ (string-set! format:fn-str i
+ (if (< i n)
+ #\0
+ (string-ref format:fn-str (- i n)))))
+ (do ((i (- format:fn-len n) (+ i 1))) ; fill n 0s to the right
+ ((= i format:fn-len))
+ (string-set! format:fn-str i #\0))))
+
+ (define (format:fn-shiftleft n) ; shift left current number n positions
+ (if (> n format:fn-len)
+ (format:error "internal error in format:fn-shiftleft (~d,~d)"
+ n format:fn-len))
+ (do ((i n (+ i 1)))
+ ((= i format:fn-len)
+ (set! format:fn-len (- format:fn-len n)))
+ (string-set! format:fn-str (- i n) (string-ref format:fn-str i))))
+
+ (define (format:fn-round digits) ; round format:fn-str
+ (set! digits (+ digits format:fn-dot))
+ (do ((i digits (- i 1)) ; "099",2 -> "10"
+ (c 5)) ; "023",2 -> "02"
+ ((or (= c 0) (< i 0)) ; "999",2 -> "100"
+ (if (= c 1) ; "005",2 -> "01"
+ (begin ; carry overflow
+ (set! format:fn-len digits)
+ (format:fn-zfill #t 1) ; add a 1 before fn-str
+ (string-set! format:fn-str 0 #\1)
+ (set! format:fn-dot (+ format:fn-dot 1)))
+ (set! format:fn-len digits)))
+ (set! c (+ (- (char->integer (string-ref format:fn-str i))
+ format:zero-ch) c))
+ (string-set! format:fn-str i (integer->char
+ (if (< c 10)
+ (+ c format:zero-ch)
+ (+ (- c 10) format:zero-ch))))
+ (set! c (if (< c 10) 0 1))))
+
+ (define (format:fn-out modifier add-leading-zero?)
+ (if format:fn-pos?
+ (if (eq? modifier 'at)
+ (format:out-char #\+))
+ (format:out-char #\-))
+ (if (= format:fn-dot 0)
+ (if add-leading-zero?
+ (format:out-char #\0))
+ (format:out-substr format:fn-str 0 format:fn-dot))
+ (format:out-char #\.)
+ (format:out-substr format:fn-str format:fn-dot format:fn-len))
+
+ (define (format:en-out edigits expch)
+ (format:out-char (if expch (integer->char expch) #\E))
+ (format:out-char (if format:en-pos? #\+ #\-))
+ (if edigits
+ (if (< format:en-len edigits)
+ (format:out-fill (- edigits format:en-len) #\0)))
+ (format:out-substr format:en-str 0 format:en-len))
+
+ (define (format:fn-strip) ; strip trailing zeros but one
+ (string-set! format:fn-str format:fn-len #\0)
+ (do ((i format:fn-len (- i 1)))
+ ((or (not (char=? (string-ref format:fn-str i) #\0))
+ (<= i format:fn-dot))
+ (set! format:fn-len (+ i 1)))))
+
+ (define (format:fn-zlead) ; count leading zeros
+ (do ((i 0 (+ i 1)))
+ ((or (= i format:fn-len)
+ (not (char=? (string-ref format:fn-str i) #\0)))
+ (if (= i format:fn-len) ; found a real zero
+ 0
+ i))))
+
+
+;;; some global functions not found in SLIB
+
+ (define (string-capitalize-first str) ; "hello" -> "Hello"
+ (let ((cap-str (string-copy str)) ; "hELLO" -> "Hello"
+ (non-first-alpha #f) ; "*hello" -> "*Hello"
+ (str-len (string-length str))) ; "hello you" -> "Hello you"
+ (do ((i 0 (+ i 1)))
+ ((= i str-len) cap-str)
+ (let ((c (string-ref str i)))
+ (if (char-alphabetic? c)
+ (if non-first-alpha
+ (string-set! cap-str i (char-downcase c))
+ (begin
+ (set! non-first-alpha #t)
+ (string-set! cap-str i (char-upcase c)))))))))
+
+ ;; Aborts the program when a formatting error occures. This is a null
+ ;; argument closure to jump to the interpreters toplevel continuation.
+
+ (define (format:abort) (error "error in format"))
+
+ (let ((arg-pos (format:format-work format-string format-args))
+ (arg-len (length format-args)))
+ (cond
+ ((> arg-pos arg-len)
+ (set! format:arg-pos (+ arg-len 1))
+ (display format:arg-pos)
+ (format:error "~a missing argument~:p" (- arg-pos arg-len)))
+ (else
+ (if flush-output?
+ (force-output port))
+ (if destination
+ #t
+ (let ((str (get-output-string port)))
+ (close-port port)
+ str)))))))
+
+(begin-deprecated
+ (set! format
+ (let ((format format))
+ (case-lambda
+ ((destination format-string . args)
+ (if (string? destination)
+ (begin
+ (issue-deprecation-warning
+ "Omitting the destination on a call to format is deprecated."
+ "Pass #f as the destination, before the format string.")
+ (apply format #f destination format-string args))
+ (apply format destination format-string args)))
+ ((deprecated-format-string-only)
+ (issue-deprecation-warning
+ "Omitting the destination port on a call to format is deprecated."
+ "Pass #f as the destination port, before the format string.")
+ (format #f deprecated-format-string-only))))))
+
+
+;; Thanks to Shuji Narazaki
+(module-set! the-root-module 'format format)
+;;;; ftw.scm --- file system tree walk
+
+;;;; Copyright (C) 2002, 2003, 2006, 2011, 2012, 2014, 2016 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
+
+;;; Commentary:
+
+;; Two procedures are provided: `ftw' and `nftw'.
+
+;; NOTE: The following description was adapted from the GNU libc info page, w/
+;; significant modifications for a more "Schemey" interface. Most noticible
+;; are the inlining of `struct FTW *' parameters `base' and `level' and the
+;; omission of `descriptors' parameters.
+
+;; * Types
+;;
+;; The X/Open specification defines two procedures to process whole
+;; hierarchies of directories and the contained files. Both procedures
+;; of this `ftw' family take as one of the arguments a callback procedure
+;; which must be of these types.
+;;
+;; - Data Type: __ftw_proc_t
+;; (lambda (filename statinfo flag) ...) => status
+;;
+;; Type for callback procedures given to the `ftw' procedure. The
+;; first parameter is a filename, the second parameter is the
+;; vector value as returned by calling `stat' on FILENAME.
+;;
+;; The last parameter is a symbol giving more information about
+;; FILENAM. It can have one of the following values:
+;;
+;; `regular'
+;; The current item is a normal file or files which do not fit
+;; into one of the following categories. This means
+;; especially special files, sockets etc.
+;;
+;; `directory'
+;; The current item is a directory.
+;;
+;; `invalid-stat'
+;; The `stat' call to fill the object pointed to by the second
+;; parameter failed and so the information is invalid.
+;;
+;; `directory-not-readable'
+;; The item is a directory which cannot be read.
+;;
+;; `symlink'
+;; The item is a symbolic link. Since symbolic links are
+;; normally followed seeing this value in a `ftw' callback
+;; procedure means the referenced file does not exist. The
+;; situation for `nftw' is different.
+;;
+;; - Data Type: __nftw_proc_t
+;; (lambda (filename statinfo flag base level) ...) => status
+;;
+;; The first three arguments have the same as for the
+;; `__ftw_proc_t' type. A difference is that for the third
+;; argument some additional values are defined to allow finer
+;; differentiation:
+;;
+;; `directory-processed'
+;; The current item is a directory and all subdirectories have
+;; already been visited and reported. This flag is returned
+;; instead of `directory' if the `depth' flag is given to
+;; `nftw' (see below).
+;;
+;; `stale-symlink'
+;; The current item is a stale symbolic link. The file it
+;; points to does not exist.
+;;
+;; The last two parameters are described below. They contain
+;; information to help interpret FILENAME and give some information
+;; about current state of the traversal of the directory hierarchy.
+;;
+;; `base'
+;; The value specifies which part of the filename argument
+;; given in the first parameter to the callback procedure is
+;; the name of the file. The rest of the string is the path
+;; to locate the file. This information is especially
+;; important if the `chdir' flag for `nftw' was set since then
+;; the current directory is the one the current item is found
+;; in.
+;;
+;; `level'
+;; While processing the directory the procedures tracks how
+;; many directories have been examined to find the current
+;; item. This nesting level is 0 for the item given starting
+;; item (file or directory) and is incremented by one for each
+;; entered directory.
+;;
+;; * Procedure: (ftw filename proc . options)
+;; Do a file system tree walk starting at FILENAME using PROC.
+;;
+;; The `ftw' procedure calls the callback procedure given in the
+;; parameter PROC for every item which is found in the directory
+;; specified by FILENAME and all directories below. The procedure
+;; follows symbolic links if necessary but does not process an item
+;; twice. If FILENAME names no directory this item is the only
+;; object reported by calling the callback procedure.
+;;
+;; The filename given to the callback procedure is constructed by
+;; taking the FILENAME parameter and appending the names of all
+;; passed directories and then the local file name. So the
+;; callback procedure can use this parameter to access the file.
+;; Before the callback procedure is called `ftw' calls `stat' for
+;; this file and passes the information up to the callback
+;; procedure. If this `stat' call was not successful the failure is
+;; indicated by setting the flag argument of the callback procedure
+;; to `invalid-stat'. Otherwise the flag is set according to the
+;; description given in the description of `__ftw_proc_t' above.
+;;
+;; The callback procedure is expected to return non-#f to indicate
+;; that no error occurred and the processing should be continued.
+;; If an error occurred in the callback procedure or the call to
+;; `ftw' shall return immediately the callback procedure can return
+;; #f. This is the only correct way to stop the procedure. The
+;; program must not use `throw' or similar techniques to continue
+;; the program in another place. [Can we relax this? --ttn]
+;;
+;; The return value of the `ftw' procedure is #t if all callback
+;; procedure calls returned #t and all actions performed by the
+;; `ftw' succeeded. If some procedure call failed (other than
+;; calling `stat' on an item) the procedure returns #f. If a
+;; callback procedure returns a value other than #t this value is
+;; returned as the return value of `ftw'.
+;;
+;; * Procedure: (nftw filename proc . control-flags)
+;; Do a new-style file system tree walk starting at FILENAME using PROC.
+;; Various optional CONTROL-FLAGS alter the default behavior.
+;;
+;; The `nftw' procedures works like the `ftw' procedures. It calls
+;; the callback procedure PROC for all items it finds in the
+;; directory FILENAME and below.
+;;
+;; The differences are that for one the callback procedure is of a
+;; different type. It takes also `base' and `level' parameters as
+;; described above.
+;;
+;; The second difference is that `nftw' takes additional optional
+;; arguments which are zero or more of the following symbols:
+;;
+;; physical'
+;; While traversing the directory symbolic links are not
+;; followed. I.e., if this flag is given symbolic links are
+;; reported using the `symlink' value for the type parameter
+;; to the callback procedure. Please note that if this flag is
+;; used the appearance of `symlink' in a callback procedure
+;; does not mean the referenced file does not exist. To
+;; indicate this the extra value `stale-symlink' exists.
+;;
+;; mount'
+;; The callback procedure is only called for items which are on
+;; the same mounted file system as the directory given as the
+;; FILENAME parameter to `nftw'.
+;;
+;; chdir'
+;; If this flag is given the current working directory is
+;; changed to the directory containing the reported object
+;; before the callback procedure is called.
+;;
+;; depth'
+;; If this option is given the procedure visits first all files
+;; and subdirectories before the callback procedure is called
+;; for the directory itself (depth-first processing). This
+;; also means the type flag given to the callback procedure is
+;; `directory-processed' and not `directory'.
+;;
+;; The return value is computed in the same way as for `ftw'.
+;; `nftw' returns #t if no failure occurred in `nftw' and all
+;; callback procedure call return values are also #t. For internal
+;; errors such as memory problems the error `ftw-error' is thrown.
+;; If the return value of a callback invocation is not #t this
+;; very same value is returned.
+
+;;; Code:
+
+(define-module (ice-9 ftw)
+ #\use-module (ice-9 match)
+ #\use-module (ice-9 vlist)
+ #\use-module (srfi srfi-1)
+ #\autoload (ice-9 i18n) (string-locale<?)
+ #\export (ftw nftw
+ file-system-fold
+ file-system-tree
+ scandir))
+
+(define (directory-files dir)
+ (let ((dir-stream (opendir dir)))
+ (let loop ((new (readdir dir-stream))
+ (acc '()))
+ (if (eof-object? new)
+ (begin
+ (closedir dir-stream)
+ acc)
+ (loop (readdir dir-stream)
+ (if (or (string=? "." new) ;;; ignore
+ (string=? ".." new)) ;;; ignore
+ acc
+ (cons new acc)))))))
+
+(define (pathify . nodes)
+ (let loop ((nodes nodes)
+ (result ""))
+ (if (null? nodes)
+ (or (and (string=? "" result) "")
+ (substring result 1 (string-length result)))
+ (loop (cdr nodes) (string-append result "/" (car nodes))))))
+
+(define (abs? filename)
+ (char=? #\/ (string-ref filename 0)))
+
+;; `visited?-proc' returns a test procedure VISITED? which when called as
+;; (VISITED? stat-obj) returns #f the first time a distinct file is seen,
+;; then #t on any subsequent sighting of it.
+;;
+;; stat:dev and stat:ino together uniquely identify a file (see "Attribute
+;; Meanings" in the glibc manual). Often there'll be just one dev, and
+;; usually there's just a handful mounted, so the strategy here is a small
+;; hash table indexed by dev, containing hash tables indexed by ino.
+;;
+;; It'd be possible to make a pair (dev . ino) and use that as the key to a
+;; single hash table. It'd use an extra pair for every file visited, but
+;; might be a little faster if it meant less scheme code.
+;;
+(define (visited?-proc size)
+ (let ((dev-hash (make-hash-table 7)))
+ (lambda (s)
+ (and s
+ (let ((ino-hash (hashv-ref dev-hash (stat:dev s)))
+ (ino (stat:ino s)))
+ (or ino-hash
+ (begin
+ (set! ino-hash (make-hash-table size))
+ (hashv-set! dev-hash (stat:dev s) ino-hash)))
+ (or (hashv-ref ino-hash ino)
+ (begin
+ (hashv-set! ino-hash ino #t)
+ #f)))))))
+
+(define (stat-dir-readable?-proc uid gid)
+ (let ((uid (getuid))
+ (gid (getgid)))
+ (lambda (s)
+ (let* ((perms (stat:perms s))
+ (perms-bit-set? (lambda (mask)
+ (not (= 0 (logand mask perms))))))
+ (or (zero? uid)
+ (and (= uid (stat:uid s))
+ (perms-bit-set? #o400))
+ (and (= gid (stat:gid s))
+ (perms-bit-set? #o040))
+ (perms-bit-set? #o004))))))
+
+(define (stat&flag-proc dir-readable? . control-flags)
+ (let* ((directory-flag (if (memq 'depth control-flags)
+ 'directory-processed
+ 'directory))
+ (stale-symlink-flag (if (memq 'nftw-style control-flags)
+ 'stale-symlink
+ 'symlink))
+ (physical? (memq 'physical control-flags))
+ (easy-flag (lambda (s)
+ (let ((type (stat:type s)))
+ (if (eq? 'directory type)
+ (if (dir-readable? s)
+ directory-flag
+ 'directory-not-readable)
+ 'regular)))))
+ (lambda (name)
+ (let ((s (false-if-exception (lstat name))))
+ (cond ((not s)
+ (values s 'invalid-stat))
+ ((eq? 'symlink (stat:type s))
+ (let ((s-follow (false-if-exception (stat name))))
+ (cond ((not s-follow)
+ (values s stale-symlink-flag))
+ ((and s-follow physical?)
+ (values s 'symlink))
+ ((and s-follow (not physical?))
+ (values s-follow (easy-flag s-follow))))))
+ (else (values s (easy-flag s))))))))
+
+(define (clean name)
+ (let ((last-char-index (1- (string-length name))))
+ (if (char=? #\/ (string-ref name last-char-index))
+ (substring name 0 last-char-index)
+ name)))
+
+(define (ftw filename proc . options)
+ (let* ((visited? (visited?-proc (cond ((memq 'hash-size options) => cadr)
+ (else 211))))
+ (stat&flag (stat&flag-proc
+ (stat-dir-readable?-proc (getuid) (getgid)))))
+ (letrec ((go (lambda (fullname)
+ (call-with-values (lambda () (stat&flag fullname))
+ (lambda (s flag)
+ (or (visited? s)
+ (let ((ret (proc fullname s flag))) ; callback
+ (or (eq? #t ret)
+ (throw 'ftw-early-exit ret))
+ (and (eq? 'directory flag)
+ (for-each
+ (lambda (child)
+ (go (pathify fullname child)))
+ (directory-files fullname)))
+ #t)))))))
+ (catch 'ftw-early-exit
+ (lambda () (go (clean filename)))
+ (lambda (key val) val)))))
+
+(define (nftw filename proc . control-flags)
+ (let* ((od (getcwd)) ; orig dir
+ (odev (let ((s (false-if-exception (lstat filename))))
+ (if s (stat:dev s) -1)))
+ (same-dev? (if (memq 'mount control-flags)
+ (lambda (s) (= (stat:dev s) odev))
+ (lambda (s) #t)))
+ (base-sub (lambda (name base) (substring name 0 base)))
+ (maybe-cd (if (memq 'chdir control-flags)
+ (if (abs? filename)
+ (lambda (fullname base)
+ (or (= 0 base)
+ (chdir (base-sub fullname base))))
+ (lambda (fullname base)
+ (chdir
+ (pathify od (base-sub fullname base)))))
+ (lambda (fullname base) #t)))
+ (maybe-cd-back (if (memq 'chdir control-flags)
+ (lambda () (chdir od))
+ (lambda () #t)))
+ (depth-first? (memq 'depth control-flags))
+ (visited? (visited?-proc
+ (cond ((memq 'hash-size control-flags) => cadr)
+ (else 211))))
+ (has-kids? (if depth-first?
+ (lambda (flag) (eq? flag 'directory-processed))
+ (lambda (flag) (eq? flag 'directory))))
+ (stat&flag (apply stat&flag-proc
+ (stat-dir-readable?-proc (getuid) (getgid))
+ (cons 'nftw-style control-flags))))
+ (letrec ((go (lambda (fullname base level)
+ (call-with-values (lambda () (stat&flag fullname))
+ (lambda (s flag)
+ (letrec ((self (lambda ()
+ (maybe-cd fullname base)
+ ;; the callback
+ (let ((ret (proc fullname s flag
+ base level)))
+ (maybe-cd-back)
+ (or (eq? #t ret)
+ (throw 'nftw-early-exit ret)))))
+ (kids (lambda ()
+ (and (has-kids? flag)
+ (for-each
+ (lambda (child)
+ (go (pathify fullname child)
+ (1+ (string-length
+ fullname))
+ (1+ level)))
+ (directory-files fullname))))))
+ (or (visited? s)
+ (not (same-dev? s))
+ (if depth-first?
+ (begin (kids) (self))
+ (begin (self) (kids)))))))
+ #t)))
+ (let ((ret (catch 'nftw-early-exit
+ (lambda () (go (clean filename) 0 0))
+ (lambda (key val) val))))
+ (chdir od)
+ ret))))
+
+
+;;;
+;;; `file-system-fold' & co.
+;;;
+
+(define-syntax-rule (errno-if-exception expr)
+ (catch 'system-error
+ (lambda ()
+ expr)
+ (lambda args
+ (system-error-errno args))))
+
+(define* (file-system-fold enter? leaf down up skip error init file-name
+ #\optional (stat lstat))
+ "Traverse the directory at FILE-NAME, recursively. Enter
+sub-directories only when (ENTER? PATH STAT RESULT) returns true. When
+a sub-directory is entered, call (DOWN PATH STAT RESULT), where PATH is
+the path of the sub-directory and STAT the result of (stat PATH); when
+it is left, call (UP PATH STAT RESULT). For each file in a directory,
+call (LEAF PATH STAT RESULT). When ENTER? returns false, call (SKIP
+PATH STAT RESULT). When an `opendir' or STAT call raises an exception,
+call (ERROR PATH STAT ERRNO RESULT), with ERRNO being the operating
+system error number that was raised.
+
+Return the result of these successive applications.
+When FILE-NAME names a flat file, (LEAF PATH STAT INIT) is returned.
+The optional STAT parameter defaults to `lstat'."
+
+ (define (mark v s)
+ (vhash-cons (cons (stat:dev s) (stat:ino s)) #t v))
+
+ (define (visited? v s)
+ (vhash-assoc (cons (stat:dev s) (stat:ino s)) v))
+
+ (let loop ((name file-name)
+ (path "")
+ (dir-stat (errno-if-exception (stat file-name)))
+ (result init)
+ (visited vlist-null))
+
+ (define full-name
+ (if (string=? path "")
+ name
+ (string-append path "/" name)))
+
+ (cond
+ ((integer? dir-stat)
+ ;; FILE-NAME is not readable.
+ (error full-name #f dir-stat result))
+ ((visited? visited dir-stat)
+ (values result visited))
+ ((eq? 'directory (stat:type dir-stat)) ; true except perhaps the 1st time
+ (if (enter? full-name dir-stat result)
+ (let ((dir (errno-if-exception (opendir full-name)))
+ (visited (mark visited dir-stat)))
+ (cond
+ ((directory-stream? dir)
+ (let liip ((entry (readdir dir))
+ (result (down full-name dir-stat result))
+ (subdirs '()))
+ (cond ((eof-object? entry)
+ (begin
+ (closedir dir)
+ (let ((r+v
+ (fold (lambda (subdir result+visited)
+ (call-with-values
+ (lambda ()
+ (loop (car subdir)
+ full-name
+ (cdr subdir)
+ (car result+visited)
+ (cdr result+visited)))
+ cons))
+ (cons result visited)
+ subdirs)))
+ (values (up full-name dir-stat (car r+v))
+ (cdr r+v)))))
+ ((or (string=? entry ".")
+ (string=? entry ".."))
+ (liip (readdir dir)
+ result
+ subdirs))
+ (else
+ (let* ((child (string-append full-name "/" entry))
+ (st (errno-if-exception (stat child))))
+ (if (integer? st) ; CHILD is a dangling symlink?
+ (liip (readdir dir)
+ (error child #f st result)
+ subdirs)
+ (if (eq? (stat:type st) 'directory)
+ (liip (readdir dir)
+ result
+ (alist-cons entry st subdirs))
+ (liip (readdir dir)
+ (leaf child st result)
+ subdirs))))))))
+ (else
+ ;; Directory FULL-NAME not readable, but it is stat'able.
+ (values (error full-name dir-stat dir result)
+ visited))))
+ (values (skip full-name dir-stat result)
+ (mark visited dir-stat))))
+ (else
+ ;; Caller passed a FILE-NAME that names a flat file, not a directory.
+ (leaf full-name dir-stat result)))))
+
+(define* (file-system-tree file-name
+ #\optional (enter? (lambda (n s) #t))
+ (stat lstat))
+ "Return a tree of the form (FILE-NAME STAT CHILDREN ...) where STAT is
+the result of (STAT FILE-NAME) and CHILDREN are similar structures for
+each file contained in FILE-NAME when it designates a directory. The
+optional ENTER? predicate is invoked as (ENTER? NAME STAT) and should
+return true to allow recursion into directory NAME; the default value is
+a procedure that always returns #t. When a directory does not match
+ENTER?, it nonetheless appears in the resulting tree, only with zero
+children. The optional STAT parameter defaults to `lstat'. Return #f
+when FILE-NAME is not readable."
+ (define (enter?* name stat result)
+ (enter? name stat))
+ (define (leaf name stat result)
+ (match result
+ (((siblings ...) rest ...)
+ (cons (alist-cons (basename name) (cons stat '()) siblings)
+ rest))))
+ (define (down name stat result)
+ (cons '() result))
+ (define (up name stat result)
+ (match result
+ (((children ...) (siblings ...) rest ...)
+ (cons (alist-cons (basename name) (cons stat children)
+ siblings)
+ rest))))
+ (define skip ; keep an entry for skipped directories
+ leaf)
+ (define (error name stat errno result)
+ (if (string=? name file-name)
+ result
+ (leaf name stat result)))
+
+ (match (file-system-fold enter?* leaf down up skip error '(())
+ file-name stat)
+ (((tree)) tree)
+ ((()) #f))) ; FILE-NAME is unreadable
+
+(define* (scandir name #\optional (select? (const #t))
+ (entry<? string-locale<?))
+ "Return the list of the names of files contained in directory NAME
+that match predicate SELECT? (by default, all files.) The returned list
+of file names is sorted according to ENTRY<?, which defaults to
+`string-locale<?'. Return #f when NAME is unreadable or is not a
+directory."
+
+ ;; This procedure is implemented in terms of 'readdir' instead of
+ ;; 'file-system-fold' to avoid the extra 'stat' call that the latter
+ ;; makes for each entry.
+
+ (define (opendir* directory)
+ (catch 'system-error
+ (lambda ()
+ (opendir directory))
+ (const #f)))
+
+ (and=> (opendir* name)
+ (lambda (stream)
+ (let loop ((entry (readdir stream))
+ (files '()))
+ (if (eof-object? entry)
+ (begin
+ (closedir stream)
+ (sort files entry<?))
+ (loop (readdir stream)
+ (if (select? entry)
+ (cons entry files)
+ files)))))))
+
+;;; ftw.scm ends here
+;;; -*- mode: scheme; coding: utf-8; -*-
+;;;
+;;; Copyright (C) 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (ice-9 futures)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-9 gnu)
+ #\use-module (srfi srfi-11)
+ #\use-module (ice-9 q)
+ #\use-module (ice-9 match)
+ #\use-module (ice-9 control)
+ #\export (future make-future future? touch))
+
+;;; Author: Ludovic Courtès <ludo@gnu.org>
+;;;
+;;; Commentary:
+;;;
+;;; This module provides an implementation of futures, a mechanism for
+;;; fine-grain parallelism. Futures were first described by Henry Baker
+;;; in ``The Incremental Garbage Collection of Processes'', 1977, and
+;;; then implemented in MultiLisp (an implicit variant thereof, i.e.,
+;;; without `touch'.)
+;;;
+;;; This modules uses a fixed thread pool, normally one per CPU core.
+;;; Futures are off-loaded to these threads, when they are idle.
+;;;
+;;; Code:
+
+
+;;;
+;;; Futures.
+;;;
+
+(define-record-type <future>
+ (%make-future thunk state mutex completion)
+ future?
+ (thunk future-thunk set-future-thunk!)
+ (state future-state set-future-state!) ; done | started | queued
+ (result future-result set-future-result!)
+ (mutex future-mutex)
+ (completion future-completion)) ; completion cond. var.
+
+(set-record-type-printer!
+ <future>
+ (lambda (future port)
+ (simple-format port "#<future ~a ~a ~s>"
+ (number->string (object-address future) 16)
+ (future-state future)
+ (future-thunk future))))
+
+(define (make-future thunk)
+ "Return a new future for THUNK. Execution may start at any point
+concurrently, or it can start at the time when the returned future is
+touched."
+ (create-workers!)
+ (let ((future (%make-future thunk 'queued
+ (make-mutex) (make-condition-variable))))
+ (register-future! future)
+ future))
+
+
+;;;
+;;; Future queues.
+;;;
+
+;; Global queue of pending futures.
+;; TODO: Use per-worker queues to reduce contention.
+(define %futures (make-q))
+
+;; Lock for %FUTURES and %FUTURES-WAITING.
+(define %futures-mutex (make-mutex))
+(define %futures-available (make-condition-variable))
+
+;; A mapping of nested futures to futures waiting for them to complete.
+(define %futures-waiting '())
+
+;; Nesting level of futures. Incremented each time a future is touched
+;; from within a future.
+(define %nesting-level (make-parameter 0))
+
+;; Maximum nesting level. The point is to avoid stack overflows when
+;; nested futures are executed on the same stack. See
+;; <http://bugs.gnu.org/13188>.
+(define %max-nesting-level 200)
+
+(define-syntax-rule (with-mutex m e0 e1 ...)
+ ;; Copied from (ice-9 threads) to avoid circular dependency.
+ (let ((x m))
+ (dynamic-wind
+ (lambda () (lock-mutex x))
+ (lambda () (begin e0 e1 ...))
+ (lambda () (unlock-mutex x)))))
+
+(define %future-prompt
+ ;; The prompt futures abort to when they want to wait for another
+ ;; future.
+ (make-prompt-tag))
+
+
+(define (register-future! future)
+ ;; Register FUTURE as being processable.
+ (lock-mutex %futures-mutex)
+ (enq! %futures future)
+ (signal-condition-variable %futures-available)
+ (unlock-mutex %futures-mutex))
+
+(define (process-future! future)
+ "Process FUTURE. When FUTURE completes, return #t and update its
+result; otherwise, when FUTURE touches a nested future that has not
+completed yet, then suspend it and return #f. Suspending a future
+consists in capturing its continuation, marking it as `queued', and
+adding it to the waiter queue."
+ (let/ec return
+ (let* ((suspend
+ (lambda (cont future-to-wait)
+ ;; FUTURE wishes to wait for the completion of FUTURE-TO-WAIT.
+ ;; At this point, FUTURE is unlocked and in `started' state,
+ ;; and FUTURE-TO-WAIT is unlocked.
+ (with-mutex %futures-mutex
+ (with-mutex (future-mutex future)
+ (set-future-thunk! future cont)
+ (set-future-state! future 'queued))
+
+ (with-mutex (future-mutex future-to-wait)
+ ;; If FUTURE-TO-WAIT completed in the meantime, then
+ ;; reschedule FUTURE directly; otherwise, add it to the
+ ;; waiter queue.
+ (if (eq? 'done (future-state future-to-wait))
+ (begin
+ (enq! %futures future)
+ (signal-condition-variable %futures-available))
+ (set! %futures-waiting
+ (alist-cons future-to-wait future
+ %futures-waiting))))
+
+ (return #f))))
+ (thunk (lambda ()
+ (call-with-prompt %future-prompt
+ (lambda ()
+ (parameterize ((%nesting-level
+ (1+ (%nesting-level))))
+ ((future-thunk future))))
+ suspend))))
+ (set-future-result! future
+ (catch #t
+ (lambda ()
+ (call-with-values thunk
+ (lambda results
+ (lambda ()
+ (apply values results)))))
+ (lambda args
+ (lambda ()
+ (apply throw args)))))
+ #t)))
+
+(define (process-one-future)
+ "Attempt to pick one future from the queue and process it."
+ ;; %FUTURES-MUTEX must be locked on entry, and is locked on exit.
+ (or (q-empty? %futures)
+ (let ((future (deq! %futures)))
+ (lock-mutex (future-mutex future))
+ (case (future-state future)
+ ((done started)
+ ;; Nothing to do.
+ (unlock-mutex (future-mutex future)))
+ (else
+ ;; Do the actual work.
+
+ ;; We want to release %FUTURES-MUTEX so that other workers can
+ ;; progress. However, to avoid deadlocks, we have to unlock
+ ;; FUTURE as well, to preserve lock ordering.
+ (unlock-mutex (future-mutex future))
+ (unlock-mutex %futures-mutex)
+
+ (lock-mutex (future-mutex future))
+ (if (eq? (future-state future) 'queued) ; lost the race?
+ (begin ; no, so let's process it
+ (set-future-state! future 'started)
+ (unlock-mutex (future-mutex future))
+
+ (let ((done? (process-future! future)))
+ (when done?
+ (with-mutex %futures-mutex
+ (with-mutex (future-mutex future)
+ (set-future-state! future 'done)
+ (notify-completion future))))))
+ (unlock-mutex (future-mutex future))) ; yes
+
+ (lock-mutex %futures-mutex))))))
+
+(define (process-futures)
+ "Continuously process futures from the queue."
+ (lock-mutex %futures-mutex)
+ (let loop ()
+ (when (q-empty? %futures)
+ (wait-condition-variable %futures-available
+ %futures-mutex))
+
+ (process-one-future)
+ (loop)))
+
+(define (notify-completion future)
+ "Notify futures and callers waiting that FUTURE completed."
+ ;; FUTURE and %FUTURES-MUTEX are locked.
+ (broadcast-condition-variable (future-completion future))
+ (let-values (((waiting remaining)
+ (partition (match-lambda ; TODO: optimize
+ ((waitee . _)
+ (eq? waitee future)))
+ %futures-waiting)))
+ (set! %futures-waiting remaining)
+ (for-each (match-lambda
+ ((_ . waiter)
+ (enq! %futures waiter)))
+ waiting)))
+
+(define (touch future)
+ "Return the result of FUTURE, computing it if not already done."
+ (define (work)
+ ;; Do some work while waiting for FUTURE to complete.
+ (lock-mutex %futures-mutex)
+ (if (q-empty? %futures)
+ (begin
+ (unlock-mutex %futures-mutex)
+ (with-mutex (future-mutex future)
+ (unless (eq? 'done (future-state future))
+ (wait-condition-variable (future-completion future)
+ (future-mutex future)))))
+ (begin
+ (process-one-future)
+ (unlock-mutex %futures-mutex))))
+
+ (let loop ()
+ (lock-mutex (future-mutex future))
+ (case (future-state future)
+ ((done)
+ (unlock-mutex (future-mutex future)))
+ ((started)
+ (unlock-mutex (future-mutex future))
+ (if (> (%nesting-level) 0)
+ (abort-to-prompt %future-prompt future)
+ (begin
+ (work)
+ (loop))))
+ (else ; queued
+ (unlock-mutex (future-mutex future))
+ (if (> (%nesting-level) %max-nesting-level)
+ (abort-to-prompt %future-prompt future)
+ (work))
+ (loop))))
+ ((future-result future)))
+
+
+;;;
+;;; Workers.
+;;;
+
+(define %worker-count
+ (if (provided? 'threads)
+ (- (current-processor-count) 1)
+ 0))
+
+;; A dock of workers that stay here forever.
+
+;; TODO
+;; 1. Allow the pool to be shrunk, as in libgomp (though that we'd
+;; need semaphores, which aren't yet in libguile!).
+;; 2. Provide a `worker-count' fluid.
+(define %workers '())
+
+(define (%create-workers!)
+ (with-mutex
+ %futures-mutex
+ ;; Setting 'create-workers!' to a no-op is an optimization, but it is
+ ;; still possible for '%create-workers!' to be called more than once
+ ;; from different threads. Therefore, to avoid creating %workers more
+ ;; than once (and thus creating too many threads), we check to make
+ ;; sure %workers is empty within the critical section.
+ (when (null? %workers)
+ (set! %workers
+ (unfold (lambda (i) (>= i %worker-count))
+ (lambda (i) (call-with-new-thread process-futures))
+ 1+
+ 0))
+ (set! create-workers! (lambda () #t)))))
+
+(define create-workers!
+ (lambda () (%create-workers!)))
+
+
+;;;
+;;; Syntax.
+;;;
+
+(define-syntax-rule (future body)
+ "Return a new future for BODY."
+ (make-future (lambda () body)))
+
+;;; Local Variables:
+;;; eval: (put 'with-mutex 'scheme-indent-function 1)
+;;; End:
+;;; gap-buffer.scm --- String buffer that supports point
+
+;;; Copyright (C) 2002, 2003, 2006 Free Software Foundation, Inc.
+;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;
+
+;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
+
+;;; Commentary:
+
+;; A gap buffer is a structure that models a string but allows relatively
+;; efficient insertion of text somewhere in the middle. The insertion
+;; location is called `point' with minimum value 1, and a maximum value of the
+;; length of the string (which is not fixed).
+;;
+;; Specifically, we allocate a continuous buffer of characters that is
+;; composed of the BEFORE, the GAP and the AFTER (reading L->R), like so:
+;;
+;; +--- POINT
+;; v
+;; +--------------------+--------------------+--------------------+
+;; | BEFORE | GAP | AFTER |
+;; +--------------------+--------------------+--------------------+
+;;
+;; <----- bef-sz ----->|<----- gap-sz ----->|<----- aft-sz ----->
+;;
+;; <-------------------| usr-sz |------------------->
+;;
+;; <-------------------------- all-sz -------------------------->
+;;
+;; This diagram also shows how the different sizes are computed, and the
+;; location of POINT. Note that the user-visible buffer size `usr-sz' does
+;; NOT include the GAP, while the allocation `all-sz' DOES.
+;;
+;; The consequence of this arrangement is that "moving point" is simply a
+;; matter of kicking characters across the GAP, while insertion can be viewed
+;; as filling up the gap, increasing `bef-sz' and decreasing `gap-sz'. When
+;; `gap-sz' falls below some threshold, we reallocate with a larger `all-sz'.
+;;
+;; In the implementation, we actually keep track of the AFTER start offset
+;; `aft-ofs' since it is used more often than `gap-sz'. In fact, most of the
+;; variables in the diagram are for conceptualization only.
+;;
+;; A gap buffer port is a soft port (see Guile manual) that wraps a gap
+;; buffer. Character and string writes, as well as character reads, are
+;; supported. Flushing and closing are not supported.
+;;
+;; These procedures are exported:
+;; (gb? OBJ)
+;; (make-gap-buffer . INIT)
+;; (gb-point GB)
+;; (gb-point-min GB)
+;; (gb-point-max GB)
+;; (gb-insert-string! GB STRING)
+;; (gb-insert-char! GB CHAR)
+;; (gb-delete-char! GB COUNT)
+;; (gb-goto-char GB LOCATION)
+;; (gb->string GB)
+;; (gb-filter! GB STRING-PROC)
+;; (gb->lines GB)
+;; (gb-filter-lines! GB LINES-PROC)
+;; (make-gap-buffer-port GB)
+;;
+;; INIT is an optional port or a string. COUNT and LOCATION are integers.
+;; STRING-PROC is a procedure that takes and returns a string. LINES-PROC is
+;; a procedure that takes and returns a list of strings, each representing a
+;; line of text (newlines are stripped and added back automatically).
+;;
+;; (The term and concept of "gap buffer" are borrowed from Emacs. We will
+;; gladly return them when libemacs.so is available. ;-)
+;;
+;; Notes:
+;; - overrun errors are suppressed silently
+
+;;; Code:
+
+(define-module (ice-9 gap-buffer)
+ \:autoload (srfi srfi-13) (string-join)
+ \:export (gb?
+ make-gap-buffer
+ gb-point
+ gb-point-min
+ gb-point-max
+ gb-insert-string!
+ gb-insert-char!
+ gb-delete-char!
+ gb-erase!
+ gb-goto-char
+ gb->string
+ gb-filter!
+ gb->lines
+ gb-filter-lines!
+ make-gap-buffer-port))
+
+(define gap-buffer
+ (make-record-type 'gap-buffer
+ '(s ; the buffer, a string
+ all-sz ; total allocation
+ gap-ofs ; GAP starts, aka (1- point)
+ aft-ofs ; AFTER starts
+ )))
+
+(define gb? (record-predicate gap-buffer))
+
+(define s\: (record-accessor gap-buffer 's))
+(define all-sz\: (record-accessor gap-buffer 'all-sz))
+(define gap-ofs\: (record-accessor gap-buffer 'gap-ofs))
+(define aft-ofs\: (record-accessor gap-buffer 'aft-ofs))
+
+(define s! (record-modifier gap-buffer 's))
+(define all-sz! (record-modifier gap-buffer 'all-sz))
+(define gap-ofs! (record-modifier gap-buffer 'gap-ofs))
+(define aft-ofs! (record-modifier gap-buffer 'aft-ofs))
+
+;; todo: expose
+(define default-initial-allocation 128)
+(define default-chunk-size 128)
+(define default-realloc-threshold 32)
+
+(define (round-up n)
+ (* default-chunk-size (+ 1 (quotient n default-chunk-size))))
+
+(define new (record-constructor gap-buffer '()))
+
+(define (realloc gb inc)
+ (let* ((old-s (s\: gb))
+ (all-sz (all-sz\: gb))
+ (new-sz (+ all-sz inc))
+ (gap-ofs (gap-ofs\: gb))
+ (aft-ofs (aft-ofs\: gb))
+ (new-s (make-string new-sz))
+ (new-aft-ofs (+ aft-ofs inc)))
+ (substring-move! old-s 0 gap-ofs new-s 0)
+ (substring-move! old-s aft-ofs all-sz new-s new-aft-ofs)
+ (s! gb new-s)
+ (all-sz! gb new-sz)
+ (aft-ofs! gb new-aft-ofs)))
+
+(define (make-gap-buffer . init) ; port/string
+ (let ((gb (new)))
+ (cond ((null? init)
+ (s! gb (make-string default-initial-allocation))
+ (all-sz! gb default-initial-allocation)
+ (gap-ofs! gb 0)
+ (aft-ofs! gb default-initial-allocation))
+ (else (let ((jam! (lambda (string len)
+ (let ((alloc (round-up len)))
+ (s! gb (make-string alloc))
+ (all-sz! gb alloc)
+ (substring-move! string 0 len (s\: gb) 0)
+ (gap-ofs! gb len)
+ (aft-ofs! gb alloc))))
+ (v (car init)))
+ (cond ((port? v)
+ (let ((next (lambda () (read-char v))))
+ (let loop ((c (next)) (acc '()) (len 0))
+ (if (eof-object? c)
+ (jam! (list->string (reverse acc)) len)
+ (loop (next) (cons c acc) (1+ len))))))
+ ((string? v)
+ (jam! v (string-length v)))
+ (else (error "bad init type"))))))
+ gb))
+
+(define (gb-point gb)
+ (1+ (gap-ofs\: gb)))
+
+(define (gb-point-min gb) 1) ; no narrowing (for now)
+
+(define (gb-point-max gb)
+ (1+ (- (all-sz\: gb) (- (aft-ofs\: gb) (gap-ofs\: gb)))))
+
+(define (insert-prep gb len)
+ (let* ((gap-ofs (gap-ofs\: gb))
+ (aft-ofs (aft-ofs\: gb))
+ (slack (- (- aft-ofs gap-ofs) len)))
+ (and (< slack default-realloc-threshold)
+ (realloc gb (round-up (- slack))))
+ gap-ofs))
+
+(define (gb-insert-string! gb string)
+ (let* ((len (string-length string))
+ (gap-ofs (insert-prep gb len)))
+ (substring-move! string 0 len (s\: gb) gap-ofs)
+ (gap-ofs! gb (+ gap-ofs len))))
+
+(define (gb-insert-char! gb char)
+ (let ((gap-ofs (insert-prep gb 1)))
+ (string-set! (s\: gb) gap-ofs char)
+ (gap-ofs! gb (+ gap-ofs 1))))
+
+(define (gb-delete-char! gb count)
+ (cond ((< count 0) ; backwards
+ (gap-ofs! gb (max 0 (+ (gap-ofs\: gb) count))))
+ ((> count 0) ; forwards
+ (aft-ofs! gb (min (all-sz\: gb) (+ (aft-ofs\: gb) count))))
+ ((= count 0) ; do nothing
+ #t)))
+
+(define (gb-erase! gb)
+ (gap-ofs! gb 0)
+ (aft-ofs! gb (all-sz\: gb)))
+
+(define (point++n! gb n s gap-ofs aft-ofs) ; n>0; warning: reckless
+ (substring-move! s aft-ofs (+ aft-ofs n) s gap-ofs)
+ (gap-ofs! gb (+ gap-ofs n))
+ (aft-ofs! gb (+ aft-ofs n)))
+
+(define (point+-n! gb n s gap-ofs aft-ofs) ; n<0; warning: reckless
+ (substring-move! s (+ gap-ofs n) gap-ofs s (+ aft-ofs n))
+ (gap-ofs! gb (+ gap-ofs n))
+ (aft-ofs! gb (+ aft-ofs n)))
+
+(define (gb-goto-char gb new-point)
+ (let ((pmax (gb-point-max gb)))
+ (or (and (< new-point 1) (gb-goto-char gb 1))
+ (and (> new-point pmax) (gb-goto-char gb pmax))
+ (let ((delta (- new-point (gb-point gb))))
+ (or (= delta 0)
+ ((if (< delta 0)
+ point+-n!
+ point++n!)
+ gb delta (s\: gb) (gap-ofs\: gb) (aft-ofs\: gb))))))
+ new-point)
+
+(define (gb->string gb)
+ (let ((s (s\: gb)))
+ (string-append (substring s 0 (gap-ofs\: gb))
+ (substring s (aft-ofs\: gb)))))
+
+(define (gb-filter! gb string-proc)
+ (let ((new (string-proc (gb->string gb))))
+ (gb-erase! gb)
+ (gb-insert-string! gb new)))
+
+(define (gb->lines gb)
+ (let ((str (gb->string gb)))
+ (let loop ((start 0) (acc '()))
+ (cond ((string-index str #\newline start)
+ => (lambda (w)
+ (loop (1+ w) (cons (substring str start w) acc))))
+ (else (reverse (cons (substring str start) acc)))))))
+
+(define (gb-filter-lines! gb lines-proc)
+ (let ((new-lines (lines-proc (gb->lines gb))))
+ (gb-erase! gb)
+ (gb-insert-string! gb (string-join new-lines #\newline))))
+
+(define (make-gap-buffer-port gb)
+ (or (gb? gb)
+ (error "not a gap-buffer:" gb))
+ (make-soft-port
+ (vector
+ (lambda (c) (gb-insert-char! gb c))
+ (lambda (s) (gb-insert-string! gb s))
+ #f
+ (lambda () (let ((gap-ofs (gap-ofs\: gb))
+ (aft-ofs (aft-ofs\: gb)))
+ (if (= aft-ofs (all-sz\: gb))
+ #f
+ (let* ((s (s\: gb))
+ (c (string-ref s aft-ofs)))
+ (string-set! s gap-ofs c)
+ (gap-ofs! gb (1+ gap-ofs))
+ (aft-ofs! gb (1+ aft-ofs))
+ c))))
+ #f)
+ "rw"))
+
+;;; gap-buffer.scm ends here
+;;; Copyright (C) 1998, 2001, 2006, 2009, 2011 Free Software Foundation, Inc.
+;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Russ McManus (rewritten by Thien-Thi Nguyen)
+
+;;; Commentary:
+
+;;; This module implements some complex command line option parsing, in
+;;; the spirit of the GNU C library function `getopt_long'. Both long
+;;; and short options are supported.
+;;;
+;;; The theory is that people should be able to constrain the set of
+;;; options they want to process using a grammar, rather than some arbitrary
+;;; structure. The grammar makes the option descriptions easy to read.
+;;;
+;;; `getopt-long' is a procedure for parsing command-line arguments in a
+;;; manner consistent with other GNU programs. `option-ref' is a procedure
+;;; that facilitates processing of the `getopt-long' return value.
+
+;;; (getopt-long ARGS GRAMMAR)
+;;; Parse the arguments ARGS according to the argument list grammar GRAMMAR.
+;;;
+;;; ARGS should be a list of strings. Its first element should be the
+;;; name of the program; subsequent elements should be the arguments
+;;; that were passed to the program on the command line. The
+;;; `program-arguments' procedure returns a list of this form.
+;;;
+;;; GRAMMAR is a list of the form:
+;;; ((OPTION (PROPERTY VALUE) ...) ...)
+;;;
+;;; Each OPTION should be a symbol. `getopt-long' will accept a
+;;; command-line option named `--OPTION'.
+;;; Each option can have the following (PROPERTY VALUE) pairs:
+;;;
+;;; (single-char CHAR) --- Accept `-CHAR' as a single-character
+;;; equivalent to `--OPTION'. This is how to specify traditional
+;;; Unix-style flags.
+;;; (required? BOOL) --- If BOOL is true, the option is required.
+;;; getopt-long will raise an error if it is not found in ARGS.
+;;; (value BOOL) --- If BOOL is #t, the option accepts a value; if
+;;; it is #f, it does not; and if it is the symbol
+;;; `optional', the option may appear in ARGS with or
+;;; without a value.
+;;; (predicate FUNC) --- If the option accepts a value (i.e. you
+;;; specified `(value #t)' for this option), then getopt
+;;; will apply FUNC to the value, and throw an exception
+;;; if it returns #f. FUNC should be a procedure which
+;;; accepts a string and returns a boolean value; you may
+;;; need to use quasiquotes to get it into GRAMMAR.
+;;;
+;;; The (PROPERTY VALUE) pairs may occur in any order, but each
+;;; property may occur only once. By default, options do not have
+;;; single-character equivalents, are not required, and do not take
+;;; values.
+;;;
+;;; In ARGS, single-character options may be combined, in the usual
+;;; Unix fashion: ("-x" "-y") is equivalent to ("-xy"). If an option
+;;; accepts values, then it must be the last option in the
+;;; combination; the value is the next argument. So, for example, using
+;;; the following grammar:
+;;; ((apples (single-char #\a))
+;;; (blimps (single-char #\b) (value #t))
+;;; (catalexis (single-char #\c) (value #t)))
+;;; the following argument lists would be acceptable:
+;;; ("-a" "-b" "bang" "-c" "couth") ("bang" and "couth" are the values
+;;; for "blimps" and "catalexis")
+;;; ("-ab" "bang" "-c" "couth") (same)
+;;; ("-ac" "couth" "-b" "bang") (same)
+;;; ("-abc" "couth" "bang") (an error, since `-b' is not the
+;;; last option in its combination)
+;;;
+;;; If an option's value is optional, then `getopt-long' decides
+;;; whether it has a value by looking at what follows it in ARGS. If
+;;; the next element is does not appear to be an option itself, then
+;;; that element is the option's value.
+;;;
+;;; The value of a long option can appear as the next element in ARGS,
+;;; or it can follow the option name, separated by an `=' character.
+;;; Thus, using the same grammar as above, the following argument lists
+;;; are equivalent:
+;;; ("--apples" "Braeburn" "--blimps" "Goodyear")
+;;; ("--apples=Braeburn" "--blimps" "Goodyear")
+;;; ("--blimps" "Goodyear" "--apples=Braeburn")
+;;;
+;;; If the option "--" appears in ARGS, argument parsing stops there;
+;;; subsequent arguments are returned as ordinary arguments, even if
+;;; they resemble options. So, in the argument list:
+;;; ("--apples" "Granny Smith" "--" "--blimp" "Goodyear")
+;;; `getopt-long' will recognize the `apples' option as having the
+;;; value "Granny Smith", but it will not recognize the `blimp'
+;;; option; it will return the strings "--blimp" and "Goodyear" as
+;;; ordinary argument strings.
+;;;
+;;; The `getopt-long' function returns the parsed argument list as an
+;;; assocation list, mapping option names --- the symbols from GRAMMAR
+;;; --- onto their values, or #t if the option does not accept a value.
+;;; Unused options do not appear in the alist.
+;;;
+;;; All arguments that are not the value of any option are returned
+;;; as a list, associated with the empty list.
+;;;
+;;; `getopt-long' throws an exception if:
+;;; - it finds an unrecognized property in GRAMMAR
+;;; - the value of the `single-char' property is not a character
+;;; - it finds an unrecognized option in ARGS
+;;; - a required option is omitted
+;;; - an option that requires an argument doesn't get one
+;;; - an option that doesn't accept an argument does get one (this can
+;;; only happen using the long option `--opt=value' syntax)
+;;; - an option predicate fails
+;;;
+;;; So, for example:
+;;;
+;;; (define grammar
+;;; `((lockfile-dir (required? #t)
+;;; (value #t)
+;;; (single-char #\k)
+;;; (predicate ,file-is-directory?))
+;;; (verbose (required? #f)
+;;; (single-char #\v)
+;;; (value #f))
+;;; (x-includes (single-char #\x))
+;;; (rnet-server (single-char #\y)
+;;; (predicate ,string?))))
+;;;
+;;; (getopt-long '("my-prog" "-vk" "/tmp" "foo1" "--x-includes=/usr/include"
+;;; "--rnet-server=lamprod" "--" "-fred" "foo2" "foo3")
+;;; grammar)
+;;; => ((() "foo1" "-fred" "foo2" "foo3")
+;;; (rnet-server . "lamprod")
+;;; (x-includes . "/usr/include")
+;;; (lockfile-dir . "/tmp")
+;;; (verbose . #t))
+
+;;; (option-ref OPTIONS KEY DEFAULT)
+;;; Return value in alist OPTIONS using KEY, a symbol; or DEFAULT if not
+;;; found. The value is either a string or `#t'.
+;;;
+;;; For example, using the `getopt-long' return value from above:
+;;;
+;;; (option-ref (getopt-long ...) 'x-includes 42) => "/usr/include"
+;;; (option-ref (getopt-long ...) 'not-a-key! 31) => 31
+
+;;; Code:
+
+(define-module (ice-9 getopt-long)
+ #\use-module ((ice-9 common-list) #\select (remove-if-not))
+ #\use-module (srfi srfi-9)
+ #\use-module (ice-9 match)
+ #\use-module (ice-9 regex)
+ #\use-module (ice-9 optargs)
+ #\export (getopt-long option-ref))
+
+(define %program-name (make-fluid "guile"))
+(define (program-name)
+ (fluid-ref %program-name))
+
+(define (fatal-error fmt . args)
+ (format (current-error-port) "~a: " (program-name))
+ (apply format (current-error-port) fmt args)
+ (newline (current-error-port))
+ (exit 1))
+
+(define-record-type option-spec
+ (%make-option-spec name required? option-spec->single-char predicate value-policy)
+ option-spec?
+ (name
+ option-spec->name set-option-spec-name!)
+ (required?
+ option-spec->required? set-option-spec-required?!)
+ (option-spec->single-char
+ option-spec->single-char set-option-spec-single-char!)
+ (predicate
+ option-spec->predicate set-option-spec-predicate!)
+ (value-policy
+ option-spec->value-policy set-option-spec-value-policy!))
+
+(define (make-option-spec name)
+ (%make-option-spec name #f #f #f #f))
+
+(define (parse-option-spec desc)
+ (let ((spec (make-option-spec (symbol->string (car desc)))))
+ (for-each (match-lambda
+ (('required? val)
+ (set-option-spec-required?! spec val))
+ (('value val)
+ (set-option-spec-value-policy! spec val))
+ (('single-char val)
+ (or (char? val)
+ (error "`single-char' value must be a char!"))
+ (set-option-spec-single-char! spec val))
+ (('predicate pred)
+ (set-option-spec-predicate!
+ spec (lambda (name val)
+ (or (not val)
+ (pred val)
+ (fatal-error "option predicate failed: --~a"
+ name)))))
+ ((prop val)
+ (error "invalid getopt-long option property:" prop)))
+ (cdr desc))
+ spec))
+
+(define (split-arg-list argument-list)
+ ;; Scan ARGUMENT-LIST for "--" and return (BEFORE-LS . AFTER-LS).
+ ;; Discard the "--". If no "--" is found, AFTER-LS is empty.
+ (let loop ((yes '()) (no argument-list))
+ (cond ((null? no) (cons (reverse yes) no))
+ ((string=? "--" (car no)) (cons (reverse yes) (cdr no)))
+ (else (loop (cons (car no) yes) (cdr no))))))
+
+(define short-opt-rx (make-regexp "^-([a-zA-Z]+)(.*)"))
+(define long-opt-no-value-rx (make-regexp "^--([^=]+)$"))
+(define long-opt-with-value-rx (make-regexp "^--([^=]+)=(.*)"))
+
+(define (looks-like-an-option string)
+ (or (regexp-exec short-opt-rx string)
+ (regexp-exec long-opt-with-value-rx string)
+ (regexp-exec long-opt-no-value-rx string)))
+
+(define (process-options specs argument-ls stop-at-first-non-option)
+ ;; Use SPECS to scan ARGUMENT-LS; return (FOUND . ETC).
+ ;; FOUND is an unordered list of option specs for found options, while ETC
+ ;; is an order-maintained list of elements in ARGUMENT-LS that are neither
+ ;; options nor their values.
+ (let ((idx (map (lambda (spec)
+ (cons (option-spec->name spec) spec))
+ specs))
+ (sc-idx (map (lambda (spec)
+ (cons (make-string 1 (option-spec->single-char spec))
+ spec))
+ (remove-if-not option-spec->single-char specs))))
+ (let loop ((unclumped 0) (argument-ls argument-ls) (found '()) (etc '()))
+ (define (eat! spec ls)
+ (cond
+ ((eq? 'optional (option-spec->value-policy spec))
+ (if (or (null? ls)
+ (looks-like-an-option (car ls)))
+ (loop (- unclumped 1) ls (acons spec #t found) etc)
+ (loop (- unclumped 2) (cdr ls) (acons spec (car ls) found) etc)))
+ ((eq? #t (option-spec->value-policy spec))
+ (if (or (null? ls)
+ (looks-like-an-option (car ls)))
+ (fatal-error "option must be specified with argument: --~a"
+ (option-spec->name spec))
+ (loop (- unclumped 2) (cdr ls) (acons spec (car ls) found) etc)))
+ (else
+ (loop (- unclumped 1) ls (acons spec #t found) etc))))
+
+ (match argument-ls
+ (()
+ (cons found (reverse etc)))
+ ((opt . rest)
+ (cond
+ ((regexp-exec short-opt-rx opt)
+ => (lambda (match)
+ (if (> unclumped 0)
+ ;; Next option is known not to be clumped.
+ (let* ((c (match:substring match 1))
+ (spec (or (assoc-ref sc-idx c)
+ (fatal-error "no such option: -~a" c))))
+ (eat! spec rest))
+ ;; Expand a clumped group of short options.
+ (let* ((extra (match:substring match 2))
+ (unclumped-opts
+ (append (map (lambda (c)
+ (string-append "-" (make-string 1 c)))
+ (string->list
+ (match:substring match 1)))
+ (if (string=? "" extra) '() (list extra)))))
+ (loop (length unclumped-opts)
+ (append unclumped-opts rest)
+ found
+ etc)))))
+ ((regexp-exec long-opt-no-value-rx opt)
+ => (lambda (match)
+ (let* ((opt (match:substring match 1))
+ (spec (or (assoc-ref idx opt)
+ (fatal-error "no such option: --~a" opt))))
+ (eat! spec rest))))
+ ((regexp-exec long-opt-with-value-rx opt)
+ => (lambda (match)
+ (let* ((opt (match:substring match 1))
+ (spec (or (assoc-ref idx opt)
+ (fatal-error "no such option: --~a" opt))))
+ (if (option-spec->value-policy spec)
+ (eat! spec (cons (match:substring match 2) rest))
+ (fatal-error "option does not support argument: --~a"
+ opt)))))
+ ((and stop-at-first-non-option
+ (<= unclumped 0))
+ (cons found (append (reverse etc) argument-ls)))
+ (else
+ (loop (- unclumped 1) rest found (cons opt etc)))))))))
+
+(define* (getopt-long program-arguments option-desc-list
+ #\key stop-at-first-non-option)
+ "Process options, handling both long and short options, similar to
+the glibc function 'getopt_long'. PROGRAM-ARGUMENTS should be a value
+similar to what (program-arguments) returns. OPTION-DESC-LIST is a
+list of option descriptions. Each option description must satisfy the
+following grammar:
+
+ <option-spec> :: (<name> . <attribute-ls>)
+ <attribute-ls> :: (<attribute> . <attribute-ls>)
+ | ()
+ <attribute> :: <required-attribute>
+ | <arg-required-attribute>
+ | <single-char-attribute>
+ | <predicate-attribute>
+ | <value-attribute>
+ <required-attribute> :: (required? <boolean>)
+ <single-char-attribute> :: (single-char <char>)
+ <value-attribute> :: (value #t)
+ (value #f)
+ (value optional)
+ <predicate-attribute> :: (predicate <1-ary-function>)
+
+ The procedure returns an alist of option names and values. Each
+option name is a symbol. The option value will be '#t' if no value
+was specified. There is a special item in the returned alist with a
+key of the empty list, (): the list of arguments that are not options
+or option values.
+ By default, options are not required, and option values are not
+required. By default, single character equivalents are not supported;
+if you want to allow the user to use single character options, you need
+to add a `single-char' clause to the option description."
+ (with-fluids ((%program-name (car program-arguments)))
+ (let* ((specifications (map parse-option-spec option-desc-list))
+ (pair (split-arg-list (cdr program-arguments)))
+ (split-ls (car pair))
+ (non-split-ls (cdr pair))
+ (found/etc (process-options specifications split-ls
+ stop-at-first-non-option))
+ (found (car found/etc))
+ (rest-ls (append (cdr found/etc) non-split-ls)))
+ (for-each (lambda (spec)
+ (let ((name (option-spec->name spec))
+ (val (assq-ref found spec)))
+ (and (option-spec->required? spec)
+ (or val
+ (fatal-error "option must be specified: --~a"
+ name)))
+ (let ((pred (option-spec->predicate spec)))
+ (and pred (pred name val)))))
+ specifications)
+ (for-each (lambda (spec+val)
+ (set-car! spec+val
+ (string->symbol (option-spec->name (car spec+val)))))
+ found)
+ (cons (cons '() rest-ls) found))))
+
+(define (option-ref options key default)
+ "Return value in alist OPTIONS using KEY, a symbol; or DEFAULT if not found.
+The value is either a string or `#t'."
+ (or (assq-ref options key) default))
+
+;;; getopt-long.scm ends here
+;;;; hash-table.scm --- Additional hash table procedures
+;;;; Copyright (C) 2013 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+(define-module (ice-9 hash-table)
+ #\export (alist->hash-table
+ alist->hashq-table
+ alist->hashv-table
+ alist->hashx-table))
+
+(define-syntax-rule (define-alist-converter name hash-set-proc)
+ (define (name alist)
+ "Convert ALIST into a hash table."
+ (let ((table (make-hash-table)))
+ (for-each (lambda (pair)
+ (hash-set-proc table (car pair) (cdr pair)))
+ (reverse alist))
+ table)))
+
+(define-alist-converter alist->hash-table hash-set!)
+(define-alist-converter alist->hashq-table hashq-set!)
+(define-alist-converter alist->hashv-table hashv-set!)
+
+(define (alist->hashx-table hash assoc alist)
+ "Convert ALIST into a hash table with custom HASH and ASSOC
+procedures."
+ (let ((table (make-hash-table)))
+ (for-each (lambda (pair)
+ (hashx-set! hash assoc table (car pair) (cdr pair)))
+ (reverse alist))
+ table))
+;;; installed-scm-file
+
+;;;; Copyright (C) 1995, 1996, 1998, 2001, 2003, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (ice-9 hcons)
+ \:export (hashq-cons-hash hashq-cons-assoc hashq-cons-get-handle
+ hashq-cons-create-handle! hashq-cons-ref hashq-cons-set! hashq-cons
+ hashq-conser make-gc-buffer))
+
+
+;;; {Eq? hash-consing}
+;;;
+;;; A hash conser maintains a private universe of pairs s.t. if
+;;; two cons calls pass eq? arguments, the pairs returned are eq?.
+;;;
+;;; A hash conser does not contribute life to the pairs it returns.
+;;;
+
+(define (hashq-cons-hash pair n)
+ (modulo (logxor (hashq (car pair) 4194303)
+ (hashq (cdr pair) 4194303))
+ n))
+
+(define (hashq-cons-assoc key l)
+ (and (not (null? l))
+ (or (and (pair? l) ; If not a pair, use its cdr?
+ (pair? (car l))
+ (pair? (caar l))
+ (eq? (car key) (caaar l))
+ (eq? (cdr key) (cdaar l))
+ (car l))
+ (hashq-cons-assoc key (cdr l)))))
+
+(define (hashq-cons-get-handle table key)
+ (hashx-get-handle hashq-cons-hash hashq-cons-assoc table key))
+
+(define (hashq-cons-create-handle! table key init)
+ (hashx-create-handle! hashq-cons-hash hashq-cons-assoc table key init))
+
+(define (hashq-cons-ref table key)
+ (hashx-ref hashq-cons-hash hashq-cons-assoc table key #f))
+
+(define (hashq-cons-set! table key val)
+ (hashx-set! hashq-cons-hash hashq-cons-assoc table key val))
+
+(define (hashq-cons table a d)
+ (car (hashq-cons-create-handle! table (cons a d) #f)))
+
+(define (hashq-conser hash-tab-or-size)
+ (let ((table (if (vector? hash-tab-or-size)
+ hash-tab-or-size
+ (make-doubly-weak-hash-table hash-tab-or-size))))
+ (lambda (a d) (hashq-cons table a d))))
+
+
+
+
+(define (make-gc-buffer n)
+ (let ((ring (make-list n #f)))
+ (append! ring ring)
+ (lambda (next)
+ (set-car! ring next)
+ (set! ring (cdr ring))
+ next)))
+;;;; Copyright (C) 2000, 2001, 2004, 2006, 2010 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;;; A simple value history support
+
+(define-module (ice-9 history)
+ #\export (value-history-enabled? enable-value-history! disable-value-history!
+ clear-value-history!))
+
+(define-module* '(value-history))
+
+(define *value-history-enabled?* #f)
+(define (value-history-enabled?)
+ *value-history-enabled?*)
+
+(define (use-value-history x)
+ (module-use! (current-module)
+ (resolve-interface '(value-history))))
+
+(define save-value-history
+ (let ((count 0)
+ (history (resolve-module '(value-history))))
+ (lambda (v)
+ (if (not (unspecified? v))
+ (let* ((c (1+ count))
+ (s (string->symbol (simple-format #f "$~A" c))))
+ (simple-format #t "~A = " s)
+ (module-define! history s v)
+ (module-export! history (list s))
+ (set! count c))))))
+
+(define (enable-value-history!)
+ (if (not (value-history-enabled?))
+ (begin
+ (add-hook! before-eval-hook use-value-history)
+ (add-hook! before-print-hook save-value-history)
+ (set! *value-history-enabled?* #t))))
+
+(define (disable-value-history!)
+ (if (value-history-enabled?)
+ (begin
+ (remove-hook! before-eval-hook use-value-history)
+ (remove-hook! before-print-hook save-value-history)
+ (set! *value-history-enabled?* #f))))
+
+(define (clear-value-history!)
+ (let ((history (resolve-module '(value-history))))
+ (hash-clear! (module-obarray history))
+ (hash-clear! (module-obarray (module-public-interface history)))))
+
+(enable-value-history!)
+;;;; i18n.scm --- internationalization support -*- coding: utf-8 -*-
+
+;;;; Copyright (C) 2006, 2007, 2009, 2010, 2012,
+;;;; 2017 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Ludovic Courtès <ludo@gnu.org>
+
+;;; Commentary:
+;;;
+;;; This module provides a number of routines that support
+;;; internationalization (e.g., locale-dependent text collation, character
+;;; mapping, etc.). It also defines `locale' objects, representing locale
+;;; settings, that may be passed around to most of these procedures.
+;;;
+
+;;; Code:
+
+(define-module (ice-9 i18n)
+ \:use-module (ice-9 optargs)
+ \:export (;; `locale' type
+ make-locale locale?
+ %global-locale
+
+ ;; text collation
+ string-locale<? string-locale>?
+ string-locale-ci<? string-locale-ci>? string-locale-ci=?
+
+ char-locale<? char-locale>?
+ char-locale-ci<? char-locale-ci>? char-locale-ci=?
+
+ ;; character mapping
+ char-locale-downcase char-locale-upcase char-locale-titlecase
+ string-locale-downcase string-locale-upcase string-locale-titlecase
+
+ ;; reading numbers
+ locale-string->integer locale-string->inexact
+
+ ;; charset/encoding
+ locale-encoding
+
+ ;; days and months
+ locale-day-short locale-day locale-month-short locale-month
+
+ ;; date and time
+ locale-am-string locale-pm-string
+ locale-date+time-format locale-date-format locale-time-format
+ locale-time+am/pm-format
+ locale-era locale-era-year
+ locale-era-date-format locale-era-date+time-format
+ locale-era-time-format
+
+ ;; monetary
+ locale-currency-symbol
+ locale-monetary-decimal-point locale-monetary-thousands-separator
+ locale-monetary-grouping locale-monetary-fractional-digits
+ locale-currency-symbol-precedes-positive?
+ locale-currency-symbol-precedes-negative?
+ locale-positive-separated-by-space?
+ locale-negative-separated-by-space?
+ locale-monetary-positive-sign locale-monetary-negative-sign
+ locale-positive-sign-position locale-negative-sign-position
+ monetary-amount->locale-string
+
+ ;; number formatting
+ locale-digit-grouping locale-decimal-point
+ locale-thousands-separator
+ number->locale-string
+
+ ;; miscellaneous
+ locale-yes-regexp locale-no-regexp))
+
+
+(eval-when (expand load eval)
+ (load-extension (string-append "libguile-" (effective-version))
+ "scm_init_i18n"))
+
+
+;;;
+;;; Charset/encoding.
+;;;
+
+(define (locale-encoding . locale)
+ (apply nl-langinfo CODESET locale))
+
+
+;;;
+;;; Months and days.
+;;;
+
+;; Helper macro: Define a procedure named NAME that maps its argument to
+;; NL-ITEMS. Gnulib guarantees that these items are available.
+(define-macro (define-vector-langinfo-mapping name nl-items)
+ (let* ((item-count (length nl-items))
+ (defines `(define %nl-items (vector #f ,@nl-items)))
+ (make-body (lambda (result)
+ `(if (and (integer? item) (exact? item))
+ (if (and (>= item 1) (<= item ,item-count))
+ ,result
+ (throw 'out-of-range "out of range" item))
+ (throw 'wrong-type-arg "wrong argument type" item)))))
+ `(define (,name item . locale)
+ ,defines
+ ,(make-body '(apply nl-langinfo (vector-ref %nl-items item) locale)))))
+
+
+(define-vector-langinfo-mapping locale-day-short
+ (ABDAY_1 ABDAY_2 ABDAY_3 ABDAY_4 ABDAY_5 ABDAY_6 ABDAY_7))
+
+(define-vector-langinfo-mapping locale-day
+ (DAY_1 DAY_2 DAY_3 DAY_4 DAY_5 DAY_6 DAY_7))
+
+(define-vector-langinfo-mapping locale-month-short
+ (ABMON_1 ABMON_2 ABMON_3 ABMON_4 ABMON_5 ABMON_6
+ ABMON_7 ABMON_8 ABMON_9 ABMON_10 ABMON_11 ABMON_12))
+
+(define-vector-langinfo-mapping locale-month
+ (MON_1 MON_2 MON_3 MON_4 MON_5 MON_6 MON_7 MON_8 MON_9 MON_10 MON_11 MON_12))
+
+
+
+;;;
+;;; Date and time.
+;;;
+
+;; Define a procedure NAME that gets langinfo item ITEM. Gnulib's
+;; `nl_langinfo' does not guarantee that all these items are supported
+;; (for instance, `GROUPING' is lacking on Darwin and Gnulib provides no
+;; replacement), so use DEFAULT as the default value when ITEM is not
+;; available.
+(define-macro (define-simple-langinfo-mapping name item default)
+ (let ((body (if (defined? item)
+ `(apply nl-langinfo ,item locale)
+ default)))
+ `(define (,name . locale)
+ ,body)))
+
+(define-simple-langinfo-mapping locale-am-string
+ AM_STR "AM")
+(define-simple-langinfo-mapping locale-pm-string
+ PM_STR "PM")
+(define-simple-langinfo-mapping locale-date+time-format
+ D_T_FMT "%a %b %e %H:%M:%S %Y")
+(define-simple-langinfo-mapping locale-date-format
+ D_FMT "%m/%d/%y")
+(define-simple-langinfo-mapping locale-time-format
+ T_FMT "%H:%M:%S")
+(define-simple-langinfo-mapping locale-time+am/pm-format
+ T_FMT_AMPM "%I:%M:%S %p")
+(define-simple-langinfo-mapping locale-era
+ ERA "")
+(define-simple-langinfo-mapping locale-era-year
+ ERA_YEAR "")
+(define-simple-langinfo-mapping locale-era-date+time-format
+ ERA_D_T_FMT "")
+(define-simple-langinfo-mapping locale-era-date-format
+ ERA_D_FMT "")
+(define-simple-langinfo-mapping locale-era-time-format
+ ERA_T_FMT "")
+
+
+
+;;;
+;;; Monetary information.
+;;;
+
+;; Define a procedure NAME that gets item LOCAL-ITEM or INTL-ITEM,
+;; depending on whether the caller asked for the international version
+;; or not. Since Gnulib's `nl_langinfo' module doesn't guarantee that
+;; all these items are available, use DEFAULT/LOCAL and DEFAULT/INTL as
+;; default values when the system does not support them.
+(define-macro (define-monetary-langinfo-mapping name local-item intl-item
+ default/local default/intl)
+ (let ((body
+ (let ((intl (if (defined? intl-item)
+ `(apply nl-langinfo ,intl-item locale)
+ default/intl))
+ (local (if (defined? local-item)
+ `(apply nl-langinfo ,local-item locale)
+ default/local)))
+ `(if intl? ,intl ,local))))
+
+ `(define (,name intl? . locale)
+ ,body)))
+
+;; FIXME: How can we use ALT_DIGITS?
+(define-monetary-langinfo-mapping locale-currency-symbol
+ CRNCYSTR INT_CURR_SYMBOL
+ "-" "")
+(define-monetary-langinfo-mapping locale-monetary-fractional-digits
+ FRAC_DIGITS INT_FRAC_DIGITS
+ 2 2)
+
+(define-simple-langinfo-mapping locale-monetary-positive-sign
+ POSITIVE_SIGN "+")
+(define-simple-langinfo-mapping locale-monetary-negative-sign
+ NEGATIVE_SIGN "-")
+(define-simple-langinfo-mapping locale-monetary-decimal-point
+ MON_DECIMAL_POINT "")
+(define-simple-langinfo-mapping locale-monetary-thousands-separator
+ MON_THOUSANDS_SEP "")
+(define-simple-langinfo-mapping locale-monetary-digit-grouping
+ MON_GROUPING '())
+
+(define-monetary-langinfo-mapping locale-currency-symbol-precedes-positive?
+ P_CS_PRECEDES INT_P_CS_PRECEDES
+ #t #t)
+(define-monetary-langinfo-mapping locale-currency-symbol-precedes-negative?
+ N_CS_PRECEDES INT_N_CS_PRECEDES
+ #t #t)
+
+
+(define-monetary-langinfo-mapping locale-positive-separated-by-space?
+ ;; Whether a space should be inserted between a positive amount and the
+ ;; currency symbol.
+ P_SEP_BY_SPACE INT_P_SEP_BY_SPACE
+ #t #t)
+(define-monetary-langinfo-mapping locale-negative-separated-by-space?
+ ;; Whether a space should be inserted between a negative amount and the
+ ;; currency symbol.
+ N_SEP_BY_SPACE INT_N_SEP_BY_SPACE
+ #t #t)
+
+(define-monetary-langinfo-mapping locale-positive-sign-position
+ ;; Position of the positive sign wrt. currency symbol and quantity in a
+ ;; monetary amount.
+ P_SIGN_POSN INT_P_SIGN_POSN
+ 'unspecified 'unspecified)
+(define-monetary-langinfo-mapping locale-negative-sign-position
+ ;; Position of the negative sign wrt. currency symbol and quantity in a
+ ;; monetary amount.
+ N_SIGN_POSN INT_N_SIGN_POSN
+ 'unspecified 'unspecified)
+
+
+(define (integer->string number)
+ "Return a string representing NUMBER, an integer, written in base 10."
+ (define (digit->char digit)
+ (integer->char (+ digit (char->integer #\0))))
+
+ (if (zero? number)
+ "0"
+ (let loop ((number number)
+ (digits '()))
+ (if (zero? number)
+ (list->string digits)
+ (loop (quotient number 10)
+ (cons (digit->char (modulo number 10))
+ digits))))))
+
+(define (number-decimal-string number digit-count)
+ "Return a string representing the decimal part of NUMBER. When
+DIGIT-COUNT is an integer, return exactly DIGIT-COUNT digits; when
+DIGIT-COUNT is #t, return as many decimals as necessary, up to an
+arbitrary limit."
+ (define max-decimals
+ 5)
+
+ ;; XXX: This is brute-force and could be improved by following one of
+ ;; the "Printing Floating-Point Numbers Quickly and Accurately"
+ ;; papers.
+ (if (integer? digit-count)
+ (let ((number (* (expt 10 digit-count)
+ (- number (floor number)))))
+ (string-pad (integer->string (round (inexact->exact number)))
+ digit-count
+ #\0))
+ (let loop ((decimals 0))
+ (let ((number\' (* number (expt 10 decimals))))
+ (if (or (= number\' (floor number\'))
+ (>= decimals max-decimals))
+ (let* ((fraction (- number\'
+ (* (floor number)
+ (expt 10 decimals))))
+ (str (integer->string
+ (round (inexact->exact fraction)))))
+ (if (zero? fraction)
+ ""
+ str))
+ (loop (+ decimals 1)))))))
+
+(define (%number-integer-part int grouping separator)
+ ;; Process INT (a string denoting a number's integer part) and return a new
+ ;; string with digit grouping and separators according to GROUPING (a list,
+ ;; potentially circular) and SEPARATOR (a string).
+
+ ;; Process INT from right to left.
+ (let loop ((int int)
+ (grouping grouping)
+ (result '()))
+ (cond ((string=? int "") (apply string-append result))
+ ((null? grouping) (apply string-append int result))
+ (else
+ (let* ((len (string-length int))
+ (cut (min (car grouping) len)))
+ (loop (substring int 0 (- len cut))
+ (cdr grouping)
+ (let ((sub (substring int (- len cut) len)))
+ (if (> len cut)
+ (cons* separator sub result)
+ (cons sub result)))))))))
+
+(define (add-monetary-sign+currency amount figure intl? locale)
+ ;; Add a sign and currency symbol around FIGURE. FIGURE should be a
+ ;; formatted unsigned amount (a string) representing AMOUNT.
+ (let* ((positive? (> amount 0))
+ (sign
+ (cond ((> amount 0) (locale-monetary-positive-sign locale))
+ ((< amount 0) (locale-monetary-negative-sign locale))
+ (else "")))
+ (currency (locale-currency-symbol intl? locale))
+ (currency-precedes?
+ (if positive?
+ locale-currency-symbol-precedes-positive?
+ locale-currency-symbol-precedes-negative?))
+ (separated?
+ (if positive?
+ locale-positive-separated-by-space?
+ locale-negative-separated-by-space?))
+ (sign-position
+ (if positive?
+ locale-positive-sign-position
+ locale-negative-sign-position))
+ (currency-space
+ (if (separated? intl? locale) " " ""))
+ (append-currency
+ (lambda (amt)
+ (if (currency-precedes? intl? locale)
+ (string-append currency currency-space amt)
+ (string-append amt currency-space currency)))))
+
+ (case (sign-position intl? locale)
+ ((parenthesize)
+ (string-append "(" (append-currency figure) ")"))
+ ((sign-before)
+ (string-append sign (append-currency figure)))
+ ((sign-after unspecified)
+ ;; following glibc's recommendation for `unspecified'.
+ (if (currency-precedes? intl? locale)
+ (string-append currency currency-space sign figure)
+ (string-append figure currency-space currency sign)))
+ ((sign-before-currency-symbol)
+ (if (currency-precedes? intl? locale)
+ (string-append sign currency currency-space figure)
+ (string-append figure currency-space sign currency))) ;; unlikely
+ ((sign-after-currency-symbol)
+ (if (currency-precedes? intl? locale)
+ (string-append currency sign currency-space figure)
+ (string-append figure currency-space currency sign)))
+ (else
+ (error "unsupported sign position" (sign-position intl? locale))))))
+
+
+(define* (monetary-amount->locale-string amount intl?
+ #\optional (locale %global-locale))
+ "Convert @var{amount} (an inexact) into a string according to the cultural
+conventions of either @var{locale} (a locale object) or the current locale.
+If @var{intl?} is true, then the international monetary format for the given
+locale is used."
+
+ (let* ((fraction-digits
+ (or (locale-monetary-fractional-digits intl? locale) 2))
+ (decimal-part
+ (lambda (dec)
+ (if (or (string=? dec "") (eq? 0 fraction-digits))
+ ""
+ (string-append (locale-monetary-decimal-point locale)
+ (if (< fraction-digits (string-length dec))
+ (substring dec 0 fraction-digits)
+ dec)))))
+
+ (int (integer->string (inexact->exact
+ (floor (abs amount)))))
+ (dec (decimal-part
+ (number-decimal-string (abs amount)
+ fraction-digits)))
+ (grouping (locale-monetary-digit-grouping locale))
+ (separator (locale-monetary-thousands-separator locale)))
+
+ (add-monetary-sign+currency amount
+ (string-append
+ (%number-integer-part int grouping
+ separator)
+ dec)
+ intl? locale)))
+
+
+
+;;;
+;;; Number formatting.
+;;;
+
+(define-simple-langinfo-mapping locale-digit-grouping
+ GROUPING '())
+(define-simple-langinfo-mapping locale-decimal-point
+ RADIXCHAR ".")
+(define-simple-langinfo-mapping locale-thousands-separator
+ THOUSEP "")
+
+(define* (number->locale-string number
+ #\optional (fraction-digits #t)
+ (locale %global-locale))
+ "Convert @var{number} (an inexact) into a string according to the cultural
+conventions of either @var{locale} (a locale object) or the current locale.
+By default, print as many fractional digits as necessary, up to an upper bound.
+Optionally, @var{fraction-digits} may be bound to an integer specifying the
+number of fractional digits to be displayed."
+
+ (let* ((sign
+ (cond ((> number 0) "")
+ ((< number 0) "-")
+ (else "")))
+ (decimal-part
+ (lambda (dec)
+ (if (or (string=? dec "") (eq? 0 fraction-digits))
+ ""
+ (string-append (locale-decimal-point locale)
+ (if (and (integer? fraction-digits)
+ (< fraction-digits
+ (string-length dec)))
+ (substring dec 0 fraction-digits)
+ dec))))))
+
+ (let* ((int (integer->string (inexact->exact
+ (floor (abs number)))))
+ (dec (decimal-part
+ (number-decimal-string (abs number)
+ fraction-digits)))
+ (grouping (locale-digit-grouping locale))
+ (separator (locale-thousands-separator locale)))
+
+ (string-append sign
+ (%number-integer-part int grouping separator)
+ dec))))
+
+
+;;;
+;;; Miscellaneous.
+;;;
+
+(define-simple-langinfo-mapping locale-yes-regexp
+ YESEXPR "^[yY]")
+(define-simple-langinfo-mapping locale-no-regexp
+ NOEXPR "^[nN]")
+
+;; `YESSTR' and `NOSTR' are considered deprecated so we don't provide them.
+
+;;; i18n.scm ends here
+;;; Encoding and decoding byte representations of strings
+
+;; Copyright (C) 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (ice-9 iconv)
+ #\use-module (rnrs bytevectors)
+ #\use-module (ice-9 binary-ports)
+ #\use-module ((ice-9 rdelim) #\select (read-string))
+ #\export (string->bytevector
+ bytevector->string
+ call-with-encoded-output-string))
+
+;; Like call-with-output-string, but actually closes the port.
+(define (call-with-output-string* proc)
+ (let ((port (open-output-string)))
+ (proc port)
+ (let ((str (get-output-string port)))
+ (close-port port)
+ str)))
+
+(define (call-with-output-bytevector* proc)
+ (call-with-values (lambda () (open-bytevector-output-port))
+ (lambda (port get-bytevector)
+ (proc port)
+ (let ((bv (get-bytevector)))
+ (close-port port)
+ bv))))
+
+(define* (call-with-encoded-output-string encoding proc
+ #\optional
+ (conversion-strategy 'error))
+ "Call PROC on a fresh port. Encode the resulting string as a
+bytevector according to ENCODING, and return the bytevector."
+ (if (and (string-ci=? encoding "utf-8")
+ (eq? conversion-strategy 'error))
+ ;; I don't know why, but this appears to be faster; at least for
+ ;; serving examples/debug-sxml.scm (1464 reqs/s versus 850
+ ;; reqs/s).
+ (string->utf8 (call-with-output-string* proc))
+ (call-with-output-bytevector*
+ (lambda (port)
+ (set-port-encoding! port encoding)
+ (if conversion-strategy
+ (set-port-conversion-strategy! port conversion-strategy))
+ (proc port)))))
+
+;; TODO: Provide C implementations that call scm_from_stringn and
+;; friends?
+
+(define* (string->bytevector str encoding
+ #\optional (conversion-strategy 'error))
+ "Encode STRING according to ENCODING, which should be a string naming
+a character encoding, like \"utf-8\"."
+ (if (and (string-ci=? encoding "utf-8")
+ (eq? conversion-strategy 'error))
+ (string->utf8 str)
+ (call-with-encoded-output-string
+ encoding
+ (lambda (port)
+ (display str port))
+ conversion-strategy)))
+
+(define* (bytevector->string bv encoding
+ #\optional (conversion-strategy 'error))
+ "Decode the string represented by BV. The bytes in the bytevector
+will be interpreted according to ENCODING, which should be a string
+naming a character encoding, like \"utf-8\"."
+ (if (and (string-ci=? encoding "utf-8")
+ (eq? conversion-strategy 'error))
+ (utf8->string bv)
+ (let ((p (open-bytevector-input-port bv)))
+ (set-port-encoding! p encoding)
+ (if conversion-strategy
+ (set-port-conversion-strategy! p conversion-strategy))
+ (let ((res (read-string p)))
+ (close-port p)
+ (if (eof-object? res)
+ ""
+ res)))))
+;;; installed-scm-file
+
+;;;; Copyright (C) 1996, 1998, 2001, 2003, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+
+(define-module (ice-9 lineio)
+ \:use-module (ice-9 rdelim)
+ \:export (unread-string read-string lineio-port?
+ make-line-buffering-input-port))
+
+
+;;; {Line Buffering Input Ports}
+;;;
+;;; [This is a work-around to get past certain deficiencies in the capabilities
+;;; of ports. Eventually, ports should be fixed and this module nuked.]
+;;;
+;;; A line buffering input port supports:
+;;;
+;;; read-string which returns the next line of input
+;;; unread-string which pushes a line back onto the stream
+;;;
+;;; The implementation of unread-string is kind of limited; it doesn't
+;;; interact properly with unread-char, or any of the other port
+;;; reading functions. Only read-string will get you back the things that
+;;; unread-string accepts.
+;;;
+;;; Normally a "line" is all characters up to and including a newline.
+;;; If lines are put back using unread-string, they can be broken arbitrarily
+;;; -- that is, read-string returns strings passed to unread-string (or
+;;; shared substrings of them).
+;;;
+
+;; read-string port
+;; unread-string port str
+;; Read (or buffer) a line from PORT.
+;;
+;; Not all ports support these functions -- only those with
+;; 'unread-string and 'read-string properties, bound to hooks
+;; implementing these functions.
+;;
+(define (unread-string str line-buffering-input-port)
+ ((object-property line-buffering-input-port 'unread-string) str))
+
+;;
+(define (read-string line-buffering-input-port)
+ ((object-property line-buffering-input-port 'read-string)))
+
+
+(define (lineio-port? port)
+ (not (not (object-property port 'read-string))))
+
+;; make-line-buffering-input-port port
+;; Return a wrapper for PORT. The wrapper handles read-string/unread-string.
+;;
+;; The port returned by this function reads newline terminated lines from PORT.
+;; It buffers these characters internally, and parsels them out via calls
+;; to read-char, read-string, and unread-string.
+;;
+
+(define (make-line-buffering-input-port underlying-port)
+ (let* (;; buffers - a list of strings put back by unread-string or cached
+ ;; using read-line.
+ ;;
+ (buffers '())
+
+ ;; getc - return the next character from a buffer or from the underlying
+ ;; port.
+ ;;
+ (getc (lambda ()
+ (if (not buffers)
+ (read-char underlying-port)
+ (let ((c (string-ref (car buffers) 0)))
+ (if (= 1 (string-length (car buffers)))
+ (set! buffers (cdr buffers))
+ (set-car! buffers (substring (car buffers) 1)))
+ c))))
+
+ (propogate-close (lambda () (close-port underlying-port)))
+
+ (self (make-soft-port (vector #f #f #f getc propogate-close) "r"))
+
+ (unread-string (lambda (str)
+ (and (< 0 (string-length str))
+ (set! buffers (cons str buffers)))))
+
+ (read-string (lambda ()
+ (cond
+ ((not (null? buffers))
+ (let ((answer (car buffers)))
+ (set! buffers (cdr buffers))
+ answer))
+ (else
+ (read-line underlying-port 'concat)))))) ;handle-newline->concat
+
+ (set-object-property! self 'unread-string unread-string)
+ (set-object-property! self 'read-string read-string)
+ self))
+
+
+;;;; List functions not provided in R5RS or srfi-1
+
+;;; Copyright (C) 2003, 2006 Free Software Foundation, Inc.
+;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (ice-9 list)
+ \:export (rassoc rassv rassq))
+
+(define (generic-rassoc key alist =)
+ (let loop ((ls alist))
+ (and (not (null? ls))
+ (if (= key (cdar ls))
+ (car ls)
+ (loop (cdr ls))))))
+
+(define (rassoc key alist . =)
+ (generic-rassoc key alist (if (null? =) equal? (car =))))
+
+(define (rassv key alist)
+ (generic-rassoc key alist eqv?))
+
+(define (rassq key alist)
+ (generic-rassoc key alist eq?))
+;;; -*- mode: scheme; coding: utf-8; -*-
+;;;
+;;; Copyright (C) 2012, 2013 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (ice-9 local-eval)
+ #\use-module (ice-9 format)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-9 gnu)
+ #\use-module (system base compile)
+ #\use-module (system syntax)
+ #\export (the-environment local-eval local-compile))
+
+(define-record-type lexical-environment-type
+ (make-lexical-environment scope wrapper boxes patterns)
+ lexical-environment?
+ (scope lexenv-scope)
+ (wrapper lexenv-wrapper)
+ (boxes lexenv-boxes)
+ (patterns lexenv-patterns))
+
+(set-record-type-printer!
+ lexical-environment-type
+ (lambda (e port)
+ (format port "#<lexical-environment ~S (~S bindings)>"
+ (syntax-module (lexenv-scope e))
+ (+ (length (lexenv-boxes e)) (length (lexenv-patterns e))))))
+
+(define-syntax syntax-object-of
+ (lambda (form)
+ (syntax-case form ()
+ ((_ x) #`(quote #,(datum->syntax #'x #'x))))))
+
+(define-syntax-rule (make-box v)
+ (case-lambda
+ (() v)
+ ((x) (set! v x))))
+
+(define (make-transformer-from-box id trans)
+ (set-procedure-property! trans 'identifier-syntax-box id)
+ trans)
+
+(define-syntax-rule (identifier-syntax-from-box box)
+ (make-transformer-from-box
+ (syntax-object-of box)
+ (identifier-syntax (id (box))
+ ((set! id x) (box x)))))
+
+(define (unsupported-binding name)
+ (make-variable-transformer
+ (lambda (x)
+ (syntax-violation
+ 'local-eval
+ "unsupported binding captured by (the-environment)"
+ x))))
+
+(define (within-nested-ellipses id lvl)
+ (let loop ((s id) (n lvl))
+ (if (zero? n)
+ s
+ (loop #`(#,s (... ...)) (- n 1)))))
+
+;; Analyze the set of bound identifiers IDS. Return four values:
+;;
+;; capture: A list of forms that will be emitted in the expansion of
+;; `the-environment' to capture lexical variables.
+;;
+;; formals: Corresponding formal parameters for use in the lambda that
+;; re-introduces those variables. These are temporary identifiers, and
+;; as such if we have a nested `the-environment', there is no need to
+;; capture them. (See the notes on nested `the-environment' and
+;; proxies, below.)
+;;
+;; wrappers: A list of procedures of type SYNTAX -> SYNTAX, used to wrap
+;; the expression to be evaluated in forms that re-introduce the
+;; variable. The forms will be nested so that the variable shadowing
+;; semantics of the original form are maintained.
+;;
+;; patterns: A terrible hack. The issue is that for pattern variables,
+;; we can't emit lexically nested with-syntax forms, like:
+;;
+;; (with-syntax ((foo 1)) (the-environment))
+;; => (with-syntax ((foo 1))
+;; ... #'(with-syntax ((foo ...)) ... exp) ...)
+;;
+;; The reason is that the outer "foo" substitutes into the inner "foo",
+;; yielding something like:
+;;
+;; (with-syntax ((foo 1))
+;; ... (with-syntax ((1 ...)) ...)
+;;
+;; Which ain't what we want. So we hide the information needed to
+;; re-make the inner pattern binding form in the lexical environment
+;; object, and then introduce those identifiers via another with-syntax.
+;;
+;;
+;; There are four different kinds of lexical bindings: normal lexicals,
+;; macros, displaced lexicals, and pattern variables. See the
+;; documentation of syntax-local-binding for more info on these.
+;;
+;; We capture normal lexicals via `make-box', which creates a
+;; case-lambda that can reference or set a variable. These get
+;; re-introduced with an identifier-syntax.
+;;
+;; We can't capture macros currently. However we do recognize our own
+;; macros that are actually proxying lexicals, so that nested
+;; `the-environment' forms are possible. In that case we drill down to
+;; the identifier for the already-existing box, and just capture that
+;; box.
+;;
+;; And that's it: we skip displaced lexicals, and the pattern variables
+;; are discussed above.
+;;
+(define (analyze-identifiers ids)
+ (define (mktmp)
+ (datum->syntax #'here (gensym "t ")))
+ (let lp ((ids ids) (capture '()) (formals '()) (wrappers '()) (patterns '()))
+ (cond
+ ((null? ids)
+ (values capture formals wrappers patterns))
+ (else
+ (let ((id (car ids)) (ids (cdr ids)))
+ (call-with-values (lambda () (syntax-local-binding id))
+ (lambda (type val)
+ (case type
+ ((lexical)
+ (if (or-map (lambda (x) (bound-identifier=? x id)) formals)
+ (lp ids capture formals wrappers patterns)
+ (let ((t (mktmp)))
+ (lp ids
+ (cons #`(make-box #,id) capture)
+ (cons t formals)
+ (cons (lambda (x)
+ #`(let-syntax ((#,id (identifier-syntax-from-box #,t)))
+ #,x))
+ wrappers)
+ patterns))))
+ ((displaced-lexical)
+ (lp ids capture formals wrappers patterns))
+ ((macro)
+ (let ((b (procedure-property val 'identifier-syntax-box)))
+ (if b
+ (lp ids (cons b capture) (cons b formals)
+ (cons (lambda (x)
+ #`(let-syntax ((#,id (identifier-syntax-from-box #,b)))
+ #,x))
+ wrappers)
+ patterns)
+ (lp ids capture formals
+ (cons (lambda (x)
+ #`(let-syntax ((#,id (unsupported-binding '#,id)))
+ #,x))
+ wrappers)
+ patterns))))
+ ((pattern-variable)
+ (let ((t (datum->syntax id (gensym "p ")))
+ (nested (within-nested-ellipses id (cdr val))))
+ (lp ids capture formals
+ (cons (lambda (x)
+ #`(with-syntax ((#,t '#,nested))
+ #,x))
+ wrappers)
+ ;; This dance is to hide these pattern variables
+ ;; from the expander.
+ (cons (list (datum->syntax #'here (syntax->datum id))
+ (cdr val)
+ t)
+ patterns))))
+ ((ellipsis)
+ (lp ids capture formals
+ (cons (lambda (x)
+ #`(with-ellipsis #,val #,x))
+ wrappers)
+ patterns))
+ (else
+ (error "what" type val))))))))))
+
+(define-syntax the-environment
+ (lambda (x)
+ (syntax-case x ()
+ ((the-environment)
+ #'(the-environment the-environment))
+ ((the-environment scope)
+ (call-with-values (lambda ()
+ (analyze-identifiers
+ (syntax-locally-bound-identifiers #'scope)))
+ (lambda (capture formals wrappers patterns)
+ (define (wrap-expression x)
+ (let lp ((x x) (wrappers wrappers))
+ (if (null? wrappers)
+ x
+ (lp ((car wrappers) x) (cdr wrappers)))))
+ (with-syntax (((f ...) formals)
+ ((c ...) capture)
+ (((pname plvl pformal) ...) patterns)
+ (wrapped (wrap-expression #'(begin #f exp))))
+ #'(make-lexical-environment
+ #'scope
+ (lambda (exp pformal ...)
+ (with-syntax ((exp exp)
+ (pformal pformal)
+ ...)
+ #'(lambda (f ...)
+ wrapped)))
+ (list c ...)
+ (list (list 'pname plvl #'pformal) ...)))))))))
+
+(define (env-module e)
+ (cond
+ ((lexical-environment? e) (resolve-module (syntax-module (lexenv-scope e))))
+ ((module? e) e)
+ (else (error "invalid lexical environment" e))))
+
+(define (env-boxes e)
+ (cond
+ ((lexical-environment? e) (lexenv-boxes e))
+ ((module? e) '())
+ (else (error "invalid lexical environment" e))))
+
+(define (local-wrap x e)
+ (cond
+ ((lexical-environment? e)
+ (apply (lexenv-wrapper e)
+ (datum->syntax (lexenv-scope e) x)
+ (map (lambda (l)
+ (let ((name (car l))
+ (lvl (cadr l))
+ (scope (caddr l)))
+ (within-nested-ellipses (datum->syntax scope name) lvl)))
+ (lexenv-patterns e))))
+ ((module? e) #`(lambda () #f #,x))
+ (else (error "invalid lexical environment" e))))
+
+(define (local-eval x e)
+ "Evaluate the expression @var{x} within the lexical environment @var{e}."
+ (apply (eval (local-wrap x e) (env-module e))
+ (env-boxes e)))
+
+(define* (local-compile x e #\key (opts '()))
+ "Compile and evaluate the expression @var{x} within the lexical
+environment @var{e}."
+ (apply (compile (local-wrap x e) #\env (env-module e)
+ #\from 'scheme #\opts opts)
+ (env-boxes e)))
+;;;; ls.scm --- functions for browsing modules
+;;;;
+;;;; Copyright (C) 1995, 1996, 1997, 1999, 2001, 2006, 2010 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+(define-module (ice-9 ls)
+ \:use-module (ice-9 common-list)
+ \:export (local-definitions-in definitions-in ls lls
+ recursive-local-define))
+
+;;;;
+;;; local-definitions-in root name
+;;; Returns a list of names defined locally in the named
+;;; subdirectory of root.
+;;; definitions-in root name
+;;; Returns a list of all names defined in the named
+;;; subdirectory of root. The list includes alll locally
+;;; defined names as well as all names inherited from a
+;;; member of a use-list.
+;;;
+;;; A convenient interface for examining the nature of things:
+;;;
+;;; ls . various-names
+;;;
+;;; With no arguments, return a list of definitions in
+;;; `(current-module)'.
+;;;
+;;; With just one argument, interpret that argument as the
+;;; name of a subdirectory of the current module and
+;;; return a list of names defined there.
+;;;
+;;; With more than one argument, still compute
+;;; subdirectory lists, but return a list:
+;;; ((<subdir-name> . <names-defined-there>)
+;;; (<subdir-name> . <names-defined-there>)
+;;; ...)
+;;;
+;;; lls . various-names
+;;;
+;;; Analogous to `ls', but with local definitions only.
+
+(define (local-definitions-in root names)
+ (let ((m (nested-ref-module root names)))
+ (if m
+ (module-map (lambda (k v) k) m)
+ (nested-ref root names))))
+
+(define (definitions-in root names)
+ (let ((m (nested-ref-module root names)))
+ (if m
+ (reduce union
+ (cons (local-definitions-in m '())
+ (map (lambda (m2) (definitions-in m2 '()))
+ (module-uses m))))
+ (nested-ref root names))))
+
+(define (ls . various-refs)
+ (if (pair? various-refs)
+ (if (cdr various-refs)
+ (map (lambda (ref)
+ (cons ref (definitions-in (current-module) ref)))
+ various-refs)
+ (definitions-in (current-module) (car various-refs)))
+ (definitions-in (current-module) '())))
+
+(define (lls . various-refs)
+ (if (pair? various-refs)
+ (if (cdr various-refs)
+ (map (lambda (ref)
+ (cons ref (local-definitions-in (current-module) ref)))
+ various-refs)
+ (local-definitions-in (current-module) (car various-refs)))
+ (local-definitions-in (current-module) '())))
+
+(define (recursive-local-define name value)
+ (let ((parent (reverse! (cdr (reverse name)))))
+ (module-define! (make-modules-in (current-module) parent)
+ name value)))
+
+;;; ls.scm ends here
+;;; installed-scm-file
+
+;;;; Copyright (C) 1996, 2001, 2006, 2013 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+
+(define-module (ice-9 mapping)
+ \:use-module (ice-9 poe)
+ \:export (mapping-hooks-type make-mapping-hooks mapping-hooks?
+ mapping-hooks-get-handle mapping-hooks-create-handle
+ mapping-hooks-remove mapping-type make-mapping mapping?
+ mapping-hooks mapping-data set-mapping-hooks! set-mapping-data!
+ mapping-get-handle mapping-create-handle! mapping-remove!
+ mapping-ref mapping-set! hash-table-mapping-hooks
+ make-hash-table-mapping hash-table-mapping))
+
+(issue-deprecation-warning
+ "(ice-9 mapping) is deprecated. Use srfi-69 or rnrs hash tables instead.")
+
+(define mapping-hooks-type (make-record-type 'mapping-hooks '(get-handle
+ create-handle
+ remove)))
+
+
+(define make-mapping-hooks (perfect-funcq 17 (record-constructor mapping-hooks-type)))
+(define mapping-hooks? (record-predicate mapping-hooks-type))
+(define mapping-hooks-get-handle (record-accessor mapping-hooks-type 'get-handle))
+(define mapping-hooks-create-handle (record-accessor mapping-hooks-type 'create-handle))
+(define mapping-hooks-remove (record-accessor mapping-hooks-type 'remove))
+
+(define mapping-type (make-record-type 'mapping '(hooks data)))
+(define make-mapping (record-constructor mapping-type))
+(define mapping? (record-predicate mapping-type))
+(define mapping-hooks (record-accessor mapping-type 'hooks))
+(define mapping-data (record-accessor mapping-type 'data))
+(define set-mapping-hooks! (record-modifier mapping-type 'hooks))
+(define set-mapping-data! (record-modifier mapping-type 'data))
+
+(define (mapping-get-handle map key)
+ ((mapping-hooks-get-handle (mapping-hooks map)) map key))
+(define (mapping-create-handle! map key init)
+ ((mapping-hooks-create-handle (mapping-hooks map)) map key init))
+(define (mapping-remove! map key)
+ ((mapping-hooks-remove (mapping-hooks map)) map key))
+
+(define* (mapping-ref map key #\optional dflt)
+ (cond
+ ((mapping-get-handle map key) => cdr)
+ (else dflt)))
+
+(define (mapping-set! map key val)
+ (set-cdr! (mapping-create-handle! map key #f) val))
+
+
+
+(define hash-table-mapping-hooks
+ (let ((wrap (lambda (proc) (lambda (1st . rest) (apply proc (mapping-data 1st) rest)))))
+
+ (perfect-funcq 17
+ (lambda (hash-proc assoc-proc)
+ (let ((procs (list hash-proc assoc-proc)))
+ (cond
+ ((equal? procs `(,hashq ,assq))
+ (make-mapping-hooks (wrap hashq-get-handle)
+ (wrap hashq-create-handle!)
+ (wrap hashq-remove!)))
+ ((equal? procs `(,hashv ,assv))
+ (make-mapping-hooks (wrap hashv-get-handle)
+ (wrap hashv-create-handle!)
+ (wrap hashv-remove!)))
+ ((equal? procs `(,hash ,assoc))
+ (make-mapping-hooks (wrap hash-get-handle)
+ (wrap hash-create-handle!)
+ (wrap hash-remove!)))
+ (else
+ (make-mapping-hooks (wrap
+ (lambda (table key)
+ (hashx-get-handle hash-proc assoc-proc table key)))
+ (wrap
+ (lambda (table key init)
+ (hashx-create-handle! hash-proc assoc-proc table key init)))
+ (wrap
+ (lambda (table key)
+ (hashx-remove! hash-proc assoc-proc table key)))))))))))
+
+(define (make-hash-table-mapping table hash-proc assoc-proc)
+ (make-mapping (hash-table-mapping-hooks hash-proc assoc-proc) table))
+
+(define* (hash-table-mapping #\optional (size 71) #\key
+ (hash-proc hash)
+ (assoc-proc
+ (or (assq-ref `((,hashq . ,assq)
+ (,hashv . ,assv)
+ (,hash . ,assoc))
+ hash-proc)
+ (error 'hash-table-mapping
+ "Hash-procedure specified with no known assoc function."
+ hash-proc)))
+ (table-constructor
+ (lambda (len) (make-vector len '()))))
+ (make-hash-table-mapping (table-constructor size)
+ hash-proc
+ assoc-proc))
+;;; -*- mode: scheme; coding: utf-8; -*-
+;;;
+;;; Copyright (C) 2010, 2011, 2012 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (ice-9 match)
+ #\export (match
+ match-lambda
+ match-lambda*
+ match-let
+ match-let*
+ match-letrec))
+
+(define (error _ . args)
+ ;; Error procedure for run-time "no matching pattern" errors.
+ (apply throw 'match-error "match" args))
+
+;; Support for record matching.
+
+(define-syntax slot-ref
+ (syntax-rules ()
+ ((_ rtd rec n)
+ (struct-ref rec n))))
+
+(define-syntax slot-set!
+ (syntax-rules ()
+ ((_ rtd rec n value)
+ (struct-set! rec n value))))
+
+(define-syntax is-a?
+ (syntax-rules ()
+ ((_ rec rtd)
+ (and (struct? rec)
+ (eq? (struct-vtable rec) rtd)))))
+
+;; Compared to Andrew K. Wright's `match', this one lacks `match-define',
+;; `match:error-control', `match:set-error-control', `match:error',
+;; `match:set-error', and all structure-related procedures. Also,
+;; `match' doesn't support clauses of the form `(pat => exp)'.
+
+;; Unmodified public domain code by Alex Shinn retrieved from
+;; the Chibi-Scheme repository, commit 1206:acd808700e91.
+;;
+;; Note: Make sure to update `match.test.upstream' when updating this
+;; file.
+(include-from-path "ice-9/match.upstream.scm")
+;;;; match.scm -- portable hygienic pattern matcher -*- coding: utf-8 -*-
+;;
+;; This code is written by Alex Shinn and placed in the
+;; Public Domain. All warranties are disclaimed.
+
+;;> @example-import[(srfi 9)]
+
+;;> This is a full superset of the popular @hyperlink[
+;;> "http://www.cs.indiana.edu/scheme-repository/code.match.html"]{match}
+;;> package by Andrew Wright, written in fully portable @scheme{syntax-rules}
+;;> and thus preserving hygiene.
+
+;;> The most notable extensions are the ability to use @emph{non-linear}
+;;> patterns - patterns in which the same identifier occurs multiple
+;;> times, tail patterns after ellipsis, and the experimental tree patterns.
+
+;;> @subsubsection{Patterns}
+
+;;> Patterns are written to look like the printed representation of
+;;> the objects they match. The basic usage is
+
+;;> @scheme{(match expr (pat body ...) ...)}
+
+;;> where the result of @var{expr} is matched against each pattern in
+;;> turn, and the corresponding body is evaluated for the first to
+;;> succeed. Thus, a list of three elements matches a list of three
+;;> elements.
+
+;;> @example{(let ((ls (list 1 2 3))) (match ls ((1 2 3) #t)))}
+
+;;> If no patterns match an error is signalled.
+
+;;> Identifiers will match anything, and make the corresponding
+;;> binding available in the body.
+
+;;> @example{(match (list 1 2 3) ((a b c) b))}
+
+;;> If the same identifier occurs multiple times, the first instance
+;;> will match anything, but subsequent instances must match a value
+;;> which is @scheme{equal?} to the first.
+
+;;> @example{(match (list 1 2 1) ((a a b) 1) ((a b a) 2))}
+
+;;> The special identifier @scheme{_} matches anything, no matter how
+;;> many times it is used, and does not bind the result in the body.
+
+;;> @example{(match (list 1 2 1) ((_ _ b) 1) ((a b a) 2))}
+
+;;> To match a literal identifier (or list or any other literal), use
+;;> @scheme{quote}.
+
+;;> @example{(match 'a ('b 1) ('a 2))}
+
+;;> Analogous to its normal usage in scheme, @scheme{quasiquote} can
+;;> be used to quote a mostly literally matching object with selected
+;;> parts unquoted.
+
+;;> @example|{(match (list 1 2 3) (`(1 ,b ,c) (list b c)))}|
+
+;;> Often you want to match any number of a repeated pattern. Inside
+;;> a list pattern you can append @scheme{...} after an element to
+;;> match zero or more of that pattern (like a regexp Kleene star).
+
+;;> @example{(match (list 1 2) ((1 2 3 ...) #t))}
+;;> @example{(match (list 1 2 3) ((1 2 3 ...) #t))}
+;;> @example{(match (list 1 2 3 3 3) ((1 2 3 ...) #t))}
+
+;;> Pattern variables matched inside the repeated pattern are bound to
+;;> a list of each matching instance in the body.
+
+;;> @example{(match (list 1 2) ((a b c ...) c))}
+;;> @example{(match (list 1 2 3) ((a b c ...) c))}
+;;> @example{(match (list 1 2 3 4 5) ((a b c ...) c))}
+
+;;> More than one @scheme{...} may not be used in the same list, since
+;;> this would require exponential backtracking in the general case.
+;;> However, @scheme{...} need not be the final element in the list,
+;;> and may be succeeded by a fixed number of patterns.
+
+;;> @example{(match (list 1 2 3 4) ((a b c ... d e) c))}
+;;> @example{(match (list 1 2 3 4 5) ((a b c ... d e) c))}
+;;> @example{(match (list 1 2 3 4 5 6 7) ((a b c ... d e) c))}
+
+;;> @scheme{___} is provided as an alias for @scheme{...} when it is
+;;> inconvenient to use the ellipsis (as in a syntax-rules template).
+
+;;> The @scheme{..1} syntax is exactly like the @scheme{...} except
+;;> that it matches one or more repetitions (like a regexp "+").
+
+;;> @example{(match (list 1 2) ((a b c ..1) c))}
+;;> @example{(match (list 1 2 3) ((a b c ..1) c))}
+
+;;> The boolean operators @scheme{and}, @scheme{or} and @scheme{not}
+;;> can be used to group and negate patterns analogously to their
+;;> Scheme counterparts.
+
+;;> The @scheme{and} operator ensures that all subpatterns match.
+;;> This operator is often used with the idiom @scheme{(and x pat)} to
+;;> bind @var{x} to the entire value that matches @var{pat}
+;;> (c.f. "as-patterns" in ML or Haskell). Another common use is in
+;;> conjunction with @scheme{not} patterns to match a general case
+;;> with certain exceptions.
+
+;;> @example{(match 1 ((and) #t))}
+;;> @example{(match 1 ((and x) x))}
+;;> @example{(match 1 ((and x 1) x))}
+
+;;> The @scheme{or} operator ensures that at least one subpattern
+;;> matches. If the same identifier occurs in different subpatterns,
+;;> it is matched independently. All identifiers from all subpatterns
+;;> are bound if the @scheme{or} operator matches, but the binding is
+;;> only defined for identifiers from the subpattern which matched.
+
+;;> @example{(match 1 ((or) #t) (else #f))}
+;;> @example{(match 1 ((or x) x))}
+;;> @example{(match 1 ((or x 2) x))}
+
+;;> The @scheme{not} operator succeeds if the given pattern doesn't
+;;> match. None of the identifiers used are available in the body.
+
+;;> @example{(match 1 ((not 2) #t))}
+
+;;> The more general operator @scheme{?} can be used to provide a
+;;> predicate. The usage is @scheme{(? predicate pat ...)} where
+;;> @var{predicate} is a Scheme expression evaluating to a predicate
+;;> called on the value to match, and any optional patterns after the
+;;> predicate are then matched as in an @scheme{and} pattern.
+
+;;> @example{(match 1 ((? odd? x) x))}
+
+;;> The field operator @scheme{=} is used to extract an arbitrary
+;;> field and match against it. It is useful for more complex or
+;;> conditional destructuring that can't be more directly expressed in
+;;> the pattern syntax. The usage is @scheme{(= field pat)}, where
+;;> @var{field} can be any expression, and should result in a
+;;> procedure of one argument, which is applied to the value to match
+;;> to generate a new value to match against @var{pat}.
+
+;;> Thus the pattern @scheme{(and (= car x) (= cdr y))} is equivalent
+;;> to @scheme{(x . y)}, except it will result in an immediate error
+;;> if the value isn't a pair.
+
+;;> @example{(match '(1 . 2) ((= car x) x))}
+;;> @example{(match 4 ((= sqrt x) x))}
+
+;;> The record operator @scheme{$} is used as a concise way to match
+;;> records defined by SRFI-9 (or SRFI-99). The usage is
+;;> @scheme{($ rtd field ...)}, where @var{rtd} should be the record
+;;> type descriptor specified as the first argument to
+;;> @scheme{define-record-type}, and each @var{field} is a subpattern
+;;> matched against the fields of the record in order. Not all fields
+;;> must be present.
+
+;;> @example{
+;;> (let ()
+;;> (define-record-type employee
+;;> (make-employee name title)
+;;> employee?
+;;> (name get-name)
+;;> (title get-title))
+;;> (match (make-employee "Bob" "Doctor")
+;;> (($ employee n t) (list t n))))
+;;> }
+
+;;> The @scheme{set!} and @scheme{get!} operators are used to bind an
+;;> identifier to the setter and getter of a field, respectively. The
+;;> setter is a procedure of one argument, which mutates the field to
+;;> that argument. The getter is a procedure of no arguments which
+;;> returns the current value of the field.
+
+;;> @example{(let ((x (cons 1 2))) (match x ((1 . (set! s)) (s 3) x)))}
+;;> @example{(match '(1 . 2) ((1 . (get! g)) (g)))}
+
+;;> The new operator @scheme{***} can be used to search a tree for
+;;> subpatterns. A pattern of the form @scheme{(x *** y)} represents
+;;> the subpattern @var{y} located somewhere in a tree where the path
+;;> from the current object to @var{y} can be seen as a list of the
+;;> form @scheme{(x ...)}. @var{y} can immediately match the current
+;;> object in which case the path is the empty list. In a sense it's
+;;> a 2-dimensional version of the @scheme{...} pattern.
+
+;;> As a common case the pattern @scheme{(_ *** y)} can be used to
+;;> search for @var{y} anywhere in a tree, regardless of the path
+;;> used.
+
+;;> @example{(match '(a (a (a b))) ((x *** 'b) x))}
+;;> @example{(match '(a (b) (c (d e) (f g))) ((x *** 'g) x))}
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Notes
+
+;; The implementation is a simple generative pattern matcher - each
+;; pattern is expanded into the required tests, calling a failure
+;; continuation if the tests fail. This makes the logic easy to
+;; follow and extend, but produces sub-optimal code in cases where you
+;; have many similar clauses due to repeating the same tests.
+;; Nonetheless a smart compiler should be able to remove the redundant
+;; tests. For MATCH-LET and DESTRUCTURING-BIND type uses there is no
+;; performance hit.
+
+;; The original version was written on 2006/11/29 and described in the
+;; following Usenet post:
+;; http://groups.google.com/group/comp.lang.scheme/msg/0941234de7112ffd
+;; and is still available at
+;; http://synthcode.com/scheme/match-simple.scm
+;; It's just 80 lines for the core MATCH, and an extra 40 lines for
+;; MATCH-LET, MATCH-LAMBDA and other syntactic sugar.
+;;
+;; A variant of this file which uses COND-EXPAND in a few places for
+;; performance can be found at
+;; http://synthcode.com/scheme/match-cond-expand.scm
+;;
+;; 2012/05/23 - fixing combinatorial explosion of code in certain or patterns
+;; 2011/09/25 - fixing bug when directly matching an identifier repeated in
+;; the pattern (thanks to Stefan Israelsson Tampe)
+;; 2011/01/27 - fixing bug when matching tail patterns against improper lists
+;; 2010/09/26 - adding `..1' patterns (thanks to Ludovic Courtès)
+;; 2010/09/07 - fixing identifier extraction in some `...' and `***' patterns
+;; 2009/11/25 - adding `***' tree search patterns
+;; 2008/03/20 - fixing bug where (a ...) matched non-lists
+;; 2008/03/15 - removing redundant check in vector patterns
+;; 2008/03/06 - you can use `...' portably now (thanks to Taylor Campbell)
+;; 2007/09/04 - fixing quasiquote patterns
+;; 2007/07/21 - allowing ellipse patterns in non-final list positions
+;; 2007/04/10 - fixing potential hygiene issue in match-check-ellipse
+;; (thanks to Taylor Campbell)
+;; 2007/04/08 - clean up, commenting
+;; 2006/12/24 - bugfixes
+;; 2006/12/01 - non-linear patterns, shared variables in OR, get!/set!
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; force compile-time syntax errors with useful messages
+
+(define-syntax match-syntax-error
+ (syntax-rules ()
+ ((_) (match-syntax-error "invalid match-syntax-error usage"))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;> @subsubsection{Syntax}
+
+;;> @subsubsubsection{@rawcode{(match expr (pattern . body) ...)@br{}
+;;> (match expr (pattern (=> failure) . body) ...)}}
+
+;;> The result of @var{expr} is matched against each @var{pattern} in
+;;> turn, according to the pattern rules described in the previous
+;;> section, until the the first @var{pattern} matches. When a match is
+;;> found, the corresponding @var{body}s are evaluated in order,
+;;> and the result of the last expression is returned as the result
+;;> of the entire @scheme{match}. If a @var{failure} is provided,
+;;> then it is bound to a procedure of no arguments which continues,
+;;> processing at the next @var{pattern}. If no @var{pattern} matches,
+;;> an error is signalled.
+
+;; The basic interface. MATCH just performs some basic syntax
+;; validation, binds the match expression to a temporary variable `v',
+;; and passes it on to MATCH-NEXT. It's a constant throughout the
+;; code below that the binding `v' is a direct variable reference, not
+;; an expression.
+
+(define-syntax match
+ (syntax-rules ()
+ ((match)
+ (match-syntax-error "missing match expression"))
+ ((match atom)
+ (match-syntax-error "no match clauses"))
+ ((match (app ...) (pat . body) ...)
+ (let ((v (app ...)))
+ (match-next v ((app ...) (set! (app ...))) (pat . body) ...)))
+ ((match #(vec ...) (pat . body) ...)
+ (let ((v #(vec ...)))
+ (match-next v (v (set! v)) (pat . body) ...)))
+ ((match atom (pat . body) ...)
+ (let ((v atom))
+ (match-next v (atom (set! atom)) (pat . body) ...)))
+ ))
+
+;; MATCH-NEXT passes each clause to MATCH-ONE in turn with its failure
+;; thunk, which is expanded by recursing MATCH-NEXT on the remaining
+;; clauses. `g+s' is a list of two elements, the get! and set!
+;; expressions respectively.
+
+(define-syntax match-next
+ (syntax-rules (=>)
+ ;; no more clauses, the match failed
+ ((match-next v g+s)
+ ;; Here we wrap error within a double set of parentheses, so that
+ ;; the call to 'error' won't be in tail position. This allows the
+ ;; backtrace to show the source location of the failing match form.
+ ((error 'match "no matching pattern" v)))
+ ;; named failure continuation
+ ((match-next v g+s (pat (=> failure) . body) . rest)
+ (let ((failure (lambda () (match-next v g+s . rest))))
+ ;; match-one analyzes the pattern for us
+ (match-one v pat g+s (match-drop-ids (begin . body)) (failure) ())))
+ ;; anonymous failure continuation, give it a dummy name
+ ((match-next v g+s (pat . body) . rest)
+ (match-next v g+s (pat (=> failure) . body) . rest))))
+
+;; MATCH-ONE first checks for ellipse patterns, otherwise passes on to
+;; MATCH-TWO.
+
+(define-syntax match-one
+ (syntax-rules ()
+ ;; If it's a list of two or more values, check to see if the
+ ;; second one is an ellipse and handle accordingly, otherwise go
+ ;; to MATCH-TWO.
+ ((match-one v (p q . r) g+s sk fk i)
+ (match-check-ellipse
+ q
+ (match-extract-vars p (match-gen-ellipses v p r g+s sk fk i) i ())
+ (match-two v (p q . r) g+s sk fk i)))
+ ;; Go directly to MATCH-TWO.
+ ((match-one . x)
+ (match-two . x))))
+
+;; This is the guts of the pattern matcher. We are passed a lot of
+;; information in the form:
+;;
+;; (match-two var pattern getter setter success-k fail-k (ids ...))
+;;
+;; usually abbreviated
+;;
+;; (match-two v p g+s sk fk i)
+;;
+;; where VAR is the symbol name of the current variable we are
+;; matching, PATTERN is the current pattern, getter and setter are the
+;; corresponding accessors (e.g. CAR and SET-CAR! of the pair holding
+;; VAR), SUCCESS-K is the success continuation, FAIL-K is the failure
+;; continuation (which is just a thunk call and is thus safe to expand
+;; multiple times) and IDS are the list of identifiers bound in the
+;; pattern so far.
+
+(define-syntax match-two
+ (syntax-rules (_ ___ \.\.1 *** quote quasiquote ? $ = and or not set! get!)
+ ((match-two v () g+s (sk ...) fk i)
+ (if (null? v) (sk ... i) fk))
+ ((match-two v (quote p) g+s (sk ...) fk i)
+ (if (equal? v 'p) (sk ... i) fk))
+ ((match-two v (quasiquote p) . x)
+ (match-quasiquote v p . x))
+ ((match-two v (and) g+s (sk ...) fk i) (sk ... i))
+ ((match-two v (and p q ...) g+s sk fk i)
+ (match-one v p g+s (match-one v (and q ...) g+s sk fk) fk i))
+ ((match-two v (or) g+s sk fk i) fk)
+ ((match-two v (or p) . x)
+ (match-one v p . x))
+ ((match-two v (or p ...) g+s sk fk i)
+ (match-extract-vars (or p ...) (match-gen-or v (p ...) g+s sk fk i) i ()))
+ ((match-two v (not p) g+s (sk ...) fk i)
+ (match-one v p g+s (match-drop-ids fk) (sk ... i) i))
+ ((match-two v (get! getter) (g s) (sk ...) fk i)
+ (let ((getter (lambda () g))) (sk ... i)))
+ ((match-two v (set! setter) (g (s ...)) (sk ...) fk i)
+ (let ((setter (lambda (x) (s ... x)))) (sk ... i)))
+ ((match-two v (? pred . p) g+s sk fk i)
+ (if (pred v) (match-one v (and . p) g+s sk fk i) fk))
+ ((match-two v (= proc p) . x)
+ (let ((w (proc v))) (match-one w p . x)))
+ ((match-two v (p ___ . r) g+s sk fk i)
+ (match-extract-vars p (match-gen-ellipses v p r g+s sk fk i) i ()))
+ ((match-two v (p) g+s sk fk i)
+ (if (and (pair? v) (null? (cdr v)))
+ (let ((w (car v)))
+ (match-one w p ((car v) (set-car! v)) sk fk i))
+ fk))
+ ((match-two v (p *** q) g+s sk fk i)
+ (match-extract-vars p (match-gen-search v p q g+s sk fk i) i ()))
+ ((match-two v (p *** . q) g+s sk fk i)
+ (match-syntax-error "invalid use of ***" (p *** . q)))
+ ((match-two v (p \.\.1) g+s sk fk i)
+ (if (pair? v)
+ (match-one v (p ___) g+s sk fk i)
+ fk))
+ ((match-two v ($ rec p ...) g+s sk fk i)
+ (if (is-a? v rec)
+ (match-record-refs v rec 0 (p ...) g+s sk fk i)
+ fk))
+ ((match-two v (p . q) g+s sk fk i)
+ (if (pair? v)
+ (let ((w (car v)) (x (cdr v)))
+ (match-one w p ((car v) (set-car! v))
+ (match-one x q ((cdr v) (set-cdr! v)) sk fk)
+ fk
+ i))
+ fk))
+ ((match-two v #(p ...) g+s . x)
+ (match-vector v 0 () (p ...) . x))
+ ((match-two v _ g+s (sk ...) fk i) (sk ... i))
+ ;; Not a pair or vector or special literal, test to see if it's a
+ ;; new symbol, in which case we just bind it, or if it's an
+ ;; already bound symbol or some other literal, in which case we
+ ;; compare it with EQUAL?.
+ ((match-two v x g+s (sk ...) fk (id ...))
+ (let-syntax
+ ((new-sym?
+ (syntax-rules (id ...)
+ ((new-sym? x sk2 fk2) sk2)
+ ((new-sym? y sk2 fk2) fk2))))
+ (new-sym? random-sym-to-match
+ (let ((x v)) (sk ... (id ... x)))
+ (if (equal? v x) (sk ... (id ...)) fk))))
+ ))
+
+;; QUASIQUOTE patterns
+
+(define-syntax match-quasiquote
+ (syntax-rules (unquote unquote-splicing quasiquote)
+ ((_ v (unquote p) g+s sk fk i)
+ (match-one v p g+s sk fk i))
+ ((_ v ((unquote-splicing p) . rest) g+s sk fk i)
+ (if (pair? v)
+ (match-one v
+ (p . tmp)
+ (match-quasiquote tmp rest g+s sk fk)
+ fk
+ i)
+ fk))
+ ((_ v (quasiquote p) g+s sk fk i . depth)
+ (match-quasiquote v p g+s sk fk i #f . depth))
+ ((_ v (unquote p) g+s sk fk i x . depth)
+ (match-quasiquote v p g+s sk fk i . depth))
+ ((_ v (unquote-splicing p) g+s sk fk i x . depth)
+ (match-quasiquote v p g+s sk fk i . depth))
+ ((_ v (p . q) g+s sk fk i . depth)
+ (if (pair? v)
+ (let ((w (car v)) (x (cdr v)))
+ (match-quasiquote
+ w p g+s
+ (match-quasiquote-step x q g+s sk fk depth)
+ fk i . depth))
+ fk))
+ ((_ v #(elt ...) g+s sk fk i . depth)
+ (if (vector? v)
+ (let ((ls (vector->list v)))
+ (match-quasiquote ls (elt ...) g+s sk fk i . depth))
+ fk))
+ ((_ v x g+s sk fk i . depth)
+ (match-one v 'x g+s sk fk i))))
+
+(define-syntax match-quasiquote-step
+ (syntax-rules ()
+ ((match-quasiquote-step x q g+s sk fk depth i)
+ (match-quasiquote x q g+s sk fk i . depth))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Utilities
+
+;; Takes two values and just expands into the first.
+(define-syntax match-drop-ids
+ (syntax-rules ()
+ ((_ expr ids ...) expr)))
+
+(define-syntax match-tuck-ids
+ (syntax-rules ()
+ ((_ (letish args (expr ...)) ids ...)
+ (letish args (expr ... ids ...)))))
+
+(define-syntax match-drop-first-arg
+ (syntax-rules ()
+ ((_ arg expr) expr)))
+
+;; To expand an OR group we try each clause in succession, passing the
+;; first that succeeds to the success continuation. On failure for
+;; any clause, we just try the next clause, finally resorting to the
+;; failure continuation fk if all clauses fail. The only trick is
+;; that we want to unify the identifiers, so that the success
+;; continuation can refer to a variable from any of the OR clauses.
+
+(define-syntax match-gen-or
+ (syntax-rules ()
+ ((_ v p g+s (sk ...) fk (i ...) ((id id-ls) ...))
+ (let ((sk2 (lambda (id ...) (sk ... (i ... id ...)))))
+ (match-gen-or-step v p g+s (match-drop-ids (sk2 id ...)) fk (i ...))))))
+
+(define-syntax match-gen-or-step
+ (syntax-rules ()
+ ((_ v () g+s sk fk . x)
+ ;; no OR clauses, call the failure continuation
+ fk)
+ ((_ v (p) . x)
+ ;; last (or only) OR clause, just expand normally
+ (match-one v p . x))
+ ((_ v (p . q) g+s sk fk i)
+ ;; match one and try the remaining on failure
+ (let ((fk2 (lambda () (match-gen-or-step v q g+s sk fk i))))
+ (match-one v p g+s sk (fk2) i)))
+ ))
+
+;; We match a pattern (p ...) by matching the pattern p in a loop on
+;; each element of the variable, accumulating the bound ids into lists.
+
+;; Look at the body of the simple case - it's just a named let loop,
+;; matching each element in turn to the same pattern. The only trick
+;; is that we want to keep track of the lists of each extracted id, so
+;; when the loop recurses we cons the ids onto their respective list
+;; variables, and on success we bind the ids (what the user input and
+;; expects to see in the success body) to the reversed accumulated
+;; list IDs.
+
+(define-syntax match-gen-ellipses
+ (syntax-rules ()
+ ((_ v p () g+s (sk ...) fk i ((id id-ls) ...))
+ (match-check-identifier p
+ ;; simplest case equivalent to (p ...), just bind the list
+ (let ((p v))
+ (if (list? p)
+ (sk ... i)
+ fk))
+ ;; simple case, match all elements of the list
+ (let loop ((ls v) (id-ls '()) ...)
+ (cond
+ ((null? ls)
+ (let ((id (reverse id-ls)) ...) (sk ... i)))
+ ((pair? ls)
+ (let ((w (car ls)))
+ (match-one w p ((car ls) (set-car! ls))
+ (match-drop-ids (loop (cdr ls) (cons id id-ls) ...))
+ fk i)))
+ (else
+ fk)))))
+ ((_ v p r g+s (sk ...) fk i ((id id-ls) ...))
+ ;; general case, trailing patterns to match, keep track of the
+ ;; remaining list length so we don't need any backtracking
+ (match-verify-no-ellipses
+ r
+ (let* ((tail-len (length 'r))
+ (ls v)
+ (len (and (list? ls) (length ls))))
+ (if (or (not len) (< len tail-len))
+ fk
+ (let loop ((ls ls) (n len) (id-ls '()) ...)
+ (cond
+ ((= n tail-len)
+ (let ((id (reverse id-ls)) ...)
+ (match-one ls r (#f #f) (sk ...) fk i)))
+ ((pair? ls)
+ (let ((w (car ls)))
+ (match-one w p ((car ls) (set-car! ls))
+ (match-drop-ids
+ (loop (cdr ls) (- n 1) (cons id id-ls) ...))
+ fk
+ i)))
+ (else
+ fk)))))))))
+
+;; This is just a safety check. Although unlike syntax-rules we allow
+;; trailing patterns after an ellipses, we explicitly disable multiple
+;; ellipses at the same level. This is because in the general case
+;; such patterns are exponential in the number of ellipses, and we
+;; don't want to make it easy to construct very expensive operations
+;; with simple looking patterns. For example, it would be O(n^2) for
+;; patterns like (a ... b ...) because we must consider every trailing
+;; element for every possible break for the leading "a ...".
+
+(define-syntax match-verify-no-ellipses
+ (syntax-rules ()
+ ((_ (x . y) sk)
+ (match-check-ellipse
+ x
+ (match-syntax-error
+ "multiple ellipse patterns not allowed at same level")
+ (match-verify-no-ellipses y sk)))
+ ((_ () sk)
+ sk)
+ ((_ x sk)
+ (match-syntax-error "dotted tail not allowed after ellipse" x))))
+
+;; To implement the tree search, we use two recursive procedures. TRY
+;; attempts to match Y once, and on success it calls the normal SK on
+;; the accumulated list ids as in MATCH-GEN-ELLIPSES. On failure, we
+;; call NEXT which first checks if the current value is a list
+;; beginning with X, then calls TRY on each remaining element of the
+;; list. Since TRY will recursively call NEXT again on failure, this
+;; effects a full depth-first search.
+;;
+;; The failure continuation throughout is a jump to the next step in
+;; the tree search, initialized with the original failure continuation
+;; FK.
+
+(define-syntax match-gen-search
+ (syntax-rules ()
+ ((match-gen-search v p q g+s sk fk i ((id id-ls) ...))
+ (letrec ((try (lambda (w fail id-ls ...)
+ (match-one w q g+s
+ (match-tuck-ids
+ (let ((id (reverse id-ls)) ...)
+ sk))
+ (next w fail id-ls ...) i)))
+ (next (lambda (w fail id-ls ...)
+ (if (not (pair? w))
+ (fail)
+ (let ((u (car w)))
+ (match-one
+ u p ((car w) (set-car! w))
+ (match-drop-ids
+ ;; accumulate the head variables from
+ ;; the p pattern, and loop over the tail
+ (let ((id-ls (cons id id-ls)) ...)
+ (let lp ((ls (cdr w)))
+ (if (pair? ls)
+ (try (car ls)
+ (lambda () (lp (cdr ls)))
+ id-ls ...)
+ (fail)))))
+ (fail) i))))))
+ ;; the initial id-ls binding here is a dummy to get the right
+ ;; number of '()s
+ (let ((id-ls '()) ...)
+ (try v (lambda () fk) id-ls ...))))))
+
+;; Vector patterns are just more of the same, with the slight
+;; exception that we pass around the current vector index being
+;; matched.
+
+(define-syntax match-vector
+ (syntax-rules (___)
+ ((_ v n pats (p q) . x)
+ (match-check-ellipse q
+ (match-gen-vector-ellipses v n pats p . x)
+ (match-vector-two v n pats (p q) . x)))
+ ((_ v n pats (p ___) sk fk i)
+ (match-gen-vector-ellipses v n pats p sk fk i))
+ ((_ . x)
+ (match-vector-two . x))))
+
+;; Check the exact vector length, then check each element in turn.
+
+(define-syntax match-vector-two
+ (syntax-rules ()
+ ((_ v n ((pat index) ...) () sk fk i)
+ (if (vector? v)
+ (let ((len (vector-length v)))
+ (if (= len n)
+ (match-vector-step v ((pat index) ...) sk fk i)
+ fk))
+ fk))
+ ((_ v n (pats ...) (p . q) . x)
+ (match-vector v (+ n 1) (pats ... (p n)) q . x))))
+
+(define-syntax match-vector-step
+ (syntax-rules ()
+ ((_ v () (sk ...) fk i) (sk ... i))
+ ((_ v ((pat index) . rest) sk fk i)
+ (let ((w (vector-ref v index)))
+ (match-one w pat ((vector-ref v index) (vector-set! v index))
+ (match-vector-step v rest sk fk)
+ fk i)))))
+
+;; With a vector ellipse pattern we first check to see if the vector
+;; length is at least the required length.
+
+(define-syntax match-gen-vector-ellipses
+ (syntax-rules ()
+ ((_ v n ((pat index) ...) p sk fk i)
+ (if (vector? v)
+ (let ((len (vector-length v)))
+ (if (>= len n)
+ (match-vector-step v ((pat index) ...)
+ (match-vector-tail v p n len sk fk)
+ fk i)
+ fk))
+ fk))))
+
+(define-syntax match-vector-tail
+ (syntax-rules ()
+ ((_ v p n len sk fk i)
+ (match-extract-vars p (match-vector-tail-two v p n len sk fk i) i ()))))
+
+(define-syntax match-vector-tail-two
+ (syntax-rules ()
+ ((_ v p n len (sk ...) fk i ((id id-ls) ...))
+ (let loop ((j n) (id-ls '()) ...)
+ (if (>= j len)
+ (let ((id (reverse id-ls)) ...) (sk ... i))
+ (let ((w (vector-ref v j)))
+ (match-one w p ((vector-ref v j) (vetor-set! v j))
+ (match-drop-ids (loop (+ j 1) (cons id id-ls) ...))
+ fk i)))))))
+
+(define-syntax match-record-refs
+ (syntax-rules ()
+ ((_ v rec n (p . q) g+s sk fk i)
+ (let ((w (slot-ref rec v n)))
+ (match-one w p ((slot-ref rec v n) (slot-set! rec v n))
+ (match-record-refs v rec (+ n 1) q g+s sk fk) fk i)))
+ ((_ v rec n () g+s (sk ...) fk i)
+ (sk ... i))))
+
+;; Extract all identifiers in a pattern. A little more complicated
+;; than just looking for symbols, we need to ignore special keywords
+;; and non-pattern forms (such as the predicate expression in ?
+;; patterns), and also ignore previously bound identifiers.
+;;
+;; Calls the continuation with all new vars as a list of the form
+;; ((orig-var tmp-name) ...), where tmp-name can be used to uniquely
+;; pair with the original variable (e.g. it's used in the ellipse
+;; generation for list variables).
+;;
+;; (match-extract-vars pattern continuation (ids ...) (new-vars ...))
+
+(define-syntax match-extract-vars
+ (syntax-rules (_ ___ \.\.1 *** ? $ = quote quasiquote and or not get! set!)
+ ((match-extract-vars (? pred . p) . x)
+ (match-extract-vars p . x))
+ ((match-extract-vars ($ rec . p) . x)
+ (match-extract-vars p . x))
+ ((match-extract-vars (= proc p) . x)
+ (match-extract-vars p . x))
+ ((match-extract-vars (quote x) (k ...) i v)
+ (k ... v))
+ ((match-extract-vars (quasiquote x) k i v)
+ (match-extract-quasiquote-vars x k i v (#t)))
+ ((match-extract-vars (and . p) . x)
+ (match-extract-vars p . x))
+ ((match-extract-vars (or . p) . x)
+ (match-extract-vars p . x))
+ ((match-extract-vars (not . p) . x)
+ (match-extract-vars p . x))
+ ;; A non-keyword pair, expand the CAR with a continuation to
+ ;; expand the CDR.
+ ((match-extract-vars (p q . r) k i v)
+ (match-check-ellipse
+ q
+ (match-extract-vars (p . r) k i v)
+ (match-extract-vars p (match-extract-vars-step (q . r) k i v) i ())))
+ ((match-extract-vars (p . q) k i v)
+ (match-extract-vars p (match-extract-vars-step q k i v) i ()))
+ ((match-extract-vars #(p ...) . x)
+ (match-extract-vars (p ...) . x))
+ ((match-extract-vars _ (k ...) i v) (k ... v))
+ ((match-extract-vars ___ (k ...) i v) (k ... v))
+ ((match-extract-vars *** (k ...) i v) (k ... v))
+ ((match-extract-vars \.\.1 (k ...) i v) (k ... v))
+ ;; This is the main part, the only place where we might add a new
+ ;; var if it's an unbound symbol.
+ ((match-extract-vars p (k ...) (i ...) v)
+ (let-syntax
+ ((new-sym?
+ (syntax-rules (i ...)
+ ((new-sym? p sk fk) sk)
+ ((new-sym? any sk fk) fk))))
+ (new-sym? random-sym-to-match
+ (k ... ((p p-ls) . v))
+ (k ... v))))
+ ))
+
+;; Stepper used in the above so it can expand the CAR and CDR
+;; separately.
+
+(define-syntax match-extract-vars-step
+ (syntax-rules ()
+ ((_ p k i v ((v2 v2-ls) ...))
+ (match-extract-vars p k (v2 ... . i) ((v2 v2-ls) ... . v)))
+ ))
+
+(define-syntax match-extract-quasiquote-vars
+ (syntax-rules (quasiquote unquote unquote-splicing)
+ ((match-extract-quasiquote-vars (quasiquote x) k i v d)
+ (match-extract-quasiquote-vars x k i v (#t . d)))
+ ((match-extract-quasiquote-vars (unquote-splicing x) k i v d)
+ (match-extract-quasiquote-vars (unquote x) k i v d))
+ ((match-extract-quasiquote-vars (unquote x) k i v (#t))
+ (match-extract-vars x k i v))
+ ((match-extract-quasiquote-vars (unquote x) k i v (#t . d))
+ (match-extract-quasiquote-vars x k i v d))
+ ((match-extract-quasiquote-vars (x . y) k i v (#t . d))
+ (match-extract-quasiquote-vars
+ x
+ (match-extract-quasiquote-vars-step y k i v d) i ()))
+ ((match-extract-quasiquote-vars #(x ...) k i v (#t . d))
+ (match-extract-quasiquote-vars (x ...) k i v d))
+ ((match-extract-quasiquote-vars x (k ...) i v (#t . d))
+ (k ... v))
+ ))
+
+(define-syntax match-extract-quasiquote-vars-step
+ (syntax-rules ()
+ ((_ x k i v d ((v2 v2-ls) ...))
+ (match-extract-quasiquote-vars x k (v2 ... . i) ((v2 v2-ls) ... . v) d))
+ ))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Gimme some sugar baby.
+
+;;> Shortcut for @scheme{lambda} + @scheme{match}. Creates a
+;;> procedure of one argument, and matches that argument against each
+;;> clause.
+
+(define-syntax match-lambda
+ (syntax-rules ()
+ ((_ (pattern . body) ...) (lambda (expr) (match expr (pattern . body) ...)))))
+
+;;> Similar to @scheme{match-lambda}. Creates a procedure of any
+;;> number of arguments, and matches the argument list against each
+;;> clause.
+
+(define-syntax match-lambda*
+ (syntax-rules ()
+ ((_ (pattern . body) ...) (lambda expr (match expr (pattern . body) ...)))))
+
+;;> Matches each var to the corresponding expression, and evaluates
+;;> the body with all match variables in scope. Raises an error if
+;;> any of the expressions fail to match. Syntax analogous to named
+;;> let can also be used for recursive functions which match on their
+;;> arguments as in @scheme{match-lambda*}.
+
+(define-syntax match-let
+ (syntax-rules ()
+ ((_ ((var value) ...) . body)
+ (match-let/helper let () () ((var value) ...) . body))
+ ((_ loop ((var init) ...) . body)
+ (match-named-let loop ((var init) ...) . body))))
+
+;;> Similar to @scheme{match-let}, but analogously to @scheme{letrec}
+;;> matches and binds the variables with all match variables in scope.
+
+(define-syntax match-letrec
+ (syntax-rules ()
+ ((_ ((var value) ...) . body)
+ (match-let/helper letrec () () ((var value) ...) . body))))
+
+(define-syntax match-let/helper
+ (syntax-rules ()
+ ((_ let ((var expr) ...) () () . body)
+ (let ((var expr) ...) . body))
+ ((_ let ((var expr) ...) ((pat tmp) ...) () . body)
+ (let ((var expr) ...)
+ (match-let* ((pat tmp) ...)
+ . body)))
+ ((_ let (v ...) (p ...) (((a . b) expr) . rest) . body)
+ (match-let/helper
+ let (v ... (tmp expr)) (p ... ((a . b) tmp)) rest . body))
+ ((_ let (v ...) (p ...) ((#(a ...) expr) . rest) . body)
+ (match-let/helper
+ let (v ... (tmp expr)) (p ... (#(a ...) tmp)) rest . body))
+ ((_ let (v ...) (p ...) ((a expr) . rest) . body)
+ (match-let/helper let (v ... (a expr)) (p ...) rest . body))))
+
+(define-syntax match-named-let
+ (syntax-rules ()
+ ((_ loop ((pat expr var) ...) () . body)
+ (let loop ((var expr) ...)
+ (match-let ((pat var) ...)
+ . body)))
+ ((_ loop (v ...) ((pat expr) . rest) . body)
+ (match-named-let loop (v ... (pat expr tmp)) rest . body))))
+
+;;> @subsubsubsection{@rawcode{(match-let* ((var value) ...) body ...)}}
+
+;;> Similar to @scheme{match-let}, but analogously to @scheme{let*}
+;;> matches and binds the variables in sequence, with preceding match
+;;> variables in scope.
+
+(define-syntax match-let*
+ (syntax-rules ()
+ ((_ () . body)
+ (begin . body))
+ ((_ ((pat expr) . rest) . body)
+ (match expr (pat (match-let* rest . body))))))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Otherwise COND-EXPANDed bits.
+
+;; This *should* work, but doesn't :(
+;; (define-syntax match-check-ellipse
+;; (syntax-rules (...)
+;; ((_ ... sk fk) sk)
+;; ((_ x sk fk) fk)))
+
+;; This is a little more complicated, and introduces a new let-syntax,
+;; but should work portably in any R[56]RS Scheme. Taylor Campbell
+;; originally came up with the idea.
+(define-syntax match-check-ellipse
+ (syntax-rules ()
+ ;; these two aren't necessary but provide fast-case failures
+ ((match-check-ellipse (a . b) success-k failure-k) failure-k)
+ ((match-check-ellipse #(a ...) success-k failure-k) failure-k)
+ ;; matching an atom
+ ((match-check-ellipse id success-k failure-k)
+ (let-syntax ((ellipse? (syntax-rules ()
+ ;; iff `id' is `...' here then this will
+ ;; match a list of any length
+ ((ellipse? (foo id) sk fk) sk)
+ ((ellipse? other sk fk) fk))))
+ ;; this list of three elements will only many the (foo id) list
+ ;; above if `id' is `...'
+ (ellipse? (a b c) success-k failure-k)))))
+
+
+;; This is portable but can be more efficient with non-portable
+;; extensions. This trick was originally discovered by Oleg Kiselyov.
+
+(define-syntax match-check-identifier
+ (syntax-rules ()
+ ;; fast-case failures, lists and vectors are not identifiers
+ ((_ (x . y) success-k failure-k) failure-k)
+ ((_ #(x ...) success-k failure-k) failure-k)
+ ;; x is an atom
+ ((_ x success-k failure-k)
+ (let-syntax
+ ((sym?
+ (syntax-rules ()
+ ;; if the symbol `abracadabra' matches x, then x is a
+ ;; symbol
+ ((sym? x sk fk) sk)
+ ;; otherwise x is a non-symbol datum
+ ((sym? y sk fk) fk))))
+ (sym? abracadabra success-k failure-k)))))
+;;; installed-scm-file
+
+;;;; Copyright (C) 1999, 2005, 2006, 2010 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+(eval-when (compile)
+ (set-current-module (resolve-module '(guile))))
+
+(define (gethostbyaddr addr) (gethost addr))
+(define (gethostbyname name) (gethost name))
+
+(define (getnetbyaddr addr) (getnet addr))
+(define (getnetbyname name) (getnet name))
+
+(define (getprotobyname name) (getproto name))
+(define (getprotobynumber addr) (getproto addr))
+
+(define (getservbyname name proto) (getserv name proto))
+(define (getservbyport port proto) (getserv port proto))
+
+(define (sethostent . stayopen)
+ (if (pair? stayopen)
+ (sethost (car stayopen))
+ (sethost #f)))
+(define (setnetent . stayopen)
+ (if (pair? stayopen)
+ (setnet (car stayopen))
+ (setnet #f)))
+(define (setprotoent . stayopen)
+ (if (pair? stayopen)
+ (setproto (car stayopen))
+ (setproto #f)))
+(define (setservent . stayopen)
+ (if (pair? stayopen)
+ (setserv (car stayopen))
+ (setserv #f)))
+
+(define (gethostent) (gethost))
+(define (getnetent) (getnet))
+(define (getprotoent) (getproto))
+(define (getservent) (getserv))
+
+(define (endhostent) (sethost))
+(define (endnetent) (setnet))
+(define (endprotoent) (setproto))
+(define (endservent) (setserv))
+
+(define (hostent:name obj) (vector-ref obj 0))
+(define (hostent:aliases obj) (vector-ref obj 1))
+(define (hostent:addrtype obj) (vector-ref obj 2))
+(define (hostent:length obj) (vector-ref obj 3))
+(define (hostent:addr-list obj) (vector-ref obj 4))
+
+(define (netent:name obj) (vector-ref obj 0))
+(define (netent:aliases obj) (vector-ref obj 1))
+(define (netent:addrtype obj) (vector-ref obj 2))
+(define (netent:net obj) (vector-ref obj 3))
+
+(define (protoent:name obj) (vector-ref obj 0))
+(define (protoent:aliases obj) (vector-ref obj 1))
+(define (protoent:proto obj) (vector-ref obj 2))
+
+(define (servent:name obj) (vector-ref obj 0))
+(define (servent:aliases obj) (vector-ref obj 1))
+(define (servent:port obj) (vector-ref obj 2))
+(define (servent:proto obj) (vector-ref obj 3))
+
+(define (sockaddr:fam obj) (vector-ref obj 0))
+(define (sockaddr:path obj) (vector-ref obj 1))
+(define (sockaddr:addr obj) (vector-ref obj 1))
+(define (sockaddr:port obj) (vector-ref obj 2))
+(define (sockaddr:flowinfo obj) (vector-ref obj 3))
+(define (sockaddr:scopeid obj) (vector-ref obj 4))
+
+(define (addrinfo:flags obj) (vector-ref obj 0))
+(define (addrinfo:fam obj) (vector-ref obj 1))
+(define (addrinfo:socktype obj) (vector-ref obj 2))
+(define (addrinfo:protocol obj) (vector-ref obj 3))
+(define (addrinfo:addr obj) (vector-ref obj 4))
+(define (addrinfo:canonname obj) (vector-ref obj 5))
+;;;; Copyright (C) 2000, 2001, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;;; The null environment - only syntactic bindings
+
+(define-module (ice-9 null)
+ \:re-export-syntax (define quote lambda if set!
+
+ cond case and or
+
+ let let* letrec
+
+ begin do
+
+ delay
+
+ quasiquote
+
+ define-syntax
+ let-syntax letrec-syntax))
+;;;; Occam-like channels
+
+;;; Copyright (C) 2003, 2006 Free Software Foundation, Inc.
+;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (ice-9 occam-channel)
+ #\use-module (oop goops)
+ #\use-module (ice-9 threads)
+ #\export-syntax (alt
+ ;; macro use:
+ oc:lock oc:unlock oc:consequence
+ oc:immediate-dispatch oc:late-dispatch oc:first-channel
+ oc:set-handshake-channel oc:unset-handshake-channel)
+ #\export (make-channel
+ ?
+ !
+ make-timer
+ ;; macro use:
+ handshake-channel mutex
+ sender-waiting?
+ immediate-receive late-receive
+ )
+ )
+
+(define no-data '(no-data))
+(define receiver-waiting '(receiver-waiting))
+
+(define-class <channel> ())
+
+(define-class <data-channel> (<channel>)
+ (handshake-channel #\accessor handshake-channel)
+ (data #\accessor data #\init-value no-data)
+ (cv #\accessor cv #\init-form (make-condition-variable))
+ (mutex #\accessor mutex #\init-form (make-mutex)))
+
+(define-method (initialize (ch <data-channel>) initargs)
+ (next-method)
+ (set! (handshake-channel ch) ch))
+
+(define-method (make-channel)
+ (make <data-channel>))
+
+(define-method (sender-waiting? (ch <data-channel>))
+ (not (eq? (data ch) no-data)))
+
+(define-method (receiver-waiting? (ch <data-channel>))
+ (eq? (data ch) receiver-waiting))
+
+(define-method (immediate-receive (ch <data-channel>))
+ (signal-condition-variable (cv ch))
+ (let ((res (data ch)))
+ (set! (data ch) no-data)
+ res))
+
+(define-method (late-receive (ch <data-channel>))
+ (let ((res (data ch)))
+ (set! (data ch) no-data)
+ res))
+
+(define-method (? (ch <data-channel>))
+ (lock-mutex (mutex ch))
+ (let ((res (cond ((receiver-waiting? ch)
+ (unlock-mutex (mutex ch))
+ (scm-error 'misc-error '?
+ "another process is already receiving on ~A"
+ (list ch) #f))
+ ((sender-waiting? ch)
+ (immediate-receive ch))
+ (else
+ (set! (data ch) receiver-waiting)
+ (wait-condition-variable (cv ch) (mutex ch))
+ (late-receive ch)))))
+ (unlock-mutex (mutex ch))
+ res))
+
+(define-method (! (ch <data-channel>))
+ (! ch *unspecified*))
+
+(define-method (! (ch <data-channel>) (x <top>))
+ (lock-mutex (mutex (handshake-channel ch)))
+ (cond ((receiver-waiting? ch)
+ (set! (data ch) x)
+ (signal-condition-variable (cv (handshake-channel ch))))
+ ((sender-waiting? ch)
+ (unlock-mutex (mutex (handshake-channel ch)))
+ (scm-error 'misc-error '! "another process is already sending on ~A"
+ (list ch) #f))
+ (else
+ (set! (data ch) x)
+ (wait-condition-variable (cv ch) (mutex ch))))
+ (unlock-mutex (mutex (handshake-channel ch))))
+
+;;; Add protocols?
+
+(define-class <port-channel> (<channel>)
+ (port #\accessor port #\init-keyword #\port))
+
+(define-method (make-channel (port <port>))
+ (make <port-channel> #\port port))
+
+(define-method (? (ch <port-channel>))
+ (read (port ch)))
+
+(define-method (! (ch <port-channel>))
+ (write (port ch)))
+
+(define-class <timer-channel> (<channel>))
+
+(define the-timer (make <timer-channel>))
+
+(define timer-cv (make-condition-variable))
+(define timer-mutex (make-mutex))
+
+(define (make-timer)
+ the-timer)
+
+(define (timeofday->us t)
+ (+ (* 1000000 (car t)) (cdr t)))
+
+(define (us->timeofday n)
+ (cons (quotient n 1000000)
+ (remainder n 1000000)))
+
+(define-method (? (ch <timer-channel>))
+ (timeofday->us (gettimeofday)))
+
+(define-method (? (ch <timer-channel>) (t <integer>))
+ (lock-mutex timer-mutex)
+ (wait-condition-variable timer-cv timer-mutex (us->timeofday t))
+ (unlock-mutex timer-mutex))
+
+;;; (alt CLAUSE ...)
+;;;
+;;; CLAUSE ::= ((? CH) FORM ...)
+;;; | (EXP (? CH) FORM ...)
+;;; | (EXP FORM ...)
+;;;
+;;; where FORM ... can be => (lambda (x) ...)
+;;;
+;;; *fixme* Currently only handles <data-channel>:s
+;;;
+
+(define-syntax oc:lock
+ (syntax-rules (?)
+ ((_ ((? ch) form ...)) (lock-mutex (mutex ch)))
+ ((_ (exp (? ch) form ...)) (lock-mutex (mutex ch)))
+ ((_ (exp form ...)) #f)))
+
+(define-syntax oc:unlock
+ (syntax-rules (?)
+ ((_ ((? ch) form ...)) (unlock-mutex (mutex ch)))
+ ((_ (exp (? ch) form ...)) (unlock-mutex (mutex ch)))
+ ((_ (exp form ...)) #f)))
+
+(define-syntax oc:consequence
+ (syntax-rules (=>)
+ ((_ data) data)
+ ((_ data => (lambda (x) e1 e2 ...))
+ (let ((x data)) e1 e2 ...))
+ ((_ data e1 e2 ...)
+ (begin data e1 e2 ...))))
+
+(define-syntax oc:immediate-dispatch
+ (syntax-rules (?)
+ ((_ ((? ch) e1 ...))
+ ((sender-waiting? ch)
+ (oc:consequence (immediate-receive ch) e1 ...)))
+ ((_ (exp (? ch) e1 ...))
+ ((and exp (sender-waiting? ch))
+ (oc:consequence (immediate-receive ch) e1 ...)))
+ ((_ (exp e1 ...))
+ (exp e1 ...))))
+
+(define-syntax oc:late-dispatch
+ (syntax-rules (?)
+ ((_ ((? ch) e1 ...))
+ ((sender-waiting? ch)
+ (oc:consequence (late-receive ch) e1 ...)))
+ ((_ (exp (? ch) e1 ...))
+ ((and exp (sender-waiting? ch))
+ (oc:consequence (late-receive ch) e1 ...)))
+ ((_ (exp e1 ...))
+ (#f))))
+
+(define-syntax oc:first-channel
+ (syntax-rules (?)
+ ((_ ((? ch) e1 ...) c2 ...)
+ ch)
+ ((_ (exp (? ch) e1 ...) c2 ...)
+ ch)
+ ((_ c1 c2 ...)
+ (first-channel c2 ...))))
+
+(define-syntax oc:set-handshake-channel
+ (syntax-rules (?)
+ ((_ ((? ch) e1 ...) handshake)
+ (set! (handshake-channel ch) handshake))
+ ((_ (exp (? ch) e1 ...) handshake)
+ (and exp (set! (handshake-channel ch) handshake)))
+ ((_ (exp e1 ...) handshake)
+ #f)))
+
+(define-syntax oc:unset-handshake-channel
+ (syntax-rules (?)
+ ((_ ((? ch) e1 ...))
+ (set! (handshake-channel ch) ch))
+ ((_ (exp (? ch) e1 ...))
+ (and exp (set! (handshake-channel ch) ch)))
+ ((_ (exp e1 ...))
+ #f)))
+
+(define-syntax alt
+ (lambda (x)
+ (define (else-clause? x)
+ (syntax-case x (else)
+ ((_) #f)
+ ((_ (else e1 e2 ...)) #t)
+ ((_ c1 c2 ...) (else-clause? (syntax (_ c2 ...))))))
+
+ (syntax-case x (else)
+ ((_ c1 c2 ...)
+ (else-clause? x)
+ (syntax (begin
+ (oc:lock c1)
+ (oc:lock c2) ...
+ (let ((res (cond (oc:immediate-dispatch c1)
+ (oc:immediate-dispatch c2) ...)))
+ (oc:unlock c1)
+ (oc:unlock c2) ...
+ res))))
+ ((_ c1 c2 ...)
+ (syntax (begin
+ (oc:lock c1)
+ (oc:lock c2) ...
+ (let ((res (cond (oc:immediate-dispatch c1)
+ (oc:immediate-dispatch c2) ...
+ (else (let ((ch (oc:first-channel c1 c2 ...)))
+ (oc:set-handshake-channel c1 ch)
+ (oc:set-handshake-channel c2 ch) ...
+ (wait-condition-variable (cv ch)
+ (mutex ch))
+ (oc:unset-handshake-channel c1)
+ (oc:unset-handshake-channel c2) ...
+ (cond (oc:late-dispatch c1)
+ (oc:late-dispatch c2) ...))))))
+ (oc:unlock c1)
+ (oc:unlock c2) ...
+ res)))))))
+;;;; optargs.scm -- support for optional arguments
+;;;;
+;;;; Copyright (C) 1997, 1998, 1999, 2001, 2002, 2004, 2006, 2009, 2010, 2011 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+;;;; Contributed by Maciej Stachowiak <mstachow@alum.mit.edu>
+
+
+
+;;; Commentary:
+
+;;; {Optional Arguments}
+;;;
+;;; The C interface for creating Guile procedures has a very handy
+;;; "optional argument" feature. This module attempts to provide
+;;; similar functionality for procedures defined in Scheme with
+;;; a convenient and attractive syntax.
+;;;
+;;; exported macros are:
+;;; let-optional
+;;; let-optional*
+;;; let-keywords
+;;; let-keywords*
+;;; lambda*
+;;; define*
+;;; define*-public
+;;; defmacro*
+;;; defmacro*-public
+;;;
+;;;
+;;; Summary of the lambda* extended parameter list syntax (brackets
+;;; are used to indicate grouping only):
+;;;
+;;; ext-param-list ::= [identifier]* [#\optional [ext-var-decl]+]?
+;;; [#\key [ext-var-decl]+ [#\allow-other-keys]?]?
+;;; [[#\rest identifier]|[. identifier]]?
+;;;
+;;; ext-var-decl ::= identifier | ( identifier expression )
+;;;
+;;; The characters `*', `+' and `?' are not to be taken literally; they
+;;; mean respectively, zero or more occurences, one or more occurences,
+;;; and one or zero occurences.
+;;;
+
+;;; Code:
+
+(define-module (ice-9 optargs)
+ #\use-module (system base pmatch)
+ #\re-export (lambda* define*)
+ #\export (let-optional
+ let-optional*
+ let-keywords
+ let-keywords*
+ define*-public
+ defmacro*
+ defmacro*-public))
+
+;; let-optional rest-arg (binding ...) . body
+;; let-optional* rest-arg (binding ...) . body
+;; macros used to bind optional arguments
+;;
+;; These two macros give you an optional argument interface that is
+;; very "Schemey" and introduces no fancy syntax. They are compatible
+;; with the scsh macros of the same name, but are slightly
+;; extended. Each of binding may be of one of the forms <var> or
+;; (<var> <default-value>). rest-arg should be the rest-argument of
+;; the procedures these are used from. The items in rest-arg are
+;; sequentially bound to the variable namess are given. When rest-arg
+;; runs out, the remaining vars are bound either to the default values
+;; or to `#f' if no default value was specified. rest-arg remains
+;; bound to whatever may have been left of rest-arg.
+;;
+
+(define (vars&inits bindings)
+ (let lp ((bindings bindings) (vars '()) (inits '()))
+ (syntax-case bindings ()
+ (()
+ (values (reverse vars) (reverse inits)))
+ (((v init) . rest) (identifier? #'v)
+ (lp #'rest (cons #'v vars) (cons #'init inits)))
+ ((v . rest) (identifier? #'v)
+ (lp #'rest (cons #'v vars) (cons #'#f inits))))))
+
+(define-syntax let-optional
+ (lambda (x)
+ (syntax-case x ()
+ ((_ rest-arg (binding ...) b0 b1 ...) (identifier? #'rest-arg)
+ (call-with-values (lambda () (vars&inits #'(binding ...)))
+ (lambda (vars inits)
+ (with-syntax ((n (length vars))
+ (n+1 (1+ (length vars)))
+ (vars (append vars (list #'rest-arg)))
+ ((t ...) (generate-temporaries vars))
+ ((i ...) inits))
+ #'(let ((t (lambda vars i))
+ ...)
+ (apply (lambda vars b0 b1 ...)
+ (or (parse-lambda-case '(0 n n n+1 #f '())
+ (list t ...)
+ rest-arg)
+ (error "sth" rest-arg)))))))))))
+
+(define-syntax let-optional*
+ (lambda (x)
+ (syntax-case x ()
+ ((_ rest-arg (binding ...) b0 b1 ...) (identifier? #'rest-arg)
+ (call-with-values (lambda () (vars&inits #'(binding ...)))
+ (lambda (vars inits)
+ (with-syntax ((n (length vars))
+ (n+1 (1+ (length vars)))
+ (vars (append vars (list #'rest-arg)))
+ ((i ...) inits))
+ #'(apply (lambda vars b0 b1 ...)
+ (or (parse-lambda-case '(0 n n n+1 #f '())
+ (list (lambda vars i) ...)
+ rest-arg)
+ (error "sth" rest-arg))))))))))
+
+
+;; let-keywords rest-arg allow-other-keys? (binding ...) . body
+;; let-keywords* rest-arg allow-other-keys? (binding ...) . body
+;; macros used to bind keyword arguments
+;;
+;; These macros pick out keyword arguments from rest-arg, but do not
+;; modify it. This is consistent at least with Common Lisp, which
+;; duplicates keyword args in the rest arg. More explanation of what
+;; keyword arguments in a lambda list look like can be found below in
+;; the documentation for lambda*. Bindings can have the same form as
+;; for let-optional. If allow-other-keys? is false, an error will be
+;; thrown if anything that looks like a keyword argument but does not
+;; match a known keyword parameter will result in an error.
+;;
+
+
+(define-syntax let-keywords
+ (lambda (x)
+ (syntax-case x ()
+ ((_ rest-arg aok (binding ...) b0 b1 ...) (identifier? #'rest-arg)
+ (call-with-values (lambda () (vars&inits #'(binding ...)))
+ (lambda (vars inits)
+ (with-syntax ((n (length vars))
+ (vars vars)
+ (ivars (generate-temporaries vars))
+ ((kw ...) (map symbol->keyword
+ (map syntax->datum vars)))
+ ((idx ...) (iota (length vars)))
+ ((t ...) (generate-temporaries vars))
+ ((i ...) inits))
+ #'(let ((t (lambda ivars i))
+ ...)
+ (apply (lambda vars b0 b1 ...)
+ (or (parse-lambda-case '(0 0 #f n aok ((kw . idx) ...))
+ (list t ...)
+ rest-arg)
+ (error "sth" rest-arg))))))))
+ ((_ rest-arg aok (binding ...) b0 b1 ...)
+ #'(let ((r rest-arg))
+ (let-keywords r aok (binding ...) b0 b1 ...))))))
+
+(define-syntax let-keywords*
+ (lambda (x)
+ (syntax-case x ()
+ ((_ rest-arg aok (binding ...) b0 b1 ...) (identifier? #'rest-arg)
+ (call-with-values (lambda () (vars&inits #'(binding ...)))
+ (lambda (vars inits)
+ (with-syntax ((n (length vars))
+ (vars vars)
+ ((kw ...) (map symbol->keyword
+ (map syntax->datum vars)))
+ ((idx ...) (iota (length vars)))
+ ((i ...) inits))
+ #'(apply (lambda vars b0 b1 ...)
+ (or (parse-lambda-case '(0 0 #f n aok ((kw . idx) ...))
+ (list (lambda vars i) ...)
+ rest-arg)
+ (error "sth" rest-arg)))))))
+ ((_ rest-arg aok (binding ...) b0 b1 ...)
+ #'(let ((r rest-arg))
+ (let-keywords* r aok (binding ...) b0 b1 ...))))))
+
+;; lambda* args . body
+;; lambda extended for optional and keyword arguments
+;;
+;; lambda* creates a procedure that takes optional arguments. These
+;; are specified by putting them inside brackets at the end of the
+;; paramater list, but before any dotted rest argument. For example,
+;; (lambda* (a b #\optional c d . e) '())
+;; creates a procedure with fixed arguments a and b, optional arguments c
+;; and d, and rest argument e. If the optional arguments are omitted
+;; in a call, the variables for them are bound to `#f'.
+;;
+;; lambda* can also take keyword arguments. For example, a procedure
+;; defined like this:
+;; (lambda* (#\key xyzzy larch) '())
+;; can be called with any of the argument lists (#\xyzzy 11)
+;; (#\larch 13) (#\larch 42 #\xyzzy 19) (). Whichever arguments
+;; are given as keywords are bound to values.
+;;
+;; Optional and keyword arguments can also be given default values
+;; which they take on when they are not present in a call, by giving a
+;; two-item list in place of an optional argument, for example in:
+;; (lambda* (foo #\optional (bar 42) #\key (baz 73)) (list foo bar baz))
+;; foo is a fixed argument, bar is an optional argument with default
+;; value 42, and baz is a keyword argument with default value 73.
+;; Default value expressions are not evaluated unless they are needed
+;; and until the procedure is called.
+;;
+;; lambda* now supports two more special parameter list keywords.
+;;
+;; lambda*-defined procedures now throw an error by default if a
+;; keyword other than one of those specified is found in the actual
+;; passed arguments. However, specifying #\allow-other-keys
+;; immediately after the keyword argument declarations restores the
+;; previous behavior of ignoring unknown keywords. lambda* also now
+;; guarantees that if the same keyword is passed more than once, the
+;; last one passed is the one that takes effect. For example,
+;; ((lambda* (#\key (heads 0) (tails 0)) (display (list heads tails)))
+;; #\heads 37 #\tails 42 #\heads 99)
+;; would result in (99 47) being displayed.
+;;
+;; #\rest is also now provided as a synonym for the dotted syntax rest
+;; argument. The argument lists (a . b) and (a #\rest b) are equivalent in
+;; all respects to lambda*. This is provided for more similarity to DSSSL,
+;; MIT-Scheme and Kawa among others, as well as for refugees from other
+;; Lisp dialects.
+
+
+;; define* args . body
+;; define*-public args . body
+;; define and define-public extended for optional and keyword arguments
+;;
+;; define* and define*-public support optional arguments with
+;; a similar syntax to lambda*. Some examples:
+;; (define* (x y #\optional a (z 3) #\key w . u) (display (list y z u)))
+;; defines a procedure x with a fixed argument y, an optional agument
+;; a, another optional argument z with default value 3, a keyword argument w,
+;; and a rest argument u.
+;;
+;; Of course, define*[-public] also supports #\rest and #\allow-other-keys
+;; in the same way as lambda*.
+
+(define-syntax define*-public
+ (lambda (x)
+ (syntax-case x ()
+ ((_ (id . args) b0 b1 ...)
+ #'(define-public id (lambda* args b0 b1 ...)))
+ ((_ id val) (identifier? #'id)
+ #'(define-public id val)))))
+
+
+;; defmacro* name args . body
+;; defmacro*-public args . body
+;; defmacro and defmacro-public extended for optional and keyword arguments
+;;
+;; These are just like defmacro and defmacro-public except that they
+;; take lambda*-style extended paramter lists, where #\optional,
+;; #\key, #\allow-other-keys and #\rest are allowed with the usual
+;; semantics. Here is an example of a macro with an optional argument:
+;; (defmacro* transmogrify (a #\optional b)
+
+(define-syntax defmacro*
+ (lambda (x)
+ (syntax-case x ()
+ ((_ id args doc b0 b1 ...) (string? (syntax->datum #'doc))
+ #'(define-macro id doc (lambda* args b0 b1 ...)))
+ ((_ id args b0 b1 ...)
+ #'(define-macro id #f (lambda* args b0 b1 ...))))))
+(define-syntax-rule (defmacro*-public id args b0 b1 ...)
+ (begin
+ (defmacro* id args b0 b1 ...)
+ (export-syntax id)))
+
+;;; Support for optional & keyword args with the interpreter.
+(define *uninitialized* (list 'uninitialized))
+(define (parse-lambda-case spec inits args)
+ (pmatch spec
+ ((,nreq ,nopt ,rest-idx ,nargs ,allow-other-keys? ,kw-indices)
+ (define (req args prev tail n)
+ (cond
+ ((zero? n)
+ (if prev (set-cdr! prev '()))
+ (let ((slots-tail (make-list (- nargs nreq) *uninitialized*)))
+ (opt (if prev (append! args slots-tail) slots-tail)
+ slots-tail tail nopt inits)))
+ ((null? tail)
+ #f) ;; fail
+ (else
+ (req args tail (cdr tail) (1- n)))))
+ (define (opt slots slots-tail args-tail n inits)
+ (cond
+ ((zero? n)
+ (rest-or-key slots slots-tail args-tail inits rest-idx))
+ ((null? args-tail)
+ (set-car! slots-tail (apply (car inits) slots))
+ (opt slots (cdr slots-tail) '() (1- n) (cdr inits)))
+ (else
+ (set-car! slots-tail (car args-tail))
+ (opt slots (cdr slots-tail) (cdr args-tail) (1- n) (cdr inits)))))
+ (define (rest-or-key slots slots-tail args-tail inits rest-idx)
+ (cond
+ (rest-idx
+ ;; it has to be this way, vars are allocated in this order
+ (set-car! slots-tail args-tail)
+ (if (pair? kw-indices)
+ (permissive-keys slots (cdr slots-tail) args-tail inits)
+ (rest-or-key slots (cdr slots-tail) '() inits #f)))
+ ((pair? kw-indices)
+ ;; fail early here, because once we're in keyword land we throw
+ ;; errors instead of failing
+ (and (or (null? args-tail) rest-idx (keyword? (car args-tail)))
+ (key slots slots-tail args-tail inits)))
+ ((pair? args-tail)
+ #f) ;; fail
+ (else
+ slots)))
+ (define (permissive-keys slots slots-tail args-tail inits)
+ (cond
+ ((null? args-tail)
+ (if (null? inits)
+ slots
+ (begin
+ (if (eq? (car slots-tail) *uninitialized*)
+ (set-car! slots-tail (apply (car inits) slots)))
+ (permissive-keys slots (cdr slots-tail) '() (cdr inits)))))
+ ((not (keyword? (car args-tail)))
+ (permissive-keys slots slots-tail (cdr args-tail) inits))
+ ((and (keyword? (car args-tail))
+ (pair? (cdr args-tail))
+ (assq-ref kw-indices (car args-tail)))
+ => (lambda (i)
+ (list-set! slots i (cadr args-tail))
+ (permissive-keys slots slots-tail (cddr args-tail) inits)))
+ ((and (keyword? (car args-tail))
+ (pair? (cdr args-tail))
+ allow-other-keys?)
+ (permissive-keys slots slots-tail (cddr args-tail) inits))
+ (else (scm-error 'keyword-argument-error #f "Unrecognized keyword"
+ '() args-tail))))
+ (define (key slots slots-tail args-tail inits)
+ (cond
+ ((null? args-tail)
+ (if (null? inits)
+ slots
+ (begin
+ (if (eq? (car slots-tail) *uninitialized*)
+ (set-car! slots-tail (apply (car inits) slots)))
+ (key slots (cdr slots-tail) '() (cdr inits)))))
+ ((not (keyword? (car args-tail)))
+ (if rest-idx
+ ;; no error checking, everything goes to the rest..
+ (key slots slots-tail '() inits)
+ (scm-error 'keyword-argument-error #f "Invalid keyword"
+ '() args-tail)))
+ ((and (keyword? (car args-tail))
+ (pair? (cdr args-tail))
+ (assq-ref kw-indices (car args-tail)))
+ => (lambda (i)
+ (list-set! slots i (cadr args-tail))
+ (key slots slots-tail (cddr args-tail) inits)))
+ ((and (keyword? (car args-tail))
+ (pair? (cdr args-tail))
+ allow-other-keys?)
+ (key slots slots-tail (cddr args-tail) inits))
+ (else (scm-error 'keyword-argument-error #f "Unrecognized keyword"
+ '() args-tail))))
+ (let ((args (list-copy args)))
+ (req args #f args nreq)))
+ (else (error "unexpected spec" spec))))
+;;; installed-scm-file
+
+;;;; Copyright (C) 1996, 2001, 2006, 2011 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (ice-9 poe)
+ \:use-module (ice-9 hcons)
+ \:export (pure-funcq perfect-funcq))
+
+
+
+
+;;; {Pure Functions}
+;;;
+;;; A pure function (of some sort) is characterized by two equality
+;;; relations: one on argument lists and one on return values.
+;;; A pure function is one that when applied to equal arguments lists
+;;; yields equal results.
+;;;
+;;; If the equality relationship on return values can be eq?, it may make
+;;; sense to cache values returned by the function. Choosing the right
+;;; equality relation on arguments is tricky.
+;;;
+
+
+;;; {pure-funcq}
+;;;
+;;; The simplest case of pure functions are those in which results
+;;; are only certainly eq? if all of the arguments are. These functions
+;;; are called "pure-funcq", for obvious reasons.
+;;;
+
+
+(define funcq-memo (make-weak-key-hash-table 523)) ; !!! randomly selected values
+(define funcq-buffer (make-gc-buffer 256))
+
+(define (funcq-hash arg-list n)
+ (let ((it (let loop ((x 0)
+ (arg-list arg-list))
+ (if (null? arg-list)
+ (modulo x n)
+ (loop (logior x (hashq (car arg-list) 4194303))
+ (cdr arg-list))))))
+ it))
+
+;; return true if lists X and Y are the same length and each element is `eq?'
+(define (eq?-list x y)
+ (if (null? x)
+ (null? y)
+ (and (not (null? y))
+ (eq? (car x) (car y))
+ (eq?-list (cdr x) (cdr y)))))
+
+(define (funcq-assoc arg-list alist)
+ (if (null? alist)
+ #f
+ (if (eq?-list arg-list (caar alist))
+ (car alist)
+ (funcq-assoc arg-list (cdr alist)))))
+
+
+(define not-found (list 'not-found))
+
+
+(define (pure-funcq base-func)
+ (lambda args
+ (let* ((key (cons base-func args))
+ (cached (hashx-ref funcq-hash funcq-assoc funcq-memo key not-found)))
+ (if (not (eq? cached not-found))
+ (begin
+ (funcq-buffer key)
+ cached)
+
+ (let ((val (apply base-func args)))
+ (funcq-buffer key)
+ (hashx-set! funcq-hash funcq-assoc funcq-memo key val)
+ val)))))
+
+
+
+;;; {Perfect funq}
+;;;
+;;; A pure funq may sometimes forget its past but a perfect
+;;; funcq never does.
+;;;
+
+(define (perfect-funcq size base-func)
+ (define funcq-memo (make-hash-table size))
+
+ (lambda args
+ (let* ((key (cons base-func args))
+ (cached (hashx-ref funcq-hash funcq-assoc funcq-memo key not-found)))
+ (if (not (eq? cached not-found))
+ (begin
+ (funcq-buffer key)
+ cached)
+
+ (let ((val (apply base-func args)))
+ (funcq-buffer key)
+ (hashx-set! funcq-hash funcq-assoc funcq-memo key val)
+ val)))))
+;; poll
+
+;;;; Copyright (C) 2010, 2011 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+(define-module (ice-9 poll)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-9 gnu)
+ #\use-module (rnrs bytevectors)
+ #\export (make-empty-poll-set
+ poll-set?
+ poll-set-nfds
+ poll-set-find-port
+ poll-set-port
+ poll-set-events
+ set-poll-set-events!
+ poll-set-revents
+ set-poll-set-revents!
+ poll-set-add!
+ poll-set-remove!
+ poll))
+
+(eval-when (expand load eval)
+ (load-extension (string-append "libguile-" (effective-version))
+ "scm_init_poll"))
+
+(if (not (= %sizeof-struct-pollfd 8))
+ (error "Unexpected struct pollfd size" %sizeof-struct-pollfd))
+
+(if (defined? 'POLLIN)
+ (export POLLIN))
+
+(if (defined? 'POLLPRI)
+ (export POLLPRI))
+
+(if (defined? 'POLLOUT)
+ (export POLLOUT))
+
+(if (defined? 'POLLRDHUP)
+ (export POLLRDHUP))
+
+(if (defined? 'POLLERR)
+ (export POLLERR))
+
+(if (defined? 'POLLHUP)
+ (export POLLHUP))
+
+(if (defined? 'POLLNVAL)
+ (export POLLNVAL))
+
+
+(define-record-type <poll-set>
+ (make-poll-set pollfds nfds ports)
+ poll-set?
+ (pollfds pset-pollfds set-pset-pollfds!)
+ (nfds poll-set-nfds set-pset-nfds!)
+ (ports pset-ports set-pset-ports!)
+ )
+
+(define-syntax-rule (pollfd-offset n)
+ (* n 8))
+
+(define* (make-empty-poll-set #\optional (pre-allocated 4))
+ (make-poll-set (make-bytevector (pollfd-offset pre-allocated) 0)
+ 0
+ (make-vector pre-allocated #f)))
+
+(define (pset-size set)
+ (vector-length (pset-ports set)))
+
+(define (ensure-pset-size! set size)
+ (let ((prev (pset-size set)))
+ (if (< prev size)
+ (let lp ((new prev))
+ (if (< new size)
+ (lp (* new 2))
+ (let ((old-pollfds (pset-pollfds set))
+ (nfds (poll-set-nfds set))
+ (old-ports (pset-ports set))
+ (new-pollfds (make-bytevector (pollfd-offset new) 0))
+ (new-ports (make-vector new #f)))
+ (bytevector-copy! old-pollfds 0 new-pollfds 0
+ (pollfd-offset nfds))
+ (vector-move-left! old-ports 0 nfds new-ports 0)
+ (set-pset-pollfds! set new-pollfds)
+ (set-pset-ports! set new-ports)))))))
+
+(define (poll-set-find-port set port)
+ (let lp ((i 0))
+ (if (< i (poll-set-nfds set))
+ (if (equal? (vector-ref (pset-ports set) i) port)
+ i
+ (lp (1+ i)))
+ #f)))
+
+(define (poll-set-port set idx)
+ (if (< idx (poll-set-nfds set))
+ (vector-ref (pset-ports set) idx)
+ (error "poll set index out of bounds" set idx)))
+
+(define (poll-set-events set idx)
+ (if (< idx (poll-set-nfds set))
+ (bytevector-u16-native-ref (pset-pollfds set) (+ (pollfd-offset idx) 4))
+ (error "poll set index out of bounds" set idx)))
+
+(define (set-poll-set-events! set idx events)
+ (if (< idx (poll-set-nfds set))
+ (bytevector-u16-native-set! (pset-pollfds set) (+ (pollfd-offset idx) 4)
+ events)
+ (error "poll set index out of bounds" set idx)))
+
+(define (poll-set-revents set idx)
+ (if (< idx (poll-set-nfds set))
+ (bytevector-u16-native-ref (pset-pollfds set) (+ (pollfd-offset idx) 6))
+ (error "poll set index out of bounds" set idx)))
+
+(define (set-poll-set-revents! set idx revents)
+ (if (< idx (poll-set-nfds set))
+ (bytevector-u16-native-set! (pset-pollfds set) (+ (pollfd-offset idx) 6)
+ revents)
+ (error "poll set index out of bounds" set idx)))
+
+(define (poll-set-add! set fd-or-port events)
+ (let* ((idx (poll-set-nfds set))
+ (off (pollfd-offset idx))
+ (fd (if (integer? fd-or-port)
+ fd-or-port
+ (port->fdes fd-or-port))))
+
+ (if (port? fd-or-port)
+ ;; As we store the port in the fdset, there is no need to
+ ;; increment the revealed count to prevent the fd from being
+ ;; closed by a gc'd port.
+ (release-port-handle fd-or-port))
+
+ (ensure-pset-size! set (1+ idx))
+ (bytevector-s32-native-set! (pset-pollfds set) off fd)
+ (bytevector-u16-native-set! (pset-pollfds set) (+ off 4) events)
+ (bytevector-u16-native-set! (pset-pollfds set) (+ off 6) 0) ; revents
+ (vector-set! (pset-ports set) idx fd-or-port)
+ (set-pset-nfds! set (1+ idx))))
+
+(define (poll-set-remove! set idx)
+ (if (not (< idx (poll-set-nfds set)))
+ (error "poll set index out of bounds" set idx))
+ (let ((nfds (poll-set-nfds set))
+ (off (pollfd-offset idx))
+ (port (vector-ref (pset-ports set) idx)))
+ (vector-move-left! (pset-ports set) (1+ idx) nfds
+ (pset-ports set) idx)
+ (vector-set! (pset-ports set) (1- nfds) #f)
+ (bytevector-copy! (pset-pollfds set) (pollfd-offset (1+ idx))
+ (pset-pollfds set) off
+ (- (pollfd-offset nfds) (pollfd-offset (1+ idx))))
+ ;; zero the struct pollfd all at once
+ (bytevector-u64-native-set! (pset-pollfds set) (pollfd-offset (1- nfds)) 0)
+ (set-pset-nfds! set (1- nfds))
+ port))
+
+(define* (poll poll-set #\optional (timeout -1))
+ (primitive-poll (pset-pollfds poll-set)
+ (poll-set-nfds poll-set)
+ (pset-ports poll-set)
+ timeout))
+;; popen emulation, for non-stdio based ports.
+
+;;;; Copyright (C) 1998, 1999, 2000, 2001, 2003, 2006, 2010, 2011, 2012,
+;;;; 2013 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+(define-module (ice-9 popen)
+ \:use-module (ice-9 threads)
+ \:use-module (srfi srfi-9)
+ \:export (port/pid-table open-pipe* open-pipe close-pipe open-input-pipe
+ open-output-pipe open-input-output-pipe))
+
+(eval-when (expand load eval)
+ (load-extension (string-append "libguile-" (effective-version))
+ "scm_init_popen"))
+
+(define-record-type <pipe-info>
+ (make-pipe-info pid)
+ pipe-info?
+ (pid pipe-info-pid set-pipe-info-pid!))
+
+(define (make-rw-port read-port write-port)
+ (make-soft-port
+ (vector
+ (lambda (c) (write-char c write-port))
+ (lambda (s) (display s write-port))
+ (lambda () (force-output write-port))
+ (lambda () (read-char read-port))
+ (lambda () (close-port read-port) (close-port write-port)))
+ "r+"))
+
+;; a guardian to ensure the cleanup is done correctly when
+;; an open pipe is gc'd or a close-port is used.
+(define pipe-guardian (make-guardian))
+
+;; a weak hash-table to store the process ids.
+;; XXX use of this table is deprecated. It is no longer used here, and
+;; is populated for backward compatibility only (since it is exported).
+(define port/pid-table (make-weak-key-hash-table 31))
+(define port/pid-table-mutex (make-mutex))
+
+(define (open-pipe* mode command . args)
+ "Executes the program @var{command} with optional arguments
+@var{args} (all strings) in a subprocess.
+A port to the process (based on pipes) is created and returned.
+@var{mode} specifies whether an input, an output or an input-output
+port to the process is created: it should be the value of
+@code{OPEN_READ}, @code{OPEN_WRITE} or @code{OPEN_BOTH}."
+ (call-with-values (lambda ()
+ (apply open-process mode command args))
+ (lambda (read-port write-port pid)
+ (let ((port (or (and read-port write-port
+ (make-rw-port read-port write-port))
+ read-port
+ write-port
+ (%make-void-port mode)))
+ (pipe-info (make-pipe-info pid)))
+
+ ;; Guard the pipe-info instead of the port, so that we can still
+ ;; call 'waitpid' even if 'close-port' is called (which clears
+ ;; the port entry).
+ (pipe-guardian pipe-info)
+ (%set-port-property! port 'popen-pipe-info pipe-info)
+
+ ;; XXX populate port/pid-table for backward compatibility.
+ (with-mutex port/pid-table-mutex
+ (hashq-set! port/pid-table port pid))
+
+ port))))
+
+(define (open-pipe command mode)
+ "Executes the shell command @var{command} (a string) in a subprocess.
+A port to the process (based on pipes) is created and returned.
+@var{mode} specifies whether an input, an output or an input-output
+port to the process is created: it should be the value of
+@code{OPEN_READ}, @code{OPEN_WRITE} or @code{OPEN_BOTH}."
+ (open-pipe* mode "/bin/sh" "-c" command))
+
+(define (fetch-pipe-info port)
+ (%port-property port 'popen-pipe-info))
+
+(define (close-process port pid)
+ (close-port port)
+ (cdr (waitpid pid)))
+
+(define (close-pipe p)
+ "Closes the pipe created by @code{open-pipe}, then waits for the process
+to terminate and returns its status value, @xref{Processes, waitpid}, for
+information on how to interpret this value."
+ (let ((pipe-info (fetch-pipe-info p)))
+ (unless pipe-info
+ (error "close-pipe: port not created by (ice-9 popen)"))
+ (let ((pid (pipe-info-pid pipe-info)))
+ (unless pid
+ (error "close-pipe: pid has already been cleared"))
+ ;; clear the pid to avoid repeated calls to 'waitpid'.
+ (set-pipe-info-pid! pipe-info #f)
+ (close-process p pid))))
+
+(define (reap-pipes)
+ (let loop ()
+ (let ((pipe-info (pipe-guardian)))
+ (when pipe-info
+ (let ((pid (pipe-info-pid pipe-info)))
+ ;; maybe 'close-pipe' was already called.
+ (when pid
+ ;; clean up without reporting errors. also avoids blocking
+ ;; the process: if the child isn't ready to be collected,
+ ;; puts it back into the guardian's live list so it can be
+ ;; tried again the next time the cleanup runs.
+ (catch 'system-error
+ (lambda ()
+ (let ((pid/status (waitpid pid WNOHANG)))
+ (if (zero? (car pid/status))
+ (pipe-guardian pipe-info) ; not ready for collection
+ (set-pipe-info-pid! pipe-info #f))))
+ (lambda args #f))))
+ (loop)))))
+
+(add-hook! after-gc-hook reap-pipes)
+
+(define (open-input-pipe command)
+ "Equivalent to @code{open-pipe} with mode @code{OPEN_READ}"
+ (open-pipe command OPEN_READ))
+
+(define (open-output-pipe command)
+ "Equivalent to @code{open-pipe} with mode @code{OPEN_WRITE}"
+ (open-pipe command OPEN_WRITE))
+
+(define (open-input-output-pipe command)
+ "Equivalent to @code{open-pipe} with mode @code{OPEN_BOTH}"
+ (open-pipe command OPEN_BOTH))
+
+;;; installed-scm-file
+
+;;;; Copyright (C) 1999, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+(eval-when (compile)
+ (set-current-module (resolve-module '(guile))))
+
+(define (stat:dev f) (vector-ref f 0))
+(define (stat:ino f) (vector-ref f 1))
+(define (stat:mode f) (vector-ref f 2))
+(define (stat:nlink f) (vector-ref f 3))
+(define (stat:uid f) (vector-ref f 4))
+(define (stat:gid f) (vector-ref f 5))
+(define (stat:rdev f) (vector-ref f 6))
+(define (stat:size f) (vector-ref f 7))
+(define (stat:atime f) (vector-ref f 8))
+(define (stat:mtime f) (vector-ref f 9))
+(define (stat:ctime f) (vector-ref f 10))
+(define (stat:blksize f) (vector-ref f 11))
+(define (stat:blocks f) (vector-ref f 12))
+(define (stat:atimensec f) (vector-ref f 15))
+(define (stat:mtimensec f) (vector-ref f 16))
+(define (stat:ctimensec f) (vector-ref f 17))
+
+;; derived from stat mode.
+(define (stat:type f) (vector-ref f 13))
+(define (stat:perms f) (vector-ref f 14))
+
+(define (passwd:name obj) (vector-ref obj 0))
+(define (passwd:passwd obj) (vector-ref obj 1))
+(define (passwd:uid obj) (vector-ref obj 2))
+(define (passwd:gid obj) (vector-ref obj 3))
+(define (passwd:gecos obj) (vector-ref obj 4))
+(define (passwd:dir obj) (vector-ref obj 5))
+(define (passwd:shell obj) (vector-ref obj 6))
+
+(define (group:name obj) (vector-ref obj 0))
+(define (group:passwd obj) (vector-ref obj 1))
+(define (group:gid obj) (vector-ref obj 2))
+(define (group:mem obj) (vector-ref obj 3))
+
+(define (utsname:sysname obj) (vector-ref obj 0))
+(define (utsname:nodename obj) (vector-ref obj 1))
+(define (utsname:release obj) (vector-ref obj 2))
+(define (utsname:version obj) (vector-ref obj 3))
+(define (utsname:machine obj) (vector-ref obj 4))
+
+(define (getpwent) (getpw))
+(define (setpwent) (setpw #t))
+(define (endpwent) (setpw))
+
+(define (getpwnam name) (getpw name))
+(define (getpwuid uid) (getpw uid))
+
+(define (getgrent) (getgr))
+(define (setgrent) (setgr #t))
+(define (endgrent) (setgr))
+
+(define (getgrnam name) (getgr name))
+(define (getgrgid id) (getgr id))
+;;;; -*- coding: utf-8; mode: scheme -*-
+;;;;
+;;;; Copyright (C) 2001, 2004, 2006, 2009, 2010,
+;;;; 2012, 2014 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+(define-module (ice-9 pretty-print)
+ #\use-module (ice-9 match)
+ #\use-module (srfi srfi-1)
+ #\use-module (rnrs bytevectors)
+ #\export (pretty-print
+ truncated-print))
+
+
+;; From SLIB.
+
+;;"genwrite.scm" generic write used by pretty-print and truncated-print.
+;; Copyright (c) 1991, Marc Feeley
+;; Author: Marc Feeley (feeley@iro.umontreal.ca)
+;; Distribution restrictions: none
+
+(define genwrite:newline-str (make-string 1 #\newline))
+
+(define (generic-write
+ obj display? width max-expr-width per-line-prefix output)
+
+ (define (read-macro? l)
+ (define (length1? l) (and (pair? l) (null? (cdr l))))
+ (let ((head (car l)) (tail (cdr l)))
+ (case head
+ ((quote quasiquote unquote unquote-splicing) (length1? tail))
+ (else #f))))
+
+ (define (read-macro-body l)
+ (cadr l))
+
+ (define (read-macro-prefix l)
+ (let ((head (car l)))
+ (case head
+ ((quote) "'")
+ ((quasiquote) "`")
+ ((unquote) ",")
+ ((unquote-splicing) ",@"))))
+
+ (define (out str col)
+ (and col (output str) (+ col (string-length str))))
+
+ (define (wr obj col)
+ (let loop ((obj obj)
+ (col col))
+ (match obj
+ (((or 'quote 'quasiquote 'unquote 'unquote-splicing) body)
+ (wr body (out (read-macro-prefix obj) col)))
+ ((head . (rest ...))
+ ;; A proper list: do our own list printing so as to catch read
+ ;; macros that appear in the middle of the list.
+ (let ((col (loop head (out "(" col))))
+ (out ")"
+ (fold (lambda (i col)
+ (loop i (out " " col)))
+ col rest))))
+ (_
+ (out (object->string obj (if display? display write)) col)))))
+
+ (define (pp obj col)
+
+ (define (spaces n col)
+ (if (> n 0)
+ (if (> n 7)
+ (spaces (- n 8) (out " " col))
+ (out (substring " " 0 n) col))
+ col))
+
+ (define (indent to col)
+ (and col
+ (if (< to col)
+ (and (out genwrite:newline-str col)
+ (out per-line-prefix 0)
+ (spaces to 0))
+ (spaces (- to col) col))))
+
+ (define (pr obj col extra pp-pair)
+ (if (or (pair? obj) (vector? obj)) ; may have to split on multiple lines
+ (let ((result '())
+ (left (min (+ (- (- width col) extra) 1) max-expr-width)))
+ (generic-write obj display? #f max-expr-width ""
+ (lambda (str)
+ (set! result (cons str result))
+ (set! left (- left (string-length str)))
+ (> left 0)))
+ (if (> left 0) ; all can be printed on one line
+ (out (reverse-string-append result) col)
+ (if (pair? obj)
+ (pp-pair obj col extra)
+ (pp-list (vector->list obj) (out "#" col) extra pp-expr))))
+ (wr obj col)))
+
+ (define (pp-expr expr col extra)
+ (if (read-macro? expr)
+ (pr (read-macro-body expr)
+ (out (read-macro-prefix expr) col)
+ extra
+ pp-expr)
+ (let ((head (car expr)))
+ (if (symbol? head)
+ (let ((proc (style head)))
+ (if proc
+ (proc expr col extra)
+ (if (> (string-length (symbol->string head))
+ max-call-head-width)
+ (pp-general expr col extra #f #f #f pp-expr)
+ (pp-call expr col extra pp-expr))))
+ (pp-list expr col extra pp-expr)))))
+
+ ; (head item1
+ ; item2
+ ; item3)
+ (define (pp-call expr col extra pp-item)
+ (let ((col* (wr (car expr) (out "(" col))))
+ (and col
+ (pp-down (cdr expr) col* (+ col* 1) extra pp-item))))
+
+ ; (item1
+ ; item2
+ ; item3)
+ (define (pp-list l col extra pp-item)
+ (let ((col (out "(" col)))
+ (pp-down l col col extra pp-item)))
+
+ (define (pp-down l col1 col2 extra pp-item)
+ (let loop ((l l) (col col1))
+ (and col
+ (cond ((pair? l)
+ (let ((rest (cdr l)))
+ (let ((extra (if (null? rest) (+ extra 1) 0)))
+ (loop rest
+ (pr (car l) (indent col2 col) extra pp-item)))))
+ ((null? l)
+ (out ")" col))
+ (else
+ (out ")"
+ (pr l
+ (indent col2 (out "." (indent col2 col)))
+ (+ extra 1)
+ pp-item)))))))
+
+ (define (pp-general expr col extra named? pp-1 pp-2 pp-3)
+
+ (define (tail1 rest col1 col2 col3)
+ (if (and pp-1 (pair? rest))
+ (let* ((val1 (car rest))
+ (rest (cdr rest))
+ (extra (if (null? rest) (+ extra 1) 0)))
+ (tail2 rest col1 (pr val1 (indent col3 col2) extra pp-1) col3))
+ (tail2 rest col1 col2 col3)))
+
+ (define (tail2 rest col1 col2 col3)
+ (if (and pp-2 (pair? rest))
+ (let* ((val1 (car rest))
+ (rest (cdr rest))
+ (extra (if (null? rest) (+ extra 1) 0)))
+ (tail3 rest col1 (pr val1 (indent col3 col2) extra pp-2)))
+ (tail3 rest col1 col2)))
+
+ (define (tail3 rest col1 col2)
+ (pp-down rest col2 col1 extra pp-3))
+
+ (let* ((head (car expr))
+ (rest (cdr expr))
+ (col* (wr head (out "(" col))))
+ (if (and named? (pair? rest))
+ (let* ((name (car rest))
+ (rest (cdr rest))
+ (col** (wr name (out " " col*))))
+ (tail1 rest (+ col indent-general) col** (+ col** 1)))
+ (tail1 rest (+ col indent-general) col* (+ col* 1)))))
+
+ (define (pp-expr-list l col extra)
+ (pp-list l col extra pp-expr))
+
+ (define (pp-LAMBDA expr col extra)
+ (pp-general expr col extra #f pp-expr-list #f pp-expr))
+
+ (define (pp-IF expr col extra)
+ (pp-general expr col extra #f pp-expr #f pp-expr))
+
+ (define (pp-COND expr col extra)
+ (pp-call expr col extra pp-expr-list))
+
+ (define (pp-CASE expr col extra)
+ (pp-general expr col extra #f pp-expr #f pp-expr-list))
+
+ (define (pp-AND expr col extra)
+ (pp-call expr col extra pp-expr))
+
+ (define (pp-LET expr col extra)
+ (let* ((rest (cdr expr))
+ (named? (and (pair? rest) (symbol? (car rest)))))
+ (pp-general expr col extra named? pp-expr-list #f pp-expr)))
+
+ (define (pp-BEGIN expr col extra)
+ (pp-general expr col extra #f #f #f pp-expr))
+
+ (define (pp-DO expr col extra)
+ (pp-general expr col extra #f pp-expr-list pp-expr-list pp-expr))
+
+ (define (pp-SYNTAX-CASE expr col extra)
+ (pp-general expr col extra #t pp-expr-list #f pp-expr))
+
+ ; define formatting style (change these to suit your style)
+
+ (define indent-general 2)
+
+ (define max-call-head-width 5)
+
+ (define (style head)
+ (case head
+ ((lambda lambda* let* letrec define define* define-public
+ define-syntax let-syntax letrec-syntax with-syntax)
+ pp-LAMBDA)
+ ((if set!) pp-IF)
+ ((cond) pp-COND)
+ ((case) pp-CASE)
+ ((and or) pp-AND)
+ ((let) pp-LET)
+ ((begin) pp-BEGIN)
+ ((do) pp-DO)
+ ((syntax-rules) pp-LAMBDA)
+ ((syntax-case) pp-SYNTAX-CASE)
+ (else #f)))
+
+ (pr obj col 0 pp-expr))
+
+ (out per-line-prefix 0)
+ (if width
+ (out genwrite:newline-str (pp obj 0))
+ (wr obj 0))
+ ;; Return `unspecified'
+ (if #f #f))
+
+; (reverse-string-append l) = (apply string-append (reverse l))
+
+(define (reverse-string-append l)
+
+ (define (rev-string-append l i)
+ (if (pair? l)
+ (let* ((str (car l))
+ (len (string-length str))
+ (result (rev-string-append (cdr l) (+ i len))))
+ (let loop ((j 0) (k (- (- (string-length result) i) len)))
+ (if (< j len)
+ (begin
+ (string-set! result k (string-ref str j))
+ (loop (+ j 1) (+ k 1)))
+ result)))
+ (make-string i)))
+
+ (rev-string-append l 0))
+
+(define* (pretty-print obj #\optional port*
+ #\key
+ (port (or port* (current-output-port)))
+ (width 79)
+ (max-expr-width 50)
+ (display? #f)
+ (per-line-prefix ""))
+ "Pretty-print OBJ on PORT, which is a keyword argument defaulting to
+the current output port. Formatting can be controlled by a number of
+keyword arguments: Each line in the output is preceded by the string
+PER-LINE-PREFIX, which is empty by default. The output lines will be
+at most WIDTH characters wide; the default is 79. If DISPLAY? is
+true, display rather than write representation will be used.
+
+Instead of with a keyword argument, you can also specify the output
+port directly after OBJ, like (pretty-print OBJ PORT)."
+ (generic-write obj display?
+ (- width (string-length per-line-prefix))
+ max-expr-width
+ per-line-prefix
+ (lambda (s) (display s port) #t)))
+
+
+;; `truncated-print' was written in 2009 by Andy Wingo, and is not from
+;; genwrite.scm.
+(define* (truncated-print x #\optional port*
+ #\key
+ (port (or port* (current-output-port)))
+ (width 79)
+ (display? #f)
+ (breadth-first? #f))
+ "Print @var{x}, truncating the output, if necessary, to make it fit
+into @var{width} characters. By default, @var{x} will be printed using
+@code{write}, though that behavior can be overriden via the
+@var{display?} keyword argument.
+
+The default behaviour is to print depth-first, meaning that the entire
+remaining width will be available to each sub-expression of @var{x} --
+e.g., if @var{x} is a vector, each member of @var{x}. One can attempt to
+\"ration\" the available width, trying to allocate it equally to each
+sub-expression, via the @var{breadth-first?} keyword argument."
+
+ ;; Make sure string ports are created with the right encoding.
+ (with-fluids ((%default-port-encoding (port-encoding port)))
+
+ (define ellipsis
+ ;; Choose between `HORIZONTAL ELLIPSIS' (U+2026) and three dots, depending
+ ;; on the encoding of PORT.
+ (let ((e "…"))
+ (catch 'encoding-error
+ (lambda ()
+ (with-fluids ((%default-port-conversion-strategy 'error))
+ (with-output-to-string
+ (lambda ()
+ (display e)))))
+ (lambda (key . args)
+ "..."))))
+
+ (let ((ellipsis-width (string-length ellipsis)))
+
+ (define (print-sequence x width len ref next)
+ (let lp ((x x)
+ (width width)
+ (i 0))
+ (if (> i 0)
+ (display #\space))
+ (cond
+ ((= i len)) ; catches 0-length case
+ ((and (= i (1- len)) (or (zero? i) (> width 1)))
+ (print (ref x i) (if (zero? i) width (1- width))))
+ ((<= width (+ 1 ellipsis-width))
+ (display ellipsis))
+ (else
+ (let ((str
+ (with-fluids ((%default-port-encoding (port-encoding port)))
+ (with-output-to-string
+ (lambda ()
+ (print (ref x i)
+ (if breadth-first?
+ (max 1
+ (1- (floor (/ width (- len i)))))
+ (- width (+ 1 ellipsis-width)))))))))
+ (display str)
+ (lp (next x) (- width 1 (string-length str)) (1+ i)))))))
+
+ (define (print-tree x width)
+ ;; width is >= the width of # . #, which is 5
+ (let lp ((x x)
+ (width width))
+ (cond
+ ((or (not (pair? x)) (<= width 4))
+ (display ". ")
+ (print x (- width 2)))
+ (else
+ ;; width >= 5
+ (let ((str (with-output-to-string
+ (lambda ()
+ (print (car x)
+ (if breadth-first?
+ (floor (/ (- width 3) 2))
+ (- width 4)))))))
+ (display str)
+ (display " ")
+ (lp (cdr x) (- width 1 (string-length str))))))))
+
+ (define (truncate-string str width)
+ ;; width is < (string-length str)
+ (let lp ((fixes '(("#<" . ">")
+ ("#(" . ")")
+ ("(" . ")")
+ ("\"" . "\""))))
+ (cond
+ ((null? fixes)
+ "#")
+ ((and (string-prefix? (caar fixes) str)
+ (string-suffix? (cdar fixes) str)
+ (>= (string-length str)
+ width
+ (+ (string-length (caar fixes))
+ (string-length (cdar fixes))
+ ellipsis-width)))
+ (format #f "~a~a~a~a"
+ (caar fixes)
+ (substring str (string-length (caar fixes))
+ (- width (string-length (cdar fixes))
+ ellipsis-width))
+ ellipsis
+ (cdar fixes)))
+ (else
+ (lp (cdr fixes))))))
+
+ (define (print x width)
+ (cond
+ ((<= width 0)
+ (error "expected a positive width" width))
+ ((list? x)
+ (cond
+ ((>= width (+ 2 ellipsis-width))
+ (display "(")
+ (print-sequence x (- width 2) (length x)
+ (lambda (x i) (car x)) cdr)
+ (display ")"))
+ (else
+ (display "#"))))
+ ((vector? x)
+ (cond
+ ((>= width (+ 3 ellipsis-width))
+ (display "#(")
+ (print-sequence x (- width 3) (vector-length x)
+ vector-ref identity)
+ (display ")"))
+ (else
+ (display "#"))))
+ ((bytevector? x)
+ (cond
+ ((>= width 9)
+ (format #t "#~a(" (array-type x))
+ (print-sequence x (- width 6) (array-length x)
+ array-ref identity)
+ (display ")"))
+ (else
+ (display "#"))))
+ ((pair? x)
+ (cond
+ ((>= width (+ 4 ellipsis-width))
+ (display "(")
+ (print-tree x (- width 2))
+ (display ")"))
+ (else
+ (display "#"))))
+ (else
+ (let* ((str (with-output-to-string
+ (lambda () (if display? (display x) (write x)))))
+ (len (string-length str)))
+ (display (if (<= (string-length str) width)
+ str
+ (truncate-string str width)))))))
+
+ (with-output-to-port port
+ (lambda ()
+ (print x width))))))
+(eval-when (compile) (set-current-module (resolve-module (quote (guile)))))
+(if #f #f)
+
+(letrec*
+ ((make-void
+ (lambda (src) (make-struct (vector-ref %expanded-vtables 0) 0 src)))
+ (make-const
+ (lambda (src exp)
+ (make-struct (vector-ref %expanded-vtables 1) 0 src exp)))
+ (make-primitive-ref
+ (lambda (src name)
+ (make-struct (vector-ref %expanded-vtables 2) 0 src name)))
+ (make-lexical-ref
+ (lambda (src name gensym)
+ (make-struct (vector-ref %expanded-vtables 3) 0 src name gensym)))
+ (make-lexical-set
+ (lambda (src name gensym exp)
+ (make-struct (vector-ref %expanded-vtables 4) 0 src name gensym exp)))
+ (make-module-ref
+ (lambda (src mod name public?)
+ (make-struct (vector-ref %expanded-vtables 5) 0 src mod name public?)))
+ (make-module-set
+ (lambda (src mod name public? exp)
+ (make-struct
+ (vector-ref %expanded-vtables 6)
+ 0
+ src
+ mod
+ name
+ public?
+ exp)))
+ (make-toplevel-ref
+ (lambda (src name)
+ (make-struct (vector-ref %expanded-vtables 7) 0 src name)))
+ (make-toplevel-set
+ (lambda (src name exp)
+ (make-struct (vector-ref %expanded-vtables 8) 0 src name exp)))
+ (make-toplevel-define
+ (lambda (src name exp)
+ (make-struct (vector-ref %expanded-vtables 9) 0 src name exp)))
+ (make-conditional
+ (lambda (src test consequent alternate)
+ (make-struct
+ (vector-ref %expanded-vtables 10)
+ 0
+ src
+ test
+ consequent
+ alternate)))
+ (make-application
+ (lambda (src proc args)
+ (make-struct (vector-ref %expanded-vtables 11) 0 src proc args)))
+ (make-sequence
+ (lambda (src exps)
+ (make-struct (vector-ref %expanded-vtables 12) 0 src exps)))
+ (make-lambda
+ (lambda (src meta body)
+ (make-struct (vector-ref %expanded-vtables 13) 0 src meta body)))
+ (make-lambda-case
+ (lambda (src req opt rest kw inits gensyms body alternate)
+ (make-struct
+ (vector-ref %expanded-vtables 14)
+ 0
+ src
+ req
+ opt
+ rest
+ kw
+ inits
+ gensyms
+ body
+ alternate)))
+ (make-let
+ (lambda (src names gensyms vals body)
+ (make-struct
+ (vector-ref %expanded-vtables 15)
+ 0
+ src
+ names
+ gensyms
+ vals
+ body)))
+ (make-letrec
+ (lambda (src in-order? names gensyms vals body)
+ (make-struct
+ (vector-ref %expanded-vtables 16)
+ 0
+ src
+ in-order?
+ names
+ gensyms
+ vals
+ body)))
+ (make-dynlet
+ (lambda (src fluids vals body)
+ (make-struct
+ (vector-ref %expanded-vtables 17)
+ 0
+ src
+ fluids
+ vals
+ body)))
+ (lambda?
+ (lambda (x)
+ (and (struct? x)
+ (eq? (struct-vtable x) (vector-ref %expanded-vtables 13)))))
+ (lambda-meta (lambda (x) (struct-ref x 1)))
+ (set-lambda-meta! (lambda (x v) (struct-set! x 1 v)))
+ (top-level-eval-hook (lambda (x mod) (primitive-eval x)))
+ (local-eval-hook (lambda (x mod) (primitive-eval x)))
+ (session-id
+ (let ((v (module-variable (current-module) 'syntax-session-id)))
+ (lambda () ((variable-ref v)))))
+ (put-global-definition-hook
+ (lambda (symbol type val)
+ (module-define!
+ (current-module)
+ symbol
+ (make-syntax-transformer symbol type val))))
+ (get-global-definition-hook
+ (lambda (symbol module)
+ (if (and (not module) (current-module))
+ (warn "module system is booted, we should have a module" symbol))
+ (let ((v (module-variable
+ (if module (resolve-module (cdr module)) (current-module))
+ symbol)))
+ (and v
+ (variable-bound? v)
+ (let ((val (variable-ref v)))
+ (and (macro? val)
+ (macro-type val)
+ (cons (macro-type val) (macro-binding val))))))))
+ (decorate-source
+ (lambda (e s)
+ (if (and s (supports-source-properties? e))
+ (set-source-properties! e s))
+ e))
+ (maybe-name-value!
+ (lambda (name val)
+ (if (lambda? val)
+ (let ((meta (lambda-meta val)))
+ (if (not (assq 'name meta))
+ (set-lambda-meta! val (acons 'name name meta)))))))
+ (build-void (lambda (source) (make-void source)))
+ (build-application
+ (lambda (source fun-exp arg-exps)
+ (make-application source fun-exp arg-exps)))
+ (build-conditional
+ (lambda (source test-exp then-exp else-exp)
+ (make-conditional source test-exp then-exp else-exp)))
+ (build-dynlet
+ (lambda (source fluids vals body)
+ (make-dynlet source fluids vals body)))
+ (build-lexical-reference
+ (lambda (type source name var) (make-lexical-ref source name var)))
+ (build-lexical-assignment
+ (lambda (source name var exp)
+ (maybe-name-value! name exp)
+ (make-lexical-set source name var exp)))
+ (analyze-variable
+ (lambda (mod var modref-cont bare-cont)
+ (if (not mod)
+ (bare-cont var)
+ (let ((kind (car mod)) (mod (cdr mod)))
+ (let ((key kind))
+ (cond ((memv key '(public)) (modref-cont mod var #t))
+ ((memv key '(private))
+ (if (not (equal? mod (module-name (current-module))))
+ (modref-cont mod var #f)
+ (bare-cont var)))
+ ((memv key '(bare)) (bare-cont var))
+ ((memv key '(hygiene))
+ (if (and (not (equal? mod (module-name (current-module))))
+ (module-variable (resolve-module mod) var))
+ (modref-cont mod var #f)
+ (bare-cont var)))
+ (else (syntax-violation #f "bad module kind" var mod))))))))
+ (build-global-reference
+ (lambda (source var mod)
+ (analyze-variable
+ mod
+ var
+ (lambda (mod var public?) (make-module-ref source mod var public?))
+ (lambda (var) (make-toplevel-ref source var)))))
+ (build-global-assignment
+ (lambda (source var exp mod)
+ (maybe-name-value! var exp)
+ (analyze-variable
+ mod
+ var
+ (lambda (mod var public?)
+ (make-module-set source mod var public? exp))
+ (lambda (var) (make-toplevel-set source var exp)))))
+ (build-global-definition
+ (lambda (source var exp)
+ (maybe-name-value! var exp)
+ (make-toplevel-define source var exp)))
+ (build-simple-lambda
+ (lambda (src req rest vars meta exp)
+ (make-lambda
+ src
+ meta
+ (make-lambda-case src req #f rest #f '() vars exp #f))))
+ (build-case-lambda
+ (lambda (src meta body) (make-lambda src meta body)))
+ (build-lambda-case
+ (lambda (src req opt rest kw inits vars body else-case)
+ (make-lambda-case src req opt rest kw inits vars body else-case)))
+ (build-primref
+ (lambda (src name)
+ (if (equal? (module-name (current-module)) '(guile))
+ (make-toplevel-ref src name)
+ (make-module-ref src '(guile) name #f))))
+ (build-data (lambda (src exp) (make-const src exp)))
+ (build-sequence
+ (lambda (src exps)
+ (if (null? (cdr exps)) (car exps) (make-sequence src exps))))
+ (build-let
+ (lambda (src ids vars val-exps body-exp)
+ (for-each maybe-name-value! ids val-exps)
+ (if (null? vars) body-exp (make-let src ids vars val-exps body-exp))))
+ (build-named-let
+ (lambda (src ids vars val-exps body-exp)
+ (let ((f (car vars)) (f-name (car ids)) (vars (cdr vars)) (ids (cdr ids)))
+ (let ((proc (build-simple-lambda src ids #f vars '() body-exp)))
+ (maybe-name-value! f-name proc)
+ (for-each maybe-name-value! ids val-exps)
+ (make-letrec
+ src
+ #f
+ (list f-name)
+ (list f)
+ (list proc)
+ (build-application
+ src
+ (build-lexical-reference 'fun src f-name f)
+ val-exps))))))
+ (build-letrec
+ (lambda (src in-order? ids vars val-exps body-exp)
+ (if (null? vars)
+ body-exp
+ (begin
+ (for-each maybe-name-value! ids val-exps)
+ (make-letrec src in-order? ids vars val-exps body-exp)))))
+ (make-syntax-object
+ (lambda (expression wrap module)
+ (vector 'syntax-object expression wrap module)))
+ (syntax-object?
+ (lambda (x)
+ (and (vector? x)
+ (= (vector-length x) 4)
+ (eq? (vector-ref x 0) 'syntax-object))))
+ (syntax-object-expression (lambda (x) (vector-ref x 1)))
+ (syntax-object-wrap (lambda (x) (vector-ref x 2)))
+ (syntax-object-module (lambda (x) (vector-ref x 3)))
+ (set-syntax-object-expression!
+ (lambda (x update) (vector-set! x 1 update)))
+ (set-syntax-object-wrap!
+ (lambda (x update) (vector-set! x 2 update)))
+ (set-syntax-object-module!
+ (lambda (x update) (vector-set! x 3 update)))
+ (source-annotation
+ (lambda (x)
+ (let ((props (source-properties
+ (if (syntax-object? x) (syntax-object-expression x) x))))
+ (and (pair? props) props))))
+ (extend-env
+ (lambda (labels bindings r)
+ (if (null? labels)
+ r
+ (extend-env
+ (cdr labels)
+ (cdr bindings)
+ (cons (cons (car labels) (car bindings)) r)))))
+ (extend-var-env
+ (lambda (labels vars r)
+ (if (null? labels)
+ r
+ (extend-var-env
+ (cdr labels)
+ (cdr vars)
+ (cons (cons (car labels) (cons 'lexical (car vars))) r)))))
+ (macros-only-env
+ (lambda (r)
+ (if (null? r)
+ '()
+ (let ((a (car r)))
+ (if (memq (cadr a) '(macro ellipsis))
+ (cons a (macros-only-env (cdr r)))
+ (macros-only-env (cdr r)))))))
+ (lookup
+ (lambda (x r mod)
+ (let ((t (assq x r)))
+ (cond (t (cdr t))
+ ((symbol? x) (or (get-global-definition-hook x mod) '(global)))
+ (else '(displaced-lexical))))))
+ (global-extend
+ (lambda (type sym val) (put-global-definition-hook sym type val)))
+ (nonsymbol-id?
+ (lambda (x)
+ (and (syntax-object? x) (symbol? (syntax-object-expression x)))))
+ (id? (lambda (x)
+ (if (symbol? x)
+ #t
+ (and (syntax-object? x) (symbol? (syntax-object-expression x))))))
+ (id-sym-name&marks
+ (lambda (x w)
+ (if (syntax-object? x)
+ (values
+ (syntax-object-expression x)
+ (join-marks (car w) (car (syntax-object-wrap x))))
+ (values x (car w)))))
+ (gen-label (lambda () (symbol->string (module-gensym "l"))))
+ (gen-labels
+ (lambda (ls)
+ (if (null? ls) '() (cons (gen-label) (gen-labels (cdr ls))))))
+ (make-ribcage
+ (lambda (symnames marks labels)
+ (vector 'ribcage symnames marks labels)))
+ (ribcage?
+ (lambda (x)
+ (and (vector? x)
+ (= (vector-length x) 4)
+ (eq? (vector-ref x 0) 'ribcage))))
+ (ribcage-symnames (lambda (x) (vector-ref x 1)))
+ (ribcage-marks (lambda (x) (vector-ref x 2)))
+ (ribcage-labels (lambda (x) (vector-ref x 3)))
+ (set-ribcage-symnames! (lambda (x update) (vector-set! x 1 update)))
+ (set-ribcage-marks! (lambda (x update) (vector-set! x 2 update)))
+ (set-ribcage-labels! (lambda (x update) (vector-set! x 3 update)))
+ (anti-mark
+ (lambda (w) (cons (cons #f (car w)) (cons 'shift (cdr w)))))
+ (extend-ribcage!
+ (lambda (ribcage id label)
+ (set-ribcage-symnames!
+ ribcage
+ (cons (syntax-object-expression id) (ribcage-symnames ribcage)))
+ (set-ribcage-marks!
+ ribcage
+ (cons (car (syntax-object-wrap id)) (ribcage-marks ribcage)))
+ (set-ribcage-labels! ribcage (cons label (ribcage-labels ribcage)))))
+ (make-binding-wrap
+ (lambda (ids labels w)
+ (if (null? ids)
+ w
+ (cons (car w)
+ (cons (let* ((labelvec (list->vector labels)) (n (vector-length labelvec)))
+ (let ((symnamevec (make-vector n)) (marksvec (make-vector n)))
+ (let f ((ids ids) (i 0))
+ (if (not (null? ids))
+ (call-with-values
+ (lambda () (id-sym-name&marks (car ids) w))
+ (lambda (symname marks)
+ (vector-set! symnamevec i symname)
+ (vector-set! marksvec i marks)
+ (f (cdr ids) (+ i 1))))))
+ (make-ribcage symnamevec marksvec labelvec)))
+ (cdr w))))))
+ (smart-append (lambda (m1 m2) (if (null? m2) m1 (append m1 m2))))
+ (join-wraps
+ (lambda (w1 w2)
+ (let ((m1 (car w1)) (s1 (cdr w1)))
+ (if (null? m1)
+ (if (null? s1) w2 (cons (car w2) (smart-append s1 (cdr w2))))
+ (cons (smart-append m1 (car w2)) (smart-append s1 (cdr w2)))))))
+ (join-marks (lambda (m1 m2) (smart-append m1 m2)))
+ (same-marks?
+ (lambda (x y)
+ (or (eq? x y)
+ (and (not (null? x))
+ (not (null? y))
+ (eq? (car x) (car y))
+ (same-marks? (cdr x) (cdr y))))))
+ (id-var-name
+ (lambda (id w)
+ (letrec*
+ ((search
+ (lambda (sym subst marks)
+ (if (null? subst)
+ (values #f marks)
+ (let ((fst (car subst)))
+ (if (eq? fst 'shift)
+ (search sym (cdr subst) (cdr marks))
+ (let ((symnames (ribcage-symnames fst)))
+ (if (vector? symnames)
+ (search-vector-rib sym subst marks symnames fst)
+ (search-list-rib sym subst marks symnames fst))))))))
+ (search-list-rib
+ (lambda (sym subst marks symnames ribcage)
+ (let f ((symnames symnames) (i 0))
+ (cond ((null? symnames) (search sym (cdr subst) marks))
+ ((and (eq? (car symnames) sym)
+ (same-marks? marks (list-ref (ribcage-marks ribcage) i)))
+ (values (list-ref (ribcage-labels ribcage) i) marks))
+ (else (f (cdr symnames) (+ i 1)))))))
+ (search-vector-rib
+ (lambda (sym subst marks symnames ribcage)
+ (let ((n (vector-length symnames)))
+ (let f ((i 0))
+ (cond ((= i n) (search sym (cdr subst) marks))
+ ((and (eq? (vector-ref symnames i) sym)
+ (same-marks? marks (vector-ref (ribcage-marks ribcage) i)))
+ (values (vector-ref (ribcage-labels ribcage) i) marks))
+ (else (f (+ i 1)))))))))
+ (cond ((symbol? id) (or (search id (cdr w) (car w)) id))
+ ((syntax-object? id)
+ (let ((id (syntax-object-expression id)) (w1 (syntax-object-wrap id)))
+ (let ((marks (join-marks (car w) (car w1))))
+ (call-with-values
+ (lambda () (search id (cdr w) marks))
+ (lambda (new-id marks) (or new-id (search id (cdr w1) marks) id))))))
+ (else (syntax-violation 'id-var-name "invalid id" id))))))
+ (locally-bound-identifiers
+ (lambda (w mod)
+ (letrec*
+ ((scan (lambda (subst results)
+ (if (null? subst)
+ results
+ (let ((fst (car subst)))
+ (if (eq? fst 'shift)
+ (scan (cdr subst) results)
+ (let ((symnames (ribcage-symnames fst)) (marks (ribcage-marks fst)))
+ (if (vector? symnames)
+ (scan-vector-rib subst symnames marks results)
+ (scan-list-rib subst symnames marks results))))))))
+ (scan-list-rib
+ (lambda (subst symnames marks results)
+ (let f ((symnames symnames) (marks marks) (results results))
+ (if (null? symnames)
+ (scan (cdr subst) results)
+ (f (cdr symnames)
+ (cdr marks)
+ (cons (wrap (car symnames) (anti-mark (cons (car marks) subst)) mod)
+ results))))))
+ (scan-vector-rib
+ (lambda (subst symnames marks results)
+ (let ((n (vector-length symnames)))
+ (let f ((i 0) (results results))
+ (if (= i n)
+ (scan (cdr subst) results)
+ (f (+ i 1)
+ (cons (wrap (vector-ref symnames i)
+ (anti-mark (cons (vector-ref marks i) subst))
+ mod)
+ results))))))))
+ (scan (cdr w) '()))))
+ (resolve-identifier
+ (lambda (id w r mod)
+ (letrec*
+ ((resolve-global
+ (lambda (var mod)
+ (let ((b (or (get-global-definition-hook var mod) '(global))))
+ (if (eq? (car b) 'global)
+ (values 'global var mod)
+ (values (car b) (cdr b) mod)))))
+ (resolve-lexical
+ (lambda (label mod)
+ (let ((b (or (assq-ref r label) '(displaced-lexical))))
+ (values (car b) (cdr b) mod)))))
+ (let ((n (id-var-name id w)))
+ (cond ((symbol? n)
+ (resolve-global
+ n
+ (if (syntax-object? id) (syntax-object-module id) mod)))
+ ((string? n)
+ (resolve-lexical
+ n
+ (if (syntax-object? id) (syntax-object-module id) mod)))
+ (else (error "unexpected id-var-name" id w n)))))))
+ (transformer-environment
+ (make-fluid
+ (lambda (k)
+ (error "called outside the dynamic extent of a syntax transformer"))))
+ (with-transformer-environment
+ (lambda (k) ((fluid-ref transformer-environment) k)))
+ (free-id=?
+ (lambda (i j)
+ (and (eq? (let ((x i)) (if (syntax-object? x) (syntax-object-expression x) x))
+ (let ((x j)) (if (syntax-object? x) (syntax-object-expression x) x)))
+ (eq? (id-var-name i '(())) (id-var-name j '(()))))))
+ (bound-id=?
+ (lambda (i j)
+ (if (and (syntax-object? i) (syntax-object? j))
+ (and (eq? (syntax-object-expression i) (syntax-object-expression j))
+ (same-marks?
+ (car (syntax-object-wrap i))
+ (car (syntax-object-wrap j))))
+ (eq? i j))))
+ (valid-bound-ids?
+ (lambda (ids)
+ (and (let all-ids? ((ids ids))
+ (or (null? ids) (and (id? (car ids)) (all-ids? (cdr ids)))))
+ (distinct-bound-ids? ids))))
+ (distinct-bound-ids?
+ (lambda (ids)
+ (let distinct? ((ids ids))
+ (or (null? ids)
+ (and (not (bound-id-member? (car ids) (cdr ids)))
+ (distinct? (cdr ids)))))))
+ (bound-id-member?
+ (lambda (x list)
+ (and (not (null? list))
+ (or (bound-id=? x (car list)) (bound-id-member? x (cdr list))))))
+ (wrap (lambda (x w defmod)
+ (cond ((and (null? (car w)) (null? (cdr w))) x)
+ ((syntax-object? x)
+ (make-syntax-object
+ (syntax-object-expression x)
+ (join-wraps w (syntax-object-wrap x))
+ (syntax-object-module x)))
+ ((null? x) x)
+ (else (make-syntax-object x w defmod)))))
+ (source-wrap
+ (lambda (x w s defmod) (wrap (decorate-source x s) w defmod)))
+ (expand-sequence
+ (lambda (body r w s mod)
+ (build-sequence
+ s
+ (let dobody ((body body) (r r) (w w) (mod mod))
+ (if (null? body)
+ '()
+ (let ((first (expand (car body) r w mod)))
+ (cons first (dobody (cdr body) r w mod))))))))
+ (expand-top-sequence
+ (lambda (body r w s m esew mod)
+ (letrec*
+ ((scan (lambda (body r w s m esew mod exps)
+ (if (null? body)
+ exps
+ (call-with-values
+ (lambda ()
+ (call-with-values
+ (lambda ()
+ (let ((e (car body)))
+ (syntax-type e r w (or (source-annotation e) s) #f mod #f)))
+ (lambda (type value form e w s mod)
+ (let ((key type))
+ (cond ((memv key '(begin-form))
+ (let* ((tmp e) (tmp-1 ($sc-dispatch tmp '(_))))
+ (if tmp-1
+ (apply (lambda () exps) tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(_ any . each-any))))
+ (if tmp-1
+ (apply (lambda (e1 e2) (scan (cons e1 e2) r w s m esew mod exps))
+ tmp-1)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp))))))
+ ((memv key '(local-syntax-form))
+ (expand-local-syntax
+ value
+ e
+ r
+ w
+ s
+ mod
+ (lambda (body r w s mod) (scan body r w s m esew mod exps))))
+ ((memv key '(eval-when-form))
+ (let* ((tmp-1 e) (tmp ($sc-dispatch tmp-1 '(_ each-any any . each-any))))
+ (if tmp
+ (apply (lambda (x e1 e2)
+ (let ((when-list (parse-when-list e x)) (body (cons e1 e2)))
+ (cond ((eq? m 'e)
+ (if (memq 'eval when-list)
+ (scan body
+ r
+ w
+ s
+ (if (memq 'expand when-list) 'c&e 'e)
+ '(eval)
+ mod
+ exps)
+ (begin
+ (if (memq 'expand when-list)
+ (top-level-eval-hook
+ (expand-top-sequence body r w s 'e '(eval) mod)
+ mod))
+ (values exps))))
+ ((memq 'load when-list)
+ (cond ((or (memq 'compile when-list)
+ (memq 'expand when-list)
+ (and (eq? m 'c&e) (memq 'eval when-list)))
+ (scan body r w s 'c&e '(compile load) mod exps))
+ ((memq m '(c c&e))
+ (scan body r w s 'c '(load) mod exps))
+ (else (values exps))))
+ ((or (memq 'compile when-list)
+ (memq 'expand when-list)
+ (and (eq? m 'c&e) (memq 'eval when-list)))
+ (top-level-eval-hook
+ (expand-top-sequence body r w s 'e '(eval) mod)
+ mod)
+ (values exps))
+ (else (values exps)))))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1))))
+ ((memv key '(define-syntax-form define-syntax-parameter-form))
+ (let ((n (id-var-name value w)) (r (macros-only-env r)))
+ (let ((key m))
+ (cond ((memv key '(c))
+ (cond ((memq 'compile esew)
+ (let ((e (expand-install-global n (expand e r w mod))))
+ (top-level-eval-hook e mod)
+ (if (memq 'load esew) (values (cons e exps)) (values exps))))
+ ((memq 'load esew)
+ (values
+ (cons (expand-install-global n (expand e r w mod)) exps)))
+ (else (values exps))))
+ ((memv key '(c&e))
+ (let ((e (expand-install-global n (expand e r w mod))))
+ (top-level-eval-hook e mod)
+ (values (cons e exps))))
+ (else
+ (if (memq 'eval esew)
+ (top-level-eval-hook
+ (expand-install-global n (expand e r w mod))
+ mod))
+ (values exps))))))
+ ((memv key '(define-form))
+ (let* ((n (id-var-name value w)) (type (car (lookup n r mod))) (key type))
+ (cond ((memv key '(global core macro module-ref))
+ (if (and (memq m '(c c&e))
+ (not (module-local-variable (current-module) n))
+ (current-module))
+ (let ((old (module-variable (current-module) n)))
+ (if (and (variable? old)
+ (variable-bound? old)
+ (not (macro? (variable-ref old))))
+ (module-define! (current-module) n (variable-ref old))
+ (module-add! (current-module) n (make-undefined-variable)))))
+ (values
+ (cons (if (eq? m 'c&e)
+ (let ((x (build-global-definition s n (expand e r w mod))))
+ (top-level-eval-hook x mod)
+ x)
+ (lambda () (build-global-definition s n (expand e r w mod))))
+ exps)))
+ ((memv key '(displaced-lexical))
+ (syntax-violation
+ #f
+ "identifier out of context"
+ (source-wrap form w s mod)
+ (wrap value w mod)))
+ (else
+ (syntax-violation
+ #f
+ "cannot define keyword at top level"
+ (source-wrap form w s mod)
+ (wrap value w mod))))))
+ (else
+ (values
+ (cons (if (eq? m 'c&e)
+ (let ((x (expand-expr type value form e r w s mod)))
+ (top-level-eval-hook x mod)
+ x)
+ (lambda () (expand-expr type value form e r w s mod)))
+ exps))))))))
+ (lambda (exps) (scan (cdr body) r w s m esew mod exps)))))))
+ (call-with-values
+ (lambda () (scan body r w s m esew mod '()))
+ (lambda (exps)
+ (if (null? exps)
+ (build-void s)
+ (build-sequence
+ s
+ (let lp ((in exps) (out '()))
+ (if (null? in)
+ out
+ (let ((e (car in)))
+ (lp (cdr in) (cons (if (procedure? e) (e) e) out))))))))))))
+ (expand-install-global
+ (lambda (name e)
+ (build-global-definition
+ #f
+ name
+ (build-application
+ #f
+ (build-primref #f 'make-syntax-transformer)
+ (list (build-data #f name) (build-data #f 'macro) e)))))
+ (parse-when-list
+ (lambda (e when-list)
+ (let ((result (strip when-list '(()))))
+ (let lp ((l result))
+ (cond ((null? l) result)
+ ((memq (car l) '(compile load eval expand)) (lp (cdr l)))
+ (else (syntax-violation 'eval-when "invalid situation" e (car l))))))))
+ (syntax-type
+ (lambda (e r w s rib mod for-car?)
+ (cond ((symbol? e)
+ (let* ((n (id-var-name e w))
+ (b (lookup n r mod))
+ (type (car b))
+ (key type))
+ (cond ((memv key '(lexical)) (values type (cdr b) e e w s mod))
+ ((memv key '(global)) (values type n e e w s mod))
+ ((memv key '(macro))
+ (if for-car?
+ (values type (cdr b) e e w s mod)
+ (syntax-type
+ (expand-macro (cdr b) e r w s rib mod)
+ r
+ '(())
+ s
+ rib
+ mod
+ #f)))
+ (else (values type (cdr b) e e w s mod)))))
+ ((pair? e)
+ (let ((first (car e)))
+ (call-with-values
+ (lambda () (syntax-type first r w s rib mod #t))
+ (lambda (ftype fval fform fe fw fs fmod)
+ (let ((key ftype))
+ (cond ((memv key '(lexical)) (values 'lexical-call fval e e w s mod))
+ ((memv key '(global))
+ (values 'global-call (make-syntax-object fval w fmod) e e w s mod))
+ ((memv key '(macro))
+ (syntax-type
+ (expand-macro fval e r w s rib mod)
+ r
+ '(())
+ s
+ rib
+ mod
+ for-car?))
+ ((memv key '(module-ref))
+ (call-with-values
+ (lambda () (fval e r w))
+ (lambda (e r w s mod) (syntax-type e r w s rib mod for-car?))))
+ ((memv key '(core)) (values 'core-form fval e e w s mod))
+ ((memv key '(local-syntax))
+ (values 'local-syntax-form fval e e w s mod))
+ ((memv key '(begin)) (values 'begin-form #f e e w s mod))
+ ((memv key '(eval-when)) (values 'eval-when-form #f e e w s mod))
+ ((memv key '(define))
+ (let* ((tmp e) (tmp-1 ($sc-dispatch tmp '(_ any any))))
+ (if (and tmp-1 (apply (lambda (name val) (id? name)) tmp-1))
+ (apply (lambda (name val) (values 'define-form name e val w s mod))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(_ (any . any) any . each-any))))
+ (if (and tmp-1
+ (apply (lambda (name args e1 e2)
+ (and (id? name) (valid-bound-ids? (lambda-var-list args))))
+ tmp-1))
+ (apply (lambda (name args e1 e2)
+ (values
+ 'define-form
+ (wrap name w mod)
+ (wrap e w mod)
+ (decorate-source
+ (cons '#(syntax-object lambda ((top)) (hygiene guile))
+ (wrap (cons args (cons e1 e2)) w mod))
+ s)
+ '(())
+ s
+ mod))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(_ any))))
+ (if (and tmp-1 (apply (lambda (name) (id? name)) tmp-1))
+ (apply (lambda (name)
+ (values
+ 'define-form
+ (wrap name w mod)
+ (wrap e w mod)
+ '(#(syntax-object if ((top)) (hygiene guile)) #f #f)
+ '(())
+ s
+ mod))
+ tmp-1)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp))))))))
+ ((memv key '(define-syntax))
+ (let* ((tmp-1 e) (tmp ($sc-dispatch tmp-1 '(_ any any))))
+ (if (and tmp (apply (lambda (name val) (id? name)) tmp))
+ (apply (lambda (name val) (values 'define-syntax-form name e val w s mod))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1))))
+ ((memv key '(define-syntax-parameter))
+ (let* ((tmp-1 e) (tmp ($sc-dispatch tmp-1 '(_ any any))))
+ (if (and tmp (apply (lambda (name val) (id? name)) tmp))
+ (apply (lambda (name val)
+ (values 'define-syntax-parameter-form name e val w s mod))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1))))
+ (else (values 'call #f e e w s mod))))))))
+ ((syntax-object? e)
+ (syntax-type
+ (syntax-object-expression e)
+ r
+ (join-wraps w (syntax-object-wrap e))
+ (or (source-annotation e) s)
+ rib
+ (or (syntax-object-module e) mod)
+ for-car?))
+ ((self-evaluating? e) (values 'constant #f e e w s mod))
+ (else (values 'other #f e e w s mod)))))
+ (expand
+ (lambda (e r w mod)
+ (call-with-values
+ (lambda () (syntax-type e r w (source-annotation e) #f mod #f))
+ (lambda (type value form e w s mod)
+ (expand-expr type value form e r w s mod)))))
+ (expand-expr
+ (lambda (type value form e r w s mod)
+ (let ((key type))
+ (cond ((memv key '(lexical)) (build-lexical-reference 'value s e value))
+ ((memv key '(core core-form)) (value e r w s mod))
+ ((memv key '(module-ref))
+ (call-with-values
+ (lambda () (value e r w))
+ (lambda (e r w s mod) (expand e r w mod))))
+ ((memv key '(lexical-call))
+ (expand-application
+ (let ((id (car e)))
+ (build-lexical-reference
+ 'fun
+ (source-annotation id)
+ (if (syntax-object? id) (syntax->datum id) id)
+ value))
+ e
+ r
+ w
+ s
+ mod))
+ ((memv key '(global-call))
+ (expand-application
+ (build-global-reference
+ (source-annotation (car e))
+ (if (syntax-object? value) (syntax-object-expression value) value)
+ (if (syntax-object? value) (syntax-object-module value) mod))
+ e
+ r
+ w
+ s
+ mod))
+ ((memv key '(constant))
+ (build-data s (strip (source-wrap e w s mod) '(()))))
+ ((memv key '(global)) (build-global-reference s value mod))
+ ((memv key '(call))
+ (expand-application (expand (car e) r w mod) e r w s mod))
+ ((memv key '(begin-form))
+ (let* ((tmp e) (tmp-1 ($sc-dispatch tmp '(_ any . each-any))))
+ (if tmp-1
+ (apply (lambda (e1 e2) (expand-sequence (cons e1 e2) r w s mod))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(_))))
+ (if tmp-1
+ (apply (lambda ()
+ (if (include-deprecated-features)
+ (begin
+ (issue-deprecation-warning
+ "Sequences of zero expressions are deprecated. Use *unspecified*.")
+ (expand-void))
+ (syntax-violation
+ #f
+ "sequence of zero expressions"
+ (source-wrap e w s mod))))
+ tmp-1)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp))))))
+ ((memv key '(local-syntax-form))
+ (expand-local-syntax value e r w s mod expand-sequence))
+ ((memv key '(eval-when-form))
+ (let* ((tmp-1 e) (tmp ($sc-dispatch tmp-1 '(_ each-any any . each-any))))
+ (if tmp
+ (apply (lambda (x e1 e2)
+ (let ((when-list (parse-when-list e x)))
+ (if (memq 'eval when-list)
+ (expand-sequence (cons e1 e2) r w s mod)
+ (expand-void))))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1))))
+ ((memv key
+ '(define-form define-syntax-form define-syntax-parameter-form))
+ (syntax-violation
+ #f
+ "definition in expression context, where definitions are not allowed,"
+ (source-wrap form w s mod)))
+ ((memv key '(syntax))
+ (syntax-violation
+ #f
+ "reference to pattern variable outside syntax form"
+ (source-wrap e w s mod)))
+ ((memv key '(displaced-lexical))
+ (syntax-violation
+ #f
+ "reference to identifier outside its scope"
+ (source-wrap e w s mod)))
+ (else
+ (syntax-violation #f "unexpected syntax" (source-wrap e w s mod)))))))
+ (expand-application
+ (lambda (x e r w s mod)
+ (let* ((tmp-1 e) (tmp ($sc-dispatch tmp-1 '(any . each-any))))
+ (if tmp
+ (apply (lambda (e0 e1)
+ (build-application s x (map (lambda (e) (expand e r w mod)) e1)))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))
+ (expand-macro
+ (lambda (p e r w s rib mod)
+ (letrec*
+ ((rebuild-macro-output
+ (lambda (x m)
+ (cond ((pair? x)
+ (decorate-source
+ (cons (rebuild-macro-output (car x) m)
+ (rebuild-macro-output (cdr x) m))
+ s))
+ ((syntax-object? x)
+ (let ((w (syntax-object-wrap x)))
+ (let ((ms (car w)) (ss (cdr w)))
+ (if (and (pair? ms) (eq? (car ms) #f))
+ (make-syntax-object
+ (syntax-object-expression x)
+ (cons (cdr ms) (if rib (cons rib (cdr ss)) (cdr ss)))
+ (syntax-object-module x))
+ (make-syntax-object
+ (decorate-source (syntax-object-expression x) s)
+ (cons (cons m ms)
+ (if rib (cons rib (cons 'shift ss)) (cons 'shift ss)))
+ (syntax-object-module x))))))
+ ((vector? x)
+ (let* ((n (vector-length x)) (v (decorate-source (make-vector n) s)))
+ (let loop ((i 0))
+ (if (= i n)
+ (begin (if #f #f) v)
+ (begin
+ (vector-set! v i (rebuild-macro-output (vector-ref x i) m))
+ (loop (+ i 1)))))))
+ ((symbol? x)
+ (syntax-violation
+ #f
+ "encountered raw symbol in macro output"
+ (source-wrap e w (cdr w) mod)
+ x))
+ (else (decorate-source x s))))))
+ (with-fluids
+ ((transformer-environment (lambda (k) (k e r w s rib mod))))
+ (rebuild-macro-output
+ (p (source-wrap e (anti-mark w) s mod))
+ (module-gensym "m"))))))
+ (expand-body
+ (lambda (body outer-form r w mod)
+ (let* ((r (cons '("placeholder" placeholder) r))
+ (ribcage (make-ribcage '() '() '()))
+ (w (cons (car w) (cons ribcage (cdr w)))))
+ (let parse ((body (map (lambda (x) (cons r (wrap x w mod))) body))
+ (ids '())
+ (labels '())
+ (var-ids '())
+ (vars '())
+ (vals '())
+ (bindings '()))
+ (if (null? body)
+ (syntax-violation #f "no expressions in body" outer-form)
+ (let ((e (cdar body)) (er (caar body)))
+ (call-with-values
+ (lambda ()
+ (syntax-type e er '(()) (source-annotation e) ribcage mod #f))
+ (lambda (type value form e w s mod)
+ (let ((key type))
+ (cond ((memv key '(define-form))
+ (let ((id (wrap value w mod)) (label (gen-label)))
+ (let ((var (gen-var id)))
+ (extend-ribcage! ribcage id label)
+ (parse (cdr body)
+ (cons id ids)
+ (cons label labels)
+ (cons id var-ids)
+ (cons var vars)
+ (cons (cons er (wrap e w mod)) vals)
+ (cons (cons 'lexical var) bindings)))))
+ ((memv key '(define-syntax-form define-syntax-parameter-form))
+ (let ((id (wrap value w mod))
+ (label (gen-label))
+ (trans-r (macros-only-env er)))
+ (extend-ribcage! ribcage id label)
+ (set-cdr!
+ r
+ (extend-env
+ (list label)
+ (list (cons 'macro (eval-local-transformer (expand e trans-r w mod) mod)))
+ (cdr r)))
+ (parse (cdr body) (cons id ids) labels var-ids vars vals bindings)))
+ ((memv key '(begin-form))
+ (let* ((tmp-1 e) (tmp ($sc-dispatch tmp-1 '(_ . each-any))))
+ (if tmp
+ (apply (lambda (e1)
+ (parse (let f ((forms e1))
+ (if (null? forms)
+ (cdr body)
+ (cons (cons er (wrap (car forms) w mod)) (f (cdr forms)))))
+ ids
+ labels
+ var-ids
+ vars
+ vals
+ bindings))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1))))
+ ((memv key '(local-syntax-form))
+ (expand-local-syntax
+ value
+ e
+ er
+ w
+ s
+ mod
+ (lambda (forms er w s mod)
+ (parse (let f ((forms forms))
+ (if (null? forms)
+ (cdr body)
+ (cons (cons er (wrap (car forms) w mod)) (f (cdr forms)))))
+ ids
+ labels
+ var-ids
+ vars
+ vals
+ bindings))))
+ ((null? ids)
+ (build-sequence
+ #f
+ (map (lambda (x) (expand (cdr x) (car x) '(()) mod))
+ (cons (cons er (source-wrap e w s mod)) (cdr body)))))
+ (else
+ (if (not (valid-bound-ids? ids))
+ (syntax-violation
+ #f
+ "invalid or duplicate identifier in definition"
+ outer-form))
+ (set-cdr! r (extend-env labels bindings (cdr r)))
+ (build-letrec
+ #f
+ #t
+ (reverse (map syntax->datum var-ids))
+ (reverse vars)
+ (map (lambda (x) (expand (cdr x) (car x) '(()) mod)) (reverse vals))
+ (build-sequence
+ #f
+ (map (lambda (x) (expand (cdr x) (car x) '(()) mod))
+ (cons (cons er (source-wrap e w s mod)) (cdr body))))))))))))))))
+ (expand-local-syntax
+ (lambda (rec? e r w s mod k)
+ (let* ((tmp e)
+ (tmp ($sc-dispatch tmp '(_ #(each (any any)) any . each-any))))
+ (if tmp
+ (apply (lambda (id val e1 e2)
+ (let ((ids id))
+ (if (not (valid-bound-ids? ids))
+ (syntax-violation #f "duplicate bound keyword" e)
+ (let* ((labels (gen-labels ids)) (new-w (make-binding-wrap ids labels w)))
+ (k (cons e1 e2)
+ (extend-env
+ labels
+ (let ((w (if rec? new-w w)) (trans-r (macros-only-env r)))
+ (map (lambda (x)
+ (cons 'macro (eval-local-transformer (expand x trans-r w mod) mod)))
+ val))
+ r)
+ new-w
+ s
+ mod)))))
+ tmp)
+ (syntax-violation
+ #f
+ "bad local syntax definition"
+ (source-wrap e w s mod))))))
+ (eval-local-transformer
+ (lambda (expanded mod)
+ (let ((p (local-eval-hook expanded mod)))
+ (if (procedure? p)
+ p
+ (syntax-violation #f "nonprocedure transformer" p)))))
+ (expand-void (lambda () (build-void #f)))
+ (ellipsis?
+ (lambda (e r mod)
+ (and (nonsymbol-id? e)
+ (let* ((id (make-syntax-object
+ '#{ $sc-ellipsis }
+ (syntax-object-wrap e)
+ (syntax-object-module e)))
+ (n (id-var-name id '(())))
+ (b (lookup n r mod)))
+ (if (eq? (car b) 'ellipsis)
+ (bound-id=? e (cdr b))
+ (free-id=? e '#(syntax-object ... ((top)) (hygiene guile))))))))
+ (lambda-formals
+ (lambda (orig-args)
+ (letrec*
+ ((req (lambda (args rreq)
+ (let* ((tmp args) (tmp-1 ($sc-dispatch tmp '())))
+ (if tmp-1
+ (apply (lambda () (check (reverse rreq) #f)) tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any . any))))
+ (if (and tmp-1 (apply (lambda (a b) (id? a)) tmp-1))
+ (apply (lambda (a b) (req b (cons a rreq))) tmp-1)
+ (let ((tmp-1 (list tmp)))
+ (if (and tmp-1 (apply (lambda (r) (id? r)) tmp-1))
+ (apply (lambda (r) (check (reverse rreq) r)) tmp-1)
+ (let ((else tmp))
+ (syntax-violation 'lambda "invalid argument list" orig-args args))))))))))
+ (check (lambda (req rest)
+ (if (distinct-bound-ids? (if rest (cons rest req) req))
+ (values req #f rest #f)
+ (syntax-violation
+ 'lambda
+ "duplicate identifier in argument list"
+ orig-args)))))
+ (req orig-args '()))))
+ (expand-simple-lambda
+ (lambda (e r w s mod req rest meta body)
+ (let* ((ids (if rest (append req (list rest)) req))
+ (vars (map gen-var ids))
+ (labels (gen-labels ids)))
+ (build-simple-lambda
+ s
+ (map syntax->datum req)
+ (and rest (syntax->datum rest))
+ vars
+ meta
+ (expand-body
+ body
+ (source-wrap e w s mod)
+ (extend-var-env labels vars r)
+ (make-binding-wrap ids labels w)
+ mod)))))
+ (lambda*-formals
+ (lambda (orig-args)
+ (letrec*
+ ((req (lambda (args rreq)
+ (let* ((tmp args) (tmp-1 ($sc-dispatch tmp '())))
+ (if tmp-1
+ (apply (lambda () (check (reverse rreq) '() #f '())) tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any . any))))
+ (if (and tmp-1 (apply (lambda (a b) (id? a)) tmp-1))
+ (apply (lambda (a b) (req b (cons a rreq))) tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any . any))))
+ (if (and tmp-1
+ (apply (lambda (a b) (eq? (syntax->datum a) #\optional)) tmp-1))
+ (apply (lambda (a b) (opt b (reverse rreq) '())) tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any . any))))
+ (if (and tmp-1
+ (apply (lambda (a b) (eq? (syntax->datum a) #\key)) tmp-1))
+ (apply (lambda (a b) (key b (reverse rreq) '() '())) tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any any))))
+ (if (and tmp-1
+ (apply (lambda (a b) (eq? (syntax->datum a) #\rest)) tmp-1))
+ (apply (lambda (a b) (rest b (reverse rreq) '() '())) tmp-1)
+ (let ((tmp-1 (list tmp)))
+ (if (and tmp-1 (apply (lambda (r) (id? r)) tmp-1))
+ (apply (lambda (r) (rest r (reverse rreq) '() '())) tmp-1)
+ (let ((else tmp))
+ (syntax-violation
+ 'lambda*
+ "invalid argument list"
+ orig-args
+ args))))))))))))))))
+ (opt (lambda (args req ropt)
+ (let* ((tmp args) (tmp-1 ($sc-dispatch tmp '())))
+ (if tmp-1
+ (apply (lambda () (check req (reverse ropt) #f '())) tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any . any))))
+ (if (and tmp-1 (apply (lambda (a b) (id? a)) tmp-1))
+ (apply (lambda (a b) (opt b req (cons (cons a '(#f)) ropt))) tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '((any any) . any))))
+ (if (and tmp-1 (apply (lambda (a init b) (id? a)) tmp-1))
+ (apply (lambda (a init b) (opt b req (cons (list a init) ropt)))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any . any))))
+ (if (and tmp-1
+ (apply (lambda (a b) (eq? (syntax->datum a) #\key)) tmp-1))
+ (apply (lambda (a b) (key b req (reverse ropt) '())) tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any any))))
+ (if (and tmp-1
+ (apply (lambda (a b) (eq? (syntax->datum a) #\rest)) tmp-1))
+ (apply (lambda (a b) (rest b req (reverse ropt) '())) tmp-1)
+ (let ((tmp-1 (list tmp)))
+ (if (and tmp-1 (apply (lambda (r) (id? r)) tmp-1))
+ (apply (lambda (r) (rest r req (reverse ropt) '())) tmp-1)
+ (let ((else tmp))
+ (syntax-violation
+ 'lambda*
+ "invalid optional argument list"
+ orig-args
+ args))))))))))))))))
+ (key (lambda (args req opt rkey)
+ (let* ((tmp args) (tmp-1 ($sc-dispatch tmp '())))
+ (if tmp-1
+ (apply (lambda () (check req opt #f (cons #f (reverse rkey)))) tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any . any))))
+ (if (and tmp-1 (apply (lambda (a b) (id? a)) tmp-1))
+ (apply (lambda (a b)
+ (let* ((tmp (symbol->keyword (syntax->datum a))) (k tmp))
+ (key b req opt (cons (cons k (cons a '(#f))) rkey))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '((any any) . any))))
+ (if (and tmp-1 (apply (lambda (a init b) (id? a)) tmp-1))
+ (apply (lambda (a init b)
+ (let* ((tmp (symbol->keyword (syntax->datum a))) (k tmp))
+ (key b req opt (cons (list k a init) rkey))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '((any any any) . any))))
+ (if (and tmp-1
+ (apply (lambda (a init k b) (and (id? a) (keyword? (syntax->datum k))))
+ tmp-1))
+ (apply (lambda (a init k b) (key b req opt (cons (list k a init) rkey)))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any))))
+ (if (and tmp-1
+ (apply (lambda (aok) (eq? (syntax->datum aok) #\allow-other-keys))
+ tmp-1))
+ (apply (lambda (aok) (check req opt #f (cons #t (reverse rkey))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any any any))))
+ (if (and tmp-1
+ (apply (lambda (aok a b)
+ (and (eq? (syntax->datum aok) #\allow-other-keys)
+ (eq? (syntax->datum a) #\rest)))
+ tmp-1))
+ (apply (lambda (aok a b) (rest b req opt (cons #t (reverse rkey))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any . any))))
+ (if (and tmp-1
+ (apply (lambda (aok r)
+ (and (eq? (syntax->datum aok) #\allow-other-keys) (id? r)))
+ tmp-1))
+ (apply (lambda (aok r) (rest r req opt (cons #t (reverse rkey))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any any))))
+ (if (and tmp-1
+ (apply (lambda (a b) (eq? (syntax->datum a) #\rest)) tmp-1))
+ (apply (lambda (a b) (rest b req opt (cons #f (reverse rkey))))
+ tmp-1)
+ (let ((tmp-1 (list tmp)))
+ (if (and tmp-1 (apply (lambda (r) (id? r)) tmp-1))
+ (apply (lambda (r) (rest r req opt (cons #f (reverse rkey))))
+ tmp-1)
+ (let ((else tmp))
+ (syntax-violation
+ 'lambda*
+ "invalid keyword argument list"
+ orig-args
+ args))))))))))))))))))))))
+ (rest (lambda (args req opt kw)
+ (let* ((tmp-1 args) (tmp (list tmp-1)))
+ (if (and tmp (apply (lambda (r) (id? r)) tmp))
+ (apply (lambda (r) (check req opt r kw)) tmp)
+ (let ((else tmp-1))
+ (syntax-violation 'lambda* "invalid rest argument" orig-args args))))))
+ (check (lambda (req opt rest kw)
+ (if (distinct-bound-ids?
+ (append
+ req
+ (map car opt)
+ (if rest (list rest) '())
+ (if (pair? kw) (map cadr (cdr kw)) '())))
+ (values req opt rest kw)
+ (syntax-violation
+ 'lambda*
+ "duplicate identifier in argument list"
+ orig-args)))))
+ (req orig-args '()))))
+ (expand-lambda-case
+ (lambda (e r w s mod get-formals clauses)
+ (letrec*
+ ((parse-req
+ (lambda (req opt rest kw body)
+ (let ((vars (map gen-var req)) (labels (gen-labels req)))
+ (let ((r* (extend-var-env labels vars r))
+ (w* (make-binding-wrap req labels w)))
+ (parse-opt
+ (map syntax->datum req)
+ opt
+ rest
+ kw
+ body
+ (reverse vars)
+ r*
+ w*
+ '()
+ '())))))
+ (parse-opt
+ (lambda (req opt rest kw body vars r* w* out inits)
+ (cond ((pair? opt)
+ (let* ((tmp-1 (car opt)) (tmp ($sc-dispatch tmp-1 '(any any))))
+ (if tmp
+ (apply (lambda (id i)
+ (let* ((v (gen-var id))
+ (l (gen-labels (list v)))
+ (r** (extend-var-env l (list v) r*))
+ (w** (make-binding-wrap (list id) l w*)))
+ (parse-opt
+ req
+ (cdr opt)
+ rest
+ kw
+ body
+ (cons v vars)
+ r**
+ w**
+ (cons (syntax->datum id) out)
+ (cons (expand i r* w* mod) inits))))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1))))
+ (rest
+ (let* ((v (gen-var rest))
+ (l (gen-labels (list v)))
+ (r* (extend-var-env l (list v) r*))
+ (w* (make-binding-wrap (list rest) l w*)))
+ (parse-kw
+ req
+ (and (pair? out) (reverse out))
+ (syntax->datum rest)
+ (if (pair? kw) (cdr kw) kw)
+ body
+ (cons v vars)
+ r*
+ w*
+ (and (pair? kw) (car kw))
+ '()
+ inits)))
+ (else
+ (parse-kw
+ req
+ (and (pair? out) (reverse out))
+ #f
+ (if (pair? kw) (cdr kw) kw)
+ body
+ vars
+ r*
+ w*
+ (and (pair? kw) (car kw))
+ '()
+ inits)))))
+ (parse-kw
+ (lambda (req opt rest kw body vars r* w* aok out inits)
+ (if (pair? kw)
+ (let* ((tmp-1 (car kw)) (tmp ($sc-dispatch tmp-1 '(any any any))))
+ (if tmp
+ (apply (lambda (k id i)
+ (let* ((v (gen-var id))
+ (l (gen-labels (list v)))
+ (r** (extend-var-env l (list v) r*))
+ (w** (make-binding-wrap (list id) l w*)))
+ (parse-kw
+ req
+ opt
+ rest
+ (cdr kw)
+ body
+ (cons v vars)
+ r**
+ w**
+ aok
+ (cons (list (syntax->datum k) (syntax->datum id) v) out)
+ (cons (expand i r* w* mod) inits))))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))
+ (parse-body
+ req
+ opt
+ rest
+ (and (or aok (pair? out)) (cons aok (reverse out)))
+ body
+ (reverse vars)
+ r*
+ w*
+ (reverse inits)
+ '()))))
+ (parse-body
+ (lambda (req opt rest kw body vars r* w* inits meta)
+ (let* ((tmp body) (tmp-1 ($sc-dispatch tmp '(any any . each-any))))
+ (if (and tmp-1
+ (apply (lambda (docstring e1 e2) (string? (syntax->datum docstring)))
+ tmp-1))
+ (apply (lambda (docstring e1 e2)
+ (parse-body
+ req
+ opt
+ rest
+ kw
+ (cons e1 e2)
+ vars
+ r*
+ w*
+ inits
+ (append meta (list (cons 'documentation (syntax->datum docstring))))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(#(vector #(each (any . any))) any . each-any))))
+ (if tmp-1
+ (apply (lambda (k v e1 e2)
+ (parse-body
+ req
+ opt
+ rest
+ kw
+ (cons e1 e2)
+ vars
+ r*
+ w*
+ inits
+ (append meta (syntax->datum (map cons k v)))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any . each-any))))
+ (if tmp-1
+ (apply (lambda (e1 e2)
+ (values
+ meta
+ req
+ opt
+ rest
+ kw
+ inits
+ vars
+ (expand-body (cons e1 e2) (source-wrap e w s mod) r* w* mod)))
+ tmp-1)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp))))))))))
+ (let* ((tmp clauses) (tmp-1 ($sc-dispatch tmp '())))
+ (if tmp-1
+ (apply (lambda () (values '() #f)) tmp-1)
+ (let ((tmp-1 ($sc-dispatch
+ tmp
+ '((any any . each-any) . #(each (any any . each-any))))))
+ (if tmp-1
+ (apply (lambda (args e1 e2 args* e1* e2*)
+ (call-with-values
+ (lambda () (get-formals args))
+ (lambda (req opt rest kw)
+ (call-with-values
+ (lambda () (parse-req req opt rest kw (cons e1 e2)))
+ (lambda (meta req opt rest kw inits vars body)
+ (call-with-values
+ (lambda ()
+ (expand-lambda-case
+ e
+ r
+ w
+ s
+ mod
+ get-formals
+ (map (lambda (tmp-bde397a-a85 tmp-bde397a-a84 tmp-bde397a-a83)
+ (cons tmp-bde397a-a83 (cons tmp-bde397a-a84 tmp-bde397a-a85)))
+ e2*
+ e1*
+ args*)))
+ (lambda (meta* else*)
+ (values
+ (append meta meta*)
+ (build-lambda-case s req opt rest kw inits vars body else*)))))))))
+ tmp-1)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp))))))))
+ (strip (lambda (x w)
+ (if (memq 'top (car w))
+ x
+ (let f ((x x))
+ (cond ((syntax-object? x)
+ (strip (syntax-object-expression x) (syntax-object-wrap x)))
+ ((pair? x)
+ (let ((a (f (car x))) (d (f (cdr x))))
+ (if (and (eq? a (car x)) (eq? d (cdr x))) x (cons a d))))
+ ((vector? x)
+ (let* ((old (vector->list x)) (new (map f old)))
+ (let lp ((l1 old) (l2 new))
+ (cond ((null? l1) x)
+ ((eq? (car l1) (car l2)) (lp (cdr l1) (cdr l2)))
+ (else (list->vector new))))))
+ (else x))))))
+ (gen-var
+ (lambda (id)
+ (let ((id (if (syntax-object? id) (syntax-object-expression id) id)))
+ (module-gensym (symbol->string id)))))
+ (lambda-var-list
+ (lambda (vars)
+ (let lvl ((vars vars) (ls '()) (w '(())))
+ (cond ((pair? vars) (lvl (cdr vars) (cons (wrap (car vars) w #f) ls) w))
+ ((id? vars) (cons (wrap vars w #f) ls))
+ ((null? vars) ls)
+ ((syntax-object? vars)
+ (lvl (syntax-object-expression vars)
+ ls
+ (join-wraps w (syntax-object-wrap vars))))
+ (else (cons vars ls)))))))
+ (global-extend 'local-syntax 'letrec-syntax #t)
+ (global-extend 'local-syntax 'let-syntax #f)
+ (global-extend
+ 'core
+ 'syntax-parameterize
+ (lambda (e r w s mod)
+ (let* ((tmp e)
+ (tmp ($sc-dispatch tmp '(_ #(each (any any)) any . each-any))))
+ (if (and tmp (apply (lambda (var val e1 e2) (valid-bound-ids? var)) tmp))
+ (apply (lambda (var val e1 e2)
+ (let ((names (map (lambda (x) (id-var-name x w)) var)))
+ (for-each
+ (lambda (id n)
+ (let ((key (car (lookup n r mod))))
+ (if (memv key '(displaced-lexical))
+ (syntax-violation
+ 'syntax-parameterize
+ "identifier out of context"
+ e
+ (source-wrap id w s mod)))))
+ var
+ names)
+ (expand-body
+ (cons e1 e2)
+ (source-wrap e w s mod)
+ (extend-env
+ names
+ (let ((trans-r (macros-only-env r)))
+ (map (lambda (x)
+ (cons 'macro (eval-local-transformer (expand x trans-r w mod) mod)))
+ val))
+ r)
+ w
+ mod)))
+ tmp)
+ (syntax-violation
+ 'syntax-parameterize
+ "bad syntax"
+ (source-wrap e w s mod))))))
+ (global-extend
+ 'core
+ 'quote
+ (lambda (e r w s mod)
+ (let* ((tmp e) (tmp ($sc-dispatch tmp '(_ any))))
+ (if tmp
+ (apply (lambda (e) (build-data s (strip e w))) tmp)
+ (syntax-violation 'quote "bad syntax" (source-wrap e w s mod))))))
+ (global-extend
+ 'core
+ 'syntax
+ (letrec*
+ ((gen-syntax
+ (lambda (src e r maps ellipsis? mod)
+ (if (id? e)
+ (let* ((label (id-var-name e '(()))) (b (lookup label r mod)))
+ (cond ((eq? (car b) 'syntax)
+ (call-with-values
+ (lambda ()
+ (let ((var.lev (cdr b)))
+ (gen-ref src (car var.lev) (cdr var.lev) maps)))
+ (lambda (var maps) (values (list 'ref var) maps))))
+ ((ellipsis? e r mod)
+ (syntax-violation 'syntax "misplaced ellipsis" src))
+ (else (values (list 'quote e) maps))))
+ (let* ((tmp e) (tmp-1 ($sc-dispatch tmp '(any any))))
+ (if (and tmp-1 (apply (lambda (dots e) (ellipsis? dots r mod)) tmp-1))
+ (apply (lambda (dots e) (gen-syntax src e r maps (lambda (e r mod) #f) mod))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any any . any))))
+ (if (and tmp-1 (apply (lambda (x dots y) (ellipsis? dots r mod)) tmp-1))
+ (apply (lambda (x dots y)
+ (let f ((y y)
+ (k (lambda (maps)
+ (call-with-values
+ (lambda () (gen-syntax src x r (cons '() maps) ellipsis? mod))
+ (lambda (x maps)
+ (if (null? (car maps))
+ (syntax-violation 'syntax "extra ellipsis" src)
+ (values (gen-map x (car maps)) (cdr maps))))))))
+ (let* ((tmp y) (tmp ($sc-dispatch tmp '(any . any))))
+ (if (and tmp (apply (lambda (dots y) (ellipsis? dots r mod)) tmp))
+ (apply (lambda (dots y)
+ (f y
+ (lambda (maps)
+ (call-with-values
+ (lambda () (k (cons '() maps)))
+ (lambda (x maps)
+ (if (null? (car maps))
+ (syntax-violation 'syntax "extra ellipsis" src)
+ (values (gen-mappend x (car maps)) (cdr maps))))))))
+ tmp)
+ (call-with-values
+ (lambda () (gen-syntax src y r maps ellipsis? mod))
+ (lambda (y maps)
+ (call-with-values
+ (lambda () (k maps))
+ (lambda (x maps) (values (gen-append x y) maps)))))))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any . any))))
+ (if tmp-1
+ (apply (lambda (x y)
+ (call-with-values
+ (lambda () (gen-syntax src x r maps ellipsis? mod))
+ (lambda (x maps)
+ (call-with-values
+ (lambda () (gen-syntax src y r maps ellipsis? mod))
+ (lambda (y maps) (values (gen-cons x y) maps))))))
+ tmp-1)
+ (let ((tmp ($sc-dispatch tmp '#(vector (any . each-any)))))
+ (if tmp
+ (apply (lambda (e1 e2)
+ (call-with-values
+ (lambda () (gen-syntax src (cons e1 e2) r maps ellipsis? mod))
+ (lambda (e maps) (values (gen-vector e) maps))))
+ tmp)
+ (values (list 'quote e) maps))))))))))))
+ (gen-ref
+ (lambda (src var level maps)
+ (cond ((= level 0) (values var maps))
+ ((null? maps) (syntax-violation 'syntax "missing ellipsis" src))
+ (else
+ (call-with-values
+ (lambda () (gen-ref src var (- level 1) (cdr maps)))
+ (lambda (outer-var outer-maps)
+ (let ((b (assq outer-var (car maps))))
+ (if b
+ (values (cdr b) maps)
+ (let ((inner-var (gen-var 'tmp)))
+ (values
+ inner-var
+ (cons (cons (cons outer-var inner-var) (car maps)) outer-maps)))))))))))
+ (gen-mappend
+ (lambda (e map-env)
+ (list 'apply '(primitive append) (gen-map e map-env))))
+ (gen-map
+ (lambda (e map-env)
+ (let ((formals (map cdr map-env))
+ (actuals (map (lambda (x) (list 'ref (car x))) map-env)))
+ (cond ((eq? (car e) 'ref) (car actuals))
+ ((and-map
+ (lambda (x) (and (eq? (car x) 'ref) (memq (cadr x) formals)))
+ (cdr e))
+ (cons 'map
+ (cons (list 'primitive (car e))
+ (map (let ((r (map cons formals actuals)))
+ (lambda (x) (cdr (assq (cadr x) r))))
+ (cdr e)))))
+ (else (cons 'map (cons (list 'lambda formals e) actuals)))))))
+ (gen-cons
+ (lambda (x y)
+ (let ((key (car y)))
+ (cond ((memv key '(quote))
+ (cond ((eq? (car x) 'quote) (list 'quote (cons (cadr x) (cadr y))))
+ ((eq? (cadr y) '()) (list 'list x))
+ (else (list 'cons x y))))
+ ((memv key '(list)) (cons 'list (cons x (cdr y))))
+ (else (list 'cons x y))))))
+ (gen-append (lambda (x y) (if (equal? y ''()) x (list 'append x y))))
+ (gen-vector
+ (lambda (x)
+ (cond ((eq? (car x) 'list) (cons 'vector (cdr x)))
+ ((eq? (car x) 'quote) (list 'quote (list->vector (cadr x))))
+ (else (list 'list->vector x)))))
+ (regen (lambda (x)
+ (let ((key (car x)))
+ (cond ((memv key '(ref))
+ (build-lexical-reference 'value #f (cadr x) (cadr x)))
+ ((memv key '(primitive)) (build-primref #f (cadr x)))
+ ((memv key '(quote)) (build-data #f (cadr x)))
+ ((memv key '(lambda))
+ (if (list? (cadr x))
+ (build-simple-lambda #f (cadr x) #f (cadr x) '() (regen (caddr x)))
+ (error "how did we get here" x)))
+ (else
+ (build-application #f (build-primref #f (car x)) (map regen (cdr x)))))))))
+ (lambda (e r w s mod)
+ (let* ((e (source-wrap e w s mod))
+ (tmp e)
+ (tmp ($sc-dispatch tmp '(_ any))))
+ (if tmp
+ (apply (lambda (x)
+ (call-with-values
+ (lambda () (gen-syntax e x r '() ellipsis? mod))
+ (lambda (e maps) (regen e))))
+ tmp)
+ (syntax-violation 'syntax "bad `syntax' form" e))))))
+ (global-extend
+ 'core
+ 'lambda
+ (lambda (e r w s mod)
+ (let* ((tmp e) (tmp ($sc-dispatch tmp '(_ any any . each-any))))
+ (if tmp
+ (apply (lambda (args e1 e2)
+ (call-with-values
+ (lambda () (lambda-formals args))
+ (lambda (req opt rest kw)
+ (let lp ((body (cons e1 e2)) (meta '()))
+ (let* ((tmp-1 body) (tmp ($sc-dispatch tmp-1 '(any any . each-any))))
+ (if (and tmp
+ (apply (lambda (docstring e1 e2) (string? (syntax->datum docstring)))
+ tmp))
+ (apply (lambda (docstring e1 e2)
+ (lp (cons e1 e2)
+ (append meta (list (cons 'documentation (syntax->datum docstring))))))
+ tmp)
+ (let ((tmp ($sc-dispatch tmp-1 '(#(vector #(each (any . any))) any . each-any))))
+ (if tmp
+ (apply (lambda (k v e1 e2)
+ (lp (cons e1 e2) (append meta (syntax->datum (map cons k v)))))
+ tmp)
+ (expand-simple-lambda e r w s mod req rest meta body)))))))))
+ tmp)
+ (syntax-violation 'lambda "bad lambda" e)))))
+ (global-extend
+ 'core
+ 'lambda*
+ (lambda (e r w s mod)
+ (let* ((tmp e) (tmp ($sc-dispatch tmp '(_ any any . each-any))))
+ (if tmp
+ (apply (lambda (args e1 e2)
+ (call-with-values
+ (lambda ()
+ (expand-lambda-case
+ e
+ r
+ w
+ s
+ mod
+ lambda*-formals
+ (list (cons args (cons e1 e2)))))
+ (lambda (meta lcase) (build-case-lambda s meta lcase))))
+ tmp)
+ (syntax-violation 'lambda "bad lambda*" e)))))
+ (global-extend
+ 'core
+ 'case-lambda
+ (lambda (e r w s mod)
+ (letrec*
+ ((build-it
+ (lambda (meta clauses)
+ (call-with-values
+ (lambda () (expand-lambda-case e r w s mod lambda-formals clauses))
+ (lambda (meta* lcase)
+ (build-case-lambda s (append meta meta*) lcase))))))
+ (let* ((tmp-1 e)
+ (tmp ($sc-dispatch tmp-1 '(_ . #(each (any any . each-any))))))
+ (if tmp
+ (apply (lambda (args e1 e2)
+ (build-it
+ '()
+ (map (lambda (tmp-bde397a-c50 tmp-bde397a-c4f tmp-bde397a-c4e)
+ (cons tmp-bde397a-c4e (cons tmp-bde397a-c4f tmp-bde397a-c50)))
+ e2
+ e1
+ args)))
+ tmp)
+ (let ((tmp ($sc-dispatch tmp-1 '(_ any . #(each (any any . each-any))))))
+ (if (and tmp
+ (apply (lambda (docstring args e1 e2) (string? (syntax->datum docstring)))
+ tmp))
+ (apply (lambda (docstring args e1 e2)
+ (build-it
+ (list (cons 'documentation (syntax->datum docstring)))
+ (map (lambda (tmp-bde397a-c66 tmp-bde397a-c65 tmp-bde397a-c64)
+ (cons tmp-bde397a-c64 (cons tmp-bde397a-c65 tmp-bde397a-c66)))
+ e2
+ e1
+ args)))
+ tmp)
+ (syntax-violation 'case-lambda "bad case-lambda" e))))))))
+ (global-extend
+ 'core
+ 'case-lambda*
+ (lambda (e r w s mod)
+ (letrec*
+ ((build-it
+ (lambda (meta clauses)
+ (call-with-values
+ (lambda () (expand-lambda-case e r w s mod lambda*-formals clauses))
+ (lambda (meta* lcase)
+ (build-case-lambda s (append meta meta*) lcase))))))
+ (let* ((tmp-1 e)
+ (tmp ($sc-dispatch tmp-1 '(_ . #(each (any any . each-any))))))
+ (if tmp
+ (apply (lambda (args e1 e2)
+ (build-it
+ '()
+ (map (lambda (tmp-bde397a-c86 tmp-bde397a-c85 tmp-bde397a-c84)
+ (cons tmp-bde397a-c84 (cons tmp-bde397a-c85 tmp-bde397a-c86)))
+ e2
+ e1
+ args)))
+ tmp)
+ (let ((tmp ($sc-dispatch tmp-1 '(_ any . #(each (any any . each-any))))))
+ (if (and tmp
+ (apply (lambda (docstring args e1 e2) (string? (syntax->datum docstring)))
+ tmp))
+ (apply (lambda (docstring args e1 e2)
+ (build-it
+ (list (cons 'documentation (syntax->datum docstring)))
+ (map (lambda (tmp-bde397a-c9c tmp-bde397a-c9b tmp-bde397a-c9a)
+ (cons tmp-bde397a-c9a (cons tmp-bde397a-c9b tmp-bde397a-c9c)))
+ e2
+ e1
+ args)))
+ tmp)
+ (syntax-violation 'case-lambda "bad case-lambda*" e))))))))
+ (global-extend
+ 'core
+ 'with-ellipsis
+ (lambda (e r w s mod)
+ (let* ((tmp e) (tmp ($sc-dispatch tmp '(_ any any . each-any))))
+ (if (and tmp (apply (lambda (dots e1 e2) (id? dots)) tmp))
+ (apply (lambda (dots e1 e2)
+ (let ((id (if (symbol? dots)
+ '#{ $sc-ellipsis }
+ (make-syntax-object
+ '#{ $sc-ellipsis }
+ (syntax-object-wrap dots)
+ (syntax-object-module dots)))))
+ (let ((ids (list id))
+ (labels (list (gen-label)))
+ (bindings (list (cons 'ellipsis (source-wrap dots w s mod)))))
+ (let ((nw (make-binding-wrap ids labels w))
+ (nr (extend-env labels bindings r)))
+ (expand-body (cons e1 e2) (source-wrap e nw s mod) nr nw mod)))))
+ tmp)
+ (syntax-violation
+ 'with-ellipsis
+ "bad syntax"
+ (source-wrap e w s mod))))))
+ (global-extend
+ 'core
+ 'let
+ (letrec*
+ ((expand-let
+ (lambda (e r w s mod constructor ids vals exps)
+ (if (not (valid-bound-ids? ids))
+ (syntax-violation 'let "duplicate bound variable" e)
+ (let ((labels (gen-labels ids)) (new-vars (map gen-var ids)))
+ (let ((nw (make-binding-wrap ids labels w))
+ (nr (extend-var-env labels new-vars r)))
+ (constructor
+ s
+ (map syntax->datum ids)
+ new-vars
+ (map (lambda (x) (expand x r w mod)) vals)
+ (expand-body exps (source-wrap e nw s mod) nr nw mod))))))))
+ (lambda (e r w s mod)
+ (let* ((tmp-1 e)
+ (tmp ($sc-dispatch tmp-1 '(_ #(each (any any)) any . each-any))))
+ (if (and tmp (apply (lambda (id val e1 e2) (and-map id? id)) tmp))
+ (apply (lambda (id val e1 e2)
+ (expand-let e r w s mod build-let id val (cons e1 e2)))
+ tmp)
+ (let ((tmp ($sc-dispatch tmp-1 '(_ any #(each (any any)) any . each-any))))
+ (if (and tmp
+ (apply (lambda (f id val e1 e2) (and (id? f) (and-map id? id))) tmp))
+ (apply (lambda (f id val e1 e2)
+ (expand-let e r w s mod build-named-let (cons f id) val (cons e1 e2)))
+ tmp)
+ (syntax-violation 'let "bad let" (source-wrap e w s mod)))))))))
+ (global-extend
+ 'core
+ 'letrec
+ (lambda (e r w s mod)
+ (let* ((tmp e)
+ (tmp ($sc-dispatch tmp '(_ #(each (any any)) any . each-any))))
+ (if (and tmp (apply (lambda (id val e1 e2) (and-map id? id)) tmp))
+ (apply (lambda (id val e1 e2)
+ (let ((ids id))
+ (if (not (valid-bound-ids? ids))
+ (syntax-violation 'letrec "duplicate bound variable" e)
+ (let ((labels (gen-labels ids)) (new-vars (map gen-var ids)))
+ (let ((w (make-binding-wrap ids labels w))
+ (r (extend-var-env labels new-vars r)))
+ (build-letrec
+ s
+ #f
+ (map syntax->datum ids)
+ new-vars
+ (map (lambda (x) (expand x r w mod)) val)
+ (expand-body (cons e1 e2) (source-wrap e w s mod) r w mod)))))))
+ tmp)
+ (syntax-violation 'letrec "bad letrec" (source-wrap e w s mod))))))
+ (global-extend
+ 'core
+ 'letrec*
+ (lambda (e r w s mod)
+ (let* ((tmp e)
+ (tmp ($sc-dispatch tmp '(_ #(each (any any)) any . each-any))))
+ (if (and tmp (apply (lambda (id val e1 e2) (and-map id? id)) tmp))
+ (apply (lambda (id val e1 e2)
+ (let ((ids id))
+ (if (not (valid-bound-ids? ids))
+ (syntax-violation 'letrec* "duplicate bound variable" e)
+ (let ((labels (gen-labels ids)) (new-vars (map gen-var ids)))
+ (let ((w (make-binding-wrap ids labels w))
+ (r (extend-var-env labels new-vars r)))
+ (build-letrec
+ s
+ #t
+ (map syntax->datum ids)
+ new-vars
+ (map (lambda (x) (expand x r w mod)) val)
+ (expand-body (cons e1 e2) (source-wrap e w s mod) r w mod)))))))
+ tmp)
+ (syntax-violation 'letrec* "bad letrec*" (source-wrap e w s mod))))))
+ (global-extend
+ 'core
+ 'set!
+ (lambda (e r w s mod)
+ (let* ((tmp-1 e) (tmp ($sc-dispatch tmp-1 '(_ any any))))
+ (if (and tmp (apply (lambda (id val) (id? id)) tmp))
+ (apply (lambda (id val)
+ (let ((n (id-var-name id w))
+ (id-mod (if (syntax-object? id) (syntax-object-module id) mod)))
+ (let* ((b (lookup n r id-mod)) (key (car b)))
+ (cond ((memv key '(lexical))
+ (build-lexical-assignment
+ s
+ (syntax->datum id)
+ (cdr b)
+ (expand val r w mod)))
+ ((memv key '(global))
+ (build-global-assignment s n (expand val r w mod) id-mod))
+ ((memv key '(macro))
+ (let ((p (cdr b)))
+ (if (procedure-property p 'variable-transformer)
+ (expand (expand-macro p e r w s #f mod) r '(()) mod)
+ (syntax-violation
+ 'set!
+ "not a variable transformer"
+ (wrap e w mod)
+ (wrap id w id-mod)))))
+ ((memv key '(displaced-lexical))
+ (syntax-violation 'set! "identifier out of context" (wrap id w mod)))
+ (else (syntax-violation 'set! "bad set!" (source-wrap e w s mod)))))))
+ tmp)
+ (let ((tmp ($sc-dispatch tmp-1 '(_ (any . each-any) any))))
+ (if tmp
+ (apply (lambda (head tail val)
+ (call-with-values
+ (lambda () (syntax-type head r '(()) #f #f mod #t))
+ (lambda (type value formform ee ww ss modmod)
+ (let ((key type))
+ (if (memv key '(module-ref))
+ (let ((val (expand val r w mod)))
+ (call-with-values
+ (lambda () (value (cons head tail) r w))
+ (lambda (e r w s* mod)
+ (let* ((tmp-1 e) (tmp (list tmp-1)))
+ (if (and tmp (apply (lambda (e) (id? e)) tmp))
+ (apply (lambda (e) (build-global-assignment s (syntax->datum e) val mod))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1))))))
+ (build-application
+ s
+ (expand
+ (list '#(syntax-object setter ((top)) (hygiene guile)) head)
+ r
+ w
+ mod)
+ (map (lambda (e) (expand e r w mod)) (append tail (list val)))))))))
+ tmp)
+ (syntax-violation 'set! "bad set!" (source-wrap e w s mod))))))))
+ (global-extend
+ 'module-ref
+ '@
+ (lambda (e r w)
+ (let* ((tmp-1 e) (tmp ($sc-dispatch tmp-1 '(_ each-any any))))
+ (if (and tmp
+ (apply (lambda (mod id) (and (and-map id? mod) (id? id))) tmp))
+ (apply (lambda (mod id)
+ (values
+ (syntax->datum id)
+ r
+ '((top))
+ #f
+ (syntax->datum
+ (cons '#(syntax-object public ((top)) (hygiene guile)) mod))))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))
+ (global-extend
+ 'module-ref
+ '@@
+ (lambda (e r w)
+ (letrec*
+ ((remodulate
+ (lambda (x mod)
+ (cond ((pair? x) (cons (remodulate (car x) mod) (remodulate (cdr x) mod)))
+ ((syntax-object? x)
+ (make-syntax-object
+ (remodulate (syntax-object-expression x) mod)
+ (syntax-object-wrap x)
+ mod))
+ ((vector? x)
+ (let* ((n (vector-length x)) (v (make-vector n)))
+ (let loop ((i 0))
+ (if (= i n)
+ (begin (if #f #f) v)
+ (begin
+ (vector-set! v i (remodulate (vector-ref x i) mod))
+ (loop (+ i 1)))))))
+ (else x)))))
+ (let* ((tmp-1 e) (tmp ($sc-dispatch tmp-1 '(_ each-any any))))
+ (if (and tmp
+ (apply (lambda (mod id) (and (and-map id? mod) (id? id))) tmp))
+ (apply (lambda (mod id)
+ (values
+ (syntax->datum id)
+ r
+ '((top))
+ #f
+ (syntax->datum
+ (cons '#(syntax-object private ((top)) (hygiene guile)) mod))))
+ tmp)
+ (let ((tmp ($sc-dispatch
+ tmp-1
+ '(_ #(free-id #(syntax-object @@ ((top)) (hygiene guile)))
+ each-any
+ any))))
+ (if (and tmp (apply (lambda (mod exp) (and-map id? mod)) tmp))
+ (apply (lambda (mod exp)
+ (let ((mod (syntax->datum
+ (cons '#(syntax-object private ((top)) (hygiene guile)) mod))))
+ (values (remodulate exp mod) r w (source-annotation exp) mod)))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1))))))))
+ (global-extend
+ 'core
+ 'if
+ (lambda (e r w s mod)
+ (let* ((tmp e) (tmp-1 ($sc-dispatch tmp '(_ any any))))
+ (if tmp-1
+ (apply (lambda (test then)
+ (build-conditional
+ s
+ (expand test r w mod)
+ (expand then r w mod)
+ (build-void #f)))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(_ any any any))))
+ (if tmp-1
+ (apply (lambda (test then else)
+ (build-conditional
+ s
+ (expand test r w mod)
+ (expand then r w mod)
+ (expand else r w mod)))
+ tmp-1)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp)))))))
+ (global-extend
+ 'core
+ 'with-fluids
+ (lambda (e r w s mod)
+ (let* ((tmp-1 e)
+ (tmp ($sc-dispatch tmp-1 '(_ #(each (any any)) any . each-any))))
+ (if tmp
+ (apply (lambda (fluid val b b*)
+ (build-dynlet
+ s
+ (map (lambda (x) (expand x r w mod)) fluid)
+ (map (lambda (x) (expand x r w mod)) val)
+ (expand-body (cons b b*) (source-wrap e w s mod) r w mod)))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))
+ (global-extend 'begin 'begin '())
+ (global-extend 'define 'define '())
+ (global-extend 'define-syntax 'define-syntax '())
+ (global-extend 'define-syntax-parameter 'define-syntax-parameter '())
+ (global-extend 'eval-when 'eval-when '())
+ (global-extend
+ 'core
+ 'syntax-case
+ (letrec*
+ ((convert-pattern
+ (lambda (pattern keys ellipsis?)
+ (letrec*
+ ((cvt* (lambda (p* n ids)
+ (let* ((tmp p*) (tmp ($sc-dispatch tmp '(any . any))))
+ (if tmp
+ (apply (lambda (x y)
+ (call-with-values
+ (lambda () (cvt* y n ids))
+ (lambda (y ids)
+ (call-with-values
+ (lambda () (cvt x n ids))
+ (lambda (x ids) (values (cons x y) ids))))))
+ tmp)
+ (cvt p* n ids)))))
+ (v-reverse
+ (lambda (x)
+ (let loop ((r '()) (x x))
+ (if (not (pair? x)) (values r x) (loop (cons (car x) r) (cdr x))))))
+ (cvt (lambda (p n ids)
+ (if (id? p)
+ (cond ((bound-id-member? p keys) (values (vector 'free-id p) ids))
+ ((free-id=? p '#(syntax-object _ ((top)) (hygiene guile)))
+ (values '_ ids))
+ (else (values 'any (cons (cons p n) ids))))
+ (let* ((tmp p) (tmp-1 ($sc-dispatch tmp '(any any))))
+ (if (and tmp-1 (apply (lambda (x dots) (ellipsis? dots)) tmp-1))
+ (apply (lambda (x dots)
+ (call-with-values
+ (lambda () (cvt x (+ n 1) ids))
+ (lambda (p ids)
+ (values (if (eq? p 'any) 'each-any (vector 'each p)) ids))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any any . any))))
+ (if (and tmp-1 (apply (lambda (x dots ys) (ellipsis? dots)) tmp-1))
+ (apply (lambda (x dots ys)
+ (call-with-values
+ (lambda () (cvt* ys n ids))
+ (lambda (ys ids)
+ (call-with-values
+ (lambda () (cvt x (+ n 1) ids))
+ (lambda (x ids)
+ (call-with-values
+ (lambda () (v-reverse ys))
+ (lambda (ys e) (values (vector 'each+ x ys e) ids))))))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any . any))))
+ (if tmp-1
+ (apply (lambda (x y)
+ (call-with-values
+ (lambda () (cvt y n ids))
+ (lambda (y ids)
+ (call-with-values
+ (lambda () (cvt x n ids))
+ (lambda (x ids) (values (cons x y) ids))))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '())))
+ (if tmp-1
+ (apply (lambda () (values '() ids)) tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '#(vector each-any))))
+ (if tmp-1
+ (apply (lambda (x)
+ (call-with-values
+ (lambda () (cvt x n ids))
+ (lambda (p ids) (values (vector 'vector p) ids))))
+ tmp-1)
+ (let ((x tmp)) (values (vector 'atom (strip p '(()))) ids))))))))))))))))
+ (cvt pattern 0 '()))))
+ (build-dispatch-call
+ (lambda (pvars exp y r mod)
+ (let ((ids (map car pvars)) (levels (map cdr pvars)))
+ (let ((labels (gen-labels ids)) (new-vars (map gen-var ids)))
+ (build-application
+ #f
+ (build-primref #f 'apply)
+ (list (build-simple-lambda
+ #f
+ (map syntax->datum ids)
+ #f
+ new-vars
+ '()
+ (expand
+ exp
+ (extend-env
+ labels
+ (map (lambda (var level) (cons 'syntax (cons var level)))
+ new-vars
+ (map cdr pvars))
+ r)
+ (make-binding-wrap ids labels '(()))
+ mod))
+ y))))))
+ (gen-clause
+ (lambda (x keys clauses r pat fender exp mod)
+ (call-with-values
+ (lambda ()
+ (convert-pattern pat keys (lambda (e) (ellipsis? e r mod))))
+ (lambda (p pvars)
+ (cond ((not (and-map (lambda (x) (not (ellipsis? (car x) r mod))) pvars))
+ (syntax-violation 'syntax-case "misplaced ellipsis" pat))
+ ((not (distinct-bound-ids? (map car pvars)))
+ (syntax-violation 'syntax-case "duplicate pattern variable" pat))
+ (else
+ (let ((y (gen-var 'tmp)))
+ (build-application
+ #f
+ (build-simple-lambda
+ #f
+ (list 'tmp)
+ #f
+ (list y)
+ '()
+ (let ((y (build-lexical-reference 'value #f 'tmp y)))
+ (build-conditional
+ #f
+ (let* ((tmp fender) (tmp ($sc-dispatch tmp '#(atom #t))))
+ (if tmp
+ (apply (lambda () y) tmp)
+ (build-conditional
+ #f
+ y
+ (build-dispatch-call pvars fender y r mod)
+ (build-data #f #f))))
+ (build-dispatch-call pvars exp y r mod)
+ (gen-syntax-case x keys clauses r mod))))
+ (list (if (eq? p 'any)
+ (build-application #f (build-primref #f 'list) (list x))
+ (build-application
+ #f
+ (build-primref #f '$sc-dispatch)
+ (list x (build-data #f p)))))))))))))
+ (gen-syntax-case
+ (lambda (x keys clauses r mod)
+ (if (null? clauses)
+ (build-application
+ #f
+ (build-primref #f 'syntax-violation)
+ (list (build-data #f #f)
+ (build-data #f "source expression failed to match any pattern")
+ x))
+ (let* ((tmp-1 (car clauses)) (tmp ($sc-dispatch tmp-1 '(any any))))
+ (if tmp
+ (apply (lambda (pat exp)
+ (if (and (id? pat)
+ (and-map
+ (lambda (x) (not (free-id=? pat x)))
+ (cons '#(syntax-object ... ((top)) (hygiene guile)) keys)))
+ (if (free-id=? pat '#(syntax-object _ ((top)) (hygiene guile)))
+ (expand exp r '(()) mod)
+ (let ((labels (list (gen-label))) (var (gen-var pat)))
+ (build-application
+ #f
+ (build-simple-lambda
+ #f
+ (list (syntax->datum pat))
+ #f
+ (list var)
+ '()
+ (expand
+ exp
+ (extend-env labels (list (cons 'syntax (cons var 0))) r)
+ (make-binding-wrap (list pat) labels '(()))
+ mod))
+ (list x))))
+ (gen-clause x keys (cdr clauses) r pat #t exp mod)))
+ tmp)
+ (let ((tmp ($sc-dispatch tmp-1 '(any any any))))
+ (if tmp
+ (apply (lambda (pat fender exp)
+ (gen-clause x keys (cdr clauses) r pat fender exp mod))
+ tmp)
+ (syntax-violation 'syntax-case "invalid clause" (car clauses))))))))))
+ (lambda (e r w s mod)
+ (let* ((e (source-wrap e w s mod))
+ (tmp-1 e)
+ (tmp ($sc-dispatch tmp-1 '(_ any each-any . each-any))))
+ (if tmp
+ (apply (lambda (val key m)
+ (if (and-map (lambda (x) (and (id? x) (not (ellipsis? x r mod)))) key)
+ (let ((x (gen-var 'tmp)))
+ (build-application
+ s
+ (build-simple-lambda
+ #f
+ (list 'tmp)
+ #f
+ (list x)
+ '()
+ (gen-syntax-case
+ (build-lexical-reference 'value #f 'tmp x)
+ key
+ m
+ r
+ mod))
+ (list (expand val r '(()) mod))))
+ (syntax-violation 'syntax-case "invalid literals list" e)))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1))))))
+ (set! macroexpand
+ (lambda* (x #\optional (m 'e) (esew '(eval)))
+ (expand-top-sequence
+ (list x)
+ '()
+ '((top))
+ #f
+ m
+ esew
+ (cons 'hygiene (module-name (current-module))))))
+ (set! identifier? (lambda (x) (nonsymbol-id? x)))
+ (set! datum->syntax
+ (lambda (id datum)
+ (make-syntax-object
+ datum
+ (syntax-object-wrap id)
+ (syntax-object-module id))))
+ (set! syntax->datum (lambda (x) (strip x '(()))))
+ (set! syntax-source (lambda (x) (source-annotation x)))
+ (set! generate-temporaries
+ (lambda (ls)
+ (let ((x ls))
+ (if (not (list? x))
+ (syntax-violation 'generate-temporaries "invalid argument" x)))
+ (let ((mod (cons 'hygiene (module-name (current-module)))))
+ (map (lambda (x) (wrap (module-gensym "t") '((top)) mod)) ls))))
+ (set! free-identifier=?
+ (lambda (x y)
+ (let ((x x))
+ (if (not (nonsymbol-id? x))
+ (syntax-violation 'free-identifier=? "invalid argument" x)))
+ (let ((x y))
+ (if (not (nonsymbol-id? x))
+ (syntax-violation 'free-identifier=? "invalid argument" x)))
+ (free-id=? x y)))
+ (set! bound-identifier=?
+ (lambda (x y)
+ (let ((x x))
+ (if (not (nonsymbol-id? x))
+ (syntax-violation 'bound-identifier=? "invalid argument" x)))
+ (let ((x y))
+ (if (not (nonsymbol-id? x))
+ (syntax-violation 'bound-identifier=? "invalid argument" x)))
+ (bound-id=? x y)))
+ (set! syntax-violation
+ (lambda* (who message form #\optional (subform #f))
+ (let ((x who))
+ (if (not (let ((x x)) (or (not x) (string? x) (symbol? x))))
+ (syntax-violation 'syntax-violation "invalid argument" x)))
+ (let ((x message))
+ (if (not (string? x))
+ (syntax-violation 'syntax-violation "invalid argument" x)))
+ (throw 'syntax-error
+ who
+ message
+ (or (source-annotation subform) (source-annotation form))
+ (strip form '(()))
+ (and subform (strip subform '(()))))))
+ (letrec*
+ ((syntax-module
+ (lambda (id)
+ (let ((x id))
+ (if (not (nonsymbol-id? x))
+ (syntax-violation 'syntax-module "invalid argument" x)))
+ (cdr (syntax-object-module id))))
+ (syntax-local-binding
+ (lambda (id)
+ (let ((x id))
+ (if (not (nonsymbol-id? x))
+ (syntax-violation 'syntax-local-binding "invalid argument" x)))
+ (with-transformer-environment
+ (lambda (e r w s rib mod)
+ (letrec*
+ ((strip-anti-mark
+ (lambda (w)
+ (let ((ms (car w)) (s (cdr w)))
+ (if (and (pair? ms) (eq? (car ms) #f))
+ (cons (cdr ms) (if rib (cons rib (cdr s)) (cdr s)))
+ (cons ms (if rib (cons rib s) s)))))))
+ (call-with-values
+ (lambda ()
+ (resolve-identifier
+ (syntax-object-expression id)
+ (strip-anti-mark (syntax-object-wrap id))
+ r
+ (syntax-object-module id)))
+ (lambda (type value mod)
+ (let ((key type))
+ (cond ((memv key '(lexical)) (values 'lexical value))
+ ((memv key '(macro)) (values 'macro value))
+ ((memv key '(syntax)) (values 'pattern-variable value))
+ ((memv key '(displaced-lexical)) (values 'displaced-lexical #f))
+ ((memv key '(global)) (values 'global (cons value (cdr mod))))
+ ((memv key '(ellipsis))
+ (values
+ 'ellipsis
+ (make-syntax-object
+ (syntax-object-expression value)
+ (anti-mark (syntax-object-wrap value))
+ (syntax-object-module value))))
+ (else (values 'other #f)))))))))))
+ (syntax-locally-bound-identifiers
+ (lambda (id)
+ (let ((x id))
+ (if (not (nonsymbol-id? x))
+ (syntax-violation
+ 'syntax-locally-bound-identifiers
+ "invalid argument"
+ x)))
+ (locally-bound-identifiers
+ (syntax-object-wrap id)
+ (syntax-object-module id)))))
+ (define! 'syntax-module syntax-module)
+ (define! 'syntax-local-binding syntax-local-binding)
+ (define!
+ 'syntax-locally-bound-identifiers
+ syntax-locally-bound-identifiers))
+ (letrec*
+ ((match-each
+ (lambda (e p w mod)
+ (cond ((pair? e)
+ (let ((first (match (car e) p w '() mod)))
+ (and first
+ (let ((rest (match-each (cdr e) p w mod)))
+ (and rest (cons first rest))))))
+ ((null? e) '())
+ ((syntax-object? e)
+ (match-each
+ (syntax-object-expression e)
+ p
+ (join-wraps w (syntax-object-wrap e))
+ (syntax-object-module e)))
+ (else #f))))
+ (match-each+
+ (lambda (e x-pat y-pat z-pat w r mod)
+ (let f ((e e) (w w))
+ (cond ((pair? e)
+ (call-with-values
+ (lambda () (f (cdr e) w))
+ (lambda (xr* y-pat r)
+ (if r
+ (if (null? y-pat)
+ (let ((xr (match (car e) x-pat w '() mod)))
+ (if xr (values (cons xr xr*) y-pat r) (values #f #f #f)))
+ (values '() (cdr y-pat) (match (car e) (car y-pat) w r mod)))
+ (values #f #f #f)))))
+ ((syntax-object? e)
+ (f (syntax-object-expression e)
+ (join-wraps w (syntax-object-wrap e))))
+ (else (values '() y-pat (match e z-pat w r mod)))))))
+ (match-each-any
+ (lambda (e w mod)
+ (cond ((pair? e)
+ (let ((l (match-each-any (cdr e) w mod)))
+ (and l (cons (wrap (car e) w mod) l))))
+ ((null? e) '())
+ ((syntax-object? e)
+ (match-each-any
+ (syntax-object-expression e)
+ (join-wraps w (syntax-object-wrap e))
+ mod))
+ (else #f))))
+ (match-empty
+ (lambda (p r)
+ (cond ((null? p) r)
+ ((eq? p '_) r)
+ ((eq? p 'any) (cons '() r))
+ ((pair? p) (match-empty (car p) (match-empty (cdr p) r)))
+ ((eq? p 'each-any) (cons '() r))
+ (else
+ (let ((key (vector-ref p 0)))
+ (cond ((memv key '(each)) (match-empty (vector-ref p 1) r))
+ ((memv key '(each+))
+ (match-empty
+ (vector-ref p 1)
+ (match-empty
+ (reverse (vector-ref p 2))
+ (match-empty (vector-ref p 3) r))))
+ ((memv key '(free-id atom)) r)
+ ((memv key '(vector)) (match-empty (vector-ref p 1) r))))))))
+ (combine
+ (lambda (r* r)
+ (if (null? (car r*)) r (cons (map car r*) (combine (map cdr r*) r)))))
+ (match*
+ (lambda (e p w r mod)
+ (cond ((null? p) (and (null? e) r))
+ ((pair? p)
+ (and (pair? e)
+ (match (car e) (car p) w (match (cdr e) (cdr p) w r mod) mod)))
+ ((eq? p 'each-any)
+ (let ((l (match-each-any e w mod))) (and l (cons l r))))
+ (else
+ (let ((key (vector-ref p 0)))
+ (cond ((memv key '(each))
+ (if (null? e)
+ (match-empty (vector-ref p 1) r)
+ (let ((l (match-each e (vector-ref p 1) w mod)))
+ (and l
+ (let collect ((l l))
+ (if (null? (car l)) r (cons (map car l) (collect (map cdr l)))))))))
+ ((memv key '(each+))
+ (call-with-values
+ (lambda ()
+ (match-each+
+ e
+ (vector-ref p 1)
+ (vector-ref p 2)
+ (vector-ref p 3)
+ w
+ r
+ mod))
+ (lambda (xr* y-pat r)
+ (and r
+ (null? y-pat)
+ (if (null? xr*) (match-empty (vector-ref p 1) r) (combine xr* r))))))
+ ((memv key '(free-id))
+ (and (id? e) (free-id=? (wrap e w mod) (vector-ref p 1)) r))
+ ((memv key '(atom)) (and (equal? (vector-ref p 1) (strip e w)) r))
+ ((memv key '(vector))
+ (and (vector? e) (match (vector->list e) (vector-ref p 1) w r mod)))))))))
+ (match (lambda (e p w r mod)
+ (cond ((not r) #f)
+ ((eq? p '_) r)
+ ((eq? p 'any) (cons (wrap e w mod) r))
+ ((syntax-object? e)
+ (match*
+ (syntax-object-expression e)
+ p
+ (join-wraps w (syntax-object-wrap e))
+ r
+ (syntax-object-module e)))
+ (else (match* e p w r mod))))))
+ (set! $sc-dispatch
+ (lambda (e p)
+ (cond ((eq? p 'any) (list e))
+ ((eq? p '_) '())
+ ((syntax-object? e)
+ (match*
+ (syntax-object-expression e)
+ p
+ (syntax-object-wrap e)
+ '()
+ (syntax-object-module e)))
+ (else (match* e p '(()) '() #f)))))))
+
+(define with-syntax
+ (make-syntax-transformer
+ 'with-syntax
+ 'macro
+ (lambda (x)
+ (let ((tmp x))
+ (let ((tmp-1 ($sc-dispatch tmp '(_ () any . each-any))))
+ (if tmp-1
+ (apply (lambda (e1 e2)
+ (cons '#(syntax-object let ((top)) (hygiene guile))
+ (cons '() (cons e1 e2))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(_ ((any any)) any . each-any))))
+ (if tmp-1
+ (apply (lambda (out in e1 e2)
+ (list '#(syntax-object syntax-case ((top)) (hygiene guile))
+ in
+ '()
+ (list out
+ (cons '#(syntax-object let ((top)) (hygiene guile))
+ (cons '() (cons e1 e2))))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(_ #(each (any any)) any . each-any))))
+ (if tmp-1
+ (apply (lambda (out in e1 e2)
+ (list '#(syntax-object syntax-case ((top)) (hygiene guile))
+ (cons '#(syntax-object list ((top)) (hygiene guile)) in)
+ '()
+ (list out
+ (cons '#(syntax-object let ((top)) (hygiene guile))
+ (cons '() (cons e1 e2))))))
+ tmp-1)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp)))))))))))
+
+(define syntax-error
+ (make-syntax-transformer
+ 'syntax-error
+ 'macro
+ (lambda (x)
+ (let ((tmp-1 x))
+ (let ((tmp ($sc-dispatch tmp-1 '(_ (any . any) any . each-any))))
+ (if (if tmp
+ (apply (lambda (keyword operands message arg)
+ (string? (syntax->datum message)))
+ tmp)
+ #f)
+ (apply (lambda (keyword operands message arg)
+ (syntax-violation
+ (syntax->datum keyword)
+ (string-join
+ (cons (syntax->datum message)
+ (map (lambda (x) (object->string (syntax->datum x))) arg)))
+ (if (syntax->datum keyword) (cons keyword operands) #f)))
+ tmp)
+ (let ((tmp ($sc-dispatch tmp-1 '(_ any . each-any))))
+ (if (if tmp
+ (apply (lambda (message arg) (string? (syntax->datum message))) tmp)
+ #f)
+ (apply (lambda (message arg)
+ (cons '#(syntax-object syntax-error ((top)) (hygiene guile))
+ (cons '(#f) (cons message arg))))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))))))
+
+(define syntax-rules
+ (make-syntax-transformer
+ 'syntax-rules
+ 'macro
+ (lambda (xx)
+ (letrec*
+ ((expand-clause
+ (lambda (clause)
+ (let ((tmp-1 clause))
+ (let ((tmp ($sc-dispatch
+ tmp-1
+ '((any . any)
+ (#(free-id #(syntax-object syntax-error ((top)) (hygiene guile)))
+ any
+ .
+ each-any)))))
+ (if (if tmp
+ (apply (lambda (keyword pattern message arg)
+ (string? (syntax->datum message)))
+ tmp)
+ #f)
+ (apply (lambda (keyword pattern message arg)
+ (list (cons '#(syntax-object dummy ((top)) (hygiene guile)) pattern)
+ (list '#(syntax-object syntax ((top)) (hygiene guile))
+ (cons '#(syntax-object syntax-error ((top)) (hygiene guile))
+ (cons (cons '#(syntax-object dummy ((top)) (hygiene guile)) pattern)
+ (cons message arg))))))
+ tmp)
+ (let ((tmp ($sc-dispatch tmp-1 '((any . any) any))))
+ (if tmp
+ (apply (lambda (keyword pattern template)
+ (list (cons '#(syntax-object dummy ((top)) (hygiene guile)) pattern)
+ (list '#(syntax-object syntax ((top)) (hygiene guile)) template)))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1))))))))
+ (expand-syntax-rules
+ (lambda (dots keys docstrings clauses)
+ (let ((tmp-1 (list keys docstrings clauses (map expand-clause clauses))))
+ (let ((tmp ($sc-dispatch
+ tmp-1
+ '(each-any each-any #(each ((any . any) any)) each-any))))
+ (if tmp
+ (apply (lambda (k docstring keyword pattern template clause)
+ (let ((tmp (cons '#(syntax-object lambda ((top)) (hygiene guile))
+ (cons '(#(syntax-object x ((top)) (hygiene guile)))
+ (append
+ docstring
+ (list (vector
+ '(#(syntax-object macro-type ((top)) (hygiene guile))
+ .
+ #(syntax-object syntax-rules ((top)) (hygiene guile)))
+ (cons '#(syntax-object patterns ((top)) (hygiene guile))
+ pattern))
+ (cons '#(syntax-object syntax-case ((top)) (hygiene guile))
+ (cons '#(syntax-object x ((top)) (hygiene guile))
+ (cons k clause)))))))))
+ (let ((form tmp))
+ (if dots
+ (let ((tmp dots))
+ (let ((dots tmp))
+ (list '#(syntax-object with-ellipsis ((top)) (hygiene guile))
+ dots
+ form)))
+ form))))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))))
+ (let ((tmp xx))
+ (let ((tmp-1 ($sc-dispatch tmp '(_ each-any . #(each ((any . any) any))))))
+ (if tmp-1
+ (apply (lambda (k keyword pattern template)
+ (expand-syntax-rules
+ #f
+ k
+ '()
+ (map (lambda (tmp-bde397a-10fd tmp-bde397a-10fc tmp-bde397a-10fb)
+ (list (cons tmp-bde397a-10fb tmp-bde397a-10fc) tmp-bde397a-10fd))
+ template
+ pattern
+ keyword)))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(_ each-any any . #(each ((any . any) any))))))
+ (if (if tmp-1
+ (apply (lambda (k docstring keyword pattern template)
+ (string? (syntax->datum docstring)))
+ tmp-1)
+ #f)
+ (apply (lambda (k docstring keyword pattern template)
+ (expand-syntax-rules
+ #f
+ k
+ (list docstring)
+ (map (lambda (tmp-bde397a-2 tmp-bde397a-1 tmp-bde397a)
+ (list (cons tmp-bde397a tmp-bde397a-1) tmp-bde397a-2))
+ template
+ pattern
+ keyword)))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(_ any each-any . #(each ((any . any) any))))))
+ (if (if tmp-1
+ (apply (lambda (dots k keyword pattern template) (identifier? dots))
+ tmp-1)
+ #f)
+ (apply (lambda (dots k keyword pattern template)
+ (expand-syntax-rules
+ dots
+ k
+ '()
+ (map (lambda (tmp-bde397a-112f tmp-bde397a-112e tmp-bde397a-112d)
+ (list (cons tmp-bde397a-112d tmp-bde397a-112e) tmp-bde397a-112f))
+ template
+ pattern
+ keyword)))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(_ any each-any any . #(each ((any . any) any))))))
+ (if (if tmp-1
+ (apply (lambda (dots k docstring keyword pattern template)
+ (if (identifier? dots) (string? (syntax->datum docstring)) #f))
+ tmp-1)
+ #f)
+ (apply (lambda (dots k docstring keyword pattern template)
+ (expand-syntax-rules
+ dots
+ k
+ (list docstring)
+ (map (lambda (tmp-bde397a-114e tmp-bde397a-114d tmp-bde397a-114c)
+ (list (cons tmp-bde397a-114c tmp-bde397a-114d) tmp-bde397a-114e))
+ template
+ pattern
+ keyword)))
+ tmp-1)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp))))))))))))))
+
+(define define-syntax-rule
+ (make-syntax-transformer
+ 'define-syntax-rule
+ 'macro
+ (lambda (x)
+ (let ((tmp-1 x))
+ (let ((tmp ($sc-dispatch tmp-1 '(_ (any . any) any))))
+ (if tmp
+ (apply (lambda (name pattern template)
+ (list '#(syntax-object define-syntax ((top)) (hygiene guile))
+ name
+ (list '#(syntax-object syntax-rules ((top)) (hygiene guile))
+ '()
+ (list (cons '#(syntax-object _ ((top)) (hygiene guile)) pattern)
+ template))))
+ tmp)
+ (let ((tmp ($sc-dispatch tmp-1 '(_ (any . any) any any))))
+ (if (if tmp
+ (apply (lambda (name pattern docstring template)
+ (string? (syntax->datum docstring)))
+ tmp)
+ #f)
+ (apply (lambda (name pattern docstring template)
+ (list '#(syntax-object define-syntax ((top)) (hygiene guile))
+ name
+ (list '#(syntax-object syntax-rules ((top)) (hygiene guile))
+ '()
+ docstring
+ (list (cons '#(syntax-object _ ((top)) (hygiene guile)) pattern)
+ template))))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))))))
+
+(define let*
+ (make-syntax-transformer
+ 'let*
+ 'macro
+ (lambda (x)
+ (let ((tmp-1 x))
+ (let ((tmp ($sc-dispatch tmp-1 '(any #(each (any any)) any . each-any))))
+ (if (if tmp
+ (apply (lambda (let* x v e1 e2) (and-map identifier? x)) tmp)
+ #f)
+ (apply (lambda (let* x v e1 e2)
+ (let f ((bindings (map list x v)))
+ (if (null? bindings)
+ (cons '#(syntax-object let ((top)) (hygiene guile))
+ (cons '() (cons e1 e2)))
+ (let ((tmp-1 (list (f (cdr bindings)) (car bindings))))
+ (let ((tmp ($sc-dispatch tmp-1 '(any any))))
+ (if tmp
+ (apply (lambda (body binding)
+ (list '#(syntax-object let ((top)) (hygiene guile))
+ (list binding)
+ body))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))))
+
+(define quasiquote
+ (make-syntax-transformer
+ 'quasiquote
+ 'macro
+ (letrec*
+ ((quasi (lambda (p lev)
+ (let ((tmp p))
+ (let ((tmp-1 ($sc-dispatch
+ tmp
+ '(#(free-id #(syntax-object unquote ((top)) (hygiene guile))) any))))
+ (if tmp-1
+ (apply (lambda (p)
+ (if (= lev 0)
+ (list "value" p)
+ (quasicons
+ '("quote" #(syntax-object unquote ((top)) (hygiene guile)))
+ (quasi (list p) (- lev 1)))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch
+ tmp
+ '(#(free-id #(syntax-object quasiquote ((top)) (hygiene guile))) any))))
+ (if tmp-1
+ (apply (lambda (p)
+ (quasicons
+ '("quote" #(syntax-object quasiquote ((top)) (hygiene guile)))
+ (quasi (list p) (+ lev 1))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(any . any))))
+ (if tmp-1
+ (apply (lambda (p q)
+ (let ((tmp-1 p))
+ (let ((tmp ($sc-dispatch
+ tmp-1
+ '(#(free-id #(syntax-object unquote ((top)) (hygiene guile)))
+ .
+ each-any))))
+ (if tmp
+ (apply (lambda (p)
+ (if (= lev 0)
+ (quasilist*
+ (map (lambda (tmp-bde397a-11b3)
+ (list "value" tmp-bde397a-11b3))
+ p)
+ (quasi q lev))
+ (quasicons
+ (quasicons
+ '("quote" #(syntax-object unquote ((top)) (hygiene guile)))
+ (quasi p (- lev 1)))
+ (quasi q lev))))
+ tmp)
+ (let ((tmp ($sc-dispatch
+ tmp-1
+ '(#(free-id
+ #(syntax-object unquote-splicing ((top)) (hygiene guile)))
+ .
+ each-any))))
+ (if tmp
+ (apply (lambda (p)
+ (if (= lev 0)
+ (quasiappend
+ (map (lambda (tmp-bde397a-11b8)
+ (list "value" tmp-bde397a-11b8))
+ p)
+ (quasi q lev))
+ (quasicons
+ (quasicons
+ '("quote"
+ #(syntax-object
+ unquote-splicing
+ ((top))
+ (hygiene guile)))
+ (quasi p (- lev 1)))
+ (quasi q lev))))
+ tmp)
+ (quasicons (quasi p lev) (quasi q lev))))))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '#(vector each-any))))
+ (if tmp-1
+ (apply (lambda (x) (quasivector (vquasi x lev))) tmp-1)
+ (let ((p tmp)) (list "quote" p)))))))))))))
+ (vquasi
+ (lambda (p lev)
+ (let ((tmp p))
+ (let ((tmp-1 ($sc-dispatch tmp '(any . any))))
+ (if tmp-1
+ (apply (lambda (p q)
+ (let ((tmp-1 p))
+ (let ((tmp ($sc-dispatch
+ tmp-1
+ '(#(free-id #(syntax-object unquote ((top)) (hygiene guile)))
+ .
+ each-any))))
+ (if tmp
+ (apply (lambda (p)
+ (if (= lev 0)
+ (quasilist*
+ (map (lambda (tmp-bde397a-11ce) (list "value" tmp-bde397a-11ce)) p)
+ (vquasi q lev))
+ (quasicons
+ (quasicons
+ '("quote" #(syntax-object unquote ((top)) (hygiene guile)))
+ (quasi p (- lev 1)))
+ (vquasi q lev))))
+ tmp)
+ (let ((tmp ($sc-dispatch
+ tmp-1
+ '(#(free-id #(syntax-object unquote-splicing ((top)) (hygiene guile)))
+ .
+ each-any))))
+ (if tmp
+ (apply (lambda (p)
+ (if (= lev 0)
+ (quasiappend
+ (map (lambda (tmp-bde397a-11d3) (list "value" tmp-bde397a-11d3)) p)
+ (vquasi q lev))
+ (quasicons
+ (quasicons
+ '("quote" #(syntax-object unquote-splicing ((top)) (hygiene guile)))
+ (quasi p (- lev 1)))
+ (vquasi q lev))))
+ tmp)
+ (quasicons (quasi p lev) (vquasi q lev))))))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '())))
+ (if tmp-1
+ (apply (lambda () '("quote" ())) tmp-1)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp))))))))
+ (quasicons
+ (lambda (x y)
+ (let ((tmp-1 (list x y)))
+ (let ((tmp ($sc-dispatch tmp-1 '(any any))))
+ (if tmp
+ (apply (lambda (x y)
+ (let ((tmp y))
+ (let ((tmp-1 ($sc-dispatch tmp '(#(atom "quote") any))))
+ (if tmp-1
+ (apply (lambda (dy)
+ (let ((tmp x))
+ (let ((tmp ($sc-dispatch tmp '(#(atom "quote") any))))
+ (if tmp
+ (apply (lambda (dx) (list "quote" (cons dx dy))) tmp)
+ (if (null? dy) (list "list" x) (list "list*" x y))))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(#(atom "list") . any))))
+ (if tmp-1
+ (apply (lambda (stuff) (cons "list" (cons x stuff))) tmp-1)
+ (let ((tmp ($sc-dispatch tmp '(#(atom "list*") . any))))
+ (if tmp
+ (apply (lambda (stuff) (cons "list*" (cons x stuff))) tmp)
+ (list "list*" x y)))))))))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1))))))
+ (quasiappend
+ (lambda (x y)
+ (let ((tmp y))
+ (let ((tmp ($sc-dispatch tmp '(#(atom "quote") ()))))
+ (if tmp
+ (apply (lambda ()
+ (if (null? x)
+ '("quote" ())
+ (if (null? (cdr x))
+ (car x)
+ (let ((tmp-1 x))
+ (let ((tmp ($sc-dispatch tmp-1 'each-any)))
+ (if tmp
+ (apply (lambda (p) (cons "append" p)) tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))))
+ tmp)
+ (if (null? x)
+ y
+ (let ((tmp-1 (list x y)))
+ (let ((tmp ($sc-dispatch tmp-1 '(each-any any))))
+ (if tmp
+ (apply (lambda (p y) (cons "append" (append p (list y)))) tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1))))))))))
+ (quasilist*
+ (lambda (x y)
+ (let f ((x x)) (if (null? x) y (quasicons (car x) (f (cdr x)))))))
+ (quasivector
+ (lambda (x)
+ (let ((tmp x))
+ (let ((tmp ($sc-dispatch tmp '(#(atom "quote") each-any))))
+ (if tmp
+ (apply (lambda (x) (list "quote" (list->vector x))) tmp)
+ (let f ((y x)
+ (k (lambda (ls)
+ (let ((tmp-1 ls))
+ (let ((tmp ($sc-dispatch tmp-1 'each-any)))
+ (if tmp
+ (apply (lambda (t-bde397a-121c) (cons "vector" t-bde397a-121c)) tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))))
+ (let ((tmp y))
+ (let ((tmp-1 ($sc-dispatch tmp '(#(atom "quote") each-any))))
+ (if tmp-1
+ (apply (lambda (y)
+ (k (map (lambda (tmp-bde397a) (list "quote" tmp-bde397a)) y)))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(#(atom "list") . each-any))))
+ (if tmp-1
+ (apply (lambda (y) (k y)) tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(#(atom "list*") . #(each+ any (any) ())))))
+ (if tmp-1
+ (apply (lambda (y z) (f z (lambda (ls) (k (append y ls))))) tmp-1)
+ (let ((else tmp))
+ (let ((tmp x))
+ (let ((t-bde397a tmp)) (list "list->vector" t-bde397a)))))))))))))))))
+ (emit (lambda (x)
+ (let ((tmp x))
+ (let ((tmp-1 ($sc-dispatch tmp '(#(atom "quote") any))))
+ (if tmp-1
+ (apply (lambda (x) (list '#(syntax-object quote ((top)) (hygiene guile)) x))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(#(atom "list") . each-any))))
+ (if tmp-1
+ (apply (lambda (x)
+ (let ((tmp-1 (map emit x)))
+ (let ((tmp ($sc-dispatch tmp-1 'each-any)))
+ (if tmp
+ (apply (lambda (t-bde397a)
+ (cons '#(syntax-object list ((top)) (hygiene guile)) t-bde397a))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(#(atom "list*") . #(each+ any (any) ())))))
+ (if tmp-1
+ (apply (lambda (x y)
+ (let f ((x* x))
+ (if (null? x*)
+ (emit y)
+ (let ((tmp-1 (list (emit (car x*)) (f (cdr x*)))))
+ (let ((tmp ($sc-dispatch tmp-1 '(any any))))
+ (if tmp
+ (apply (lambda (t-bde397a-125a t-bde397a)
+ (list '#(syntax-object cons ((top)) (hygiene guile))
+ t-bde397a-125a
+ t-bde397a))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(#(atom "append") . each-any))))
+ (if tmp-1
+ (apply (lambda (x)
+ (let ((tmp-1 (map emit x)))
+ (let ((tmp ($sc-dispatch tmp-1 'each-any)))
+ (if tmp
+ (apply (lambda (t-bde397a)
+ (cons '#(syntax-object append ((top)) (hygiene guile))
+ t-bde397a))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(#(atom "vector") . each-any))))
+ (if tmp-1
+ (apply (lambda (x)
+ (let ((tmp-1 (map emit x)))
+ (let ((tmp ($sc-dispatch tmp-1 'each-any)))
+ (if tmp
+ (apply (lambda (t-bde397a)
+ (cons '#(syntax-object vector ((top)) (hygiene guile))
+ t-bde397a))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(#(atom "list->vector") any))))
+ (if tmp-1
+ (apply (lambda (x)
+ (let ((tmp (emit x)))
+ (let ((t-bde397a-127e tmp))
+ (list '#(syntax-object list->vector ((top)) (hygiene guile))
+ t-bde397a-127e))))
+ tmp-1)
+ (let ((tmp-1 ($sc-dispatch tmp '(#(atom "value") any))))
+ (if tmp-1
+ (apply (lambda (x) x) tmp-1)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp)))))))))))))))))))
+ (lambda (x)
+ (let ((tmp-1 x))
+ (let ((tmp ($sc-dispatch tmp-1 '(_ any))))
+ (if tmp
+ (apply (lambda (e) (emit (quasi e 0))) tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1))))))))
+
+(define include
+ (make-syntax-transformer
+ 'include
+ 'macro
+ (lambda (x)
+ (letrec*
+ ((read-file
+ (lambda (fn dir k)
+ (let ((p (open-input-file
+ (if (absolute-file-name? fn)
+ fn
+ (if dir
+ (in-vicinity dir fn)
+ (syntax-violation
+ 'include
+ "relative file name only allowed when the include form is in a file"
+ x))))))
+ (let ((enc (file-encoding p)))
+ (set-port-encoding! p (let ((t enc)) (if t t "UTF-8")))
+ (let f ((x (read p)) (result '()))
+ (if (eof-object? x)
+ (begin (close-input-port p) (reverse result))
+ (f (read p) (cons (datum->syntax k x) result)))))))))
+ (let ((src (syntax-source x)))
+ (let ((file (if src (assq-ref src 'filename) #f)))
+ (let ((dir (if (string? file) (dirname file) #f)))
+ (let ((tmp-1 x))
+ (let ((tmp ($sc-dispatch tmp-1 '(any any))))
+ (if tmp
+ (apply (lambda (k filename)
+ (let ((fn (syntax->datum filename)))
+ (let ((tmp-1 (read-file fn dir filename)))
+ (let ((tmp ($sc-dispatch tmp-1 'each-any)))
+ (if tmp
+ (apply (lambda (exp)
+ (cons '#(syntax-object begin ((top)) (hygiene guile)) exp))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1))))))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))))))))
+
+(define include-from-path
+ (make-syntax-transformer
+ 'include-from-path
+ 'macro
+ (lambda (x)
+ (let ((tmp-1 x))
+ (let ((tmp ($sc-dispatch tmp-1 '(any any))))
+ (if tmp
+ (apply (lambda (k filename)
+ (let ((fn (syntax->datum filename)))
+ (let ((tmp (datum->syntax
+ filename
+ (let ((t (%search-load-path fn)))
+ (if t
+ t
+ (syntax-violation
+ 'include-from-path
+ "file not found in path"
+ x
+ filename))))))
+ (let ((fn tmp))
+ (list '#(syntax-object include ((top)) (hygiene guile)) fn)))))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))))
+
+(define unquote
+ (make-syntax-transformer
+ 'unquote
+ 'macro
+ (lambda (x)
+ (syntax-violation
+ 'unquote
+ "expression not valid outside of quasiquote"
+ x))))
+
+(define unquote-splicing
+ (make-syntax-transformer
+ 'unquote-splicing
+ 'macro
+ (lambda (x)
+ (syntax-violation
+ 'unquote-splicing
+ "expression not valid outside of quasiquote"
+ x))))
+
+(define make-variable-transformer
+ (lambda (proc)
+ (if (procedure? proc)
+ (let ((trans (lambda (x) (proc x))))
+ (set-procedure-property! trans 'variable-transformer #t)
+ trans)
+ (error "variable transformer not a procedure" proc))))
+
+(define identifier-syntax
+ (make-syntax-transformer
+ 'identifier-syntax
+ 'macro
+ (lambda (xx)
+ (let ((tmp-1 xx))
+ (let ((tmp ($sc-dispatch tmp-1 '(_ any))))
+ (if tmp
+ (apply (lambda (e)
+ (list '#(syntax-object lambda ((top)) (hygiene guile))
+ '(#(syntax-object x ((top)) (hygiene guile)))
+ '#((#(syntax-object macro-type ((top)) (hygiene guile))
+ .
+ #(syntax-object identifier-syntax ((top)) (hygiene guile))))
+ (list '#(syntax-object syntax-case ((top)) (hygiene guile))
+ '#(syntax-object x ((top)) (hygiene guile))
+ '()
+ (list '#(syntax-object id ((top)) (hygiene guile))
+ '(#(syntax-object identifier? ((top)) (hygiene guile))
+ (#(syntax-object syntax ((top)) (hygiene guile))
+ #(syntax-object id ((top)) (hygiene guile))))
+ (list '#(syntax-object syntax ((top)) (hygiene guile)) e))
+ (list '(#(syntax-object _ ((top)) (hygiene guile))
+ #(syntax-object x ((top)) (hygiene guile))
+ #(syntax-object ... ((top)) (hygiene guile)))
+ (list '#(syntax-object syntax ((top)) (hygiene guile))
+ (cons e
+ '(#(syntax-object x ((top)) (hygiene guile))
+ #(syntax-object ... ((top)) (hygiene guile)))))))))
+ tmp)
+ (let ((tmp ($sc-dispatch
+ tmp-1
+ '(_ (any any)
+ ((#(free-id #(syntax-object set! ((top)) (hygiene guile))) any any)
+ any)))))
+ (if (if tmp
+ (apply (lambda (id exp1 var val exp2)
+ (if (identifier? id) (identifier? var) #f))
+ tmp)
+ #f)
+ (apply (lambda (id exp1 var val exp2)
+ (list '#(syntax-object make-variable-transformer ((top)) (hygiene guile))
+ (list '#(syntax-object lambda ((top)) (hygiene guile))
+ '(#(syntax-object x ((top)) (hygiene guile)))
+ '#((#(syntax-object macro-type ((top)) (hygiene guile))
+ .
+ #(syntax-object variable-transformer ((top)) (hygiene guile))))
+ (list '#(syntax-object syntax-case ((top)) (hygiene guile))
+ '#(syntax-object x ((top)) (hygiene guile))
+ '(#(syntax-object set! ((top)) (hygiene guile)))
+ (list (list '#(syntax-object set! ((top)) (hygiene guile)) var val)
+ (list '#(syntax-object syntax ((top)) (hygiene guile)) exp2))
+ (list (cons id
+ '(#(syntax-object x ((top)) (hygiene guile))
+ #(syntax-object ... ((top)) (hygiene guile))))
+ (list '#(syntax-object syntax ((top)) (hygiene guile))
+ (cons exp1
+ '(#(syntax-object x ((top)) (hygiene guile))
+ #(syntax-object ... ((top)) (hygiene guile))))))
+ (list id
+ (list '#(syntax-object identifier? ((top)) (hygiene guile))
+ (list '#(syntax-object syntax ((top)) (hygiene guile)) id))
+ (list '#(syntax-object syntax ((top)) (hygiene guile)) exp1))))))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))))))
+
+(define define*
+ (make-syntax-transformer
+ 'define*
+ 'macro
+ (lambda (x)
+ (let ((tmp-1 x))
+ (let ((tmp ($sc-dispatch tmp-1 '(_ (any . any) any . each-any))))
+ (if tmp
+ (apply (lambda (id args b0 b1)
+ (list '#(syntax-object define ((top)) (hygiene guile))
+ id
+ (cons '#(syntax-object lambda* ((top)) (hygiene guile))
+ (cons args (cons b0 b1)))))
+ tmp)
+ (let ((tmp ($sc-dispatch tmp-1 '(_ any any))))
+ (if (if tmp (apply (lambda (id val) (identifier? id)) tmp) #f)
+ (apply (lambda (id val)
+ (list '#(syntax-object define ((top)) (hygiene guile)) id val))
+ tmp)
+ (syntax-violation
+ #f
+ "source expression failed to match any pattern"
+ tmp-1)))))))))
+
+;;;; -*-scheme-*-
+;;;;
+;;;; Copyright (C) 2001, 2003, 2006, 2009, 2010, 2011,
+;;;; 2012, 2013, 2016 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+;;; Portable implementation of syntax-case
+;;; Originally extracted from Chez Scheme Version 5.9f
+;;; Authors: R. Kent Dybvig, Oscar Waddell, Bob Hieb, Carl Bruggeman
+
+;;; Copyright (c) 1992-1997 Cadence Research Systems
+;;; Permission to copy this software, in whole or in part, to use this
+;;; software for any lawful purpose, and to redistribute this software
+;;; is granted subject to the restriction that all copies made of this
+;;; software must include this copyright notice in full. This software
+;;; is provided AS IS, with NO WARRANTY, EITHER EXPRESS OR IMPLIED,
+;;; INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY
+;;; OR FITNESS FOR ANY PARTICULAR PURPOSE. IN NO EVENT SHALL THE
+;;; AUTHORS BE LIABLE FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES OF ANY
+;;; NATURE WHATSOEVER.
+
+;;; Modified by Mikael Djurfeldt <djurfeldt@nada.kth.se> according
+;;; to the ChangeLog distributed in the same directory as this file:
+;;; 1997-08-19, 1997-09-03, 1997-09-10, 2000-08-13, 2000-08-24,
+;;; 2000-09-12, 2001-03-08
+
+;;; Modified by Andy Wingo <wingo@pobox.com> according to the Git
+;;; revision control logs corresponding to this file: 2009, 2010.
+
+;;; Modified by Mark H Weaver <mhw@netris.org> according to the Git
+;;; revision control logs corresponding to this file: 2012, 2013.
+
+
+;;; This code is based on "Syntax Abstraction in Scheme"
+;;; by R. Kent Dybvig, Robert Hieb, and Carl Bruggeman.
+;;; Lisp and Symbolic Computation 5:4, 295-326, 1992.
+;;; <http://www.cs.indiana.edu/~dyb/pubs/LaSC-5-4-pp295-326.pdf>
+
+
+;;; This file defines the syntax-case expander, macroexpand, and a set
+;;; of associated syntactic forms and procedures. Of these, the
+;;; following are documented in The Scheme Programming Language,
+;;; Fourth Edition (R. Kent Dybvig, MIT Press, 2009), and in the
+;;; R6RS:
+;;;
+;;; bound-identifier=?
+;;; datum->syntax
+;;; define-syntax
+;;; syntax-parameterize
+;;; free-identifier=?
+;;; generate-temporaries
+;;; identifier?
+;;; identifier-syntax
+;;; let-syntax
+;;; letrec-syntax
+;;; syntax
+;;; syntax-case
+;;; syntax->datum
+;;; syntax-rules
+;;; with-syntax
+;;;
+;;; Additionally, the expander provides definitions for a number of core
+;;; Scheme syntactic bindings, such as `let', `lambda', and the like.
+
+;;; The remaining exports are listed below:
+;;;
+;;; (macroexpand datum)
+;;; if datum represents a valid expression, macroexpand returns an
+;;; expanded version of datum in a core language that includes no
+;;; syntactic abstractions. The core language includes begin,
+;;; define, if, lambda, letrec, quote, and set!.
+;;; (eval-when situations expr ...)
+;;; conditionally evaluates expr ... at compile-time or run-time
+;;; depending upon situations (see the Chez Scheme System Manual,
+;;; Revision 3, for a complete description)
+;;; (syntax-violation who message form [subform])
+;;; used to report errors found during expansion
+;;; ($sc-dispatch e p)
+;;; used by expanded code to handle syntax-case matching
+
+;;; This file is shipped along with an expanded version of itself,
+;;; psyntax-pp.scm, which is loaded when psyntax.scm has not yet been
+;;; compiled. In this way, psyntax bootstraps off of an expanded
+;;; version of itself.
+
+;;; This implementation of the expander sometimes uses syntactic
+;;; abstractions when procedural abstractions would suffice. For
+;;; example, we define top-wrap and top-marked? as
+;;;
+;;; (define-syntax top-wrap (identifier-syntax '((top))))
+;;; (define-syntax top-marked?
+;;; (syntax-rules ()
+;;; ((_ w) (memq 'top (wrap-marks w)))))
+;;;
+;;; rather than
+;;;
+;;; (define top-wrap '((top)))
+;;; (define top-marked?
+;;; (lambda (w) (memq 'top (wrap-marks w))))
+;;;
+;;; On the other hand, we don't do this consistently; we define
+;;; make-wrap, wrap-marks, and wrap-subst simply as
+;;;
+;;; (define make-wrap cons)
+;;; (define wrap-marks car)
+;;; (define wrap-subst cdr)
+;;;
+;;; In Chez Scheme, the syntactic and procedural forms of these
+;;; abstractions are equivalent, since the optimizer consistently
+;;; integrates constants and small procedures. This will be true of
+;;; Guile as well, once we implement a proper inliner.
+
+
+;;; Implementation notes:
+
+;;; Objects with no standard print syntax, including objects containing
+;;; cycles and syntax object, are allowed in quoted data as long as they
+;;; are contained within a syntax form or produced by datum->syntax.
+;;; Such objects are never copied.
+
+;;; All identifiers that don't have macro definitions and are not bound
+;;; lexically are assumed to be global variables.
+
+;;; Top-level definitions of macro-introduced identifiers are allowed.
+;;; This may not be appropriate for implementations in which the
+;;; model is that bindings are created by definitions, as opposed to
+;;; one in which initial values are assigned by definitions.
+
+;;; Identifiers and syntax objects are implemented as vectors for
+;;; portability. As a result, it is possible to "forge" syntax objects.
+
+;;; The implementation of generate-temporaries assumes that it is
+;;; possible to generate globally unique symbols (gensyms).
+
+;;; The source location associated with incoming expressions is tracked
+;;; via the source-properties mechanism, a weak map from expression to
+;;; source information. At times the source is separated from the
+;;; expression; see the note below about "efficiency and confusion".
+
+
+;;; Bootstrapping:
+
+;;; When changing syntax-object representations, it is necessary to support
+;;; both old and new syntax-object representations in id-var-name. It
+;;; should be sufficient to recognize old representations and treat
+;;; them as not lexically bound.
+
+
+
+(eval-when (compile)
+ (set-current-module (resolve-module '(guile))))
+
+(let ()
+ (define-syntax define-expansion-constructors
+ (lambda (x)
+ (syntax-case x ()
+ ((_)
+ (let lp ((n 0) (out '()))
+ (if (< n (vector-length %expanded-vtables))
+ (lp (1+ n)
+ (let* ((vtable (vector-ref %expanded-vtables n))
+ (stem (struct-ref vtable (+ vtable-offset-user 0)))
+ (fields (struct-ref vtable (+ vtable-offset-user 2)))
+ (sfields (map (lambda (f) (datum->syntax x f)) fields))
+ (ctor (datum->syntax x (symbol-append 'make- stem))))
+ (cons #`(define (#,ctor #,@sfields)
+ (make-struct (vector-ref %expanded-vtables #,n) 0
+ #,@sfields))
+ out)))
+ #`(begin #,@(reverse out))))))))
+
+ (define-syntax define-expansion-accessors
+ (lambda (x)
+ (syntax-case x ()
+ ((_ stem field ...)
+ (let lp ((n 0))
+ (let ((vtable (vector-ref %expanded-vtables n))
+ (stem (syntax->datum #'stem)))
+ (if (eq? (struct-ref vtable (+ vtable-offset-user 0)) stem)
+ #`(begin
+ (define (#,(datum->syntax x (symbol-append stem '?)) x)
+ (and (struct? x)
+ (eq? (struct-vtable x)
+ (vector-ref %expanded-vtables #,n))))
+ #,@(map
+ (lambda (f)
+ (let ((get (datum->syntax x (symbol-append stem '- f)))
+ (set (datum->syntax x (symbol-append 'set- stem '- f '!)))
+ (idx (list-index (struct-ref vtable
+ (+ vtable-offset-user 2))
+ f)))
+ #`(begin
+ (define (#,get x)
+ (struct-ref x #,idx))
+ (define (#,set x v)
+ (struct-set! x #,idx v)))))
+ (syntax->datum #'(field ...))))
+ (lp (1+ n)))))))))
+
+ (define-syntax define-structure
+ (lambda (x)
+ (define construct-name
+ (lambda (template-identifier . args)
+ (datum->syntax
+ template-identifier
+ (string->symbol
+ (apply string-append
+ (map (lambda (x)
+ (if (string? x)
+ x
+ (symbol->string (syntax->datum x))))
+ args))))))
+ (syntax-case x ()
+ ((_ (name id1 ...))
+ (and-map identifier? #'(name id1 ...))
+ (with-syntax
+ ((constructor (construct-name #'name "make-" #'name))
+ (predicate (construct-name #'name #'name "?"))
+ ((access ...)
+ (map (lambda (x) (construct-name x #'name "-" x))
+ #'(id1 ...)))
+ ((assign ...)
+ (map (lambda (x)
+ (construct-name x "set-" #'name "-" x "!"))
+ #'(id1 ...)))
+ (structure-length
+ (+ (length #'(id1 ...)) 1))
+ ((index ...)
+ (let f ((i 1) (ids #'(id1 ...)))
+ (if (null? ids)
+ '()
+ (cons i (f (+ i 1) (cdr ids)))))))
+ #'(begin
+ (define constructor
+ (lambda (id1 ...)
+ (vector 'name id1 ... )))
+ (define predicate
+ (lambda (x)
+ (and (vector? x)
+ (= (vector-length x) structure-length)
+ (eq? (vector-ref x 0) 'name))))
+ (define access
+ (lambda (x)
+ (vector-ref x index)))
+ ...
+ (define assign
+ (lambda (x update)
+ (vector-set! x index update)))
+ ...))))))
+
+ (let ()
+ (define-expansion-constructors)
+ (define-expansion-accessors lambda meta)
+
+ ;; hooks to nonportable run-time helpers
+ (begin
+ (define-syntax fx+ (identifier-syntax +))
+ (define-syntax fx- (identifier-syntax -))
+ (define-syntax fx= (identifier-syntax =))
+ (define-syntax fx< (identifier-syntax <))
+
+ (define top-level-eval-hook
+ (lambda (x mod)
+ (primitive-eval x)))
+
+ (define local-eval-hook
+ (lambda (x mod)
+ (primitive-eval x)))
+
+ ;; Capture syntax-session-id before we shove it off into a module.
+ (define session-id
+ (let ((v (module-variable (current-module) 'syntax-session-id)))
+ (lambda ()
+ ((variable-ref v)))))
+
+ (define put-global-definition-hook
+ (lambda (symbol type val)
+ (module-define! (current-module)
+ symbol
+ (make-syntax-transformer symbol type val))))
+
+ (define get-global-definition-hook
+ (lambda (symbol module)
+ (if (and (not module) (current-module))
+ (warn "module system is booted, we should have a module" symbol))
+ (let ((v (module-variable (if module
+ (resolve-module (cdr module))
+ (current-module))
+ symbol)))
+ (and v (variable-bound? v)
+ (let ((val (variable-ref v)))
+ (and (macro? val) (macro-type val)
+ (cons (macro-type val)
+ (macro-binding val)))))))))
+
+
+ (define (decorate-source e s)
+ (if (and s (supports-source-properties? e))
+ (set-source-properties! e s))
+ e)
+
+ (define (maybe-name-value! name val)
+ (if (lambda? val)
+ (let ((meta (lambda-meta val)))
+ (if (not (assq 'name meta))
+ (set-lambda-meta! val (acons 'name name meta))))))
+
+ ;; output constructors
+ (define build-void
+ (lambda (source)
+ (make-void source)))
+
+ (define build-application
+ (lambda (source fun-exp arg-exps)
+ (make-application source fun-exp arg-exps)))
+
+ (define build-conditional
+ (lambda (source test-exp then-exp else-exp)
+ (make-conditional source test-exp then-exp else-exp)))
+
+ (define build-dynlet
+ (lambda (source fluids vals body)
+ (make-dynlet source fluids vals body)))
+
+ (define build-lexical-reference
+ (lambda (type source name var)
+ (make-lexical-ref source name var)))
+
+ (define build-lexical-assignment
+ (lambda (source name var exp)
+ (maybe-name-value! name exp)
+ (make-lexical-set source name var exp)))
+
+ (define (analyze-variable mod var modref-cont bare-cont)
+ (if (not mod)
+ (bare-cont var)
+ (let ((kind (car mod))
+ (mod (cdr mod)))
+ (case kind
+ ((public) (modref-cont mod var #t))
+ ((private) (if (not (equal? mod (module-name (current-module))))
+ (modref-cont mod var #f)
+ (bare-cont var)))
+ ((bare) (bare-cont var))
+ ((hygiene) (if (and (not (equal? mod (module-name (current-module))))
+ (module-variable (resolve-module mod) var))
+ (modref-cont mod var #f)
+ (bare-cont var)))
+ (else (syntax-violation #f "bad module kind" var mod))))))
+
+ (define build-global-reference
+ (lambda (source var mod)
+ (analyze-variable
+ mod var
+ (lambda (mod var public?)
+ (make-module-ref source mod var public?))
+ (lambda (var)
+ (make-toplevel-ref source var)))))
+
+ (define build-global-assignment
+ (lambda (source var exp mod)
+ (maybe-name-value! var exp)
+ (analyze-variable
+ mod var
+ (lambda (mod var public?)
+ (make-module-set source mod var public? exp))
+ (lambda (var)
+ (make-toplevel-set source var exp)))))
+
+ (define build-global-definition
+ (lambda (source var exp)
+ (maybe-name-value! var exp)
+ (make-toplevel-define source var exp)))
+
+ (define build-simple-lambda
+ (lambda (src req rest vars meta exp)
+ (make-lambda src
+ meta
+ ;; hah, a case in which kwargs would be nice.
+ (make-lambda-case
+ ;; src req opt rest kw inits vars body else
+ src req #f rest #f '() vars exp #f))))
+
+ (define build-case-lambda
+ (lambda (src meta body)
+ (make-lambda src meta body)))
+
+ (define build-lambda-case
+ ;; req := (name ...)
+ ;; opt := (name ...) | #f
+ ;; rest := name | #f
+ ;; kw := (allow-other-keys? (keyword name var) ...) | #f
+ ;; inits: (init ...)
+ ;; vars: (sym ...)
+ ;; vars map to named arguments in the following order:
+ ;; required, optional (positional), rest, keyword.
+ ;; the body of a lambda: anything, already expanded
+ ;; else: lambda-case | #f
+ (lambda (src req opt rest kw inits vars body else-case)
+ (make-lambda-case src req opt rest kw inits vars body else-case)))
+
+ (define build-primref
+ (lambda (src name)
+ (if (equal? (module-name (current-module)) '(guile))
+ (make-toplevel-ref src name)
+ (make-module-ref src '(guile) name #f))))
+
+ (define (build-data src exp)
+ (make-const src exp))
+
+ (define build-sequence
+ (lambda (src exps)
+ (if (null? (cdr exps))
+ (car exps)
+ (make-sequence src exps))))
+
+ (define build-let
+ (lambda (src ids vars val-exps body-exp)
+ (for-each maybe-name-value! ids val-exps)
+ (if (null? vars)
+ body-exp
+ (make-let src ids vars val-exps body-exp))))
+
+ (define build-named-let
+ (lambda (src ids vars val-exps body-exp)
+ (let ((f (car vars))
+ (f-name (car ids))
+ (vars (cdr vars))
+ (ids (cdr ids)))
+ (let ((proc (build-simple-lambda src ids #f vars '() body-exp)))
+ (maybe-name-value! f-name proc)
+ (for-each maybe-name-value! ids val-exps)
+ (make-letrec
+ src #f
+ (list f-name) (list f) (list proc)
+ (build-application src (build-lexical-reference 'fun src f-name f)
+ val-exps))))))
+
+ (define build-letrec
+ (lambda (src in-order? ids vars val-exps body-exp)
+ (if (null? vars)
+ body-exp
+ (begin
+ (for-each maybe-name-value! ids val-exps)
+ (make-letrec src in-order? ids vars val-exps body-exp)))))
+
+
+ (define-syntax-rule (build-lexical-var src id)
+ ;; Use a per-module counter instead of the global counter of
+ ;; 'gensym' so that the generated identifier is reproducible.
+ (module-gensym (symbol->string id)))
+
+ (define-structure (syntax-object expression wrap module))
+
+ (define-syntax no-source (identifier-syntax #f))
+
+ (define source-annotation
+ (lambda (x)
+ (let ((props (source-properties
+ (if (syntax-object? x)
+ (syntax-object-expression x)
+ x))))
+ (and (pair? props) props))))
+
+ (define-syntax-rule (arg-check pred? e who)
+ (let ((x e))
+ (if (not (pred? x)) (syntax-violation who "invalid argument" x))))
+
+ ;; compile-time environments
+
+ ;; wrap and environment comprise two level mapping.
+ ;; wrap : id --> label
+ ;; env : label --> <element>
+
+ ;; environments are represented in two parts: a lexical part and a global
+ ;; part. The lexical part is a simple list of associations from labels
+ ;; to bindings. The global part is implemented by
+ ;; {put,get}-global-definition-hook and associates symbols with
+ ;; bindings.
+
+ ;; global (assumed global variable) and displaced-lexical (see below)
+ ;; do not show up in any environment; instead, they are fabricated by
+ ;; lookup when it finds no other bindings.
+
+ ;; <environment> ::= ((<label> . <binding>)*)
+
+ ;; identifier bindings include a type and a value
+
+ ;; <binding> ::= (macro . <procedure>) macros
+ ;; (core . <procedure>) core forms
+ ;; (module-ref . <procedure>) @ or @@
+ ;; (begin) begin
+ ;; (define) define
+ ;; (define-syntax) define-syntax
+ ;; (define-syntax-parameter) define-syntax-parameter
+ ;; (local-syntax . rec?) let-syntax/letrec-syntax
+ ;; (eval-when) eval-when
+ ;; (syntax . (<var> . <level>)) pattern variables
+ ;; (global) assumed global variable
+ ;; (lexical . <var>) lexical variables
+ ;; (ellipsis . <identifier>) custom ellipsis
+ ;; (displaced-lexical) displaced lexicals
+ ;; <level> ::= <nonnegative integer>
+ ;; <var> ::= variable returned by build-lexical-var
+
+ ;; a macro is a user-defined syntactic-form. a core is a
+ ;; system-defined syntactic form. begin, define, define-syntax,
+ ;; define-syntax-parameter, and eval-when are treated specially
+ ;; since they are sensitive to whether the form is at top-level and
+ ;; (except for eval-when) can denote valid internal definitions.
+
+ ;; a pattern variable is a variable introduced by syntax-case and can
+ ;; be referenced only within a syntax form.
+
+ ;; any identifier for which no top-level syntax definition or local
+ ;; binding of any kind has been seen is assumed to be a global
+ ;; variable.
+
+ ;; a lexical variable is a lambda- or letrec-bound variable.
+
+ ;; an ellipsis binding is introduced by the 'with-ellipsis' special
+ ;; form.
+
+ ;; a displaced-lexical identifier is a lexical identifier removed from
+ ;; it's scope by the return of a syntax object containing the identifier.
+ ;; a displaced lexical can also appear when a letrec-syntax-bound
+ ;; keyword is referenced on the rhs of one of the letrec-syntax clauses.
+ ;; a displaced lexical should never occur with properly written macros.
+
+ (define-syntax make-binding
+ (syntax-rules (quote)
+ ((_ type value) (cons type value))
+ ((_ 'type) '(type))
+ ((_ type) (cons type '()))))
+ (define-syntax-rule (binding-type x)
+ (car x))
+ (define-syntax-rule (binding-value x)
+ (cdr x))
+
+ (define-syntax null-env (identifier-syntax '()))
+
+ (define extend-env
+ (lambda (labels bindings r)
+ (if (null? labels)
+ r
+ (extend-env (cdr labels) (cdr bindings)
+ (cons (cons (car labels) (car bindings)) r)))))
+
+ (define extend-var-env
+ ;; variant of extend-env that forms "lexical" binding
+ (lambda (labels vars r)
+ (if (null? labels)
+ r
+ (extend-var-env (cdr labels) (cdr vars)
+ (cons (cons (car labels) (make-binding 'lexical (car vars))) r)))))
+
+ ;; we use a "macros only" environment in expansion of local macro
+ ;; definitions so that their definitions can use local macros without
+ ;; attempting to use other lexical identifiers.
+ (define macros-only-env
+ (lambda (r)
+ (if (null? r)
+ '()
+ (let ((a (car r)))
+ (if (memq (cadr a) '(macro ellipsis))
+ (cons a (macros-only-env (cdr r)))
+ (macros-only-env (cdr r)))))))
+
+ (define lookup
+ ;; x may be a label or a symbol
+ ;; although symbols are usually global, we check the environment first
+ ;; anyway because a temporary binding may have been established by
+ ;; fluid-let-syntax
+ (lambda (x r mod)
+ (cond
+ ((assq x r) => cdr)
+ ((symbol? x)
+ (or (get-global-definition-hook x mod) (make-binding 'global)))
+ (else (make-binding 'displaced-lexical)))))
+
+ (define global-extend
+ (lambda (type sym val)
+ (put-global-definition-hook sym type val)))
+
+
+ ;; Conceptually, identifiers are always syntax objects. Internally,
+ ;; however, the wrap is sometimes maintained separately (a source of
+ ;; efficiency and confusion), so that symbols are also considered
+ ;; identifiers by id?. Externally, they are always wrapped.
+
+ (define nonsymbol-id?
+ (lambda (x)
+ (and (syntax-object? x)
+ (symbol? (syntax-object-expression x)))))
+
+ (define id?
+ (lambda (x)
+ (cond
+ ((symbol? x) #t)
+ ((syntax-object? x) (symbol? (syntax-object-expression x)))
+ (else #f))))
+
+ (define-syntax-rule (id-sym-name e)
+ (let ((x e))
+ (if (syntax-object? x)
+ (syntax-object-expression x)
+ x)))
+
+ (define id-sym-name&marks
+ (lambda (x w)
+ (if (syntax-object? x)
+ (values
+ (syntax-object-expression x)
+ (join-marks (wrap-marks w) (wrap-marks (syntax-object-wrap x))))
+ (values x (wrap-marks w)))))
+
+ ;; syntax object wraps
+
+ ;; <wrap> ::= ((<mark> ...) . (<subst> ...))
+ ;; <subst> ::= shift | <subs>
+ ;; <subs> ::= #(ribcage #(<sym> ...) #(<mark> ...) #(<label> ...))
+ ;; | #(ribcage (<sym> ...) (<mark> ...) (<label> ...))
+
+ (define-syntax make-wrap (identifier-syntax cons))
+ (define-syntax wrap-marks (identifier-syntax car))
+ (define-syntax wrap-subst (identifier-syntax cdr))
+
+ ;; labels must be comparable with "eq?", have read-write invariance,
+ ;; and distinct from symbols.
+ (define (gen-label)
+ (symbol->string (module-gensym "l")))
+
+ (define gen-labels
+ (lambda (ls)
+ (if (null? ls)
+ '()
+ (cons (gen-label) (gen-labels (cdr ls))))))
+
+ (define-structure (ribcage symnames marks labels))
+
+ (define-syntax empty-wrap (identifier-syntax '(())))
+
+ (define-syntax top-wrap (identifier-syntax '((top))))
+
+ (define-syntax-rule (top-marked? w)
+ (memq 'top (wrap-marks w)))
+
+ ;; Marks must be comparable with "eq?" and distinct from pairs and
+ ;; the symbol top. We do not use integers so that marks will remain
+ ;; unique even across file compiles.
+
+ (define-syntax the-anti-mark (identifier-syntax #f))
+
+ (define anti-mark
+ (lambda (w)
+ (make-wrap (cons the-anti-mark (wrap-marks w))
+ (cons 'shift (wrap-subst w)))))
+
+ (define-syntax-rule (new-mark)
+ (module-gensym "m"))
+
+ ;; make-empty-ribcage and extend-ribcage maintain list-based ribcages for
+ ;; internal definitions, in which the ribcages are built incrementally
+ (define-syntax-rule (make-empty-ribcage)
+ (make-ribcage '() '() '()))
+
+ (define extend-ribcage!
+ ;; must receive ids with complete wraps
+ (lambda (ribcage id label)
+ (set-ribcage-symnames! ribcage
+ (cons (syntax-object-expression id)
+ (ribcage-symnames ribcage)))
+ (set-ribcage-marks! ribcage
+ (cons (wrap-marks (syntax-object-wrap id))
+ (ribcage-marks ribcage)))
+ (set-ribcage-labels! ribcage
+ (cons label (ribcage-labels ribcage)))))
+
+ ;; make-binding-wrap creates vector-based ribcages
+ (define make-binding-wrap
+ (lambda (ids labels w)
+ (if (null? ids)
+ w
+ (make-wrap
+ (wrap-marks w)
+ (cons
+ (let ((labelvec (list->vector labels)))
+ (let ((n (vector-length labelvec)))
+ (let ((symnamevec (make-vector n)) (marksvec (make-vector n)))
+ (let f ((ids ids) (i 0))
+ (if (not (null? ids))
+ (call-with-values
+ (lambda () (id-sym-name&marks (car ids) w))
+ (lambda (symname marks)
+ (vector-set! symnamevec i symname)
+ (vector-set! marksvec i marks)
+ (f (cdr ids) (fx+ i 1))))))
+ (make-ribcage symnamevec marksvec labelvec))))
+ (wrap-subst w))))))
+
+ (define smart-append
+ (lambda (m1 m2)
+ (if (null? m2)
+ m1
+ (append m1 m2))))
+
+ (define join-wraps
+ (lambda (w1 w2)
+ (let ((m1 (wrap-marks w1)) (s1 (wrap-subst w1)))
+ (if (null? m1)
+ (if (null? s1)
+ w2
+ (make-wrap
+ (wrap-marks w2)
+ (smart-append s1 (wrap-subst w2))))
+ (make-wrap
+ (smart-append m1 (wrap-marks w2))
+ (smart-append s1 (wrap-subst w2)))))))
+
+ (define join-marks
+ (lambda (m1 m2)
+ (smart-append m1 m2)))
+
+ (define same-marks?
+ (lambda (x y)
+ (or (eq? x y)
+ (and (not (null? x))
+ (not (null? y))
+ (eq? (car x) (car y))
+ (same-marks? (cdr x) (cdr y))))))
+
+ (define id-var-name
+ (lambda (id w)
+ (define-syntax-rule (first e)
+ ;; Rely on Guile's multiple-values truncation.
+ e)
+ (define search
+ (lambda (sym subst marks)
+ (if (null? subst)
+ (values #f marks)
+ (let ((fst (car subst)))
+ (if (eq? fst 'shift)
+ (search sym (cdr subst) (cdr marks))
+ (let ((symnames (ribcage-symnames fst)))
+ (if (vector? symnames)
+ (search-vector-rib sym subst marks symnames fst)
+ (search-list-rib sym subst marks symnames fst))))))))
+ (define search-list-rib
+ (lambda (sym subst marks symnames ribcage)
+ (let f ((symnames symnames) (i 0))
+ (cond
+ ((null? symnames) (search sym (cdr subst) marks))
+ ((and (eq? (car symnames) sym)
+ (same-marks? marks (list-ref (ribcage-marks ribcage) i)))
+ (values (list-ref (ribcage-labels ribcage) i) marks))
+ (else (f (cdr symnames) (fx+ i 1)))))))
+ (define search-vector-rib
+ (lambda (sym subst marks symnames ribcage)
+ (let ((n (vector-length symnames)))
+ (let f ((i 0))
+ (cond
+ ((fx= i n) (search sym (cdr subst) marks))
+ ((and (eq? (vector-ref symnames i) sym)
+ (same-marks? marks (vector-ref (ribcage-marks ribcage) i)))
+ (values (vector-ref (ribcage-labels ribcage) i) marks))
+ (else (f (fx+ i 1))))))))
+ (cond
+ ((symbol? id)
+ (or (first (search id (wrap-subst w) (wrap-marks w))) id))
+ ((syntax-object? id)
+ (let ((id (syntax-object-expression id))
+ (w1 (syntax-object-wrap id)))
+ (let ((marks (join-marks (wrap-marks w) (wrap-marks w1))))
+ (call-with-values (lambda () (search id (wrap-subst w) marks))
+ (lambda (new-id marks)
+ (or new-id
+ (first (search id (wrap-subst w1) marks))
+ id))))))
+ (else (syntax-violation 'id-var-name "invalid id" id)))))
+
+ ;; A helper procedure for syntax-locally-bound-identifiers, which
+ ;; itself is a helper for transformer procedures.
+ ;; `locally-bound-identifiers' returns a list of all bindings
+ ;; visible to a syntax object with the given wrap. They are in
+ ;; order from outer to inner.
+ ;;
+ ;; The purpose of this procedure is to give a transformer procedure
+ ;; references on bound identifiers, that the transformer can then
+ ;; introduce some of them in its output. As such, the identifiers
+ ;; are anti-marked, so that rebuild-macro-output doesn't apply new
+ ;; marks to them.
+ ;;
+ (define locally-bound-identifiers
+ (lambda (w mod)
+ (define scan
+ (lambda (subst results)
+ (if (null? subst)
+ results
+ (let ((fst (car subst)))
+ (if (eq? fst 'shift)
+ (scan (cdr subst) results)
+ (let ((symnames (ribcage-symnames fst))
+ (marks (ribcage-marks fst)))
+ (if (vector? symnames)
+ (scan-vector-rib subst symnames marks results)
+ (scan-list-rib subst symnames marks results))))))))
+ (define scan-list-rib
+ (lambda (subst symnames marks results)
+ (let f ((symnames symnames) (marks marks) (results results))
+ (if (null? symnames)
+ (scan (cdr subst) results)
+ (f (cdr symnames) (cdr marks)
+ (cons (wrap (car symnames)
+ (anti-mark (make-wrap (car marks) subst))
+ mod)
+ results))))))
+ (define scan-vector-rib
+ (lambda (subst symnames marks results)
+ (let ((n (vector-length symnames)))
+ (let f ((i 0) (results results))
+ (if (fx= i n)
+ (scan (cdr subst) results)
+ (f (fx+ i 1)
+ (cons (wrap (vector-ref symnames i)
+ (anti-mark (make-wrap (vector-ref marks i) subst))
+ mod)
+ results)))))))
+ (scan (wrap-subst w) '())))
+
+ ;; Returns three values: binding type, binding value, the module (for
+ ;; resolving toplevel vars).
+ (define (resolve-identifier id w r mod)
+ (define (resolve-global var mod)
+ (let ((b (or (get-global-definition-hook var mod)
+ (make-binding 'global))))
+ (if (eq? (binding-type b) 'global)
+ (values 'global var mod)
+ (values (binding-type b) (binding-value b) mod))))
+ (define (resolve-lexical label mod)
+ (let ((b (or (assq-ref r label)
+ (make-binding 'displaced-lexical))))
+ (values (binding-type b) (binding-value b) mod)))
+ (let ((n (id-var-name id w)))
+ (cond
+ ((symbol? n)
+ (resolve-global n (if (syntax-object? id)
+ (syntax-object-module id)
+ mod)))
+ ((string? n)
+ (resolve-lexical n (if (syntax-object? id)
+ (syntax-object-module id)
+ mod)))
+ (else
+ (error "unexpected id-var-name" id w n)))))
+
+ (define transformer-environment
+ (make-fluid
+ (lambda (k)
+ (error "called outside the dynamic extent of a syntax transformer"))))
+
+ (define (with-transformer-environment k)
+ ((fluid-ref transformer-environment) k))
+
+ ;; free-id=? must be passed fully wrapped ids since (free-id=? x y)
+ ;; may be true even if (free-id=? (wrap x w) (wrap y w)) is not.
+
+ (define free-id=?
+ (lambda (i j)
+ (and (eq? (id-sym-name i) (id-sym-name j)) ; accelerator
+ (eq? (id-var-name i empty-wrap) (id-var-name j empty-wrap)))))
+
+ ;; bound-id=? may be passed unwrapped (or partially wrapped) ids as
+ ;; long as the missing portion of the wrap is common to both of the ids
+ ;; since (bound-id=? x y) iff (bound-id=? (wrap x w) (wrap y w))
+
+ (define bound-id=?
+ (lambda (i j)
+ (if (and (syntax-object? i) (syntax-object? j))
+ (and (eq? (syntax-object-expression i)
+ (syntax-object-expression j))
+ (same-marks? (wrap-marks (syntax-object-wrap i))
+ (wrap-marks (syntax-object-wrap j))))
+ (eq? i j))))
+
+ ;; "valid-bound-ids?" returns #t if it receives a list of distinct ids.
+ ;; valid-bound-ids? may be passed unwrapped (or partially wrapped) ids
+ ;; as long as the missing portion of the wrap is common to all of the
+ ;; ids.
+
+ (define valid-bound-ids?
+ (lambda (ids)
+ (and (let all-ids? ((ids ids))
+ (or (null? ids)
+ (and (id? (car ids))
+ (all-ids? (cdr ids)))))
+ (distinct-bound-ids? ids))))
+
+ ;; distinct-bound-ids? expects a list of ids and returns #t if there are
+ ;; no duplicates. It is quadratic on the length of the id list; long
+ ;; lists could be sorted to make it more efficient. distinct-bound-ids?
+ ;; may be passed unwrapped (or partially wrapped) ids as long as the
+ ;; missing portion of the wrap is common to all of the ids.
+
+ (define distinct-bound-ids?
+ (lambda (ids)
+ (let distinct? ((ids ids))
+ (or (null? ids)
+ (and (not (bound-id-member? (car ids) (cdr ids)))
+ (distinct? (cdr ids)))))))
+
+ (define bound-id-member?
+ (lambda (x list)
+ (and (not (null? list))
+ (or (bound-id=? x (car list))
+ (bound-id-member? x (cdr list))))))
+
+ ;; wrapping expressions and identifiers
+
+ (define wrap
+ (lambda (x w defmod)
+ (cond
+ ((and (null? (wrap-marks w)) (null? (wrap-subst w))) x)
+ ((syntax-object? x)
+ (make-syntax-object
+ (syntax-object-expression x)
+ (join-wraps w (syntax-object-wrap x))
+ (syntax-object-module x)))
+ ((null? x) x)
+ (else (make-syntax-object x w defmod)))))
+
+ (define source-wrap
+ (lambda (x w s defmod)
+ (wrap (decorate-source x s) w defmod)))
+
+ ;; expanding
+
+ (define expand-sequence
+ (lambda (body r w s mod)
+ (build-sequence s
+ (let dobody ((body body) (r r) (w w) (mod mod))
+ (if (null? body)
+ '()
+ (let ((first (expand (car body) r w mod)))
+ (cons first (dobody (cdr body) r w mod))))))))
+
+ ;; At top-level, we allow mixed definitions and expressions. Like
+ ;; expand-body we expand in two passes.
+ ;;
+ ;; First, from left to right, we expand just enough to know what
+ ;; expressions are definitions, syntax definitions, and splicing
+ ;; statements (`begin'). If we anything needs evaluating at
+ ;; expansion-time, it is expanded directly.
+ ;;
+ ;; Otherwise we collect expressions to expand, in thunks, and then
+ ;; expand them all at the end. This allows all syntax expanders
+ ;; visible in a toplevel sequence to be visible during the
+ ;; expansions of all normal definitions and expressions in the
+ ;; sequence.
+ ;;
+ (define expand-top-sequence
+ (lambda (body r w s m esew mod)
+ (define (scan body r w s m esew mod exps)
+ (cond
+ ((null? body)
+ ;; in reversed order
+ exps)
+ (else
+ (call-with-values
+ (lambda ()
+ (call-with-values
+ (lambda ()
+ (let ((e (car body)))
+ (syntax-type e r w (or (source-annotation e) s) #f mod #f)))
+ (lambda (type value form e w s mod)
+ (case type
+ ((begin-form)
+ (syntax-case e ()
+ ((_) exps)
+ ((_ e1 e2 ...)
+ (scan #'(e1 e2 ...) r w s m esew mod exps))))
+ ((local-syntax-form)
+ (expand-local-syntax value e r w s mod
+ (lambda (body r w s mod)
+ (scan body r w s m esew mod exps))))
+ ((eval-when-form)
+ (syntax-case e ()
+ ((_ (x ...) e1 e2 ...)
+ (let ((when-list (parse-when-list e #'(x ...)))
+ (body #'(e1 e2 ...)))
+ (cond
+ ((eq? m 'e)
+ (if (memq 'eval when-list)
+ (scan body r w s
+ (if (memq 'expand when-list) 'c&e 'e)
+ '(eval)
+ mod exps)
+ (begin
+ (if (memq 'expand when-list)
+ (top-level-eval-hook
+ (expand-top-sequence body r w s 'e '(eval) mod)
+ mod))
+ (values exps))))
+ ((memq 'load when-list)
+ (if (or (memq 'compile when-list)
+ (memq 'expand when-list)
+ (and (eq? m 'c&e) (memq 'eval when-list)))
+ (scan body r w s 'c&e '(compile load) mod exps)
+ (if (memq m '(c c&e))
+ (scan body r w s 'c '(load) mod exps)
+ (values exps))))
+ ((or (memq 'compile when-list)
+ (memq 'expand when-list)
+ (and (eq? m 'c&e) (memq 'eval when-list)))
+ (top-level-eval-hook
+ (expand-top-sequence body r w s 'e '(eval) mod)
+ mod)
+ (values exps))
+ (else
+ (values exps)))))))
+ ((define-syntax-form define-syntax-parameter-form)
+ (let ((n (id-var-name value w)) (r (macros-only-env r)))
+ (case m
+ ((c)
+ (if (memq 'compile esew)
+ (let ((e (expand-install-global n (expand e r w mod))))
+ (top-level-eval-hook e mod)
+ (if (memq 'load esew)
+ (values (cons e exps))
+ (values exps)))
+ (if (memq 'load esew)
+ (values (cons (expand-install-global n (expand e r w mod))
+ exps))
+ (values exps))))
+ ((c&e)
+ (let ((e (expand-install-global n (expand e r w mod))))
+ (top-level-eval-hook e mod)
+ (values (cons e exps))))
+ (else
+ (if (memq 'eval esew)
+ (top-level-eval-hook
+ (expand-install-global n (expand e r w mod))
+ mod))
+ (values exps)))))
+ ((define-form)
+ (let* ((n (id-var-name value w))
+ ;; Lookup the name in the module of the define form.
+ (type (binding-type (lookup n r mod))))
+ (case type
+ ((global core macro module-ref)
+ ;; affect compile-time environment (once we have booted)
+ (if (and (memq m '(c c&e))
+ (not (module-local-variable (current-module) n))
+ (current-module))
+ (let ((old (module-variable (current-module) n)))
+ ;; use value of the same-named imported variable, if
+ ;; any
+ (if (and (variable? old)
+ (variable-bound? old)
+ (not (macro? (variable-ref old))))
+ (module-define! (current-module) n (variable-ref old))
+ (module-add! (current-module) n (make-undefined-variable)))))
+ (values
+ (cons
+ (if (eq? m 'c&e)
+ (let ((x (build-global-definition s n (expand e r w mod))))
+ (top-level-eval-hook x mod)
+ x)
+ (lambda ()
+ (build-global-definition s n (expand e r w mod))))
+ exps)))
+ ((displaced-lexical)
+ (syntax-violation #f "identifier out of context"
+ (source-wrap form w s mod)
+ (wrap value w mod)))
+ (else
+ (syntax-violation #f "cannot define keyword at top level"
+ (source-wrap form w s mod)
+ (wrap value w mod))))))
+ (else
+ (values (cons
+ (if (eq? m 'c&e)
+ (let ((x (expand-expr type value form e r w s mod)))
+ (top-level-eval-hook x mod)
+ x)
+ (lambda ()
+ (expand-expr type value form e r w s mod)))
+ exps)))))))
+ (lambda (exps)
+ (scan (cdr body) r w s m esew mod exps))))))
+
+ (call-with-values (lambda ()
+ (scan body r w s m esew mod '()))
+ (lambda (exps)
+ (if (null? exps)
+ (build-void s)
+ (build-sequence
+ s
+ (let lp ((in exps) (out '()))
+ (if (null? in) out
+ (let ((e (car in)))
+ (lp (cdr in)
+ (cons (if (procedure? e) (e) e) out)))))))))))
+
+ (define expand-install-global
+ (lambda (name e)
+ (build-global-definition
+ no-source
+ name
+ (build-application
+ no-source
+ (build-primref no-source 'make-syntax-transformer)
+ (list (build-data no-source name)
+ (build-data no-source 'macro)
+ e)))))
+
+ (define parse-when-list
+ (lambda (e when-list)
+ ;; when-list is syntax'd version of list of situations
+ (let ((result (strip when-list empty-wrap)))
+ (let lp ((l result))
+ (if (null? l)
+ result
+ (if (memq (car l) '(compile load eval expand))
+ (lp (cdr l))
+ (syntax-violation 'eval-when "invalid situation" e
+ (car l))))))))
+
+ ;; syntax-type returns seven values: type, value, form, e, w, s, and
+ ;; mod. The first two are described in the table below.
+ ;;
+ ;; type value explanation
+ ;; -------------------------------------------------------------------
+ ;; core procedure core singleton
+ ;; core-form procedure core form
+ ;; module-ref procedure @ or @@ singleton
+ ;; lexical name lexical variable reference
+ ;; global name global variable reference
+ ;; begin none begin keyword
+ ;; define none define keyword
+ ;; define-syntax none define-syntax keyword
+ ;; define-syntax-parameter none define-syntax-parameter keyword
+ ;; local-syntax rec? letrec-syntax/let-syntax keyword
+ ;; eval-when none eval-when keyword
+ ;; syntax level pattern variable
+ ;; displaced-lexical none displaced lexical identifier
+ ;; lexical-call name call to lexical variable
+ ;; global-call name call to global variable
+ ;; call none any other call
+ ;; begin-form none begin expression
+ ;; define-form id variable definition
+ ;; define-syntax-form id syntax definition
+ ;; define-syntax-parameter-form id syntax parameter definition
+ ;; local-syntax-form rec? syntax definition
+ ;; eval-when-form none eval-when form
+ ;; constant none self-evaluating datum
+ ;; other none anything else
+ ;;
+ ;; form is the entire form. For definition forms (define-form,
+ ;; define-syntax-form, and define-syntax-parameter-form), e is the
+ ;; rhs expression. For all others, e is the entire form. w is the
+ ;; wrap for both form and e. s is the source for the entire form.
+ ;; mod is the module for both form and e.
+ ;;
+ ;; syntax-type expands macros and unwraps as necessary to get to one
+ ;; of the forms above. It also parses definition forms, although
+ ;; perhaps this should be done by the consumer.
+
+ (define syntax-type
+ (lambda (e r w s rib mod for-car?)
+ (cond
+ ((symbol? e)
+ (let* ((n (id-var-name e w))
+ (b (lookup n r mod))
+ (type (binding-type b)))
+ (case type
+ ((lexical) (values type (binding-value b) e e w s mod))
+ ((global) (values type n e e w s mod))
+ ((macro)
+ (if for-car?
+ (values type (binding-value b) e e w s mod)
+ (syntax-type (expand-macro (binding-value b) e r w s rib mod)
+ r empty-wrap s rib mod #f)))
+ (else (values type (binding-value b) e e w s mod)))))
+ ((pair? e)
+ (let ((first (car e)))
+ (call-with-values
+ (lambda () (syntax-type first r w s rib mod #t))
+ (lambda (ftype fval fform fe fw fs fmod)
+ (case ftype
+ ((lexical)
+ (values 'lexical-call fval e e w s mod))
+ ((global)
+ ;; If we got here via an (@@ ...) expansion, we need to
+ ;; make sure the fmod information is propagated back
+ ;; correctly -- hence this consing.
+ (values 'global-call (make-syntax-object fval w fmod)
+ e e w s mod))
+ ((macro)
+ (syntax-type (expand-macro fval e r w s rib mod)
+ r empty-wrap s rib mod for-car?))
+ ((module-ref)
+ (call-with-values (lambda () (fval e r w))
+ (lambda (e r w s mod)
+ (syntax-type e r w s rib mod for-car?))))
+ ((core)
+ (values 'core-form fval e e w s mod))
+ ((local-syntax)
+ (values 'local-syntax-form fval e e w s mod))
+ ((begin)
+ (values 'begin-form #f e e w s mod))
+ ((eval-when)
+ (values 'eval-when-form #f e e w s mod))
+ ((define)
+ (syntax-case e ()
+ ((_ name val)
+ (id? #'name)
+ (values 'define-form #'name e #'val w s mod))
+ ((_ (name . args) e1 e2 ...)
+ (and (id? #'name)
+ (valid-bound-ids? (lambda-var-list #'args)))
+ ;; need lambda here...
+ (values 'define-form (wrap #'name w mod)
+ (wrap e w mod)
+ (decorate-source
+ (cons #'lambda (wrap #'(args e1 e2 ...) w mod))
+ s)
+ empty-wrap s mod))
+ ((_ name)
+ (id? #'name)
+ (values 'define-form (wrap #'name w mod)
+ (wrap e w mod)
+ #'(if #f #f)
+ empty-wrap s mod))))
+ ((define-syntax)
+ (syntax-case e ()
+ ((_ name val)
+ (id? #'name)
+ (values 'define-syntax-form #'name e #'val w s mod))))
+ ((define-syntax-parameter)
+ (syntax-case e ()
+ ((_ name val)
+ (id? #'name)
+ (values 'define-syntax-parameter-form #'name e #'val w s mod))))
+ (else
+ (values 'call #f e e w s mod)))))))
+ ((syntax-object? e)
+ (syntax-type (syntax-object-expression e)
+ r
+ (join-wraps w (syntax-object-wrap e))
+ (or (source-annotation e) s) rib
+ (or (syntax-object-module e) mod) for-car?))
+ ((self-evaluating? e) (values 'constant #f e e w s mod))
+ (else (values 'other #f e e w s mod)))))
+
+ (define expand
+ (lambda (e r w mod)
+ (call-with-values
+ (lambda () (syntax-type e r w (source-annotation e) #f mod #f))
+ (lambda (type value form e w s mod)
+ (expand-expr type value form e r w s mod)))))
+
+ (define expand-expr
+ (lambda (type value form e r w s mod)
+ (case type
+ ((lexical)
+ (build-lexical-reference 'value s e value))
+ ((core core-form)
+ ;; apply transformer
+ (value e r w s mod))
+ ((module-ref)
+ (call-with-values (lambda () (value e r w))
+ (lambda (e r w s mod)
+ (expand e r w mod))))
+ ((lexical-call)
+ (expand-application
+ (let ((id (car e)))
+ (build-lexical-reference 'fun (source-annotation id)
+ (if (syntax-object? id)
+ (syntax->datum id)
+ id)
+ value))
+ e r w s mod))
+ ((global-call)
+ (expand-application
+ (build-global-reference (source-annotation (car e))
+ (if (syntax-object? value)
+ (syntax-object-expression value)
+ value)
+ (if (syntax-object? value)
+ (syntax-object-module value)
+ mod))
+ e r w s mod))
+ ((constant) (build-data s (strip (source-wrap e w s mod) empty-wrap)))
+ ((global) (build-global-reference s value mod))
+ ((call) (expand-application (expand (car e) r w mod) e r w s mod))
+ ((begin-form)
+ (syntax-case e ()
+ ((_ e1 e2 ...) (expand-sequence #'(e1 e2 ...) r w s mod))
+ ((_)
+ (if (include-deprecated-features)
+ (begin
+ (issue-deprecation-warning
+ "Sequences of zero expressions are deprecated. Use *unspecified*.")
+ (expand-void))
+ (syntax-violation #f "sequence of zero expressions"
+ (source-wrap e w s mod))))))
+ ((local-syntax-form)
+ (expand-local-syntax value e r w s mod expand-sequence))
+ ((eval-when-form)
+ (syntax-case e ()
+ ((_ (x ...) e1 e2 ...)
+ (let ((when-list (parse-when-list e #'(x ...))))
+ (if (memq 'eval when-list)
+ (expand-sequence #'(e1 e2 ...) r w s mod)
+ (expand-void))))))
+ ((define-form define-syntax-form define-syntax-parameter-form)
+ (syntax-violation #f "definition in expression context, where definitions are not allowed,"
+ (source-wrap form w s mod)))
+ ((syntax)
+ (syntax-violation #f "reference to pattern variable outside syntax form"
+ (source-wrap e w s mod)))
+ ((displaced-lexical)
+ (syntax-violation #f "reference to identifier outside its scope"
+ (source-wrap e w s mod)))
+ (else (syntax-violation #f "unexpected syntax"
+ (source-wrap e w s mod))))))
+
+ (define expand-application
+ (lambda (x e r w s mod)
+ (syntax-case e ()
+ ((e0 e1 ...)
+ (build-application s x
+ (map (lambda (e) (expand e r w mod)) #'(e1 ...)))))))
+
+ ;; (What follows is my interpretation of what's going on here -- Andy)
+ ;;
+ ;; A macro takes an expression, a tree, the leaves of which are identifiers
+ ;; and datums. Identifiers are symbols along with a wrap and a module. For
+ ;; efficiency, subtrees that share wraps and modules may be grouped as one
+ ;; syntax object.
+ ;;
+ ;; Going into the expansion, the expression is given an anti-mark, which
+ ;; logically propagates to all leaves. Then, in the new expression returned
+ ;; from the transfomer, if we see an expression with an anti-mark, we know it
+ ;; pertains to the original expression; conversely, expressions without the
+ ;; anti-mark are known to be introduced by the transformer.
+ ;;
+ ;; OK, good until now. We know this algorithm does lexical scoping
+ ;; appropriately because it's widely known in the literature, and psyntax is
+ ;; widely used. But what about modules? Here we're on our own. What we do is
+ ;; to mark the module of expressions produced by a macro as pertaining to the
+ ;; module that was current when the macro was defined -- that is, free
+ ;; identifiers introduced by a macro are scoped in the macro's module, not in
+ ;; the expansion's module. Seems to work well.
+ ;;
+ ;; The only wrinkle is when we want a macro to expand to code in another
+ ;; module, as is the case for the r6rs `library' form -- the body expressions
+ ;; should be scoped relative the new module, the one defined by the macro.
+ ;; For that, use `(@@ mod-name body)'.
+ ;;
+ ;; Part of the macro output will be from the site of the macro use and part
+ ;; from the macro definition. We allow source information from the macro use
+ ;; to pass through, but we annotate the parts coming from the macro with the
+ ;; source location information corresponding to the macro use. It would be
+ ;; really nice if we could also annotate introduced expressions with the
+ ;; locations corresponding to the macro definition, but that is not yet
+ ;; possible.
+ (define expand-macro
+ (lambda (p e r w s rib mod)
+ (define rebuild-macro-output
+ (lambda (x m)
+ (cond ((pair? x)
+ (decorate-source
+ (cons (rebuild-macro-output (car x) m)
+ (rebuild-macro-output (cdr x) m))
+ s))
+ ((syntax-object? x)
+ (let ((w (syntax-object-wrap x)))
+ (let ((ms (wrap-marks w)) (ss (wrap-subst w)))
+ (if (and (pair? ms) (eq? (car ms) the-anti-mark))
+ ;; output is from original text
+ (make-syntax-object
+ (syntax-object-expression x)
+ (make-wrap (cdr ms) (if rib (cons rib (cdr ss)) (cdr ss)))
+ (syntax-object-module x))
+ ;; output introduced by macro
+ (make-syntax-object
+ (decorate-source (syntax-object-expression x) s)
+ (make-wrap (cons m ms)
+ (if rib
+ (cons rib (cons 'shift ss))
+ (cons 'shift ss)))
+ (syntax-object-module x))))))
+
+ ((vector? x)
+ (let* ((n (vector-length x))
+ (v (decorate-source (make-vector n) s)))
+ (do ((i 0 (fx+ i 1)))
+ ((fx= i n) v)
+ (vector-set! v i
+ (rebuild-macro-output (vector-ref x i) m)))))
+ ((symbol? x)
+ (syntax-violation #f "encountered raw symbol in macro output"
+ (source-wrap e w (wrap-subst w) mod) x))
+ (else (decorate-source x s)))))
+ (with-fluids ((transformer-environment
+ (lambda (k) (k e r w s rib mod))))
+ (rebuild-macro-output (p (source-wrap e (anti-mark w) s mod))
+ (new-mark)))))
+
+ (define expand-body
+ ;; In processing the forms of the body, we create a new, empty wrap.
+ ;; This wrap is augmented (destructively) each time we discover that
+ ;; the next form is a definition. This is done:
+ ;;
+ ;; (1) to allow the first nondefinition form to be a call to
+ ;; one of the defined ids even if the id previously denoted a
+ ;; definition keyword or keyword for a macro expanding into a
+ ;; definition;
+ ;; (2) to prevent subsequent definition forms (but unfortunately
+ ;; not earlier ones) and the first nondefinition form from
+ ;; confusing one of the bound identifiers for an auxiliary
+ ;; keyword; and
+ ;; (3) so that we do not need to restart the expansion of the
+ ;; first nondefinition form, which is problematic anyway
+ ;; since it might be the first element of a begin that we
+ ;; have just spliced into the body (meaning if we restarted,
+ ;; we'd really need to restart with the begin or the macro
+ ;; call that expanded into the begin, and we'd have to give
+ ;; up allowing (begin <defn>+ <expr>+), which is itself
+ ;; problematic since we don't know if a begin contains only
+ ;; definitions until we've expanded it).
+ ;;
+ ;; Before processing the body, we also create a new environment
+ ;; containing a placeholder for the bindings we will add later and
+ ;; associate this environment with each form. In processing a
+ ;; let-syntax or letrec-syntax, the associated environment may be
+ ;; augmented with local keyword bindings, so the environment may
+ ;; be different for different forms in the body. Once we have
+ ;; gathered up all of the definitions, we evaluate the transformer
+ ;; expressions and splice into r at the placeholder the new variable
+ ;; and keyword bindings. This allows let-syntax or letrec-syntax
+ ;; forms local to a portion or all of the body to shadow the
+ ;; definition bindings.
+ ;;
+ ;; Subforms of a begin, let-syntax, or letrec-syntax are spliced
+ ;; into the body.
+ ;;
+ ;; outer-form is fully wrapped w/source
+ (lambda (body outer-form r w mod)
+ (let* ((r (cons '("placeholder" . (placeholder)) r))
+ (ribcage (make-empty-ribcage))
+ (w (make-wrap (wrap-marks w) (cons ribcage (wrap-subst w)))))
+ (let parse ((body (map (lambda (x) (cons r (wrap x w mod))) body))
+ (ids '()) (labels '())
+ (var-ids '()) (vars '()) (vals '()) (bindings '()))
+ (if (null? body)
+ (syntax-violation #f "no expressions in body" outer-form)
+ (let ((e (cdar body)) (er (caar body)))
+ (call-with-values
+ (lambda () (syntax-type e er empty-wrap (source-annotation e) ribcage mod #f))
+ (lambda (type value form e w s mod)
+ (case type
+ ((define-form)
+ (let ((id (wrap value w mod)) (label (gen-label)))
+ (let ((var (gen-var id)))
+ (extend-ribcage! ribcage id label)
+ (parse (cdr body)
+ (cons id ids) (cons label labels)
+ (cons id var-ids)
+ (cons var vars) (cons (cons er (wrap e w mod)) vals)
+ (cons (make-binding 'lexical var) bindings)))))
+ ((define-syntax-form define-syntax-parameter-form)
+ (let ((id (wrap value w mod))
+ (label (gen-label))
+ (trans-r (macros-only-env er)))
+ (extend-ribcage! ribcage id label)
+ ;; As required by R6RS, evaluate the right-hand-sides of internal
+ ;; syntax definition forms and add their transformers to the
+ ;; compile-time environment immediately, so that the newly-defined
+ ;; keywords may be used in definition context within the same
+ ;; lexical contour.
+ (set-cdr! r (extend-env (list label)
+ (list (make-binding 'macro
+ (eval-local-transformer
+ (expand e trans-r w mod)
+ mod)))
+ (cdr r)))
+ (parse (cdr body) (cons id ids) labels var-ids vars vals bindings)))
+ ((begin-form)
+ (syntax-case e ()
+ ((_ e1 ...)
+ (parse (let f ((forms #'(e1 ...)))
+ (if (null? forms)
+ (cdr body)
+ (cons (cons er (wrap (car forms) w mod))
+ (f (cdr forms)))))
+ ids labels var-ids vars vals bindings))))
+ ((local-syntax-form)
+ (expand-local-syntax value e er w s mod
+ (lambda (forms er w s mod)
+ (parse (let f ((forms forms))
+ (if (null? forms)
+ (cdr body)
+ (cons (cons er (wrap (car forms) w mod))
+ (f (cdr forms)))))
+ ids labels var-ids vars vals bindings))))
+ (else ; found a non-definition
+ (if (null? ids)
+ (build-sequence no-source
+ (map (lambda (x)
+ (expand (cdr x) (car x) empty-wrap mod))
+ (cons (cons er (source-wrap e w s mod))
+ (cdr body))))
+ (begin
+ (if (not (valid-bound-ids? ids))
+ (syntax-violation
+ #f "invalid or duplicate identifier in definition"
+ outer-form))
+ (set-cdr! r (extend-env labels bindings (cdr r)))
+ (build-letrec no-source #t
+ (reverse (map syntax->datum var-ids))
+ (reverse vars)
+ (map (lambda (x)
+ (expand (cdr x) (car x) empty-wrap mod))
+ (reverse vals))
+ (build-sequence no-source
+ (map (lambda (x)
+ (expand (cdr x) (car x) empty-wrap mod))
+ (cons (cons er (source-wrap e w s mod))
+ (cdr body)))))))))))))))))
+
+ (define expand-local-syntax
+ (lambda (rec? e r w s mod k)
+ (syntax-case e ()
+ ((_ ((id val) ...) e1 e2 ...)
+ (let ((ids #'(id ...)))
+ (if (not (valid-bound-ids? ids))
+ (syntax-violation #f "duplicate bound keyword" e)
+ (let ((labels (gen-labels ids)))
+ (let ((new-w (make-binding-wrap ids labels w)))
+ (k #'(e1 e2 ...)
+ (extend-env
+ labels
+ (let ((w (if rec? new-w w))
+ (trans-r (macros-only-env r)))
+ (map (lambda (x)
+ (make-binding 'macro
+ (eval-local-transformer
+ (expand x trans-r w mod)
+ mod)))
+ #'(val ...)))
+ r)
+ new-w
+ s
+ mod))))))
+ (_ (syntax-violation #f "bad local syntax definition"
+ (source-wrap e w s mod))))))
+
+ (define eval-local-transformer
+ (lambda (expanded mod)
+ (let ((p (local-eval-hook expanded mod)))
+ (if (procedure? p)
+ p
+ (syntax-violation #f "nonprocedure transformer" p)))))
+
+ (define expand-void
+ (lambda ()
+ (build-void no-source)))
+
+ (define ellipsis?
+ (lambda (e r mod)
+ (and (nonsymbol-id? e)
+ ;; If there is a binding for the special identifier
+ ;; #{ $sc-ellipsis }# in the lexical environment of E,
+ ;; and if the associated binding type is 'ellipsis',
+ ;; then the binding's value specifies the custom ellipsis
+ ;; identifier within that lexical environment, and the
+ ;; comparison is done using 'bound-id=?'.
+ (let* ((id (make-syntax-object '#{ $sc-ellipsis }
+ (syntax-object-wrap e)
+ (syntax-object-module e)))
+ (n (id-var-name id empty-wrap))
+ (b (lookup n r mod)))
+ (if (eq? (binding-type b) 'ellipsis)
+ (bound-id=? e (binding-value b))
+ (free-id=? e #'(... ...)))))))
+
+ (define lambda-formals
+ (lambda (orig-args)
+ (define (req args rreq)
+ (syntax-case args ()
+ (()
+ (check (reverse rreq) #f))
+ ((a . b) (id? #'a)
+ (req #'b (cons #'a rreq)))
+ (r (id? #'r)
+ (check (reverse rreq) #'r))
+ (else
+ (syntax-violation 'lambda "invalid argument list" orig-args args))))
+ (define (check req rest)
+ (cond
+ ((distinct-bound-ids? (if rest (cons rest req) req))
+ (values req #f rest #f))
+ (else
+ (syntax-violation 'lambda "duplicate identifier in argument list"
+ orig-args))))
+ (req orig-args '())))
+
+ (define expand-simple-lambda
+ (lambda (e r w s mod req rest meta body)
+ (let* ((ids (if rest (append req (list rest)) req))
+ (vars (map gen-var ids))
+ (labels (gen-labels ids)))
+ (build-simple-lambda
+ s
+ (map syntax->datum req) (and rest (syntax->datum rest)) vars
+ meta
+ (expand-body body (source-wrap e w s mod)
+ (extend-var-env labels vars r)
+ (make-binding-wrap ids labels w)
+ mod)))))
+
+ (define lambda*-formals
+ (lambda (orig-args)
+ (define (req args rreq)
+ (syntax-case args ()
+ (()
+ (check (reverse rreq) '() #f '()))
+ ((a . b) (id? #'a)
+ (req #'b (cons #'a rreq)))
+ ((a . b) (eq? (syntax->datum #'a) #\optional)
+ (opt #'b (reverse rreq) '()))
+ ((a . b) (eq? (syntax->datum #'a) #\key)
+ (key #'b (reverse rreq) '() '()))
+ ((a b) (eq? (syntax->datum #'a) #\rest)
+ (rest #'b (reverse rreq) '() '()))
+ (r (id? #'r)
+ (rest #'r (reverse rreq) '() '()))
+ (else
+ (syntax-violation 'lambda* "invalid argument list" orig-args args))))
+ (define (opt args req ropt)
+ (syntax-case args ()
+ (()
+ (check req (reverse ropt) #f '()))
+ ((a . b) (id? #'a)
+ (opt #'b req (cons #'(a #f) ropt)))
+ (((a init) . b) (id? #'a)
+ (opt #'b req (cons #'(a init) ropt)))
+ ((a . b) (eq? (syntax->datum #'a) #\key)
+ (key #'b req (reverse ropt) '()))
+ ((a b) (eq? (syntax->datum #'a) #\rest)
+ (rest #'b req (reverse ropt) '()))
+ (r (id? #'r)
+ (rest #'r req (reverse ropt) '()))
+ (else
+ (syntax-violation 'lambda* "invalid optional argument list"
+ orig-args args))))
+ (define (key args req opt rkey)
+ (syntax-case args ()
+ (()
+ (check req opt #f (cons #f (reverse rkey))))
+ ((a . b) (id? #'a)
+ (with-syntax ((k (symbol->keyword (syntax->datum #'a))))
+ (key #'b req opt (cons #'(k a #f) rkey))))
+ (((a init) . b) (id? #'a)
+ (with-syntax ((k (symbol->keyword (syntax->datum #'a))))
+ (key #'b req opt (cons #'(k a init) rkey))))
+ (((a init k) . b) (and (id? #'a)
+ (keyword? (syntax->datum #'k)))
+ (key #'b req opt (cons #'(k a init) rkey)))
+ ((aok) (eq? (syntax->datum #'aok) #\allow-other-keys)
+ (check req opt #f (cons #t (reverse rkey))))
+ ((aok a b) (and (eq? (syntax->datum #'aok) #\allow-other-keys)
+ (eq? (syntax->datum #'a) #\rest))
+ (rest #'b req opt (cons #t (reverse rkey))))
+ ((aok . r) (and (eq? (syntax->datum #'aok) #\allow-other-keys)
+ (id? #'r))
+ (rest #'r req opt (cons #t (reverse rkey))))
+ ((a b) (eq? (syntax->datum #'a) #\rest)
+ (rest #'b req opt (cons #f (reverse rkey))))
+ (r (id? #'r)
+ (rest #'r req opt (cons #f (reverse rkey))))
+ (else
+ (syntax-violation 'lambda* "invalid keyword argument list"
+ orig-args args))))
+ (define (rest args req opt kw)
+ (syntax-case args ()
+ (r (id? #'r)
+ (check req opt #'r kw))
+ (else
+ (syntax-violation 'lambda* "invalid rest argument"
+ orig-args args))))
+ (define (check req opt rest kw)
+ (cond
+ ((distinct-bound-ids?
+ (append req (map car opt) (if rest (list rest) '())
+ (if (pair? kw) (map cadr (cdr kw)) '())))
+ (values req opt rest kw))
+ (else
+ (syntax-violation 'lambda* "duplicate identifier in argument list"
+ orig-args))))
+ (req orig-args '())))
+
+ (define expand-lambda-case
+ (lambda (e r w s mod get-formals clauses)
+ (define (parse-req req opt rest kw body)
+ (let ((vars (map gen-var req))
+ (labels (gen-labels req)))
+ (let ((r* (extend-var-env labels vars r))
+ (w* (make-binding-wrap req labels w)))
+ (parse-opt (map syntax->datum req)
+ opt rest kw body (reverse vars) r* w* '() '()))))
+ (define (parse-opt req opt rest kw body vars r* w* out inits)
+ (cond
+ ((pair? opt)
+ (syntax-case (car opt) ()
+ ((id i)
+ (let* ((v (gen-var #'id))
+ (l (gen-labels (list v)))
+ (r** (extend-var-env l (list v) r*))
+ (w** (make-binding-wrap (list #'id) l w*)))
+ (parse-opt req (cdr opt) rest kw body (cons v vars)
+ r** w** (cons (syntax->datum #'id) out)
+ (cons (expand #'i r* w* mod) inits))))))
+ (rest
+ (let* ((v (gen-var rest))
+ (l (gen-labels (list v)))
+ (r* (extend-var-env l (list v) r*))
+ (w* (make-binding-wrap (list rest) l w*)))
+ (parse-kw req (if (pair? out) (reverse out) #f)
+ (syntax->datum rest)
+ (if (pair? kw) (cdr kw) kw)
+ body (cons v vars) r* w*
+ (if (pair? kw) (car kw) #f)
+ '() inits)))
+ (else
+ (parse-kw req (if (pair? out) (reverse out) #f) #f
+ (if (pair? kw) (cdr kw) kw)
+ body vars r* w*
+ (if (pair? kw) (car kw) #f)
+ '() inits))))
+ (define (parse-kw req opt rest kw body vars r* w* aok out inits)
+ (cond
+ ((pair? kw)
+ (syntax-case (car kw) ()
+ ((k id i)
+ (let* ((v (gen-var #'id))
+ (l (gen-labels (list v)))
+ (r** (extend-var-env l (list v) r*))
+ (w** (make-binding-wrap (list #'id) l w*)))
+ (parse-kw req opt rest (cdr kw) body (cons v vars)
+ r** w** aok
+ (cons (list (syntax->datum #'k)
+ (syntax->datum #'id)
+ v)
+ out)
+ (cons (expand #'i r* w* mod) inits))))))
+ (else
+ (parse-body req opt rest
+ (if (or aok (pair? out)) (cons aok (reverse out)) #f)
+ body (reverse vars) r* w* (reverse inits) '()))))
+ (define (parse-body req opt rest kw body vars r* w* inits meta)
+ (syntax-case body ()
+ ((docstring e1 e2 ...) (string? (syntax->datum #'docstring))
+ (parse-body req opt rest kw #'(e1 e2 ...) vars r* w* inits
+ (append meta
+ `((documentation
+ . ,(syntax->datum #'docstring))))))
+ ((#((k . v) ...) e1 e2 ...)
+ (parse-body req opt rest kw #'(e1 e2 ...) vars r* w* inits
+ (append meta (syntax->datum #'((k . v) ...)))))
+ ((e1 e2 ...)
+ (values meta req opt rest kw inits vars
+ (expand-body #'(e1 e2 ...) (source-wrap e w s mod)
+ r* w* mod)))))
+
+ (syntax-case clauses ()
+ (() (values '() #f))
+ (((args e1 e2 ...) (args* e1* e2* ...) ...)
+ (call-with-values (lambda () (get-formals #'args))
+ (lambda (req opt rest kw)
+ (call-with-values (lambda ()
+ (parse-req req opt rest kw #'(e1 e2 ...)))
+ (lambda (meta req opt rest kw inits vars body)
+ (call-with-values
+ (lambda ()
+ (expand-lambda-case e r w s mod get-formals
+ #'((args* e1* e2* ...) ...)))
+ (lambda (meta* else*)
+ (values
+ (append meta meta*)
+ (build-lambda-case s req opt rest kw inits vars
+ body else*))))))))))))
+
+ ;; data
+
+ ;; strips syntax-objects down to top-wrap
+ ;;
+ ;; since only the head of a list is annotated by the reader, not each pair
+ ;; in the spine, we also check for pairs whose cars are annotated in case
+ ;; we've been passed the cdr of an annotated list
+
+ (define strip
+ (lambda (x w)
+ (if (top-marked? w)
+ x
+ (let f ((x x))
+ (cond
+ ((syntax-object? x)
+ (strip (syntax-object-expression x) (syntax-object-wrap x)))
+ ((pair? x)
+ (let ((a (f (car x))) (d (f (cdr x))))
+ (if (and (eq? a (car x)) (eq? d (cdr x)))
+ x
+ (cons a d))))
+ ((vector? x)
+ (let ((old (vector->list x)))
+ (let ((new (map f old)))
+ ;; inlined and-map with two args
+ (let lp ((l1 old) (l2 new))
+ (if (null? l1)
+ x
+ (if (eq? (car l1) (car l2))
+ (lp (cdr l1) (cdr l2))
+ (list->vector new)))))))
+ (else x))))))
+
+ ;; lexical variables
+
+ (define gen-var
+ (lambda (id)
+ (let ((id (if (syntax-object? id) (syntax-object-expression id) id)))
+ (build-lexical-var no-source id))))
+
+ ;; appears to return a reversed list
+ (define lambda-var-list
+ (lambda (vars)
+ (let lvl ((vars vars) (ls '()) (w empty-wrap))
+ (cond
+ ((pair? vars) (lvl (cdr vars) (cons (wrap (car vars) w #f) ls) w))
+ ((id? vars) (cons (wrap vars w #f) ls))
+ ((null? vars) ls)
+ ((syntax-object? vars)
+ (lvl (syntax-object-expression vars)
+ ls
+ (join-wraps w (syntax-object-wrap vars))))
+ ;; include anything else to be caught by subsequent error
+ ;; checking
+ (else (cons vars ls))))))
+
+ ;; core transformers
+
+ (global-extend 'local-syntax 'letrec-syntax #t)
+ (global-extend 'local-syntax 'let-syntax #f)
+
+ (global-extend 'core 'syntax-parameterize
+ (lambda (e r w s mod)
+ (syntax-case e ()
+ ((_ ((var val) ...) e1 e2 ...)
+ (valid-bound-ids? #'(var ...))
+ (let ((names (map (lambda (x) (id-var-name x w)) #'(var ...))))
+ (for-each
+ (lambda (id n)
+ (case (binding-type (lookup n r mod))
+ ((displaced-lexical)
+ (syntax-violation 'syntax-parameterize
+ "identifier out of context"
+ e
+ (source-wrap id w s mod)))))
+ #'(var ...)
+ names)
+ (expand-body
+ #'(e1 e2 ...)
+ (source-wrap e w s mod)
+ (extend-env
+ names
+ (let ((trans-r (macros-only-env r)))
+ (map (lambda (x)
+ (make-binding 'macro
+ (eval-local-transformer (expand x trans-r w mod)
+ mod)))
+ #'(val ...)))
+ r)
+ w
+ mod)))
+ (_ (syntax-violation 'syntax-parameterize "bad syntax"
+ (source-wrap e w s mod))))))
+
+ (global-extend 'core 'quote
+ (lambda (e r w s mod)
+ (syntax-case e ()
+ ((_ e) (build-data s (strip #'e w)))
+ (_ (syntax-violation 'quote "bad syntax"
+ (source-wrap e w s mod))))))
+
+ (global-extend 'core 'syntax
+ (let ()
+ (define gen-syntax
+ (lambda (src e r maps ellipsis? mod)
+ (if (id? e)
+ (let ((label (id-var-name e empty-wrap)))
+ ;; Mod does not matter, we are looking to see if
+ ;; the id is lexical syntax.
+ (let ((b (lookup label r mod)))
+ (if (eq? (binding-type b) 'syntax)
+ (call-with-values
+ (lambda ()
+ (let ((var.lev (binding-value b)))
+ (gen-ref src (car var.lev) (cdr var.lev) maps)))
+ (lambda (var maps) (values `(ref ,var) maps)))
+ (if (ellipsis? e r mod)
+ (syntax-violation 'syntax "misplaced ellipsis" src)
+ (values `(quote ,e) maps)))))
+ (syntax-case e ()
+ ((dots e)
+ (ellipsis? #'dots r mod)
+ (gen-syntax src #'e r maps (lambda (e r mod) #f) mod))
+ ((x dots . y)
+ ;; this could be about a dozen lines of code, except that we
+ ;; choose to handle #'(x ... ...) forms
+ (ellipsis? #'dots r mod)
+ (let f ((y #'y)
+ (k (lambda (maps)
+ (call-with-values
+ (lambda ()
+ (gen-syntax src #'x r
+ (cons '() maps) ellipsis? mod))
+ (lambda (x maps)
+ (if (null? (car maps))
+ (syntax-violation 'syntax "extra ellipsis"
+ src)
+ (values (gen-map x (car maps))
+ (cdr maps))))))))
+ (syntax-case y ()
+ ((dots . y)
+ (ellipsis? #'dots r mod)
+ (f #'y
+ (lambda (maps)
+ (call-with-values
+ (lambda () (k (cons '() maps)))
+ (lambda (x maps)
+ (if (null? (car maps))
+ (syntax-violation 'syntax "extra ellipsis" src)
+ (values (gen-mappend x (car maps))
+ (cdr maps))))))))
+ (_ (call-with-values
+ (lambda () (gen-syntax src y r maps ellipsis? mod))
+ (lambda (y maps)
+ (call-with-values
+ (lambda () (k maps))
+ (lambda (x maps)
+ (values (gen-append x y) maps)))))))))
+ ((x . y)
+ (call-with-values
+ (lambda () (gen-syntax src #'x r maps ellipsis? mod))
+ (lambda (x maps)
+ (call-with-values
+ (lambda () (gen-syntax src #'y r maps ellipsis? mod))
+ (lambda (y maps) (values (gen-cons x y) maps))))))
+ (#(e1 e2 ...)
+ (call-with-values
+ (lambda ()
+ (gen-syntax src #'(e1 e2 ...) r maps ellipsis? mod))
+ (lambda (e maps) (values (gen-vector e) maps))))
+ (_ (values `(quote ,e) maps))))))
+
+ (define gen-ref
+ (lambda (src var level maps)
+ (if (fx= level 0)
+ (values var maps)
+ (if (null? maps)
+ (syntax-violation 'syntax "missing ellipsis" src)
+ (call-with-values
+ (lambda () (gen-ref src var (fx- level 1) (cdr maps)))
+ (lambda (outer-var outer-maps)
+ (let ((b (assq outer-var (car maps))))
+ (if b
+ (values (cdr b) maps)
+ (let ((inner-var (gen-var 'tmp)))
+ (values inner-var
+ (cons (cons (cons outer-var inner-var)
+ (car maps))
+ outer-maps)))))))))))
+
+ (define gen-mappend
+ (lambda (e map-env)
+ `(apply (primitive append) ,(gen-map e map-env))))
+
+ (define gen-map
+ (lambda (e map-env)
+ (let ((formals (map cdr map-env))
+ (actuals (map (lambda (x) `(ref ,(car x))) map-env)))
+ (cond
+ ((eq? (car e) 'ref)
+ ;; identity map equivalence:
+ ;; (map (lambda (x) x) y) == y
+ (car actuals))
+ ((and-map
+ (lambda (x) (and (eq? (car x) 'ref) (memq (cadr x) formals)))
+ (cdr e))
+ ;; eta map equivalence:
+ ;; (map (lambda (x ...) (f x ...)) y ...) == (map f y ...)
+ `(map (primitive ,(car e))
+ ,@(map (let ((r (map cons formals actuals)))
+ (lambda (x) (cdr (assq (cadr x) r))))
+ (cdr e))))
+ (else `(map (lambda ,formals ,e) ,@actuals))))))
+
+ (define gen-cons
+ (lambda (x y)
+ (case (car y)
+ ((quote)
+ (if (eq? (car x) 'quote)
+ `(quote (,(cadr x) . ,(cadr y)))
+ (if (eq? (cadr y) '())
+ `(list ,x)
+ `(cons ,x ,y))))
+ ((list) `(list ,x ,@(cdr y)))
+ (else `(cons ,x ,y)))))
+
+ (define gen-append
+ (lambda (x y)
+ (if (equal? y '(quote ()))
+ x
+ `(append ,x ,y))))
+
+ (define gen-vector
+ (lambda (x)
+ (cond
+ ((eq? (car x) 'list) `(vector ,@(cdr x)))
+ ((eq? (car x) 'quote) `(quote #(,@(cadr x))))
+ (else `(list->vector ,x)))))
+
+
+ (define regen
+ (lambda (x)
+ (case (car x)
+ ((ref) (build-lexical-reference 'value no-source (cadr x) (cadr x)))
+ ((primitive) (build-primref no-source (cadr x)))
+ ((quote) (build-data no-source (cadr x)))
+ ((lambda)
+ (if (list? (cadr x))
+ (build-simple-lambda no-source (cadr x) #f (cadr x) '() (regen (caddr x)))
+ (error "how did we get here" x)))
+ (else (build-application no-source
+ (build-primref no-source (car x))
+ (map regen (cdr x)))))))
+
+ (lambda (e r w s mod)
+ (let ((e (source-wrap e w s mod)))
+ (syntax-case e ()
+ ((_ x)
+ (call-with-values
+ (lambda () (gen-syntax e #'x r '() ellipsis? mod))
+ (lambda (e maps) (regen e))))
+ (_ (syntax-violation 'syntax "bad `syntax' form" e)))))))
+
+ (global-extend 'core 'lambda
+ (lambda (e r w s mod)
+ (syntax-case e ()
+ ((_ args e1 e2 ...)
+ (call-with-values (lambda () (lambda-formals #'args))
+ (lambda (req opt rest kw)
+ (let lp ((body #'(e1 e2 ...)) (meta '()))
+ (syntax-case body ()
+ ((docstring e1 e2 ...) (string? (syntax->datum #'docstring))
+ (lp #'(e1 e2 ...)
+ (append meta
+ `((documentation
+ . ,(syntax->datum #'docstring))))))
+ ((#((k . v) ...) e1 e2 ...)
+ (lp #'(e1 e2 ...)
+ (append meta (syntax->datum #'((k . v) ...)))))
+ (_ (expand-simple-lambda e r w s mod req rest meta body)))))))
+ (_ (syntax-violation 'lambda "bad lambda" e)))))
+
+ (global-extend 'core 'lambda*
+ (lambda (e r w s mod)
+ (syntax-case e ()
+ ((_ args e1 e2 ...)
+ (call-with-values
+ (lambda ()
+ (expand-lambda-case e r w s mod
+ lambda*-formals #'((args e1 e2 ...))))
+ (lambda (meta lcase)
+ (build-case-lambda s meta lcase))))
+ (_ (syntax-violation 'lambda "bad lambda*" e)))))
+
+ (global-extend 'core 'case-lambda
+ (lambda (e r w s mod)
+ (define (build-it meta clauses)
+ (call-with-values
+ (lambda ()
+ (expand-lambda-case e r w s mod
+ lambda-formals
+ clauses))
+ (lambda (meta* lcase)
+ (build-case-lambda s (append meta meta*) lcase))))
+ (syntax-case e ()
+ ((_ (args e1 e2 ...) ...)
+ (build-it '() #'((args e1 e2 ...) ...)))
+ ((_ docstring (args e1 e2 ...) ...)
+ (string? (syntax->datum #'docstring))
+ (build-it `((documentation
+ . ,(syntax->datum #'docstring)))
+ #'((args e1 e2 ...) ...)))
+ (_ (syntax-violation 'case-lambda "bad case-lambda" e)))))
+
+ (global-extend 'core 'case-lambda*
+ (lambda (e r w s mod)
+ (define (build-it meta clauses)
+ (call-with-values
+ (lambda ()
+ (expand-lambda-case e r w s mod
+ lambda*-formals
+ clauses))
+ (lambda (meta* lcase)
+ (build-case-lambda s (append meta meta*) lcase))))
+ (syntax-case e ()
+ ((_ (args e1 e2 ...) ...)
+ (build-it '() #'((args e1 e2 ...) ...)))
+ ((_ docstring (args e1 e2 ...) ...)
+ (string? (syntax->datum #'docstring))
+ (build-it `((documentation
+ . ,(syntax->datum #'docstring)))
+ #'((args e1 e2 ...) ...)))
+ (_ (syntax-violation 'case-lambda "bad case-lambda*" e)))))
+
+ (global-extend 'core 'with-ellipsis
+ (lambda (e r w s mod)
+ (syntax-case e ()
+ ((_ dots e1 e2 ...)
+ (id? #'dots)
+ (let ((id (if (symbol? #'dots)
+ '#{ $sc-ellipsis }
+ (make-syntax-object '#{ $sc-ellipsis }
+ (syntax-object-wrap #'dots)
+ (syntax-object-module #'dots)))))
+ (let ((ids (list id))
+ (labels (list (gen-label)))
+ (bindings (list (make-binding 'ellipsis (source-wrap #'dots w s mod)))))
+ (let ((nw (make-binding-wrap ids labels w))
+ (nr (extend-env labels bindings r)))
+ (expand-body #'(e1 e2 ...) (source-wrap e nw s mod) nr nw mod)))))
+ (_ (syntax-violation 'with-ellipsis "bad syntax"
+ (source-wrap e w s mod))))))
+
+ (global-extend 'core 'let
+ (let ()
+ (define (expand-let e r w s mod constructor ids vals exps)
+ (if (not (valid-bound-ids? ids))
+ (syntax-violation 'let "duplicate bound variable" e)
+ (let ((labels (gen-labels ids))
+ (new-vars (map gen-var ids)))
+ (let ((nw (make-binding-wrap ids labels w))
+ (nr (extend-var-env labels new-vars r)))
+ (constructor s
+ (map syntax->datum ids)
+ new-vars
+ (map (lambda (x) (expand x r w mod)) vals)
+ (expand-body exps (source-wrap e nw s mod)
+ nr nw mod))))))
+ (lambda (e r w s mod)
+ (syntax-case e ()
+ ((_ ((id val) ...) e1 e2 ...)
+ (and-map id? #'(id ...))
+ (expand-let e r w s mod
+ build-let
+ #'(id ...)
+ #'(val ...)
+ #'(e1 e2 ...)))
+ ((_ f ((id val) ...) e1 e2 ...)
+ (and (id? #'f) (and-map id? #'(id ...)))
+ (expand-let e r w s mod
+ build-named-let
+ #'(f id ...)
+ #'(val ...)
+ #'(e1 e2 ...)))
+ (_ (syntax-violation 'let "bad let" (source-wrap e w s mod)))))))
+
+
+ (global-extend 'core 'letrec
+ (lambda (e r w s mod)
+ (syntax-case e ()
+ ((_ ((id val) ...) e1 e2 ...)
+ (and-map id? #'(id ...))
+ (let ((ids #'(id ...)))
+ (if (not (valid-bound-ids? ids))
+ (syntax-violation 'letrec "duplicate bound variable" e)
+ (let ((labels (gen-labels ids))
+ (new-vars (map gen-var ids)))
+ (let ((w (make-binding-wrap ids labels w))
+ (r (extend-var-env labels new-vars r)))
+ (build-letrec s #f
+ (map syntax->datum ids)
+ new-vars
+ (map (lambda (x) (expand x r w mod)) #'(val ...))
+ (expand-body #'(e1 e2 ...)
+ (source-wrap e w s mod) r w mod)))))))
+ (_ (syntax-violation 'letrec "bad letrec" (source-wrap e w s mod))))))
+
+
+ (global-extend 'core 'letrec*
+ (lambda (e r w s mod)
+ (syntax-case e ()
+ ((_ ((id val) ...) e1 e2 ...)
+ (and-map id? #'(id ...))
+ (let ((ids #'(id ...)))
+ (if (not (valid-bound-ids? ids))
+ (syntax-violation 'letrec* "duplicate bound variable" e)
+ (let ((labels (gen-labels ids))
+ (new-vars (map gen-var ids)))
+ (let ((w (make-binding-wrap ids labels w))
+ (r (extend-var-env labels new-vars r)))
+ (build-letrec s #t
+ (map syntax->datum ids)
+ new-vars
+ (map (lambda (x) (expand x r w mod)) #'(val ...))
+ (expand-body #'(e1 e2 ...)
+ (source-wrap e w s mod) r w mod)))))))
+ (_ (syntax-violation 'letrec* "bad letrec*" (source-wrap e w s mod))))))
+
+
+ (global-extend 'core 'set!
+ (lambda (e r w s mod)
+ (syntax-case e ()
+ ((_ id val)
+ (id? #'id)
+ (let ((n (id-var-name #'id w))
+ ;; Lookup id in its module
+ (id-mod (if (syntax-object? #'id)
+ (syntax-object-module #'id)
+ mod)))
+ (let ((b (lookup n r id-mod)))
+ (case (binding-type b)
+ ((lexical)
+ (build-lexical-assignment s
+ (syntax->datum #'id)
+ (binding-value b)
+ (expand #'val r w mod)))
+ ((global)
+ (build-global-assignment s n (expand #'val r w mod) id-mod))
+ ((macro)
+ (let ((p (binding-value b)))
+ (if (procedure-property p 'variable-transformer)
+ ;; As syntax-type does, call expand-macro with
+ ;; the mod of the expression. Hmm.
+ (expand (expand-macro p e r w s #f mod) r empty-wrap mod)
+ (syntax-violation 'set! "not a variable transformer"
+ (wrap e w mod)
+ (wrap #'id w id-mod)))))
+ ((displaced-lexical)
+ (syntax-violation 'set! "identifier out of context"
+ (wrap #'id w mod)))
+ (else (syntax-violation 'set! "bad set!"
+ (source-wrap e w s mod)))))))
+ ((_ (head tail ...) val)
+ (call-with-values
+ (lambda () (syntax-type #'head r empty-wrap no-source #f mod #t))
+ (lambda (type value formform ee ww ss modmod)
+ (case type
+ ((module-ref)
+ (let ((val (expand #'val r w mod)))
+ (call-with-values (lambda () (value #'(head tail ...) r w))
+ (lambda (e r w s* mod)
+ (syntax-case e ()
+ (e (id? #'e)
+ (build-global-assignment s (syntax->datum #'e)
+ val mod)))))))
+ (else
+ (build-application s
+ (expand #'(setter head) r w mod)
+ (map (lambda (e) (expand e r w mod))
+ #'(tail ... val))))))))
+ (_ (syntax-violation 'set! "bad set!" (source-wrap e w s mod))))))
+
+ (global-extend 'module-ref '@
+ (lambda (e r w)
+ (syntax-case e ()
+ ((_ (mod ...) id)
+ (and (and-map id? #'(mod ...)) (id? #'id))
+ ;; Strip the wrap from the identifier and return top-wrap
+ ;; so that the identifier will not be captured by lexicals.
+ (values (syntax->datum #'id) r top-wrap #f
+ (syntax->datum
+ #'(public mod ...)))))))
+
+ (global-extend 'module-ref '@@
+ (lambda (e r w)
+ (define remodulate
+ (lambda (x mod)
+ (cond ((pair? x)
+ (cons (remodulate (car x) mod)
+ (remodulate (cdr x) mod)))
+ ((syntax-object? x)
+ (make-syntax-object
+ (remodulate (syntax-object-expression x) mod)
+ (syntax-object-wrap x)
+ ;; hither the remodulation
+ mod))
+ ((vector? x)
+ (let* ((n (vector-length x)) (v (make-vector n)))
+ (do ((i 0 (fx+ i 1)))
+ ((fx= i n) v)
+ (vector-set! v i (remodulate (vector-ref x i) mod)))))
+ (else x))))
+ (syntax-case e (@@)
+ ((_ (mod ...) id)
+ (and (and-map id? #'(mod ...)) (id? #'id))
+ ;; Strip the wrap from the identifier and return top-wrap
+ ;; so that the identifier will not be captured by lexicals.
+ (values (syntax->datum #'id) r top-wrap #f
+ (syntax->datum
+ #'(private mod ...))))
+ ((_ @@ (mod ...) exp)
+ (and-map id? #'(mod ...))
+ ;; This is a special syntax used to support R6RS library forms.
+ ;; Unlike the syntax above, the last item is not restricted to
+ ;; be a single identifier, and the syntax objects are kept
+ ;; intact, with only their module changed.
+ (let ((mod (syntax->datum #'(private mod ...))))
+ (values (remodulate #'exp mod)
+ r w (source-annotation #'exp)
+ mod))))))
+
+ (global-extend 'core 'if
+ (lambda (e r w s mod)
+ (syntax-case e ()
+ ((_ test then)
+ (build-conditional
+ s
+ (expand #'test r w mod)
+ (expand #'then r w mod)
+ (build-void no-source)))
+ ((_ test then else)
+ (build-conditional
+ s
+ (expand #'test r w mod)
+ (expand #'then r w mod)
+ (expand #'else r w mod))))))
+
+ (global-extend 'core 'with-fluids
+ (lambda (e r w s mod)
+ (syntax-case e ()
+ ((_ ((fluid val) ...) b b* ...)
+ (build-dynlet
+ s
+ (map (lambda (x) (expand x r w mod)) #'(fluid ...))
+ (map (lambda (x) (expand x r w mod)) #'(val ...))
+ (expand-body #'(b b* ...)
+ (source-wrap e w s mod) r w mod))))))
+
+ (global-extend 'begin 'begin '())
+
+ (global-extend 'define 'define '())
+
+ (global-extend 'define-syntax 'define-syntax '())
+ (global-extend 'define-syntax-parameter 'define-syntax-parameter '())
+
+ (global-extend 'eval-when 'eval-when '())
+
+ (global-extend 'core 'syntax-case
+ (let ()
+ (define convert-pattern
+ ;; accepts pattern & keys
+ ;; returns $sc-dispatch pattern & ids
+ (lambda (pattern keys ellipsis?)
+ (define cvt*
+ (lambda (p* n ids)
+ (syntax-case p* ()
+ ((x . y)
+ (call-with-values
+ (lambda () (cvt* #'y n ids))
+ (lambda (y ids)
+ (call-with-values
+ (lambda () (cvt #'x n ids))
+ (lambda (x ids)
+ (values (cons x y) ids))))))
+ (_ (cvt p* n ids)))))
+
+ (define (v-reverse x)
+ (let loop ((r '()) (x x))
+ (if (not (pair? x))
+ (values r x)
+ (loop (cons (car x) r) (cdr x)))))
+
+ (define cvt
+ (lambda (p n ids)
+ (if (id? p)
+ (cond
+ ((bound-id-member? p keys)
+ (values (vector 'free-id p) ids))
+ ((free-id=? p #'_)
+ (values '_ ids))
+ (else
+ (values 'any (cons (cons p n) ids))))
+ (syntax-case p ()
+ ((x dots)
+ (ellipsis? (syntax dots))
+ (call-with-values
+ (lambda () (cvt (syntax x) (fx+ n 1) ids))
+ (lambda (p ids)
+ (values (if (eq? p 'any) 'each-any (vector 'each p))
+ ids))))
+ ((x dots . ys)
+ (ellipsis? (syntax dots))
+ (call-with-values
+ (lambda () (cvt* (syntax ys) n ids))
+ (lambda (ys ids)
+ (call-with-values
+ (lambda () (cvt (syntax x) (+ n 1) ids))
+ (lambda (x ids)
+ (call-with-values
+ (lambda () (v-reverse ys))
+ (lambda (ys e)
+ (values `#(each+ ,x ,ys ,e)
+ ids))))))))
+ ((x . y)
+ (call-with-values
+ (lambda () (cvt (syntax y) n ids))
+ (lambda (y ids)
+ (call-with-values
+ (lambda () (cvt (syntax x) n ids))
+ (lambda (x ids)
+ (values (cons x y) ids))))))
+ (() (values '() ids))
+ (#(x ...)
+ (call-with-values
+ (lambda () (cvt (syntax (x ...)) n ids))
+ (lambda (p ids) (values (vector 'vector p) ids))))
+ (x (values (vector 'atom (strip p empty-wrap)) ids))))))
+ (cvt pattern 0 '())))
+
+ (define build-dispatch-call
+ (lambda (pvars exp y r mod)
+ (let ((ids (map car pvars)) (levels (map cdr pvars)))
+ (let ((labels (gen-labels ids)) (new-vars (map gen-var ids)))
+ (build-application no-source
+ (build-primref no-source 'apply)
+ (list (build-simple-lambda no-source (map syntax->datum ids) #f new-vars '()
+ (expand exp
+ (extend-env
+ labels
+ (map (lambda (var level)
+ (make-binding 'syntax `(,var . ,level)))
+ new-vars
+ (map cdr pvars))
+ r)
+ (make-binding-wrap ids labels empty-wrap)
+ mod))
+ y))))))
+
+ (define gen-clause
+ (lambda (x keys clauses r pat fender exp mod)
+ (call-with-values
+ (lambda () (convert-pattern pat keys (lambda (e) (ellipsis? e r mod))))
+ (lambda (p pvars)
+ (cond
+ ((not (and-map (lambda (x) (not (ellipsis? (car x) r mod))) pvars))
+ (syntax-violation 'syntax-case "misplaced ellipsis" pat))
+ ((not (distinct-bound-ids? (map car pvars)))
+ (syntax-violation 'syntax-case "duplicate pattern variable" pat))
+ (else
+ (let ((y (gen-var 'tmp)))
+ ;; fat finger binding and references to temp variable y
+ (build-application no-source
+ (build-simple-lambda no-source (list 'tmp) #f (list y) '()
+ (let ((y (build-lexical-reference 'value no-source
+ 'tmp y)))
+ (build-conditional no-source
+ (syntax-case fender ()
+ (#t y)
+ (_ (build-conditional no-source
+ y
+ (build-dispatch-call pvars fender y r mod)
+ (build-data no-source #f))))
+ (build-dispatch-call pvars exp y r mod)
+ (gen-syntax-case x keys clauses r mod))))
+ (list (if (eq? p 'any)
+ (build-application no-source
+ (build-primref no-source 'list)
+ (list x))
+ (build-application no-source
+ (build-primref no-source '$sc-dispatch)
+ (list x (build-data no-source p)))))))))))))
+
+ (define gen-syntax-case
+ (lambda (x keys clauses r mod)
+ (if (null? clauses)
+ (build-application no-source
+ (build-primref no-source 'syntax-violation)
+ (list (build-data no-source #f)
+ (build-data no-source
+ "source expression failed to match any pattern")
+ x))
+ (syntax-case (car clauses) ()
+ ((pat exp)
+ (if (and (id? #'pat)
+ (and-map (lambda (x) (not (free-id=? #'pat x)))
+ (cons #'(... ...) keys)))
+ (if (free-id=? #'pat #'_)
+ (expand #'exp r empty-wrap mod)
+ (let ((labels (list (gen-label)))
+ (var (gen-var #'pat)))
+ (build-application no-source
+ (build-simple-lambda
+ no-source (list (syntax->datum #'pat)) #f (list var)
+ '()
+ (expand #'exp
+ (extend-env labels
+ (list (make-binding 'syntax `(,var . 0)))
+ r)
+ (make-binding-wrap #'(pat)
+ labels empty-wrap)
+ mod))
+ (list x))))
+ (gen-clause x keys (cdr clauses) r
+ #'pat #t #'exp mod)))
+ ((pat fender exp)
+ (gen-clause x keys (cdr clauses) r
+ #'pat #'fender #'exp mod))
+ (_ (syntax-violation 'syntax-case "invalid clause"
+ (car clauses)))))))
+
+ (lambda (e r w s mod)
+ (let ((e (source-wrap e w s mod)))
+ (syntax-case e ()
+ ((_ val (key ...) m ...)
+ (if (and-map (lambda (x) (and (id? x) (not (ellipsis? x r mod))))
+ #'(key ...))
+ (let ((x (gen-var 'tmp)))
+ ;; fat finger binding and references to temp variable x
+ (build-application s
+ (build-simple-lambda no-source (list 'tmp) #f (list x) '()
+ (gen-syntax-case (build-lexical-reference 'value no-source
+ 'tmp x)
+ #'(key ...) #'(m ...)
+ r
+ mod))
+ (list (expand #'val r empty-wrap mod))))
+ (syntax-violation 'syntax-case "invalid literals list" e))))))))
+
+ ;; The portable macroexpand seeds expand-top's mode m with 'e (for
+ ;; evaluating) and esew (which stands for "eval syntax expanders
+ ;; when") with '(eval). In Chez Scheme, m is set to 'c instead of e
+ ;; if we are compiling a file, and esew is set to
+ ;; (eval-syntactic-expanders-when), which defaults to the list
+ ;; '(compile load eval). This means that, by default, top-level
+ ;; syntactic definitions are evaluated immediately after they are
+ ;; expanded, and the expanded definitions are also residualized into
+ ;; the object file if we are compiling a file.
+ (set! macroexpand
+ (lambda* (x #\optional (m 'e) (esew '(eval)))
+ (expand-top-sequence (list x) null-env top-wrap #f m esew
+ (cons 'hygiene (module-name (current-module))))))
+
+ (set! identifier?
+ (lambda (x)
+ (nonsymbol-id? x)))
+
+ (set! datum->syntax
+ (lambda (id datum)
+ (make-syntax-object datum (syntax-object-wrap id)
+ (syntax-object-module id))))
+
+ (set! syntax->datum
+ ;; accepts any object, since syntax objects may consist partially
+ ;; or entirely of unwrapped, nonsymbolic data
+ (lambda (x)
+ (strip x empty-wrap)))
+
+ (set! syntax-source
+ (lambda (x) (source-annotation x)))
+
+ (set! generate-temporaries
+ (lambda (ls)
+ (arg-check list? ls 'generate-temporaries)
+ (let ((mod (cons 'hygiene (module-name (current-module)))))
+ (map (lambda (x)
+ (wrap (module-gensym "t") top-wrap mod))
+ ls))))
+
+ (set! free-identifier=?
+ (lambda (x y)
+ (arg-check nonsymbol-id? x 'free-identifier=?)
+ (arg-check nonsymbol-id? y 'free-identifier=?)
+ (free-id=? x y)))
+
+ (set! bound-identifier=?
+ (lambda (x y)
+ (arg-check nonsymbol-id? x 'bound-identifier=?)
+ (arg-check nonsymbol-id? y 'bound-identifier=?)
+ (bound-id=? x y)))
+
+ (set! syntax-violation
+ (lambda* (who message form #\optional subform)
+ (arg-check (lambda (x) (or (not x) (string? x) (symbol? x)))
+ who 'syntax-violation)
+ (arg-check string? message 'syntax-violation)
+ (throw 'syntax-error who message
+ (or (source-annotation subform)
+ (source-annotation form))
+ (strip form empty-wrap)
+ (and subform (strip subform empty-wrap)))))
+
+ (let ()
+ (define (syntax-module id)
+ (arg-check nonsymbol-id? id 'syntax-module)
+ (cdr (syntax-object-module id)))
+
+ (define (syntax-local-binding id)
+ (arg-check nonsymbol-id? id 'syntax-local-binding)
+ (with-transformer-environment
+ (lambda (e r w s rib mod)
+ (define (strip-anti-mark w)
+ (let ((ms (wrap-marks w)) (s (wrap-subst w)))
+ (if (and (pair? ms) (eq? (car ms) the-anti-mark))
+ ;; output is from original text
+ (make-wrap (cdr ms) (if rib (cons rib (cdr s)) (cdr s)))
+ ;; output introduced by macro
+ (make-wrap ms (if rib (cons rib s) s)))))
+ (call-with-values (lambda ()
+ (resolve-identifier
+ (syntax-object-expression id)
+ (strip-anti-mark (syntax-object-wrap id))
+ r
+ (syntax-object-module id)))
+ (lambda (type value mod)
+ (case type
+ ((lexical) (values 'lexical value))
+ ((macro) (values 'macro value))
+ ((syntax) (values 'pattern-variable value))
+ ((displaced-lexical) (values 'displaced-lexical #f))
+ ((global) (values 'global (cons value (cdr mod))))
+ ((ellipsis)
+ (values 'ellipsis
+ (make-syntax-object (syntax-object-expression value)
+ (anti-mark (syntax-object-wrap value))
+ (syntax-object-module value))))
+ (else (values 'other #f))))))))
+
+ (define (syntax-locally-bound-identifiers id)
+ (arg-check nonsymbol-id? id 'syntax-locally-bound-identifiers)
+ (locally-bound-identifiers (syntax-object-wrap id)
+ (syntax-object-module id)))
+
+ ;; Using define! instead of set! to avoid warnings at
+ ;; compile-time, after the variables are stolen away into (system
+ ;; syntax). See the end of boot-9.scm.
+ ;;
+ (define! 'syntax-module syntax-module)
+ (define! 'syntax-local-binding syntax-local-binding)
+ (define! 'syntax-locally-bound-identifiers syntax-locally-bound-identifiers))
+
+ ;; $sc-dispatch expects an expression and a pattern. If the expression
+ ;; matches the pattern a list of the matching expressions for each
+ ;; "any" is returned. Otherwise, #f is returned. (This use of #f will
+ ;; not work on r4rs implementations that violate the ieee requirement
+ ;; that #f and () be distinct.)
+
+ ;; The expression is matched with the pattern as follows:
+
+ ;; pattern: matches:
+ ;; () empty list
+ ;; any anything
+ ;; (<pattern>1 . <pattern>2) (<pattern>1 . <pattern>2)
+ ;; each-any (any*)
+ ;; #(free-id <key>) <key> with free-identifier=?
+ ;; #(each <pattern>) (<pattern>*)
+ ;; #(each+ p1 (p2_1 ... p2_n) p3) (p1* (p2_n ... p2_1) . p3)
+ ;; #(vector <pattern>) (list->vector <pattern>)
+ ;; #(atom <object>) <object> with "equal?"
+
+ ;; Vector cops out to pair under assumption that vectors are rare. If
+ ;; not, should convert to:
+ ;; #(vector <pattern>*) #(<pattern>*)
+
+ (let ()
+
+ (define match-each
+ (lambda (e p w mod)
+ (cond
+ ((pair? e)
+ (let ((first (match (car e) p w '() mod)))
+ (and first
+ (let ((rest (match-each (cdr e) p w mod)))
+ (and rest (cons first rest))))))
+ ((null? e) '())
+ ((syntax-object? e)
+ (match-each (syntax-object-expression e)
+ p
+ (join-wraps w (syntax-object-wrap e))
+ (syntax-object-module e)))
+ (else #f))))
+
+ (define match-each+
+ (lambda (e x-pat y-pat z-pat w r mod)
+ (let f ((e e) (w w))
+ (cond
+ ((pair? e)
+ (call-with-values (lambda () (f (cdr e) w))
+ (lambda (xr* y-pat r)
+ (if r
+ (if (null? y-pat)
+ (let ((xr (match (car e) x-pat w '() mod)))
+ (if xr
+ (values (cons xr xr*) y-pat r)
+ (values #f #f #f)))
+ (values
+ '()
+ (cdr y-pat)
+ (match (car e) (car y-pat) w r mod)))
+ (values #f #f #f)))))
+ ((syntax-object? e)
+ (f (syntax-object-expression e)
+ (join-wraps w (syntax-object-wrap e))))
+ (else
+ (values '() y-pat (match e z-pat w r mod)))))))
+
+ (define match-each-any
+ (lambda (e w mod)
+ (cond
+ ((pair? e)
+ (let ((l (match-each-any (cdr e) w mod)))
+ (and l (cons (wrap (car e) w mod) l))))
+ ((null? e) '())
+ ((syntax-object? e)
+ (match-each-any (syntax-object-expression e)
+ (join-wraps w (syntax-object-wrap e))
+ mod))
+ (else #f))))
+
+ (define match-empty
+ (lambda (p r)
+ (cond
+ ((null? p) r)
+ ((eq? p '_) r)
+ ((eq? p 'any) (cons '() r))
+ ((pair? p) (match-empty (car p) (match-empty (cdr p) r)))
+ ((eq? p 'each-any) (cons '() r))
+ (else
+ (case (vector-ref p 0)
+ ((each) (match-empty (vector-ref p 1) r))
+ ((each+) (match-empty (vector-ref p 1)
+ (match-empty
+ (reverse (vector-ref p 2))
+ (match-empty (vector-ref p 3) r))))
+ ((free-id atom) r)
+ ((vector) (match-empty (vector-ref p 1) r)))))))
+
+ (define combine
+ (lambda (r* r)
+ (if (null? (car r*))
+ r
+ (cons (map car r*) (combine (map cdr r*) r)))))
+
+ (define match*
+ (lambda (e p w r mod)
+ (cond
+ ((null? p) (and (null? e) r))
+ ((pair? p)
+ (and (pair? e) (match (car e) (car p) w
+ (match (cdr e) (cdr p) w r mod)
+ mod)))
+ ((eq? p 'each-any)
+ (let ((l (match-each-any e w mod))) (and l (cons l r))))
+ (else
+ (case (vector-ref p 0)
+ ((each)
+ (if (null? e)
+ (match-empty (vector-ref p 1) r)
+ (let ((l (match-each e (vector-ref p 1) w mod)))
+ (and l
+ (let collect ((l l))
+ (if (null? (car l))
+ r
+ (cons (map car l) (collect (map cdr l)))))))))
+ ((each+)
+ (call-with-values
+ (lambda ()
+ (match-each+ e (vector-ref p 1) (vector-ref p 2) (vector-ref p 3) w r mod))
+ (lambda (xr* y-pat r)
+ (and r
+ (null? y-pat)
+ (if (null? xr*)
+ (match-empty (vector-ref p 1) r)
+ (combine xr* r))))))
+ ((free-id) (and (id? e) (free-id=? (wrap e w mod) (vector-ref p 1)) r))
+ ((atom) (and (equal? (vector-ref p 1) (strip e w)) r))
+ ((vector)
+ (and (vector? e)
+ (match (vector->list e) (vector-ref p 1) w r mod))))))))
+
+ (define match
+ (lambda (e p w r mod)
+ (cond
+ ((not r) #f)
+ ((eq? p '_) r)
+ ((eq? p 'any) (cons (wrap e w mod) r))
+ ((syntax-object? e)
+ (match*
+ (syntax-object-expression e)
+ p
+ (join-wraps w (syntax-object-wrap e))
+ r
+ (syntax-object-module e)))
+ (else (match* e p w r mod)))))
+
+ (set! $sc-dispatch
+ (lambda (e p)
+ (cond
+ ((eq? p 'any) (list e))
+ ((eq? p '_) '())
+ ((syntax-object? e)
+ (match* (syntax-object-expression e)
+ p (syntax-object-wrap e) '() (syntax-object-module e)))
+ (else (match* e p empty-wrap '() #f))))))))
+
+
+(define-syntax with-syntax
+ (lambda (x)
+ (syntax-case x ()
+ ((_ () e1 e2 ...)
+ #'(let () e1 e2 ...))
+ ((_ ((out in)) e1 e2 ...)
+ #'(syntax-case in ()
+ (out (let () e1 e2 ...))))
+ ((_ ((out in) ...) e1 e2 ...)
+ #'(syntax-case (list in ...) ()
+ ((out ...) (let () e1 e2 ...)))))))
+
+(define-syntax syntax-error
+ (lambda (x)
+ (syntax-case x ()
+ ;; Extended internal syntax which provides the original form
+ ;; as the first operand, for improved error reporting.
+ ((_ (keyword . operands) message arg ...)
+ (string? (syntax->datum #'message))
+ (syntax-violation (syntax->datum #'keyword)
+ (string-join (cons (syntax->datum #'message)
+ (map (lambda (x)
+ (object->string
+ (syntax->datum x)))
+ #'(arg ...))))
+ (and (syntax->datum #'keyword)
+ #'(keyword . operands))))
+ ;; Standard R7RS syntax
+ ((_ message arg ...)
+ (string? (syntax->datum #'message))
+ #'(syntax-error (#f) message arg ...)))))
+
+(define-syntax syntax-rules
+ (lambda (xx)
+ (define (expand-clause clause)
+ ;; Convert a 'syntax-rules' clause into a 'syntax-case' clause.
+ (syntax-case clause (syntax-error)
+ ;; If the template is a 'syntax-error' form, use the extended
+ ;; internal syntax, which adds the original form as the first
+ ;; operand for improved error reporting.
+ (((keyword . pattern) (syntax-error message arg ...))
+ (string? (syntax->datum #'message))
+ #'((dummy . pattern) #'(syntax-error (dummy . pattern) message arg ...)))
+ ;; Normal case
+ (((keyword . pattern) template)
+ #'((dummy . pattern) #'template))))
+ (define (expand-syntax-rules dots keys docstrings clauses)
+ (with-syntax
+ (((k ...) keys)
+ ((docstring ...) docstrings)
+ ((((keyword . pattern) template) ...) clauses)
+ ((clause ...) (map expand-clause clauses)))
+ (with-syntax
+ ((form #'(lambda (x)
+ docstring ... ; optional docstring
+ #((macro-type . syntax-rules)
+ (patterns pattern ...)) ; embed patterns as procedure metadata
+ (syntax-case x (k ...)
+ clause ...))))
+ (if dots
+ (with-syntax ((dots dots))
+ #'(with-ellipsis dots form))
+ #'form))))
+ (syntax-case xx ()
+ ((_ (k ...) ((keyword . pattern) template) ...)
+ (expand-syntax-rules #f #'(k ...) #'() #'(((keyword . pattern) template) ...)))
+ ((_ (k ...) docstring ((keyword . pattern) template) ...)
+ (string? (syntax->datum #'docstring))
+ (expand-syntax-rules #f #'(k ...) #'(docstring) #'(((keyword . pattern) template) ...)))
+ ((_ dots (k ...) ((keyword . pattern) template) ...)
+ (identifier? #'dots)
+ (expand-syntax-rules #'dots #'(k ...) #'() #'(((keyword . pattern) template) ...)))
+ ((_ dots (k ...) docstring ((keyword . pattern) template) ...)
+ (and (identifier? #'dots) (string? (syntax->datum #'docstring)))
+ (expand-syntax-rules #'dots #'(k ...) #'(docstring) #'(((keyword . pattern) template) ...))))))
+
+(define-syntax define-syntax-rule
+ (lambda (x)
+ (syntax-case x ()
+ ((_ (name . pattern) template)
+ #'(define-syntax name
+ (syntax-rules ()
+ ((_ . pattern) template))))
+ ((_ (name . pattern) docstring template)
+ (string? (syntax->datum #'docstring))
+ #'(define-syntax name
+ (syntax-rules ()
+ docstring
+ ((_ . pattern) template)))))))
+
+(define-syntax let*
+ (lambda (x)
+ (syntax-case x ()
+ ((let* ((x v) ...) e1 e2 ...)
+ (and-map identifier? #'(x ...))
+ (let f ((bindings #'((x v) ...)))
+ (if (null? bindings)
+ #'(let () e1 e2 ...)
+ (with-syntax ((body (f (cdr bindings)))
+ (binding (car bindings)))
+ #'(let (binding) body))))))))
+
+(define-syntax quasiquote
+ (let ()
+ (define (quasi p lev)
+ (syntax-case p (unquote quasiquote)
+ ((unquote p)
+ (if (= lev 0)
+ #'("value" p)
+ (quasicons #'("quote" unquote) (quasi #'(p) (- lev 1)))))
+ ((quasiquote p) (quasicons #'("quote" quasiquote) (quasi #'(p) (+ lev 1))))
+ ((p . q)
+ (syntax-case #'p (unquote unquote-splicing)
+ ((unquote p ...)
+ (if (= lev 0)
+ (quasilist* #'(("value" p) ...) (quasi #'q lev))
+ (quasicons
+ (quasicons #'("quote" unquote) (quasi #'(p ...) (- lev 1)))
+ (quasi #'q lev))))
+ ((unquote-splicing p ...)
+ (if (= lev 0)
+ (quasiappend #'(("value" p) ...) (quasi #'q lev))
+ (quasicons
+ (quasicons #'("quote" unquote-splicing) (quasi #'(p ...) (- lev 1)))
+ (quasi #'q lev))))
+ (_ (quasicons (quasi #'p lev) (quasi #'q lev)))))
+ (#(x ...) (quasivector (vquasi #'(x ...) lev)))
+ (p #'("quote" p))))
+ (define (vquasi p lev)
+ (syntax-case p ()
+ ((p . q)
+ (syntax-case #'p (unquote unquote-splicing)
+ ((unquote p ...)
+ (if (= lev 0)
+ (quasilist* #'(("value" p) ...) (vquasi #'q lev))
+ (quasicons
+ (quasicons #'("quote" unquote) (quasi #'(p ...) (- lev 1)))
+ (vquasi #'q lev))))
+ ((unquote-splicing p ...)
+ (if (= lev 0)
+ (quasiappend #'(("value" p) ...) (vquasi #'q lev))
+ (quasicons
+ (quasicons
+ #'("quote" unquote-splicing)
+ (quasi #'(p ...) (- lev 1)))
+ (vquasi #'q lev))))
+ (_ (quasicons (quasi #'p lev) (vquasi #'q lev)))))
+ (() #'("quote" ()))))
+ (define (quasicons x y)
+ (with-syntax ((x x) (y y))
+ (syntax-case #'y ()
+ (("quote" dy)
+ (syntax-case #'x ()
+ (("quote" dx) #'("quote" (dx . dy)))
+ (_ (if (null? #'dy) #'("list" x) #'("list*" x y)))))
+ (("list" . stuff) #'("list" x . stuff))
+ (("list*" . stuff) #'("list*" x . stuff))
+ (_ #'("list*" x y)))))
+ (define (quasiappend x y)
+ (syntax-case y ()
+ (("quote" ())
+ (cond
+ ((null? x) #'("quote" ()))
+ ((null? (cdr x)) (car x))
+ (else (with-syntax (((p ...) x)) #'("append" p ...)))))
+ (_
+ (cond
+ ((null? x) y)
+ (else (with-syntax (((p ...) x) (y y)) #'("append" p ... y)))))))
+ (define (quasilist* x y)
+ (let f ((x x))
+ (if (null? x)
+ y
+ (quasicons (car x) (f (cdr x))))))
+ (define (quasivector x)
+ (syntax-case x ()
+ (("quote" (x ...)) #'("quote" #(x ...)))
+ (_
+ (let f ((y x) (k (lambda (ls) #`("vector" #,@ls))))
+ (syntax-case y ()
+ (("quote" (y ...)) (k #'(("quote" y) ...)))
+ (("list" y ...) (k #'(y ...)))
+ (("list*" y ... z) (f #'z (lambda (ls) (k (append #'(y ...) ls)))))
+ (else #`("list->vector" #,x)))))))
+ (define (emit x)
+ (syntax-case x ()
+ (("quote" x) #''x)
+ (("list" x ...) #`(list #,@(map emit #'(x ...))))
+ ;; could emit list* for 3+ arguments if implementation supports
+ ;; list*
+ (("list*" x ... y)
+ (let f ((x* #'(x ...)))
+ (if (null? x*)
+ (emit #'y)
+ #`(cons #,(emit (car x*)) #,(f (cdr x*))))))
+ (("append" x ...) #`(append #,@(map emit #'(x ...))))
+ (("vector" x ...) #`(vector #,@(map emit #'(x ...))))
+ (("list->vector" x) #`(list->vector #,(emit #'x)))
+ (("value" x) #'x)))
+ (lambda (x)
+ (syntax-case x ()
+ ;; convert to intermediate language, combining introduced (but
+ ;; not unquoted source) quote expressions where possible and
+ ;; choosing optimal construction code otherwise, then emit
+ ;; Scheme code corresponding to the intermediate language forms.
+ ((_ e) (emit (quasi #'e 0)))))))
+
+(define-syntax include
+ (lambda (x)
+ (define read-file
+ (lambda (fn dir k)
+ (let* ((p (open-input-file
+ (cond ((absolute-file-name? fn)
+ fn)
+ (dir
+ (in-vicinity dir fn))
+ (else
+ (syntax-violation
+ 'include
+ "relative file name only allowed when the include form is in a file"
+ x)))))
+ (enc (file-encoding p)))
+
+ ;; Choose the input encoding deterministically.
+ (set-port-encoding! p (or enc "UTF-8"))
+
+ (let f ((x (read p))
+ (result '()))
+ (if (eof-object? x)
+ (begin
+ (close-input-port p)
+ (reverse result))
+ (f (read p)
+ (cons (datum->syntax k x) result)))))))
+ (let* ((src (syntax-source x))
+ (file (and src (assq-ref src 'filename)))
+ (dir (and (string? file) (dirname file))))
+ (syntax-case x ()
+ ((k filename)
+ (let ((fn (syntax->datum #'filename)))
+ (with-syntax (((exp ...) (read-file fn dir #'filename)))
+ #'(begin exp ...))))))))
+
+(define-syntax include-from-path
+ (lambda (x)
+ (syntax-case x ()
+ ((k filename)
+ (let ((fn (syntax->datum #'filename)))
+ (with-syntax ((fn (datum->syntax
+ #'filename
+ (or (%search-load-path fn)
+ (syntax-violation 'include-from-path
+ "file not found in path"
+ x #'filename)))))
+ #'(include fn)))))))
+
+(define-syntax unquote
+ (lambda (x)
+ (syntax-violation 'unquote
+ "expression not valid outside of quasiquote"
+ x)))
+
+(define-syntax unquote-splicing
+ (lambda (x)
+ (syntax-violation 'unquote-splicing
+ "expression not valid outside of quasiquote"
+ x)))
+
+(define (make-variable-transformer proc)
+ (if (procedure? proc)
+ (let ((trans (lambda (x)
+ #((macro-type . variable-transformer))
+ (proc x))))
+ (set-procedure-property! trans 'variable-transformer #t)
+ trans)
+ (error "variable transformer not a procedure" proc)))
+
+(define-syntax identifier-syntax
+ (lambda (xx)
+ (syntax-case xx (set!)
+ ((_ e)
+ #'(lambda (x)
+ #((macro-type . identifier-syntax))
+ (syntax-case x ()
+ (id
+ (identifier? #'id)
+ #'e)
+ ((_ x (... ...))
+ #'(e x (... ...))))))
+ ((_ (id exp1) ((set! var val) exp2))
+ (and (identifier? #'id) (identifier? #'var))
+ #'(make-variable-transformer
+ (lambda (x)
+ #((macro-type . variable-transformer))
+ (syntax-case x (set!)
+ ((set! var val) #'exp2)
+ ((id x (... ...)) #'(exp1 x (... ...)))
+ (id (identifier? #'id) #'exp1))))))))
+
+(define-syntax define*
+ (lambda (x)
+ (syntax-case x ()
+ ((_ (id . args) b0 b1 ...)
+ #'(define id (lambda* args b0 b1 ...)))
+ ((_ id val) (identifier? #'id)
+ #'(define id val)))))
+;;;; q.scm --- Queues
+;;;;
+;;;; Copyright (C) 1995, 2001, 2004, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+
+;;; Q: Based on the interface to
+;;;
+;;; "queue.scm" Queues/Stacks for Scheme
+;;; Written by Andrew Wilcox (awilcox@astro.psu.edu) on April 1, 1992.
+
+;;; {Q}
+;;;
+;;; A list is just a bunch of cons pairs that follows some constrains,
+;;; right? Association lists are the same. Hash tables are just
+;;; vectors and association lists. You can print them, read them,
+;;; write them as constants, pun them off as other data structures
+;;; etc. This is good. This is lisp. These structures are fast and
+;;; compact and easy to manipulate arbitrarily because of their
+;;; simple, regular structure and non-disjointedness (associations
+;;; being lists and so forth).
+;;;
+;;; So I figured, queues should be the same -- just a "subtype" of cons-pair
+;;; structures in general.
+;;;
+;;; A queue is a cons pair:
+;;; ( <the-q> . <last-pair> )
+;;;
+;;; <the-q> is a list of things in the q. New elements go at the end
+;;; of that list.
+;;;
+;;; <last-pair> is #f if the q is empty, and otherwise is the last
+;;; pair of <the-q>.
+;;;
+;;; q's print nicely, but alas, they do not read well because the
+;;; eq?-ness of <last-pair> and (last-pair <the-q>) is lost by read.
+;;;
+;;; All the functions that aren't explicitly defined to return
+;;; something else (a queue element; a boolean value) return the queue
+;;; object itself.
+
+;;; Code:
+
+(define-module (ice-9 q)
+ \:export (sync-q! make-q q? q-empty? q-empty-check q-front q-rear
+ q-remove! q-push! enq! q-pop! deq! q-length))
+
+;;; sync-q!
+;;; The procedure
+;;;
+;;; (sync-q! q)
+;;;
+;;; recomputes and resets the <last-pair> component of a queue.
+;;;
+(define (sync-q! q)
+ (set-cdr! q (if (pair? (car q)) (last-pair (car q))
+ #f))
+ q)
+
+;;; make-q
+;;; return a new q.
+;;;
+(define (make-q) (cons '() #f))
+
+;;; q? obj
+;;; Return true if obj is a Q.
+;;; An object is a queue if it is equal? to '(() . #f)
+;;; or it is a pair P with (list? (car P))
+;;; and (eq? (cdr P) (last-pair (car P))).
+;;;
+(define (q? obj)
+ (and (pair? obj)
+ (if (pair? (car obj))
+ (eq? (cdr obj) (last-pair (car obj)))
+ (and (null? (car obj))
+ (not (cdr obj))))))
+
+;;; q-empty? obj
+;;;
+(define (q-empty? obj) (null? (car obj)))
+
+;;; q-empty-check q
+;;; Throw a q-empty exception if Q is empty.
+(define (q-empty-check q) (if (q-empty? q) (throw 'q-empty q)))
+
+;;; q-front q
+;;; Return the first element of Q.
+(define (q-front q) (q-empty-check q) (caar q))
+
+;;; q-rear q
+;;; Return the last element of Q.
+(define (q-rear q) (q-empty-check q) (cadr q))
+
+;;; q-remove! q obj
+;;; Remove all occurences of obj from Q.
+(define (q-remove! q obj)
+ (set-car! q (delq! obj (car q)))
+ (sync-q! q))
+
+;;; q-push! q obj
+;;; Add obj to the front of Q
+(define (q-push! q obj)
+ (let ((h (cons obj (car q))))
+ (set-car! q h)
+ (or (cdr q) (set-cdr! q h)))
+ q)
+
+;;; enq! q obj
+;;; Add obj to the rear of Q
+(define (enq! q obj)
+ (let ((h (cons obj '())))
+ (if (null? (car q))
+ (set-car! q h)
+ (set-cdr! (cdr q) h))
+ (set-cdr! q h))
+ q)
+
+;;; q-pop! q
+;;; Take the front of Q and return it.
+(define (q-pop! q)
+ (q-empty-check q)
+ (let ((it (caar q))
+ (next (cdar q)))
+ (if (null? next)
+ (set-cdr! q #f))
+ (set-car! q next)
+ it))
+
+;;; deq! q
+;;; Take the front of Q and return it.
+(define deq! q-pop!)
+
+;;; q-length q
+;;; Return the number of enqueued elements.
+;;;
+(define (q-length q) (length (car q)))
+
+;;; q.scm ends here
+;; Quasisyntax in terms of syntax-case.
+;;
+;; Code taken from
+;; <http://www.het.brown.edu/people/andre/macros/index.html>;
+;; Copyright (c) 2006 Andre van Tonder. All Rights Reserved.
+;;
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;=========================================================
+;;
+;; To make nested unquote-splicing behave in a useful way,
+;; the R5RS-compatible extension of quasiquote in appendix B
+;; of the following paper is here ported to quasisyntax:
+;;
+;; Alan Bawden - Quasiquotation in Lisp
+;; http://citeseer.ist.psu.edu/bawden99quasiquotation.html
+;;
+;; The algorithm converts a quasisyntax expression to an
+;; equivalent with-syntax expression.
+;; For example:
+;;
+;; (quasisyntax (set! #,a #,b))
+;; ==> (with-syntax ((t0 a)
+;; (t1 b))
+;; (syntax (set! t0 t1)))
+;;
+;; (quasisyntax (list #,@args))
+;; ==> (with-syntax (((t ...) args))
+;; (syntax (list t ...)))
+;;
+;; Note that quasisyntax is expanded first, before any
+;; ellipses act. For example:
+;;
+;; (quasisyntax (f ((b #,a) ...))
+;; ==> (with-syntax ((t a))
+;; (syntax (f ((b t) ...))))
+;;
+;; so that
+;;
+;; (let-syntax ((test-ellipses-over-unsyntax
+;; (lambda (e)
+;; (let ((a (syntax a)))
+;; (with-syntax (((b ...) (syntax (1 2 3))))
+;; (quasisyntax
+;; (quote ((b #,a) ...))))))))
+;; (test-ellipses-over-unsyntax))
+;;
+;; ==> ((1 a) (2 a) (3 a))
+(define-syntax quasisyntax
+ (lambda (e)
+
+ ;; Expand returns a list of the form
+ ;; [template[t/e, ...] (replacement ...)]
+ ;; Here template[t/e ...] denotes the original template
+ ;; with unquoted expressions e replaced by fresh
+ ;; variables t, followed by the appropriate ellipses
+ ;; if e is also spliced.
+ ;; The second part of the return value is the list of
+ ;; replacements, each of the form (t e) if e is just
+ ;; unquoted, or ((t ...) e) if e is also spliced.
+ ;; This will be the list of bindings of the resulting
+ ;; with-syntax expression.
+
+ (define (expand x level)
+ (syntax-case x (quasisyntax unsyntax unsyntax-splicing)
+ ((quasisyntax e)
+ (with-syntax (((k _) x) ;; original identifier must be copied
+ ((e* reps) (expand (syntax e) (+ level 1))))
+ (syntax ((k e*) reps))))
+ ((unsyntax e)
+ (= level 0)
+ (with-syntax (((t) (generate-temporaries '(t))))
+ (syntax (t ((t e))))))
+ (((unsyntax e ...) . r)
+ (= level 0)
+ (with-syntax (((r* (rep ...)) (expand (syntax r) 0))
+ ((t ...) (generate-temporaries (syntax (e ...)))))
+ (syntax ((t ... . r*)
+ ((t e) ... rep ...)))))
+ (((unsyntax-splicing e ...) . r)
+ (= level 0)
+ (with-syntax (((r* (rep ...)) (expand (syntax r) 0))
+ ((t ...) (generate-temporaries (syntax (e ...)))))
+ (with-syntax ((((t ...) ...) (syntax ((t (... ...)) ...))))
+ (syntax ((t ... ... . r*)
+ (((t ...) e) ... rep ...))))))
+ ((k . r)
+ (and (> level 0)
+ (identifier? (syntax k))
+ (or (free-identifier=? (syntax k) (syntax unsyntax))
+ (free-identifier=? (syntax k) (syntax unsyntax-splicing))))
+ (with-syntax (((r* reps) (expand (syntax r) (- level 1))))
+ (syntax ((k . r*) reps))))
+ ((h . t)
+ (with-syntax (((h* (rep1 ...)) (expand (syntax h) level))
+ ((t* (rep2 ...)) (expand (syntax t) level)))
+ (syntax ((h* . t*)
+ (rep1 ... rep2 ...)))))
+ (#(e ...)
+ (with-syntax ((((e* ...) reps)
+ (expand (vector->list (syntax #(e ...))) level)))
+ (syntax (#(e* ...) reps))))
+ (other
+ (syntax (other ())))))
+
+ (syntax-case e ()
+ ((_ template)
+ (with-syntax (((template* replacements) (expand (syntax template) 0)))
+ (syntax
+ (with-syntax replacements (syntax template*))))))))
+
+(define-syntax unsyntax
+ (lambda (e)
+ (syntax-violation 'unsyntax "Invalid expression" e)))
+
+(define-syntax unsyntax-splicing
+ (lambda (e)
+ (syntax-violation 'unsyntax "Invalid expression" e)))
+;;;; r4rs.scm --- definitions needed for libguile to be R4RS compliant
+;;;; Jim Blandy <jimb@cyclic.com> --- October 1996
+
+;;;; Copyright (C) 1996, 1997, 1998, 2000, 2001, 2006, 2010, 2011, 2012 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(eval-when (compile)
+ (set-current-module (resolve-module '(guile))))
+
+
+;;;; apply and call-with-current-continuation
+
+;;; The deal with these is that they are the procedural wrappers around the
+;;; primitives of Guile's language. There are about 20 different kinds of
+;;; expression in Guile, and e.g. @apply is one of them. (It has to be that way
+;;; to preserve tail recursion.)
+;;;
+;;; Usually we recognize (apply foo bar) to be an instance of @apply, but in the
+;;; case that apply is passed to apply, or we're bootstrapping, we need a
+;;; trampoline -- and here they are.
+(define (apply fun . args)
+ (@apply fun (apply:nconc2last args)))
+(define (call-with-current-continuation proc)
+ (@call-with-current-continuation proc))
+(define (call-with-values producer consumer)
+ (@call-with-values producer consumer))
+(define (dynamic-wind in thunk out)
+ "All three arguments must be 0-argument procedures.
+Guard @var{in} is called, then @var{thunk}, then
+guard @var{out}.
+
+If, any time during the execution of @var{thunk}, the
+continuation of the @code{dynamic_wind} expression is escaped
+non-locally, @var{out} is called. If the continuation of
+the dynamic-wind is re-entered, @var{in} is called. Thus
+@var{in} and @var{out} may be called any number of
+times.
+@lisp
+ (define x 'normal-binding)
+@result{} x
+ (define a-cont
+ (call-with-current-continuation
+ (lambda (escape)
+ (let ((old-x x))
+ (dynamic-wind
+ ;; in-guard:
+ ;;
+ (lambda () (set! x 'special-binding))
+
+ ;; thunk
+ ;;
+ (lambda () (display x) (newline)
+ (call-with-current-continuation escape)
+ (display x) (newline)
+ x)
+
+ ;; out-guard:
+ ;;
+ (lambda () (set! x old-x)))))))
+
+;; Prints:
+special-binding
+;; Evaluates to:
+@result{} a-cont
+x
+@result{} normal-binding
+ (a-cont #f)
+;; Prints:
+special-binding
+;; Evaluates to:
+@result{} a-cont ;; the value of the (define a-cont...)
+x
+@result{} normal-binding
+a-cont
+@result{} special-binding
+@end lisp"
+ (@dynamic-wind in (thunk) out))
+
+
+;;;; Basic Port Code
+
+;;; Specifically, the parts of the low-level port code that are written in
+;;; Scheme rather than C.
+;;;
+;;; WARNING: the parts of this interface that refer to file ports
+;;; are going away. It would be gone already except that it is used
+;;; "internally" in a few places.
+
+
+;;; OPEN_READ, OPEN_WRITE, and OPEN_BOTH are used to request the
+;;; proper mode to open files in.
+;;;
+;;; If we want to support systems that do CRLF->LF translation, like
+;;; Windows, then we should have a symbol in scmconfig.h made visible
+;;; to the Scheme level that we can test here, and autoconf magic to
+;;; #define it when appropriate. Windows will probably just have a
+;;; hand-generated scmconfig.h file.
+(define OPEN_READ "r")
+(define OPEN_WRITE "w")
+(define OPEN_BOTH "r+")
+
+(define *null-device* "/dev/null")
+
+(define (open-input-file str)
+ "Takes a string naming an existing file and returns an input port
+capable of delivering characters from the file. If the file
+cannot be opened, an error is signalled."
+ (open-file str OPEN_READ))
+
+(define (open-output-file str)
+ "Takes a string naming an output file to be created and returns an
+output port capable of writing characters to a new file by that
+name. If the file cannot be opened, an error is signalled. If a
+file with the given name already exists, the effect is unspecified."
+ (open-file str OPEN_WRITE))
+
+(define (open-io-file str)
+ "Open file with name STR for both input and output."
+ (open-file str OPEN_BOTH))
+
+(define (call-with-input-file str proc)
+ "PROC should be a procedure of one argument, and STR should be a
+string naming a file. The file must
+already exist. These procedures call PROC
+with one argument: the port obtained by opening the named file for
+input or output. If the file cannot be opened, an error is
+signalled. If the procedure returns, then the port is closed
+automatically and the values yielded by the procedure are returned.
+If the procedure does not return, then the port will not be closed
+automatically unless it is possible to prove that the port will
+never again be used for a read or write operation."
+ (let ((p (open-input-file str)))
+ (call-with-values
+ (lambda () (proc p))
+ (lambda vals
+ (close-input-port p)
+ (apply values vals)))))
+
+(define (call-with-output-file str proc)
+ "PROC should be a procedure of one argument, and STR should be a
+string naming a file. The behaviour is unspecified if the file
+already exists. These procedures call PROC
+with one argument: the port obtained by opening the named file for
+input or output. If the file cannot be opened, an error is
+signalled. If the procedure returns, then the port is closed
+automatically and the values yielded by the procedure are returned.
+If the procedure does not return, then the port will not be closed
+automatically unless it is possible to prove that the port will
+never again be used for a read or write operation."
+ (let ((p (open-output-file str)))
+ (call-with-values
+ (lambda () (proc p))
+ (lambda vals
+ (close-output-port p)
+ (apply values vals)))))
+
+(define (with-input-from-port port thunk)
+ (let* ((swaports (lambda () (set! port (set-current-input-port port)))))
+ (dynamic-wind swaports thunk swaports)))
+
+(define (with-output-to-port port thunk)
+ (let* ((swaports (lambda () (set! port (set-current-output-port port)))))
+ (dynamic-wind swaports thunk swaports)))
+
+(define (with-error-to-port port thunk)
+ (let* ((swaports (lambda () (set! port (set-current-error-port port)))))
+ (dynamic-wind swaports thunk swaports)))
+
+(define (with-input-from-file file thunk)
+ "THUNK must be a procedure of no arguments, and FILE must be a
+string naming a file. The file must already exist. The file is opened for
+input, an input port connected to it is made
+the default value returned by `current-input-port',
+and the THUNK is called with no arguments.
+When the THUNK returns, the port is closed and the previous
+default is restored. Returns the values yielded by THUNK. If an
+escape procedure is used to escape from the continuation of these
+procedures, their behavior is implementation dependent."
+ (call-with-input-file file
+ (lambda (p) (with-input-from-port p thunk))))
+
+(define (with-output-to-file file thunk)
+ "THUNK must be a procedure of no arguments, and FILE must be a
+string naming a file. The effect is unspecified if the file already exists.
+The file is opened for output, an output port connected to it is made
+the default value returned by `current-output-port',
+and the THUNK is called with no arguments.
+When the THUNK returns, the port is closed and the previous
+default is restored. Returns the values yielded by THUNK. If an
+escape procedure is used to escape from the continuation of these
+procedures, their behavior is implementation dependent."
+ (call-with-output-file file
+ (lambda (p) (with-output-to-port p thunk))))
+
+(define (with-error-to-file file thunk)
+ "THUNK must be a procedure of no arguments, and FILE must be a
+string naming a file. The effect is unspecified if the file already exists.
+The file is opened for output, an output port connected to it is made
+the default value returned by `current-error-port',
+and the THUNK is called with no arguments.
+When the THUNK returns, the port is closed and the previous
+default is restored. Returns the values yielded by THUNK. If an
+escape procedure is used to escape from the continuation of these
+procedures, their behavior is implementation dependent."
+ (call-with-output-file file
+ (lambda (p) (with-error-to-port p thunk))))
+
+(define (with-input-from-string string thunk)
+ "THUNK must be a procedure of no arguments.
+The test of STRING is opened for
+input, an input port connected to it is made,
+and the THUNK is called with no arguments.
+When the THUNK returns, the port is closed.
+Returns the values yielded by THUNK. If an
+escape procedure is used to escape from the continuation of these
+procedures, their behavior is implementation dependent."
+ (call-with-input-string string
+ (lambda (p) (with-input-from-port p thunk))))
+
+(define (with-output-to-string thunk)
+ "Calls THUNK and returns its output as a string."
+ (call-with-output-string
+ (lambda (p) (with-output-to-port p thunk))))
+
+(define (with-error-to-string thunk)
+ "Calls THUNK and returns its error output as a string."
+ (call-with-output-string
+ (lambda (p) (with-error-to-port p thunk))))
+
+(define the-eof-object (call-with-input-string "" (lambda (p) (read-char p))))
+;;;; Copyright (C) 2000, 2001, 2006, 2010 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;;; R5RS bindings
+
+(define-module (ice-9 r5rs)
+ \:export (scheme-report-environment
+ ;;transcript-on
+ ;;transcript-off
+ )
+ \:re-export (interaction-environment
+
+ call-with-input-file call-with-output-file
+ with-input-from-file with-output-to-file
+ open-input-file open-output-file
+ close-input-port close-output-port
+
+ load))
+
+(module-use! (module-public-interface (current-module))
+ (resolve-interface '(ice-9 safe-r5rs)))
+
+(define scheme-report-interface (module-public-interface (current-module)))
+
+(define (scheme-report-environment n)
+ (if (not (= n 5))
+ (scm-error 'misc-error 'scheme-report-environment
+ "~A is not a valid version"
+ (list n)
+ '()))
+ scheme-report-interface)
+;;; r6rs-libraries.scm --- Support for the R6RS `library' and `import' forms
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+;; This file is included from boot-9.scm and assumes the existence of (and
+;; expands into) procedures and syntactic forms defined therein.
+
+(define (resolve-r6rs-interface import-spec)
+ (define (make-custom-interface mod)
+ (let ((iface (make-module)))
+ (set-module-kind! iface 'custom-interface)
+ (set-module-name! iface (module-name mod))
+ iface))
+ (define (sym? x) (symbol? (syntax->datum x)))
+
+ (syntax-case import-spec (library only except prefix rename srfi)
+ ;; (srfi n ...) -> (srfi srfi-n ...)
+ ((library (srfi colon-n rest ... (version ...)))
+ (and (and-map sym? #'(srfi rest ...))
+ (symbol? (syntax->datum #'colon-n))
+ (eqv? (string-ref (symbol->string (syntax->datum #'colon-n)) 0) #\:))
+ (let ((srfi-n (string->symbol
+ (string-append
+ "srfi-"
+ (substring (symbol->string (syntax->datum #'colon-n))
+ 1)))))
+ (resolve-r6rs-interface
+ (syntax-case #'(rest ...) ()
+ (()
+ #`(library (srfi #,srfi-n (version ...))))
+ ((name rest ...)
+ ;; SRFI 97 says that the first identifier after the colon-n
+ ;; is used for the libraries name, so it must be ignored.
+ #`(library (srfi #,srfi-n rest ... (version ...))))))))
+
+ ((library (name name* ... (version ...)))
+ (and-map sym? #'(name name* ...))
+ (resolve-interface (syntax->datum #'(name name* ...))
+ #\version (syntax->datum #'(version ...))))
+
+ ((library (name name* ...))
+ (and-map sym? #'(name name* ...))
+ (resolve-r6rs-interface #'(library (name name* ... ()))))
+
+ ((only import-set identifier ...)
+ (and-map sym? #'(identifier ...))
+ (let* ((mod (resolve-r6rs-interface #'import-set))
+ (iface (make-custom-interface mod)))
+ (for-each (lambda (sym)
+ (module-add! iface sym
+ (or (module-local-variable mod sym)
+ (error "no binding `~A' in module ~A"
+ sym mod))))
+ (syntax->datum #'(identifier ...)))
+ iface))
+
+ ((except import-set identifier ...)
+ (and-map sym? #'(identifier ...))
+ (let* ((mod (resolve-r6rs-interface #'import-set))
+ (iface (make-custom-interface mod)))
+ (module-for-each (lambda (sym var) (module-add! iface sym var)) mod)
+ (for-each (lambda (sym)
+ (if (module-local-variable iface sym)
+ (module-remove! iface sym)
+ (error "no binding `~A' in module ~A" sym mod)))
+ (syntax->datum #'(identifier ...)))
+ iface))
+
+ ((prefix import-set identifier)
+ (sym? #'identifier)
+ (let* ((mod (resolve-r6rs-interface #'import-set))
+ (iface (make-custom-interface mod))
+ (pre (syntax->datum #'identifier)))
+ (module-for-each (lambda (sym var)
+ (module-add! iface (symbol-append pre sym) var))
+ mod)
+ iface))
+
+ ((rename import-set (from to) ...)
+ (and (and-map sym? #'(from ...)) (and-map sym? #'(to ...)))
+ (let* ((mod (resolve-r6rs-interface #'import-set))
+ (iface (make-custom-interface mod)))
+ (module-for-each (lambda (sym var) (module-add! iface sym var)) mod)
+ (let lp ((in (syntax->datum #'((from . to) ...))) (out '()))
+ (cond
+ ((null? in)
+ (for-each
+ (lambda (pair)
+ (if (module-local-variable iface (car pair))
+ (error "duplicate binding for `~A' in module ~A"
+ (car pair) mod)
+ (module-add! iface (car pair) (cdr pair))))
+ out)
+ iface)
+ (else
+ (let ((var (or (module-local-variable mod (caar in))
+ (error "no binding `~A' in module ~A"
+ (caar in) mod))))
+ (module-remove! iface (caar in))
+ (lp (cdr in) (acons (cdar in) var out))))))))
+
+ ((name name* ... (version ...))
+ (and-map sym? #'(name name* ...))
+ (resolve-r6rs-interface #'(library (name name* ... (version ...)))))
+
+ ((name name* ...)
+ (and-map sym? #'(name name* ...))
+ (resolve-r6rs-interface #'(library (name name* ... ()))))))
+
+(define-syntax library
+ (lambda (stx)
+ (define (compute-exports ifaces specs)
+ (define (re-export? sym)
+ (or-map (lambda (iface) (module-local-variable iface sym)) ifaces))
+ (define (replace? sym)
+ (module-local-variable the-scm-module sym))
+
+ (let lp ((specs specs) (e '()) (r '()) (x '()))
+ (syntax-case specs (rename)
+ (() (values e r x))
+ (((rename (from to) ...) . rest)
+ (and (and-map identifier? #'(from ...))
+ (and-map identifier? #'(to ...)))
+ (let lp2 ((in #'((from . to) ...)) (e e) (r r) (x x))
+ (syntax-case in ()
+ (() (lp #'rest e r x))
+ (((from . to) . in)
+ (cond
+ ((re-export? (syntax->datum #'from))
+ (lp2 #'in e (cons #'(from . to) r) x))
+ ((replace? (syntax->datum #'from))
+ (lp2 #'in e r (cons #'(from . to) x)))
+ (else
+ (lp2 #'in (cons #'(from . to) e) r x)))))))
+ ((id . rest)
+ (identifier? #'id)
+ (let ((sym (syntax->datum #'id)))
+ (cond
+ ((re-export? sym)
+ (lp #'rest e (cons #'id r) x))
+ ((replace? sym)
+ (lp #'rest e r (cons #'id x)))
+ (else
+ (lp #'rest (cons #'id e) r x))))))))
+
+ (syntax-case stx (export import)
+ ((_ (name name* ...)
+ (export espec ...)
+ (import ispec ...)
+ body ...)
+ (and-map identifier? #'(name name* ...))
+ ;; Add () as the version.
+ #'(library (name name* ... ())
+ (export espec ...)
+ (import ispec ...)
+ body ...))
+
+ ((_ (name name* ... (version ...))
+ (export espec ...)
+ (import ispec ...)
+ body ...)
+ (and-map identifier? #'(name name* ...))
+ (call-with-values
+ (lambda ()
+ (compute-exports
+ (map (lambda (im)
+ (syntax-case im (for)
+ ((for import-set import-level ...)
+ (resolve-r6rs-interface #'import-set))
+ (import-set (resolve-r6rs-interface #'import-set))))
+ #'(ispec ...))
+ #'(espec ...)))
+ (lambda (exports re-exports replacements)
+ (with-syntax (((e ...) exports)
+ ((r ...) re-exports)
+ ((x ...) replacements))
+ ;; It would be nice to push the module that was current before the
+ ;; definition, and pop it after the library definition, but I
+ ;; actually can't see a way to do that. Helper procedures perhaps,
+ ;; around a fluid that is rebound in save-module-excursion? Patches
+ ;; welcome!
+ #'(begin
+ (define-module (name name* ...)
+ #\pure
+ #\version (version ...))
+ (import ispec)
+ ...
+ (export e ...)
+ (re-export r ...)
+ (export! x ...)
+ (@@ @@ (name name* ...) body)
+ ...))))))))
+
+(define-syntax import
+ (lambda (stx)
+ (define (strip-for import-set)
+ (syntax-case import-set (for)
+ ((for import-set import-level ...)
+ #'import-set)
+ (import-set
+ #'import-set)))
+ (syntax-case stx ()
+ ((_ import-set ...)
+ (with-syntax (((library-reference ...) (map strip-for #'(import-set ...))))
+ #'(eval-when (expand load eval)
+ (let ((iface (resolve-r6rs-interface 'library-reference)))
+ (call-with-deferred-observers
+ (lambda ()
+ (module-use-interfaces! (current-module) (list iface)))))
+ ...
+ (if #f #f)))))))
+;;; installed-scm-file
+
+;;;; Copyright (C) 1997, 1999, 2000, 2001, 2006, 2010, 2013,
+;;;; 2014 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+;;; This is the Scheme part of the module for delimited I/O. It's
+;;; similar to (scsh rdelim) but somewhat incompatible.
+
+(define-module (ice-9 rdelim)
+ #\export (read-line
+ read-line!
+ read-delimited
+ read-delimited!
+ read-string
+ read-string!
+ %read-delimited!
+ %read-line
+ write-line))
+
+
+(%init-rdelim-builtins)
+
+(define* (read-line! string #\optional (port current-input-port))
+ ;; corresponds to SCM_LINE_INCREMENTORS in libguile.
+ (define scm-line-incrementors "\n")
+ (let* ((rv (%read-delimited! scm-line-incrementors
+ string
+ #t
+ port))
+ (terminator (car rv))
+ (nchars (cdr rv)))
+ (cond ((and (= nchars 0)
+ (eof-object? terminator))
+ terminator)
+ ((not terminator) #f)
+ (else nchars))))
+
+(define* (read-delimited! delims buf #\optional
+ (port (current-input-port)) (handle-delim 'trim)
+ (start 0) (end (string-length buf)))
+ (let* ((rv (%read-delimited! delims
+ buf
+ (not (eq? handle-delim 'peek))
+ port
+ start
+ end))
+ (terminator (car rv))
+ (nchars (cdr rv)))
+ (cond ((or (not terminator) ; buffer filled
+ (eof-object? terminator))
+ (if (zero? nchars)
+ (if (eq? handle-delim 'split)
+ (cons terminator terminator)
+ terminator)
+ (if (eq? handle-delim 'split)
+ (cons nchars terminator)
+ nchars)))
+ (else
+ (case handle-delim
+ ((trim peek) nchars)
+ ((concat) (string-set! buf (+ nchars start) terminator)
+ (+ nchars 1))
+ ((split) (cons nchars terminator))
+ (else (error "unexpected handle-delim value: "
+ handle-delim)))))))
+
+(define* (read-delimited delims #\optional (port (current-input-port))
+ (handle-delim 'trim))
+ (let loop ((substrings '())
+ (total-chars 0)
+ (buf-size 100)) ; doubled each time through.
+ (let* ((buf (make-string buf-size))
+ (rv (%read-delimited! delims
+ buf
+ (not (eq? handle-delim 'peek))
+ port))
+ (terminator (car rv))
+ (nchars (cdr rv))
+ (new-total (+ total-chars nchars)))
+ (cond
+ ((not terminator)
+ ;; buffer filled.
+ (loop (cons (substring buf 0 nchars) substrings)
+ new-total
+ (* buf-size 2)))
+ ((and (eof-object? terminator) (zero? new-total))
+ (if (eq? handle-delim 'split)
+ (cons terminator terminator)
+ terminator))
+ (else
+ (let ((joined
+ (string-concatenate-reverse
+ (cons (substring buf 0 nchars) substrings))))
+ (case handle-delim
+ ((concat)
+ (if (eof-object? terminator)
+ joined
+ (string-append joined (string terminator))))
+ ((trim peek) joined)
+ ((split) (cons joined terminator))
+ (else (error "unexpected handle-delim value: "
+ handle-delim)))))))))
+
+(define-syntax-rule (check-arg exp message arg ...)
+ (unless exp
+ (error message arg ...)))
+
+(define (index? n)
+ (and (integer? n) (exact? n) (>= n 0)))
+
+(define* (read-string! buf #\optional
+ (port (current-input-port))
+ (start 0) (end (string-length buf)))
+ "Read all of the characters out of PORT and write them to BUF.
+Returns the number of characters read.
+
+This function only reads out characters from PORT if it will be able to
+write them to BUF. That is to say, if BUF is smaller than the number of
+available characters, then BUF will be filled, and characters will be
+left in the port."
+ (check-arg (string? buf) "not a string" buf)
+ (check-arg (index? start) "bad index" start)
+ (check-arg (index? end) "bad index" end)
+ (check-arg (<= start end) "start beyond end" start end)
+ (check-arg (<= end (string-length buf)) "end beyond string length" end)
+ (let lp ((n start))
+ (if (< n end)
+ (let ((c (read-char port)))
+ (if (eof-object? c)
+ (- n start)
+ (begin
+ (string-set! buf n c)
+ (lp (1+ n)))))
+ (- n start))))
+
+(define* read-string
+ (case-lambda*
+ "Read all of the characters out of PORT and return them as a string.
+If the COUNT argument is present, treat it as a limit to the number of
+characters to read. By default, there is no limit."
+ ((#\optional (port (current-input-port)))
+ ;; Fast path.
+ ;; This creates more garbage than using 'string-set!' as in
+ ;; 'read-string!', but currently that is faster nonetheless.
+ (let loop ((chars '()))
+ (let ((char (read-char port)))
+ (if (eof-object? char)
+ (list->string (reverse! chars))
+ (loop (cons char chars))))))
+ ((port count)
+ ;; Slower path.
+ (let loop ((chars '())
+ (total 0))
+ (let ((char (read-char port)))
+ (if (or (eof-object? char) (>= total count))
+ (list->string (reverse chars))
+ (loop (cons char chars) (+ 1 total))))))))
+
+
+;;; read-line [PORT [HANDLE-DELIM]] reads a newline-terminated string
+;;; from PORT. The return value depends on the value of HANDLE-DELIM,
+;;; which may be one of the symbols `trim', `concat', `peek' and
+;;; `split'. If it is `trim' (the default), the trailing newline is
+;;; removed and the string is returned. If `concat', the string is
+;;; returned with the trailing newline intact. If `peek', the newline
+;;; is left in the input port buffer and the string is returned. If
+;;; `split', the newline is split from the string and read-line
+;;; returns a pair consisting of the truncated string and the newline.
+
+(define* (read-line #\optional (port (current-input-port))
+ (handle-delim 'trim))
+ (let* ((line/delim (%read-line port))
+ (line (car line/delim))
+ (delim (cdr line/delim)))
+ (case handle-delim
+ ((trim) line)
+ ((split) line/delim)
+ ((concat) (if (and (string? line) (char? delim))
+ (string-append line (string delim))
+ line))
+ ((peek) (if (char? delim)
+ (unread-char delim port))
+ line)
+ (else
+ (error "unexpected handle-delim value: " handle-delim)))))
+;;;; SRFI-8
+
+;;; Copyright (C) 2000, 2001, 2004, 2006, 2010, 2011 Free Software Foundation, Inc.
+;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (ice-9 receive)
+ #\export (receive))
+
+(define-syntax-rule (receive vars vals . body)
+ (call-with-values (lambda () vals)
+ (lambda vars . body)))
+
+(cond-expand-provide (current-module) '(srfi-8))
+;;;; Copyright (C) 1997, 1999, 2001, 2004, 2005, 2006, 2008, 2010 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+
+;; These procedures are exported:
+;; (match:count match)
+;; (match:string match)
+;; (match:prefix match)
+;; (match:suffix match)
+;; (regexp-match? match)
+;; (regexp-quote string)
+;; (match:start match . submatch-num)
+;; (match:end match . submatch-num)
+;; (match:substring match . submatch-num)
+;; (string-match pattern str . start)
+;; (regexp-substitute port match . items)
+;; (fold-matches regexp string init proc . flags)
+;; (list-matches regexp string . flags)
+;; (regexp-substitute/global port regexp string . items)
+
+;;; Code:
+
+;;;; POSIX regex support functions.
+
+(define-module (ice-9 regex)
+ #\export (match:count match:string match:prefix match:suffix
+ regexp-match? regexp-quote match:start match:end match:substring
+ string-match regexp-substitute fold-matches list-matches
+ regexp-substitute/global))
+
+;; References:
+;;
+;; POSIX spec:
+;; http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap09.html
+
+;;; FIXME:
+;;; It is not clear what should happen if a `match' function
+;;; is passed a `match number' which is out of bounds for the
+;;; regexp match: return #f, or throw an error? These routines
+;;; throw an out-of-range error.
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;; These procedures are not defined in SCSH, but I found them useful.
+
+(define (match:count match)
+ (- (vector-length match) 1))
+
+(define (match:string match)
+ (vector-ref match 0))
+
+(define (match:prefix match)
+ (substring (match:string match) 0 (match:start match 0)))
+
+(define (match:suffix match)
+ (substring (match:string match) (match:end match 0)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;; SCSH compatibility routines.
+
+(define (regexp-match? match)
+ (and (vector? match)
+ (string? (vector-ref match 0))
+ (let loop ((i 1))
+ (cond ((>= i (vector-length match)) #t)
+ ((and (pair? (vector-ref match i))
+ (integer? (car (vector-ref match i)))
+ (integer? (cdr (vector-ref match i))))
+ (loop (+ 1 i)))
+ (else #f)))))
+
+;; * . \ ^ $ and [ are special in both regexp/basic and regexp/extended and
+;; can be backslash escaped.
+;;
+;; ( ) + ? { } and | are special in regexp/extended so must be quoted. But
+;; that can't be done with a backslash since in regexp/basic where they're
+;; not special, adding a backslash makes them become special. Character
+;; class forms [(] etc are used instead.
+;;
+;; ) is not special when not preceded by a (, and * and ? are not special at
+;; the start of a string, but we quote all of these always, so the result
+;; can be concatenated or merged into some larger regexp.
+;;
+;; ] is not special outside a [ ] character class, so doesn't need to be
+;; quoted.
+;;
+(define (regexp-quote string)
+ (call-with-output-string
+ (lambda (p)
+ (string-for-each (lambda (c)
+ (case c
+ ((#\* #\. #\\ #\^ #\$ #\[)
+ (write-char #\\ p)
+ (write-char c p))
+ ((#\( #\) #\+ #\? #\{ #\} #\|)
+ (write-char #\[ p)
+ (write-char c p)
+ (write-char #\] p))
+ (else
+ (write-char c p))))
+ string))))
+
+(define* (match:start match #\optional (n 0))
+ (let ((start (car (vector-ref match (1+ n)))))
+ (if (= start -1) #f start)))
+
+(define* (match:end match #\optional (n 0))
+ (let* ((end (cdr (vector-ref match (1+ n)))))
+ (if (= end -1) #f end)))
+
+(define* (match:substring match #\optional (n 0))
+ (let* ((start (match:start match n))
+ (end (match:end match n)))
+ (and start end (substring (match:string match) start end))))
+
+(define (string-match pattern str . args)
+ (let ((rx (make-regexp pattern))
+ (start (if (pair? args) (car args) 0)))
+ (regexp-exec rx str start)))
+
+(define (regexp-substitute port match . items)
+ ;; If `port' is #f, send output to a string.
+ (if (not port)
+ (call-with-output-string
+ (lambda (p)
+ (apply regexp-substitute p match items)))
+
+ ;; Otherwise, process each substitution argument in `items'.
+ (for-each (lambda (obj)
+ (cond ((string? obj) (display obj port))
+ ((integer? obj) (display (match:substring match obj) port))
+ ((eq? 'pre obj) (display (match:prefix match) port))
+ ((eq? 'post obj) (display (match:suffix match) port))
+ (else (error 'wrong-type-arg obj))))
+ items)))
+
+;;; If we call fold-matches, below, with a regexp that can match the
+;;; empty string, it's not obvious what "all the matches" means. How
+;;; many empty strings are there in the string "a"? Our answer:
+;;;
+;;; This function applies PROC to every non-overlapping, maximal
+;;; match of REGEXP in STRING.
+;;;
+;;; "non-overlapping": There are two non-overlapping matches of "" in
+;;; "a" --- one before the `a', and one after. There are three
+;;; non-overlapping matches of "q|x*" in "aqb": the empty strings
+;;; before `a' and after `b', and `q'. The two empty strings before
+;;; and after `q' don't count, because they overlap with the match of
+;;; "q".
+;;;
+;;; "maximal": There are three distinct maximal matches of "x*" in
+;;; "axxxb": one before the `a', one covering `xxx', and one after the
+;;; `b'. Around or within `xxx', only the match covering all three
+;;; x's counts, because the rest are not maximal.
+
+(define* (fold-matches regexp string init proc #\optional (flags 0))
+ (let ((regexp (if (regexp? regexp) regexp (make-regexp regexp))))
+ (let loop ((start 0)
+ (value init)
+ (abuts #f)) ; True if start abuts a previous match.
+ (define bol (if (zero? start) 0 regexp/notbol))
+ (let ((m (if (> start (string-length string)) #f
+ (regexp-exec regexp string start (logior flags bol)))))
+ (cond
+ ((not m) value)
+ ((and (= (match:start m) (match:end m)) abuts)
+ ;; We matched an empty string, but that would overlap the
+ ;; match immediately before. Try again at a position
+ ;; further to the right.
+ (loop (+ start 1) value #f))
+ (else
+ (loop (match:end m) (proc m value) #t)))))))
+
+(define* (list-matches regexp string #\optional (flags 0))
+ (reverse! (fold-matches regexp string '() cons flags)))
+
+(define (regexp-substitute/global port regexp string . items)
+
+ ;; If `port' is #f, send output to a string.
+ (if (not port)
+ (call-with-output-string
+ (lambda (p)
+ (apply regexp-substitute/global p regexp string items)))
+
+ ;; Walk the set of non-overlapping, maximal matches.
+ (let next-match ((matches (list-matches regexp string))
+ (start 0))
+ (if (null? matches)
+ (display (substring string start) port)
+ (let ((m (car matches)))
+
+ ;; Process all of the items for this match. Don't use
+ ;; for-each, because we need to make sure 'post at the
+ ;; end of the item list is a tail call.
+ (let next-item ((items items))
+
+ (define (do-item item)
+ (cond
+ ((string? item) (display item port))
+ ((integer? item) (display (match:substring m item) port))
+ ((procedure? item) (display (item m) port))
+ ((eq? item 'pre)
+ (display
+ (substring string start (match:start m))
+ port))
+ ((eq? item 'post)
+ (next-match (cdr matches) (match:end m)))
+ (else (error 'wrong-type-arg item))))
+
+ (if (pair? items)
+ (if (null? (cdr items))
+ (do-item (car items)) ; This is a tail call.
+ (begin
+ (do-item (car items)) ; This is not.
+ (next-item (cdr items)))))))))))
+;;;; runq.scm --- the runq data structure
+;;;;
+;;;; Copyright (C) 1996, 2001, 2006, 2010 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+
+;;; One way to schedule parallel computations in a serial environment is
+;;; to explicitly divide each task up into small, finite execution time,
+;;; strips. Then you interleave the execution of strips from various
+;;; tasks to achieve a kind of parallelism. Runqs are a handy data
+;;; structure for this style of programming.
+;;;
+;;; We use thunks (nullary procedures) and lists of thunks to represent
+;;; strips. By convention, the return value of a strip-thunk must either
+;;; be another strip or the value #f.
+;;;
+;;; A runq is a procedure that manages a queue of strips. Called with no
+;;; arguments, it processes one strip from the queue. Called with
+;;; arguments, the arguments form a control message for the queue. The
+;;; first argument is a symbol which is the message selector.
+;;;
+;;; A strip is processed this way: If the strip is a thunk, the thunk is
+;;; called -- if it returns a strip, that strip is added back to the
+;;; queue. To process a strip which is a list of thunks, the CAR of that
+;;; list is called. After a call to that CAR, there are 0, 1, or 2 strips
+;;; -- perhaps one returned by the thunk, and perhaps the CDR of the
+;;; original strip if that CDR is not nil. The runq puts whichever of
+;;; these strips exist back on the queue. (The exact order in which
+;;; strips are put back on the queue determines the scheduling behavior of
+;;; a particular queue -- it's a parameter.)
+
+;;; Code:
+
+(define-module (ice-9 runq)
+ \:use-module (ice-9 q)
+ \:export (runq-control make-void-runq make-fair-runq
+ make-exclusive-runq make-subordinate-runq-to strip-sequence
+ fair-strip-subtask))
+
+;;;;
+;;; (runq-control q msg . args)
+;;;
+;;; processes in the default way the control messages that
+;;; can be sent to a runq. Q should be an ordinary
+;;; Q (see utils/q.scm).
+;;;
+;;; The standard runq messages are:
+;;;
+;;; 'add! strip0 strip1... ;; to enqueue one or more strips
+;;; 'enqueue! strip0 strip1... ;; to enqueue one or more strips
+;;; 'push! strip0 ... ;; add strips to the front of the queue
+;;; 'empty? ;; true if it is
+;;; 'length ;; how many strips in the queue?
+;;; 'kill! ;; empty the queue
+;;; else ;; throw 'not-understood
+;;;
+(define (runq-control q msg . args)
+ (case msg
+ ((add!) (for-each (lambda (t) (enq! q t)) args) '*unspecified*)
+ ((enqueue!) (for-each (lambda (t) (enq! q t)) args) '*unspecified*)
+ ((push!) (for-each (lambda (t) (q-push! q t)) args) '*unspecified*)
+ ((empty?) (q-empty? q))
+ ((length) (q-length q))
+ ((kill!) (set! q (make-q)))
+ (else (throw 'not-understood msg args))))
+
+(define (run-strip thunk) (catch #t thunk (lambda ign (warn 'runq-strip thunk ign) #f)))
+
+;;;;
+;;; make-void-runq
+;;;
+;;; Make a runq that discards all messages except "length", for which
+;;; it returns 0.
+;;;
+(define (make-void-runq)
+ (lambda opts
+ (and opts
+ (apply-to-args opts
+ (lambda (msg . args)
+ (case msg
+ ((length) 0)
+ (else #f)))))))
+
+;;;;
+;;; (make-fair-runq)
+;;;
+;;; Returns a runq procedure.
+;;; Called with no arguments, the procedure processes one strip from the queue.
+;;; Called with arguments, it uses runq-control.
+;;;
+;;; In a fair runq, if a strip returns a new strip X, X is added
+;;; to the end of the queue, meaning it will be the last to execute
+;;; of all the remaining procedures.
+;;;
+(define (make-fair-runq)
+ (letrec ((q (make-q))
+ (self
+ (lambda ctl
+ (if ctl
+ (apply runq-control q ctl)
+ (and (not (q-empty? q))
+ (let ((next-strip (deq! q)))
+ (cond
+ ((procedure? next-strip) (let ((k (run-strip next-strip)))
+ (and k (enq! q k))))
+ ((pair? next-strip) (let ((k (run-strip (car next-strip))))
+ (and k (enq! q k)))
+ (if (not (null? (cdr next-strip)))
+ (enq! q (cdr next-strip)))))
+ self))))))
+ self))
+
+
+;;;;
+;;; (make-exclusive-runq)
+;;;
+;;; Returns a runq procedure.
+;;; Called with no arguments, the procedure processes one strip from the queue.
+;;; Called with arguments, it uses runq-control.
+;;;
+;;; In an exclusive runq, if a strip W returns a new strip X, X is added
+;;; to the front of the queue, meaning it will be the next to execute
+;;; of all the remaining procedures.
+;;;
+;;; An exception to this occurs if W was the CAR of a list of strips.
+;;; In that case, after the return value of W is pushed onto the front
+;;; of the queue, the CDR of the list of strips is pushed in front
+;;; of that (if the CDR is not nil). This way, the rest of the thunks
+;;; in the list that contained W have priority over the return value of W.
+;;;
+(define (make-exclusive-runq)
+ (letrec ((q (make-q))
+ (self
+ (lambda ctl
+ (if ctl
+ (apply runq-control q ctl)
+ (and (not (q-empty? q))
+ (let ((next-strip (deq! q)))
+ (cond
+ ((procedure? next-strip) (let ((k (run-strip next-strip)))
+ (and k (q-push! q k))))
+ ((pair? next-strip) (let ((k (run-strip (car next-strip))))
+ (and k (q-push! q k)))
+ (if (not (null? (cdr next-strip)))
+ (q-push! q (cdr next-strip)))))
+ self))))))
+ self))
+
+
+;;;;
+;;; (make-subordinate-runq-to superior basic-inferior)
+;;;
+;;; Returns a runq proxy for the runq basic-inferior.
+;;;
+;;; The proxy watches for operations on the basic-inferior that cause
+;;; a transition from a queue length of 0 to a non-zero length and
+;;; vice versa. While the basic-inferior queue is not empty,
+;;; the proxy installs a task on the superior runq. Each strip
+;;; of that task processes N strips from the basic-inferior where
+;;; N is the length of the basic-inferior queue when the proxy
+;;; strip is entered. [Countless scheduling variations are possible.]
+;;;
+(define (make-subordinate-runq-to superior-runq basic-runq)
+ (let ((runq-task (cons #f #f)))
+ (set-car! runq-task
+ (lambda ()
+ (if (basic-runq 'empty?)
+ (set-cdr! runq-task #f)
+ (do ((n (basic-runq 'length) (1- n)))
+ ((<= n 0) #f)
+ (basic-runq)))))
+ (letrec ((self
+ (lambda ctl
+ (if (not ctl)
+ (let ((answer (basic-runq)))
+ (self 'empty?)
+ answer)
+ (begin
+ (case (car ctl)
+ ((suspend) (set-cdr! runq-task #f))
+ (else (let ((answer (apply basic-runq ctl)))
+ (if (and (not (cdr runq-task)) (not (basic-runq 'empty?)))
+ (begin
+ (set-cdr! runq-task runq-task)
+ (superior-runq 'add! runq-task)))
+ answer))))))))
+ self)))
+
+;;;;
+;;; (define fork-strips (lambda args args))
+;;; Return a strip that starts several strips in
+;;; parallel. If this strip is enqueued on a fair
+;;; runq, strips of the parallel subtasks will run
+;;; round-robin style.
+;;;
+
+
+;;;;
+;;; (strip-sequence . strips)
+;;;
+;;; Returns a new strip which is the concatenation of the argument strips.
+;;;
+(define (strip-sequence . strips)
+ (lambda ()
+ (let loop ((st (let ((a strips)) (set! strips #f) a)))
+ (and (not (null? st))
+ (let ((then ((car st))))
+ (if then
+ (lambda () (loop (cons then (cdr st))))
+ (lambda () (loop (cdr st)))))))))
+
+
+;;;;
+;;; (fair-strip-subtask . initial-strips)
+;;;
+;;; Returns a new strip which is the synchronos, fair,
+;;; parallel execution of the argument strips.
+;;;
+;;;
+;;;
+(define (fair-strip-subtask . initial-strips)
+ (let ((st (make-fair-runq)))
+ (apply st 'add! initial-strips)
+ st))
+
+;;; runq.scm ends here
+;;; installed-scm-file
+
+;;;; Copyright (C) 2001, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+;;; This is the Scheme part of (ice-9 rw), which is a subset of
+;;; (scsh rw).
+
+(define-module (ice-9 rw)
+ \:export (read-string!/partial write-string/partial))
+
+(%init-rw-builtins)
+;;;; Copyright (C) 2000, 2001, 2004, 2006, 2010 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;;; Safe subset of R5RS bindings
+
+(define-module (ice-9 safe-r5rs)
+ \:re-export (eqv? eq? equal?
+ number? complex? real? rational? integer?
+ exact? inexact?
+ = < > <= >=
+ zero? positive? negative? odd? even?
+ max min
+ + * - /
+ abs
+ quotient remainder modulo
+ gcd lcm
+ numerator denominator
+ rationalize
+ floor ceiling truncate round
+ exp log sin cos tan asin acos atan
+ sqrt
+ expt
+ make-rectangular make-polar real-part imag-part magnitude angle
+ exact->inexact inexact->exact
+
+ number->string string->number
+
+ boolean?
+ not
+
+ pair?
+ cons car cdr
+ set-car! set-cdr!
+ caar cadr cdar cddr
+ caaar caadr cadar caddr cdaar cdadr cddar cdddr
+ caaaar caaadr caadar caaddr cadaar cadadr caddar cadddr
+ cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar cddddr
+ null?
+ list?
+ list
+ length
+ append
+ reverse
+ list-tail list-ref
+ memq memv member
+ assq assv assoc
+
+ symbol?
+ symbol->string string->symbol
+
+ char?
+ char=? char<? char>? char<=? char>=?
+ char-ci=? char-ci<? char-ci>? char-ci<=? char-ci>=?
+ char-alphabetic? char-numeric? char-whitespace?
+ char-upper-case? char-lower-case?
+ char->integer integer->char
+ char-upcase
+ char-downcase
+
+ string?
+ make-string
+ string
+ string-length
+ string-ref string-set!
+ string=? string-ci=?
+ string<? string>? string<=? string>=?
+ string-ci<? string-ci>? string-ci<=? string-ci>=?
+ substring
+ string-length
+ string-append
+ string->list list->string
+ string-copy string-fill!
+
+ vector?
+ make-vector
+ vector
+ vector-length
+ vector-ref vector-set!
+ vector->list list->vector
+ vector-fill!
+
+ procedure?
+ apply
+ map
+ for-each
+ force
+
+ call-with-current-continuation
+
+ values
+ call-with-values
+ dynamic-wind
+
+ eval
+
+ input-port? output-port?
+ current-input-port current-output-port
+
+ read
+ read-char
+ peek-char
+ eof-object?
+ char-ready?
+
+ write
+ display
+ newline
+ write-char
+
+ ;;transcript-on
+ ;;transcript-off
+ )
+
+ \:export (null-environment))
+
+(define null-interface (resolve-interface '(ice-9 null)))
+
+(module-use! (module-public-interface (current-module))
+ null-interface)
+
+(define (null-environment n)
+ (if (not (= n 5))
+ (scm-error 'misc-error 'null-environment
+ "~A is not a valid version"
+ (list n)
+ '()))
+ ;; Note that we need to create a *fresh* interface
+ (let ((interface (make-module 31)))
+ (set-module-kind! interface 'interface)
+ (module-use! interface null-interface)
+ interface))
+;;;; Copyright (C) 2000, 2001, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;;; Safe subset of R5RS bindings
+
+(define-module (ice-9 safe)
+ \:export (safe-environment make-safe-module))
+
+(define safe-r5rs-interface (resolve-interface '(ice-9 safe-r5rs)))
+
+(define (safe-environment n)
+ (if (not (= n 5))
+ (scm-error 'misc-error 'safe-environment
+ "~A is not a valid version"
+ (list n)
+ '()))
+ safe-r5rs-interface)
+
+(define (make-safe-module)
+ (make-module 1021 (list safe-r5rs-interface)))
+;;; -*- mode: scheme; coding: utf-8; -*-
+
+;;;; Copyright (C) 1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010
+;;;; Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+
+;;; Commentary:
+
+;;; An older approack to debugging, in which the user installs a pre-unwind
+;;; handler that saves the stack at the time of the error. The last stack can
+;;; then be debugged later.
+;;;
+
+;;; Code:
+
+(define-module (ice-9 save-stack)
+ ;; Replace deprecated root-module bindings, if present.
+ #\replace (stack-saved?
+ the-last-stack
+ save-stack))
+
+;; FIXME: stack-saved? is broken in the presence of threads.
+(define stack-saved? #f)
+
+(define the-last-stack (make-fluid))
+
+(define (save-stack . narrowing)
+ (if (not stack-saved?)
+ (begin
+ (let ((stacks (fluid-ref %stacks)))
+ (fluid-set! the-last-stack
+ ;; (make-stack obj inner outer inner outer ...)
+ ;;
+ ;; In this case, cut away the make-stack frame, the
+ ;; save-stack frame, and then narrow as specified by the
+ ;; user, delimited by the nearest start-stack invocation,
+ ;; if any.
+ (apply make-stack #t
+ 2
+ (if (pair? stacks) (cdar stacks) 0)
+ narrowing)))
+ (set! stack-saved? #t))))
+;;;; Copyright (C) 1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010
+;;;; Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+(define-module (ice-9 scm-style-repl)
+ #\use-module (ice-9 save-stack)
+
+ #\export (scm-repl-silent
+ scm-repl-print-unspecified
+ scm-repl-verbose
+ scm-repl-prompt)
+
+ ;; #\replace, as with deprecated code enabled these will be in the root env
+ #\replace (assert-repl-silence
+ assert-repl-print-unspecified
+ assert-repl-verbosity
+
+ default-pre-unwind-handler
+ bad-throw
+ error-catching-loop
+ error-catching-repl
+ scm-style-repl
+ handle-system-error))
+
+(define scm-repl-silent #f)
+(define (assert-repl-silence v) (set! scm-repl-silent v))
+
+(define scm-repl-print-unspecified #f)
+(define (assert-repl-print-unspecified v) (set! scm-repl-print-unspecified v))
+
+(define scm-repl-verbose #f)
+(define (assert-repl-verbosity v) (set! scm-repl-verbose v))
+
+(define scm-repl-prompt "guile> ")
+
+
+
+;; bad-throw is the hook that is called upon a throw to a an unhandled
+;; key (unless the throw has four arguments, in which case
+;; it's usually interpreted as an error throw.)
+;; If the key has a default handler (a throw-handler-default property),
+;; it is applied to the throw.
+;;
+(define (bad-throw key . args)
+ (let ((default (symbol-property key 'throw-handler-default)))
+ (or (and default (apply default key args))
+ (apply error "unhandled-exception:" key args))))
+
+
+
+(define (default-pre-unwind-handler key . args)
+ ;; Narrow by two more frames: this one, and the throw handler.
+ (save-stack 2)
+ (apply throw key args))
+
+
+
+(define has-shown-debugger-hint? #f)
+
+(define (error-catching-loop thunk)
+ (let ((status #f)
+ (interactive #t))
+ (define (loop first)
+ (let ((next
+ (catch #t
+
+ (lambda ()
+ (call-with-unblocked-asyncs
+ (lambda ()
+ (first)
+
+ ;; This line is needed because mark
+ ;; doesn't do closures quite right.
+ ;; Unreferenced locals should be
+ ;; collected.
+ (set! first #f)
+ (let loop ((v (thunk)))
+ (loop (thunk)))
+ #f)))
+
+ (lambda (key . args)
+ (case key
+ ((quit)
+ (set! status args)
+ #f)
+
+ ((switch-repl)
+ (apply throw 'switch-repl args))
+
+ ((abort)
+ ;; This is one of the closures that require
+ ;; (set! first #f) above
+ ;;
+ (lambda ()
+ (run-hook abort-hook)
+ (force-output (current-output-port))
+ (display "ABORT: " (current-error-port))
+ (write args (current-error-port))
+ (newline (current-error-port))
+ (if interactive
+ (begin
+ (if (and
+ (not has-shown-debugger-hint?)
+ (not (memq 'backtrace
+ (debug-options-interface)))
+ (stack? (fluid-ref the-last-stack)))
+ (begin
+ (newline (current-error-port))
+ (display
+ "Type \"(backtrace)\" to get more information or \"(debug)\" to enter the debugger.\n"
+ (current-error-port))
+ (set! has-shown-debugger-hint? #t)))
+ (force-output (current-error-port)))
+ (begin
+ (primitive-exit 1)))
+ (set! stack-saved? #f)))
+
+ (else
+ ;; This is the other cons-leak closure...
+ (lambda ()
+ (cond ((= (length args) 4)
+ (apply handle-system-error key args))
+ (else
+ (apply bad-throw key args)))))))
+
+ default-pre-unwind-handler)))
+
+ (if next (loop next) status)))
+ (set! ensure-batch-mode! (lambda ()
+ (set! interactive #f)
+ (restore-signals)))
+ (set! batch-mode? (lambda () (not interactive)))
+ (call-with-blocked-asyncs
+ (lambda () (loop (lambda () #t))))))
+
+(define (error-catching-repl r e p)
+ (error-catching-loop
+ (lambda ()
+ (call-with-values (lambda () (e (r)))
+ (lambda the-values (for-each p the-values))))))
+
+(define (scm-style-repl)
+ (letrec (
+ (start-gc-rt #f)
+ (start-rt #f)
+ (repl-report-start-timing (lambda ()
+ (set! start-gc-rt (gc-run-time))
+ (set! start-rt (get-internal-run-time))))
+ (repl-report (lambda ()
+ (display ";;; ")
+ (display (inexact->exact
+ (* 1000 (/ (- (get-internal-run-time) start-rt)
+ internal-time-units-per-second))))
+ (display " msec (")
+ (display (inexact->exact
+ (* 1000 (/ (- (gc-run-time) start-gc-rt)
+ internal-time-units-per-second))))
+ (display " msec in gc)\n")))
+
+ (consume-trailing-whitespace
+ (lambda ()
+ (let ((ch (peek-char)))
+ (cond
+ ((eof-object? ch))
+ ((or (char=? ch #\space) (char=? ch #\tab))
+ (read-char)
+ (consume-trailing-whitespace))
+ ((char=? ch #\newline)
+ (read-char))))))
+ (-read (lambda ()
+ (let ((val
+ (let ((prompt (cond ((string? scm-repl-prompt)
+ scm-repl-prompt)
+ ((thunk? scm-repl-prompt)
+ (scm-repl-prompt))
+ (scm-repl-prompt "> ")
+ (else ""))))
+ (repl-reader prompt))))
+
+ ;; As described in R4RS, the READ procedure updates the
+ ;; port to point to the first character past the end of
+ ;; the external representation of the object. This
+ ;; means that it doesn't consume the newline typically
+ ;; found after an expression. This means that, when
+ ;; debugging Guile with GDB, GDB gets the newline, which
+ ;; it often interprets as a "continue" command, making
+ ;; breakpoints kind of useless. So, consume any
+ ;; trailing newline here, as well as any whitespace
+ ;; before it.
+ ;; But not if EOF, for control-D.
+ (if (not (eof-object? val))
+ (consume-trailing-whitespace))
+ (run-hook after-read-hook)
+ (if (eof-object? val)
+ (begin
+ (repl-report-start-timing)
+ (if scm-repl-verbose
+ (begin
+ (newline)
+ (display ";;; EOF -- quitting")
+ (newline)))
+ (quit 0)))
+ val)))
+
+ (-eval (lambda (sourc)
+ (repl-report-start-timing)
+ (run-hook before-eval-hook sourc)
+ (let ((val (start-stack 'repl-stack
+ ;; If you change this procedure
+ ;; (primitive-eval), please also
+ ;; modify the repl-stack case in
+ ;; save-stack so that stack cutting
+ ;; continues to work.
+ (primitive-eval sourc))))
+ (run-hook after-eval-hook sourc)
+ val)))
+
+
+ (-print (let ((maybe-print (lambda (result)
+ (if (or scm-repl-print-unspecified
+ (not (unspecified? result)))
+ (begin
+ (write result)
+ (newline))))))
+ (lambda (result)
+ (if (not scm-repl-silent)
+ (begin
+ (run-hook before-print-hook result)
+ (maybe-print result)
+ (run-hook after-print-hook result)
+ (if scm-repl-verbose
+ (repl-report))
+ (force-output))))))
+
+ (-quit (lambda (args)
+ (if scm-repl-verbose
+ (begin
+ (display ";;; QUIT executed, repl exitting")
+ (newline)
+ (repl-report)))
+ args)))
+
+ (let ((status (error-catching-repl -read
+ -eval
+ -print)))
+ (-quit status))))
+
+(define (handle-system-error key . args)
+ (let ((cep (current-error-port)))
+ (cond ((not (stack? (fluid-ref the-last-stack))))
+ ((memq 'backtrace (debug-options-interface))
+ (let ((highlights (if (or (eq? key 'wrong-type-arg)
+ (eq? key 'out-of-range))
+ (list-ref args 3)
+ '())))
+ (run-hook before-backtrace-hook)
+ (newline cep)
+ (display "Backtrace:\n")
+ (display-backtrace (fluid-ref the-last-stack) cep
+ #f #f highlights)
+ (newline cep)
+ (run-hook after-backtrace-hook))))
+ (run-hook before-error-hook)
+ (apply display-error (fluid-ref the-last-stack) cep args)
+ (run-hook after-error-hook)
+ (force-output cep)
+ (throw 'abort key)))
+;;;; Copyright (C) 2003, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+
+;; (serialize FORM1 ...) and (parallelize FORM1 ...) are useful when
+;; you don't trust the thread safety of most of your program, but
+;; where you have some section(s) of code which you consider can run
+;; in parallel to other sections.
+;;
+;; They "flag" (with dynamic extent) sections of code to be of
+;; "serial" or "parallel" nature and have the single effect of
+;; preventing a serial section from being run in parallel with any
+;; serial section (including itself).
+;;
+;; Both serialize and parallelize can be nested. If so, the
+;; inner-most construct is in effect.
+;;
+;; NOTE 1: A serial section can run in parallel with a parallel
+;; section.
+;;
+;; NOTE 2: If a serial section S is "interrupted" by a parallel
+;; section P in the following manner: S = S1 P S2, S2 is not
+;; guaranteed to be resumed by the same thread that previously
+;; executed S1.
+;;
+;; WARNING: Spawning new threads within a serial section have
+;; undefined effects. It is OK, though, to spawn threads in unflagged
+;; sections of code where neither serialize or parallelize is in
+;; effect.
+;;
+;; A typical usage is when Guile is used as scripting language in some
+;; application doing heavy computations. If each thread is
+;; encapsulated with a serialize form, you can then put a parallelize
+;; form around the code performing the heavy computations (typically a
+;; C code primitive), enabling the computations to run in parallel
+;; while the scripting code runs single-threadedly.
+;;
+
+;;; Code:
+
+(define-module (ice-9 serialize)
+ \:use-module (ice-9 threads)
+ \:export (call-with-serialization
+ call-with-parallelization)
+ \:export-syntax (serialize
+ parallelize))
+
+
+(define serialization-mutex (make-mutex))
+(define admin-mutex (make-mutex))
+(define owner #f)
+
+(define (call-with-serialization thunk)
+ (let ((outer-owner #f))
+ (dynamic-wind
+ (lambda ()
+ (lock-mutex admin-mutex)
+ (set! outer-owner owner)
+ (if (not (eqv? outer-owner (dynamic-root)))
+ (begin
+ (unlock-mutex admin-mutex)
+ (lock-mutex serialization-mutex)
+ (set! owner (dynamic-root)))
+ (unlock-mutex admin-mutex)))
+ thunk
+ (lambda ()
+ (lock-mutex admin-mutex)
+ (if (not (eqv? outer-owner (dynamic-root)))
+ (begin
+ (set! owner #f)
+ (unlock-mutex serialization-mutex)))
+ (unlock-mutex admin-mutex)))))
+
+(define-macro (serialize . forms)
+ `(call-with-serialization (lambda () ,@forms)))
+
+(define (call-with-parallelization thunk)
+ (let ((outer-owner #f))
+ (dynamic-wind
+ (lambda ()
+ (lock-mutex admin-mutex)
+ (set! outer-owner owner)
+ (if (eqv? outer-owner (dynamic-root))
+ (begin
+ (set! owner #f)
+ (unlock-mutex serialization-mutex)))
+ (unlock-mutex admin-mutex))
+ thunk
+ (lambda ()
+ (lock-mutex admin-mutex)
+ (if (eqv? outer-owner (dynamic-root))
+ (begin
+ (unlock-mutex admin-mutex)
+ (lock-mutex serialization-mutex)
+ (set! owner outer-owner))
+ (unlock-mutex admin-mutex))))))
+
+(define-macro (parallelize . forms)
+ `(call-with-parallelization (lambda () ,@forms)))
+;;;; Copyright (C) 1997, 2000, 2001, 2003, 2006, 2009, 2010, 2011,
+;;;; 2012 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (ice-9 session)
+ #\use-module (ice-9 documentation)
+ #\use-module (ice-9 regex)
+ #\use-module (ice-9 rdelim)
+ #\use-module (ice-9 match)
+ #\export (help
+ add-value-help-handler! remove-value-help-handler!
+ add-name-help-handler! remove-name-help-handler!
+ apropos-hook
+ apropos apropos-internal apropos-fold apropos-fold-accessible
+ apropos-fold-exported apropos-fold-all source arity
+ procedure-arguments
+ module-commentary))
+
+
+
+(define *value-help-handlers*
+ `(,(lambda (name value)
+ (object-documentation value))))
+
+(define (add-value-help-handler! proc)
+ "Adds a handler for performing `help' on a value.
+
+`proc' will be called as (PROC NAME VALUE). `proc' should return #t to
+indicate that it has performed help, a string to override the default
+object documentation, or #f to try the other handlers, potentially
+falling back on the normal behavior for `help'."
+ (set! *value-help-handlers* (cons proc *value-help-handlers*)))
+
+(define (remove-value-help-handler! proc)
+ "Removes a handler for performing `help' on a value."
+ (set! *value-help-handlers* (delete! proc *value-help-handlers*)))
+
+(define (try-value-help name value)
+ (or-map (lambda (proc) (proc name value)) *value-help-handlers*))
+
+
+(define *name-help-handlers* '())
+
+(define (add-name-help-handler! proc)
+ "Adds a handler for performing `help' on a name.
+
+`proc' will be called with the unevaluated name as its argument. That is
+to say, when the user calls `(help FOO)', the name is FOO, exactly as
+the user types it.
+
+`proc' should return #t to indicate that it has performed help, a string
+to override the default object documentation, or #f to try the other
+handlers, potentially falling back on the normal behavior for `help'."
+ (set! *name-help-handlers* (cons proc *name-help-handlers*)))
+
+(define (remove-name-help-handler! proc)
+ "Removes a handler for performing `help' on a name."
+ (set! *name-help-handlers* (delete! proc *name-help-handlers*)))
+
+(define (try-name-help name)
+ (or-map (lambda (proc) (proc name)) *name-help-handlers*))
+
+
+;;; Documentation
+;;;
+(define-macro (help . exp)
+ "(help [NAME])
+Prints useful information. Try `(help)'."
+ (cond ((not (= (length exp) 1))
+ (help-usage)
+ '(begin))
+ ((not (provided? 'regex))
+ (display "`help' depends on the `regex' feature.
+You don't seem to have regular expressions installed.\n")
+ '(begin))
+ (else
+ (let ((name (car exp))
+ (not-found (lambda (type x)
+ (simple-format #t "No ~A found for ~A\n"
+ type x))))
+ (cond
+
+ ;; User-specified
+ ((try-name-help name)
+ => (lambda (x) (if (not (eq? x #t)) (display x))))
+
+ ;; SYMBOL
+ ((symbol? name)
+ (help-doc name
+ (simple-format
+ #f "^~A$"
+ (regexp-quote (symbol->string name)))))
+
+ ;; "STRING"
+ ((string? name)
+ (help-doc name name))
+
+ ;; (unquote SYMBOL)
+ ((and (list? name)
+ (= (length name) 2)
+ (eq? (car name) 'unquote))
+ (let ((doc (try-value-help (cadr name)
+ (module-ref (current-module)
+ (cadr name)))))
+ (cond ((not doc) (not-found 'documentation (cadr name)))
+ ((eq? doc #t)) ;; pass
+ (else (write-line doc)))))
+
+ ;; (quote SYMBOL)
+ ((and (list? name)
+ (= (length name) 2)
+ (eq? (car name) 'quote)
+ (symbol? (cadr name)))
+ (cond ((search-documentation-files (cadr name))
+ => write-line)
+ (else (not-found 'documentation (cadr name)))))
+
+ ;; (SYM1 SYM2 ...)
+ ((and (list? name)
+ (and-map symbol? name)
+ (not (null? name))
+ (not (eq? (car name) 'quote)))
+ (cond ((module-commentary name)
+ => (lambda (doc)
+ (display name) (write-line " commentary:")
+ (write-line doc)))
+ (else (not-found 'commentary name))))
+
+ ;; unrecognized
+ (else
+ (help-usage)))
+ '(begin)))))
+
+(define (module-filename name) ; fixme: better way? / done elsewhere?
+ (let* ((name (map symbol->string name))
+ (reverse-name (reverse name))
+ (leaf (car reverse-name))
+ (dir-hint-module-name (reverse (cdr reverse-name)))
+ (dir-hint (apply string-append
+ (map (lambda (elt)
+ (string-append elt "/"))
+ dir-hint-module-name))))
+ (%search-load-path (in-vicinity dir-hint leaf))))
+
+(define (module-commentary name)
+ (cond ((module-filename name) => file-commentary)
+ (else #f)))
+
+(define (help-doc term regexp)
+ (let ((entries (apropos-fold (lambda (module name object data)
+ (cons (list module
+ name
+ (try-value-help name object)
+ (cond ((procedure? object)
+ "a procedure")
+ (else
+ "an object")))
+ data))
+ '()
+ regexp
+ apropos-fold-exported))
+ (module car)
+ (name cadr)
+ (doc caddr)
+ (type cadddr))
+ (cond ((not (null? entries))
+ (let ((first? #t)
+ (undocumented-entries '())
+ (documented-entries '())
+ (documentations '()))
+
+ (for-each (lambda (entry)
+ (let ((entry-summary (simple-format
+ #f "~S: ~S\n"
+ (module-name (module entry))
+ (name entry))))
+ (if (doc entry)
+ (begin
+ (set! documented-entries
+ (cons entry-summary documented-entries))
+ ;; *fixme*: Use `describe' when we have GOOPS?
+ (set! documentations
+ (cons (simple-format
+ #f "`~S' is ~A in the ~S module.\n\n~A\n"
+ (name entry)
+ (type entry)
+ (module-name (module entry))
+ (doc entry))
+ documentations)))
+ (set! undocumented-entries
+ (cons entry-summary
+ undocumented-entries)))))
+ entries)
+
+ (if (and (not (null? documented-entries))
+ (or (> (length documented-entries) 1)
+ (not (null? undocumented-entries))))
+ (begin
+ (display "Documentation found for:\n")
+ (for-each (lambda (entry) (display entry))
+ documented-entries)
+ (set! first? #f)))
+
+ (for-each (lambda (entry)
+ (if first?
+ (set! first? #f)
+ (newline))
+ (display entry))
+ documentations)
+
+ (if (not (null? undocumented-entries))
+ (begin
+ (if first?
+ (set! first? #f)
+ (newline))
+ (display "No documentation found for:\n")
+ (for-each (lambda (entry) (display entry))
+ undocumented-entries)))))
+ ((search-documentation-files term)
+ => (lambda (doc)
+ (write-line "Documentation from file:")
+ (write-line doc)))
+ (else
+ ;; no matches
+ (display "Did not find any object ")
+ (simple-format #t
+ (if (symbol? term)
+ "named `~A'\n"
+ "matching regexp \"~A\"\n")
+ term)))))
+
+(define (help-usage)
+ (display "Usage: (help NAME) gives documentation about objects named NAME (a symbol)
+ (help REGEXP) ditto for objects with names matching REGEXP (a string)
+ (help 'NAME) gives documentation for NAME, even if it is not an object
+ (help ,EXPR) gives documentation for object returned by EXPR
+ (help (my module)) gives module commentary for `(my module)'
+ (help) gives this text
+
+`help' searches among bindings exported from loaded modules, while
+`apropos' searches among bindings visible from the \"current\" module.
+
+Examples: (help help)
+ (help cons)
+ (help \"output-string\")
+
+Other useful sources of helpful information:
+
+(apropos STRING)
+(arity PROCEDURE)
+(name PROCEDURE-OR-MACRO)
+(source PROCEDURE-OR-MACRO)
+
+Tools:
+
+(backtrace) ;show backtrace from last error
+(debug) ;enter the debugger
+(trace [PROCEDURE]) ;trace procedure (no arg => show)
+(untrace [PROCEDURE]) ;untrace (no arg => untrace all)
+
+(OPTIONSET-options 'full) ;display option information
+(OPTIONSET-enable 'OPTION)
+(OPTIONSET-disable 'OPTION)
+(OPTIONSET-set! OPTION VALUE)
+
+where OPTIONSET is one of debug, read, eval, print
+
+"))
+
+;;; {Apropos}
+;;;
+;;; Author: Roland Orre <orre@nada.kth.se>
+;;;
+
+;; Two arguments: the module, and the pattern, as a string.
+;;
+(define apropos-hook (make-hook 2))
+
+(define (apropos rgx . options)
+ "Search for bindings: apropos regexp {options= 'full 'shadow 'value}"
+ (run-hook apropos-hook (current-module) rgx)
+ (if (zero? (string-length rgx))
+ "Empty string not allowed"
+ (let* ((match (make-regexp rgx))
+ (uses (module-uses (current-module)))
+ (modules (cons (current-module)
+ (if (and (not (null? uses))
+ (eq? (module-name (car uses))
+ 'duplicates))
+ (cdr uses)
+ uses)))
+ (separator #\tab)
+ (shadow (member 'shadow options))
+ (value (member 'value options)))
+ (cond ((member 'full options)
+ (set! shadow #t)
+ (set! value #t)))
+ (for-each
+ (lambda (module)
+ (let* ((name (module-name module))
+ (obarray (module-obarray module)))
+ ;; XXX - should use hash-fold here
+ (hash-for-each
+ (lambda (symbol variable)
+ (cond ((regexp-exec match (symbol->string symbol))
+ (display name)
+ (display ": ")
+ (display symbol)
+ (cond ((variable-bound? variable)
+ (let ((val (variable-ref variable)))
+ (cond ((or (procedure? val) value)
+ (display separator)
+ (display val)))))
+ (else
+ (display separator)
+ (display "(unbound)")))
+ (if (and shadow
+ (not (eq? (module-ref module symbol)
+ (module-ref (current-module) symbol))))
+ (display " shadowed"))
+ (newline))))
+ obarray)))
+ modules))))
+
+(define (apropos-internal rgx)
+ "Return a list of accessible variable names."
+ (apropos-fold (lambda (module name var data)
+ (cons name data))
+ '()
+ rgx
+ (apropos-fold-accessible (current-module))))
+
+(define (apropos-fold proc init rgx folder)
+ "Folds PROCEDURE over bindings matching third arg REGEXP.
+
+Result is
+
+ (PROCEDURE MODULE1 NAME1 VALUE1
+ (PROCEDURE MODULE2 NAME2 VALUE2
+ ...
+ (PROCEDURE MODULEn NAMEn VALUEn INIT)))
+
+where INIT is the second arg to `apropos-fold'.
+
+Fourth arg FOLDER is one of
+
+ (apropos-fold-accessible MODULE) ;fold over bindings accessible in MODULE
+ apropos-fold-exported ;fold over all exported bindings
+ apropos-fold-all ;fold over all bindings"
+ (run-hook apropos-hook (current-module) rgx)
+ (let ((match (make-regexp rgx))
+ (recorded (make-hash-table)))
+ (let ((fold-module
+ (lambda (module data)
+ (let* ((obarray-filter
+ (lambda (name val data)
+ (if (and (regexp-exec match (symbol->string name))
+ (not (hashq-get-handle recorded name)))
+ (begin
+ (hashq-set! recorded name #t)
+ (proc module name val data))
+ data)))
+ (module-filter
+ (lambda (name var data)
+ (if (variable-bound? var)
+ (obarray-filter name (variable-ref var) data)
+ data))))
+ (cond (module (hash-fold module-filter
+ data
+ (module-obarray module)))
+ (else data))))))
+ (folder fold-module init))))
+
+(define (make-fold-modules init-thunk traverse extract)
+ "Return procedure capable of traversing a forest of modules.
+The forest traversed is the image of the forest generated by root
+modules returned by INIT-THUNK and the generator TRAVERSE.
+It is an image under the mapping EXTRACT."
+ (lambda (fold-module init)
+ (let* ((table (make-hash-table 31))
+ (first? (lambda (obj)
+ (let* ((handle (hash-create-handle! table obj #t))
+ (first? (cdr handle)))
+ (set-cdr! handle #f)
+ first?))))
+ (let rec ((data init)
+ (modules (init-thunk)))
+ (do ((modules modules (cdr modules))
+ (data data (if (first? (car modules))
+ (rec (fold-module (extract (car modules)) data)
+ (traverse (car modules)))
+ data)))
+ ((null? modules) data))))))
+
+(define (apropos-fold-accessible module)
+ (make-fold-modules (lambda () (list module))
+ module-uses
+ identity))
+
+(define (root-modules)
+ (submodules (resolve-module '() #f)))
+
+(define (submodules mod)
+ (hash-map->list (lambda (k v) v) (module-submodules mod)))
+
+(define apropos-fold-exported
+ (make-fold-modules root-modules submodules module-public-interface))
+
+(define apropos-fold-all
+ (make-fold-modules root-modules submodules identity))
+
+(define (source obj)
+ (cond ((procedure? obj) (procedure-source obj))
+ ((macro? obj) (procedure-source (macro-transformer obj)))
+ (else #f)))
+
+(define (arity obj)
+ (define (display-arg-list arg-list)
+ (display #\`)
+ (display (car arg-list))
+ (let loop ((ls (cdr arg-list)))
+ (cond ((null? ls)
+ (display #\'))
+ ((not (pair? ls))
+ (display "', the rest in `")
+ (display ls)
+ (display #\'))
+ (else
+ (if (pair? (cdr ls))
+ (display "', `")
+ (display "' and `"))
+ (display (car ls))
+ (loop (cdr ls))))))
+ (define (display-arg-list/summary arg-list type)
+ (let ((len (length arg-list)))
+ (display len)
+ (display " ")
+ (display type)
+ (if (> len 1)
+ (display " arguments: ")
+ (display " argument: "))
+ (display-arg-list arg-list)))
+ (cond
+ ((procedure-property obj 'arglist)
+ => (lambda (arglist)
+ (let ((required-args (car arglist))
+ (optional-args (cadr arglist))
+ (keyword-args (caddr arglist))
+ (allow-other-keys? (cadddr arglist))
+ (rest-arg (car (cddddr arglist)))
+ (need-punctuation #f))
+ (cond ((not (null? required-args))
+ (display-arg-list/summary required-args "required")
+ (set! need-punctuation #t)))
+ (cond ((not (null? optional-args))
+ (if need-punctuation (display ", "))
+ (display-arg-list/summary optional-args "optional")
+ (set! need-punctuation #t)))
+ (cond ((not (null? keyword-args))
+ (if need-punctuation (display ", "))
+ (display-arg-list/summary keyword-args "keyword")
+ (set! need-punctuation #t)))
+ (cond (allow-other-keys?
+ (if need-punctuation (display ", "))
+ (display "other keywords allowed")
+ (set! need-punctuation #t)))
+ (cond (rest-arg
+ (if need-punctuation (display ", "))
+ (display "the rest in `")
+ (display rest-arg)
+ (display "'"))))))
+ (else
+ (let ((arity (procedure-minimum-arity obj)))
+ (display (car arity))
+ (cond ((caddr arity)
+ (display " or more"))
+ ((not (zero? (cadr arity)))
+ (display " required and ")
+ (display (cadr arity))
+ (display " optional")))
+ (if (and (not (caddr arity))
+ (= (car arity) 1)
+ (<= (cadr arity) 1))
+ (display " argument")
+ (display " arguments")))))
+ (display ".\n"))
+
+
+(define (procedure-arguments proc)
+ "Return an alist describing the arguments that `proc' accepts, or `#f'
+if the information cannot be obtained.
+
+The alist keys that are currently defined are `required', `optional',
+`keyword', `allow-other-keys?', and `rest'."
+ (cond
+ ((procedure-property proc 'arglist)
+ => (match-lambda
+ ((req opt keyword aok? rest)
+ `((required . ,(if (number? req)
+ (make-list req '_)
+ req))
+ (optional . ,(if (number? opt)
+ (make-list opt '_)
+ opt))
+ (keyword . ,keyword)
+ (allow-other-keys? . ,aok?)
+ (rest . ,rest)))))
+ ((procedure-source proc)
+ => cadr)
+ (((@ (system vm program) program?) proc)
+ ((@ (system vm program) program-arguments-alist) proc))
+ (else #f)))
+
+
+;;; session.scm ends here
+;;;; slib.scm --- definitions needed to get SLIB to work with Guile
+;;;;
+;;;; Copyright (C) 1997, 1998, 2000, 2001, 2002, 2003, 2004, 2006, 2007, 2013 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Look for slib.init in the $datadir, in /usr/share, and finally in
+;;; the load path. It's not usually in the load path on common distros,
+;;; but it could be if the user put it there. The init file takes care
+;;; of defining the module.
+
+(let ((try-load (lambda (dir)
+ (let ((init (string-append dir "/slib/guile.init")))
+ (and (file-exists? init)
+ (begin
+ (load init)
+ #t))))))
+ (or (try-load (assq-ref %guile-build-info 'datadir))
+ (try-load "/usr/share")
+ (load-from-path "slib/guile.init")))
+;;; installed-scm-file
+
+;;;; Copyright (C) 2001, 2006, 2010 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+(define-module (ice-9 stack-catch)
+ #\use-module (ice-9 save-stack)
+ #\export (stack-catch))
+
+(define (stack-catch key thunk handler)
+ "Like @code{catch}, invoke @var{thunk} in the dynamic context of
+@var{handler} for exceptions matching @var{key}, but also save the
+current stack state in the @var{the-last-stack} fluid, for the purpose
+of debugging or re-throwing of an error. If thunk throws to the
+symbol @var{key}, then @var{handler} is invoked this way:\n
+@example
+ (handler key args ...)
+@end example\n
+@var{key} is a symbol or #t.\n
+@var{thunk} takes no arguments. If @var{thunk} returns normally, that
+is the return value of @code{catch}.\n
+Handler is invoked outside the scope of its own @code{catch}. If
+@var{handler} again throws to the same key, a new handler from further
+up the call chain is invoked.\n
+If the key is @code{#t}, then a throw to @emph{any} symbol will match
+this call to @code{catch}."
+ (catch key
+ thunk
+ handler
+ (lambda (key . args)
+ ;; Narrow by two more frames: this one, and the throw handler.
+ (save-stack 2)
+ (apply throw key args))))
+;;;; streams.scm --- general lazy streams
+;;;; -*- Scheme -*-
+
+;;;; Copyright (C) 1999, 2001, 2004, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;; the basic stream operations are inspired by
+;; (i.e. ripped off) Scheme48's `stream' package,
+;; modulo stream-empty? -> stream-null? renaming.
+
+(define-module (ice-9 streams)
+ \:export (make-stream
+ stream-car stream-cdr stream-null?
+ list->stream vector->stream port->stream
+ stream->list stream->reversed-list
+ stream->list&length stream->reversed-list&length
+ stream->vector
+ stream-fold stream-for-each stream-map))
+
+;; Use:
+;;
+;; (make-stream producer initial-state)
+;; - PRODUCER is a function of one argument, the current state.
+;; it should return either a pair or an atom (i.e. anything that
+;; is not a pair). if PRODUCER returns a pair, then the car of the pair
+;; is the stream's head value, and the cdr is the state to be fed
+;; to PRODUCER later. if PRODUCER returns an atom, then the stream is
+;; considered depleted.
+;;
+;; (stream-car stream)
+;; (stream-cdr stream)
+;; (stream-null? stream)
+;; - yes.
+;;
+;; (list->stream list)
+;; (vector->stream vector)
+;; - make a stream with the same contents as LIST/VECTOR.
+;;
+;; (port->stream port read)
+;; - makes a stream of values which are obtained by READing from PORT.
+;;
+;; (stream->list stream)
+;; - returns a list with the same contents as STREAM.
+;;
+;; (stream->reversed-list stream)
+;; - as above, except the contents are in reversed order.
+;;
+;; (stream->list&length stream)
+;; (stream->reversed-list&length stream)
+;; - multiple-valued versions of the above two, the second value is the
+;; length of the resulting list (so you get it for free).
+;;
+;; (stream->vector stream)
+;; - yes.
+;;
+;; (stream-fold proc init stream0 ...)
+;; - PROC must take (+ 1 <number-of-stream-arguments>) arguments, like this:
+;; (PROC car0 ... init). *NOTE*: the INIT argument is last, not first.
+;; I don't have any preference either way, but it's consistent with
+;; `fold[lr]' procedures from SRFI-1. PROC is applied to successive
+;; elements of the given STREAM(s) and to the value of the previous
+;; invocation (INIT on the first invocation). the last result from PROC
+;; is returned.
+;;
+;; (stream-for-each proc stream0 ...)
+;; - like `for-each' we all know and love.
+;;
+;; (stream-map proc stream0 ...)
+;; - like `map', except returns a stream of results, and not a list.
+
+;; Code:
+
+(define (make-stream m state)
+ (delay
+ (let ((o (m state)))
+ (if (pair? o)
+ (cons (car o)
+ (make-stream m (cdr o)))
+ '()))))
+
+(define (stream-car stream)
+ "Returns the first element in STREAM. This is equivalent to `car'."
+ (car (force stream)))
+
+(define (stream-cdr stream)
+ "Returns the first tail of STREAM. Equivalent to `(force (cdr STREAM))'."
+ (cdr (force stream)))
+
+(define (stream-null? stream)
+ "Returns `#t' if STREAM is the end-of-stream marker; otherwise
+returns `#f'. This is equivalent to `null?', but should be used
+whenever testing for the end of a stream."
+ (null? (force stream)))
+
+(define (list->stream l)
+ "Returns a newly allocated stream whose elements are the elements of
+LIST. Equivalent to `(apply stream LIST)'."
+ (make-stream
+ (lambda (l) l)
+ l))
+
+(define (vector->stream v)
+ (make-stream
+ (let ((len (vector-length v)))
+ (lambda (i)
+ (or (= i len)
+ (cons (vector-ref v i) (+ 1 i)))))
+ 0))
+
+(define (stream->reversed-list&length stream)
+ (let loop ((s stream) (acc '()) (len 0))
+ (if (stream-null? s)
+ (values acc len)
+ (loop (stream-cdr s) (cons (stream-car s) acc) (+ 1 len)))))
+
+(define (stream->reversed-list stream)
+ (call-with-values
+ (lambda () (stream->reversed-list&length stream))
+ (lambda (l len) l)))
+
+(define (stream->list&length stream)
+ (call-with-values
+ (lambda () (stream->reversed-list&length stream))
+ (lambda (l len) (values (reverse! l) len))))
+
+(define (stream->list stream)
+ "Returns a newly allocated list whose elements are the elements of STREAM.
+If STREAM has infinite length this procedure will not terminate."
+ (reverse! (stream->reversed-list stream)))
+
+(define (stream->vector stream)
+ (call-with-values
+ (lambda () (stream->reversed-list&length stream))
+ (lambda (l len)
+ (let ((v (make-vector len)))
+ (let loop ((i 0) (l l))
+ (if (not (null? l))
+ (begin
+ (vector-set! v (- len i 1) (car l))
+ (loop (+ 1 i) (cdr l)))))
+ v))))
+
+(define (stream-fold f init stream . rest)
+ (if (null? rest) ;fast path
+ (stream-fold-one f init stream)
+ (stream-fold-many f init (cons stream rest))))
+
+(define (stream-fold-one f r stream)
+ (if (stream-null? stream)
+ r
+ (stream-fold-one f (f (stream-car stream) r) (stream-cdr stream))))
+
+(define (stream-fold-many f r streams)
+ (if (or-map stream-null? streams)
+ r
+ (stream-fold-many f
+ (apply f (let recur ((cars
+ (map stream-car streams)))
+ (if (null? cars)
+ (list r)
+ (cons (car cars)
+ (recur (cdr cars))))))
+ (map stream-cdr streams))))
+
+(define (stream-for-each f stream . rest)
+ (if (null? rest) ;fast path
+ (stream-for-each-one f stream)
+ (stream-for-each-many f (cons stream rest))))
+
+(define (stream-for-each-one f stream)
+ (if (not (stream-null? stream))
+ (begin
+ (f (stream-car stream))
+ (stream-for-each-one f (stream-cdr stream)))))
+
+(define (stream-for-each-many f streams)
+ (if (not (or-map stream-null? streams))
+ (begin
+ (apply f (map stream-car streams))
+ (stream-for-each-many f (map stream-cdr streams)))))
+
+(define (stream-map f stream . rest)
+ "Returns a newly allocated stream, each element being the result of
+invoking F with the corresponding elements of the STREAMs
+as its arguments."
+ (if (null? rest) ;fast path
+ (make-stream (lambda (s)
+ (or (stream-null? s)
+ (cons (f (stream-car s)) (stream-cdr s))))
+ stream)
+ (make-stream (lambda (streams)
+ (or (or-map stream-null? streams)
+ (cons (apply f (map stream-car streams))
+ (map stream-cdr streams))))
+ (cons stream rest))))
+
+(define (port->stream port read)
+ (make-stream (lambda (p)
+ (let ((o (read p)))
+ (or (eof-object? o)
+ (cons o p))))
+ port))
+
+;;; streams.scm ends here
+;;;; string-fun.scm --- string manipulation functions
+;;;;
+;;;; Copyright (C) 1995, 1996, 1997, 1999, 2001, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+(define-module (ice-9 string-fun)
+ \:export (split-after-char split-before-char split-discarding-char
+ split-after-char-last split-before-char-last
+ split-discarding-char-last split-before-predicate
+ split-after-predicate split-discarding-predicate
+ separate-fields-discarding-char separate-fields-after-char
+ separate-fields-before-char string-prefix-predicate string-prefix=?
+ sans-surrounding-whitespace sans-trailing-whitespace
+ sans-leading-whitespace sans-final-newline has-trailing-newline?))
+
+;;;;
+;;;
+;;; Various string funcitons, particularly those that take
+;;; advantage of the "shared substring" capability.
+;;;
+
+;;; {String Fun: Dividing Strings Into Fields}
+;;;
+;;; The names of these functions are very regular.
+;;; Here is a grammar of a call to one of these:
+;;;
+;;; <string-function-invocation>
+;;; := (<action>-<seperator-disposition>-<seperator-determination> <seperator-param> <str> <ret>)
+;;;
+;;; <str> = the string
+;;;
+;;; <ret> = The continuation. String functions generally return
+;;; multiple values by passing them to this procedure.
+;;;
+;;; <action> = split
+;;; | separate-fields
+;;;
+;;; "split" means to divide a string into two parts.
+;;; <ret> will be called with two arguments.
+;;;
+;;; "separate-fields" means to divide a string into as many
+;;; parts as possible. <ret> will be called with
+;;; however many fields are found.
+;;;
+;;; <seperator-disposition> = before
+;;; | after
+;;; | discarding
+;;;
+;;; "before" means to leave the seperator attached to
+;;; the beginning of the field to its right.
+;;; "after" means to leave the seperator attached to
+;;; the end of the field to its left.
+;;; "discarding" means to discard seperators.
+;;;
+;;; Other dispositions might be handy. For example, "isolate"
+;;; could mean to treat the separator as a field unto itself.
+;;;
+;;; <seperator-determination> = char
+;;; | predicate
+;;;
+;;; "char" means to use a particular character as field seperator.
+;;; "predicate" means to check each character using a particular predicate.
+;;;
+;;; Other determinations might be handy. For example, "character-set-member".
+;;;
+;;; <seperator-param> = A parameter that completes the meaning of the determinations.
+;;; For example, if the determination is "char", then this parameter
+;;; says which character. If it is "predicate", the parameter is the
+;;; predicate.
+;;;
+;;;
+;;; For example:
+;;;
+;;; (separate-fields-discarding-char #\, "foo, bar, baz, , bat" list)
+;;; => ("foo" " bar" " baz" " " " bat")
+;;;
+;;; (split-after-char #\- 'an-example-of-split list)
+;;; => ("an-" "example-of-split")
+;;;
+;;; As an alternative to using a determination "predicate", or to trying to do anything
+;;; complicated with these functions, consider using regular expressions.
+;;;
+
+(define (split-after-char char str ret)
+ (let ((end (cond
+ ((string-index str char) => 1+)
+ (else (string-length str)))))
+ (ret (substring str 0 end)
+ (substring str end))))
+
+(define (split-before-char char str ret)
+ (let ((end (or (string-index str char)
+ (string-length str))))
+ (ret (substring str 0 end)
+ (substring str end))))
+
+(define (split-discarding-char char str ret)
+ (let ((end (string-index str char)))
+ (if (not end)
+ (ret str "")
+ (ret (substring str 0 end)
+ (substring str (1+ end))))))
+
+(define (split-after-char-last char str ret)
+ (let ((end (cond
+ ((string-rindex str char) => 1+)
+ (else 0))))
+ (ret (substring str 0 end)
+ (substring str end))))
+
+(define (split-before-char-last char str ret)
+ (let ((end (or (string-rindex str char) 0)))
+ (ret (substring str 0 end)
+ (substring str end))))
+
+(define (split-discarding-char-last char str ret)
+ (let ((end (string-rindex str char)))
+ (if (not end)
+ (ret str "")
+ (ret (substring str 0 end)
+ (substring str (1+ end))))))
+
+(define (split-before-predicate pred str ret)
+ (let loop ((n 0))
+ (cond
+ ((= n (string-length str)) (ret str ""))
+ ((not (pred (string-ref str n))) (loop (1+ n)))
+ (else (ret (substring str 0 n)
+ (substring str n))))))
+(define (split-after-predicate pred str ret)
+ (let loop ((n 0))
+ (cond
+ ((= n (string-length str)) (ret str ""))
+ ((not (pred (string-ref str n))) (loop (1+ n)))
+ (else (ret (substring str 0 (1+ n))
+ (substring str (1+ n)))))))
+
+(define (split-discarding-predicate pred str ret)
+ (let loop ((n 0))
+ (cond
+ ((= n (string-length str)) (ret str ""))
+ ((not (pred (string-ref str n))) (loop (1+ n)))
+ (else (ret (substring str 0 n)
+ (substring str (1+ n)))))))
+
+(define (separate-fields-discarding-char ch str ret)
+ (let loop ((fields '())
+ (str str))
+ (cond
+ ((string-rindex str ch)
+ => (lambda (w) (loop (cons (substring str (+ 1 w)) fields)
+ (substring str 0 w))))
+ (else (apply ret str fields)))))
+
+(define (separate-fields-after-char ch str ret)
+ (reverse
+ (let loop ((fields '())
+ (str str))
+ (cond
+ ((string-index str ch)
+ => (lambda (w) (loop (cons (substring str 0 (+ 1 w)) fields)
+ (substring str (+ 1 w)))))
+ (else (apply ret str fields))))))
+
+(define (separate-fields-before-char ch str ret)
+ (let loop ((fields '())
+ (str str))
+ (cond
+ ((string-rindex str ch)
+ => (lambda (w) (loop (cons (substring str w) fields)
+ (substring str 0 w))))
+ (else (apply ret str fields)))))
+
+
+;;; {String Fun: String Prefix Predicates}
+;;;
+;;; Very simple:
+;;;
+;;; (define-public ((string-prefix-predicate pred?) prefix str)
+;;; (and (<= (string-length prefix) (string-length str))
+;;; (pred? prefix (substring str 0 (string-length prefix)))))
+;;;
+;;; (define-public string-prefix=? (string-prefix-predicate string=?))
+;;;
+
+(define (string-prefix-predicate pred?)
+ (lambda (prefix str)
+ (and (<= (string-length prefix) (string-length str))
+ (pred? prefix (substring str 0 (string-length prefix))))))
+
+(define string-prefix=? (string-prefix-predicate string=?))
+
+
+;;; {String Fun: Strippers}
+;;;
+;;; <stripper> = sans-<removable-part>
+;;;
+;;; <removable-part> = surrounding-whitespace
+;;; | trailing-whitespace
+;;; | leading-whitespace
+;;; | final-newline
+;;;
+
+(define (sans-surrounding-whitespace s)
+ (let ((st 0)
+ (end (string-length s)))
+ (while (and (< st (string-length s))
+ (char-whitespace? (string-ref s st)))
+ (set! st (1+ st)))
+ (while (and (< 0 end)
+ (char-whitespace? (string-ref s (1- end))))
+ (set! end (1- end)))
+ (if (< end st)
+ ""
+ (substring s st end))))
+
+(define (sans-trailing-whitespace s)
+ (let ((st 0)
+ (end (string-length s)))
+ (while (and (< 0 end)
+ (char-whitespace? (string-ref s (1- end))))
+ (set! end (1- end)))
+ (if (< end st)
+ ""
+ (substring s st end))))
+
+(define (sans-leading-whitespace s)
+ (let ((st 0)
+ (end (string-length s)))
+ (while (and (< st (string-length s))
+ (char-whitespace? (string-ref s st)))
+ (set! st (1+ st)))
+ (if (< end st)
+ ""
+ (substring s st end))))
+
+(define (sans-final-newline str)
+ (cond
+ ((= 0 (string-length str))
+ str)
+
+ ((char=? #\nl (string-ref str (1- (string-length str))))
+ (substring str 0 (1- (string-length str))))
+
+ (else str)))
+
+;;; {String Fun: has-trailing-newline?}
+;;;
+
+(define (has-trailing-newline? str)
+ (and (< 0 (string-length str))
+ (char=? #\nl (string-ref str (1- (string-length str))))))
+
+
+
+;;; {String Fun: with-regexp-parts}
+
+;;; This relies on the older, hairier regexp interface, which we don't
+;;; particularly want to implement, and it's not used anywhere, so
+;;; we're just going to drop it for now.
+;;; (define-public (with-regexp-parts regexp fields str return fail)
+;;; (let ((parts (regexec regexp str fields)))
+;;; (if (number? parts)
+;;; (fail parts)
+;;; (apply return parts))))
+
+;;;; Copyright (C) 1997, 2000, 2001, 2002, 2003, 2006, 2010 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (ice-9 syncase)
+ ;; FIXME re-export other procs
+ #\export (datum->syntax-object syntax-object->datum
+ sc-expand))
+
+(issue-deprecation-warning
+ "Syntax-case macros are now a part of Guile core; importing (ice-9 syncase) is no longer necessary.")
+
+(define datum->syntax-object datum->syntax)
+(define syntax-object->datum syntax->datum)
+(define sc-expand macroexpand)
+
+;;; Hack to make syncase macros work in the slib module
+;; FIXME wingo is this still necessary?
+;; (let ((m (nested-ref the-root-module '(%app modules ice-9 slib))))
+;; (if m
+;; (set-object-property! (module-local-variable m 'define)
+;; '*sc-expander*
+;; '(define))))
+;; Copyright (C) 1991, 1992, 1993, 1994, 1995, 2006 Free Software Foundation, Inc.
+;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;;; "test.scm" Test correctness of scheme implementations.
+;;; Author: Aubrey Jaffer
+;;; Modified: Mikael Djurfeldt (Removed tests which Guile deliberately
+;;; won't pass. Made the tests (test-cont), (test-sc4), and
+;;; (test-delay) start to run automatically.
+
+;;; This includes examples from
+;;; William Clinger and Jonathan Rees, editors.
+;;; Revised^4 Report on the Algorithmic Language Scheme
+;;; and the IEEE specification.
+
+;;; The input tests read this file expecting it to be named
+;;; "test.scm", so you'll have to run it from the ice-9 source
+;;; directory, or copy this file elsewhere
+;;; Files `tmp1', `tmp2' and `tmp3' will be created in the course of running
+;;; these tests. You may need to delete them in order to run
+;;; "test.scm" more than once.
+
+;;; There are three optional tests:
+;;; (TEST-CONT) tests multiple returns from call-with-current-continuation
+;;;
+;;; (TEST-SC4) tests procedures required by R4RS but not by IEEE
+;;;
+;;; (TEST-DELAY) tests DELAY and FORCE, which are not required by
+;;; either standard.
+
+;;; If you are testing a R3RS version which does not have `list?' do:
+;;; (define list? #f)
+
+;;; send corrections or additions to jaffer@ai.mit.edu or
+;;; Aubrey Jaffer, 84 Pleasant St., Wakefield MA 01880, USA
+
+(define cur-section '())(define errs '())
+(define SECTION (lambda args
+ (display "SECTION") (write args) (newline)
+ (set! cur-section args) #t))
+(define record-error (lambda (e) (set! errs (cons (list cur-section e) errs))))
+
+(define test
+ (lambda (expect fun . args)
+ (write (cons fun args))
+ (display " ==> ")
+ ((lambda (res)
+ (write res)
+ (newline)
+ (cond ((not (equal? expect res))
+ (record-error (list res expect (cons fun args)))
+ (display " BUT EXPECTED ")
+ (write expect)
+ (newline)
+ #f)
+ (else #t)))
+ (if (procedure? fun) (apply fun args) (car args)))))
+(define (report-errs)
+ (newline)
+ (if (null? errs) (display "Passed all tests")
+ (begin
+ (display "errors were:")
+ (newline)
+ (display "(SECTION (got expected (call)))")
+ (newline)
+ (for-each (lambda (l) (write l) (newline))
+ errs)))
+ (newline))
+
+(SECTION 2 1);; test that all symbol characters are supported.
+;'(+ - ... !.. $.+ %.- &.! *.: /:. :+. <-. =. >. ?. ~. _. ^.)
+
+(SECTION 3 4)
+(define disjoint-type-functions
+ (list boolean? char? null? number? pair? procedure? string? symbol? vector?))
+(define type-examples
+ (list
+ #t #f #\a '() 9739 '(test) record-error "test" "" 'test '#() '#(a b c) ))
+(define i 1)
+(for-each (lambda (x) (display (make-string i #\space))
+ (set! i (+ 3 i))
+ (write x)
+ (newline))
+ disjoint-type-functions)
+(define type-matrix
+ (map (lambda (x)
+ (let ((t (map (lambda (f) (f x)) disjoint-type-functions)))
+ (write t)
+ (write x)
+ (newline)
+ t))
+ type-examples))
+(SECTION 4 1 2)
+(test '(quote a) 'quote (quote 'a))
+(test '(quote a) 'quote ''a)
+(SECTION 4 1 3)
+(test 12 (if #f + *) 3 4)
+(SECTION 4 1 4)
+(test 8 (lambda (x) (+ x x)) 4)
+(define reverse-subtract
+ (lambda (x y) (- y x)))
+(test 3 reverse-subtract 7 10)
+(define add4
+ (let ((x 4))
+ (lambda (y) (+ x y))))
+(test 10 add4 6)
+(test '(3 4 5 6) (lambda x x) 3 4 5 6)
+(test '(5 6) (lambda (x y . z) z) 3 4 5 6)
+(SECTION 4 1 5)
+(test 'yes 'if (if (> 3 2) 'yes 'no))
+(test 'no 'if (if (> 2 3) 'yes 'no))
+(test '1 'if (if (> 3 2) (- 3 2) (+ 3 2)))
+(SECTION 4 1 6)
+(define x 2)
+(test 3 'define (+ x 1))
+(set! x 4)
+(test 5 'set! (+ x 1))
+(SECTION 4 2 1)
+(test 'greater 'cond (cond ((> 3 2) 'greater)
+ ((< 3 2) 'less)))
+(test 'equal 'cond (cond ((> 3 3) 'greater)
+ ((< 3 3) 'less)
+ (else 'equal)))
+(test 2 'cond (cond ((assv 'b '((a 1) (b 2))) => cadr)
+ (else #f)))
+(test 'composite 'case (case (* 2 3)
+ ((2 3 5 7) 'prime)
+ ((1 4 6 8 9) 'composite)))
+(test 'consonant 'case (case (car '(c d))
+ ((a e i o u) 'vowel)
+ ((w y) 'semivowel)
+ (else 'consonant)))
+(test #t 'and (and (= 2 2) (> 2 1)))
+(test #f 'and (and (= 2 2) (< 2 1)))
+(test '(f g) 'and (and 1 2 'c '(f g)))
+(test #t 'and (and))
+(test #t 'or (or (= 2 2) (> 2 1)))
+(test #t 'or (or (= 2 2) (< 2 1)))
+(test #f 'or (or #f #f #f))
+(test #f 'or (or))
+(test '(b c) 'or (or (memq 'b '(a b c)) (+ 3 0)))
+(SECTION 4 2 2)
+(test 6 'let (let ((x 2) (y 3)) (* x y)))
+(test 35 'let (let ((x 2) (y 3)) (let ((x 7) (z (+ x y))) (* z x))))
+(test 70 'let* (let ((x 2) (y 3)) (let* ((x 7) (z (+ x y))) (* z x))))
+(test #t 'letrec (letrec ((even?
+ (lambda (n) (if (zero? n) #t (odd? (- n 1)))))
+ (odd?
+ (lambda (n) (if (zero? n) #f (even? (- n 1))))))
+ (even? 88)))
+(define x 34)
+(test 5 'let (let ((x 3)) (define x 5) x))
+(test 34 'let x)
+(test 6 'let (let () (define x 6) x))
+(test 34 'let x)
+(test 7 'let* (let* ((x 3)) (define x 7) x))
+(test 34 'let* x)
+(test 8 'let* (let* () (define x 8) x))
+(test 34 'let* x)
+(test 9 'letrec (letrec () (define x 9) x))
+(test 34 'letrec x)
+(test 10 'letrec (letrec ((x 3)) (define x 10) x))
+(test 34 'letrec x)
+(SECTION 4 2 3)
+(define x 0)
+(test 6 'begin (begin (set! x 5) (+ x 1)))
+(SECTION 4 2 4)
+(test '#(0 1 2 3 4) 'do (do ((vec (make-vector 5))
+ (i 0 (+ i 1)))
+ ((= i 5) vec)
+ (vector-set! vec i i)))
+(test 25 'do (let ((x '(1 3 5 7 9)))
+ (do ((x x (cdr x))
+ (sum 0 (+ sum (car x))))
+ ((null? x) sum))))
+(test 1 'let (let foo () 1))
+(test '((6 1 3) (-5 -2)) 'let
+ (let loop ((numbers '(3 -2 1 6 -5))
+ (nonneg '())
+ (neg '()))
+ (cond ((null? numbers) (list nonneg neg))
+ ((negative? (car numbers))
+ (loop (cdr numbers)
+ nonneg
+ (cons (car numbers) neg)))
+ (else
+ (loop (cdr numbers)
+ (cons (car numbers) nonneg)
+ neg)))))
+(SECTION 4 2 6)
+(test '(list 3 4) 'quasiquote `(list ,(+ 1 2) 4))
+(test '(list a (quote a)) 'quasiquote (let ((name 'a)) `(list ,name ',name)))
+(test '(a 3 4 5 6 b) 'quasiquote `(a ,(+ 1 2) ,@(map abs '(4 -5 6)) b))
+(test '((foo 7) . cons)
+ 'quasiquote
+ `((foo ,(- 10 3)) ,@(cdr '(c)) . ,(car '(cons))))
+
+;;; sqt is defined here because not all implementations are required to
+;;; support it.
+(define (sqt x)
+ (do ((i 0 (+ i 1)))
+ ((> (* i i) x) (- i 1))))
+
+(test '#(10 5 2 4 3 8) 'quasiquote `#(10 5 ,(sqt 4) ,@(map sqt '(16 9)) 8))
+(test 5 'quasiquote `,(+ 2 3))
+(test '(a `(b ,(+ 1 2) ,(foo 4 d) e) f)
+ 'quasiquote `(a `(b ,(+ 1 2) ,(foo ,(+ 1 3) d) e) f))
+(test '(a `(b ,x ,'y d) e) 'quasiquote
+ (let ((name1 'x) (name2 'y)) `(a `(b ,,name1 ,',name2 d) e)))
+(test '(list 3 4) 'quasiquote (quasiquote (list (unquote (+ 1 2)) 4)))
+(test '`(list ,(+ 1 2) 4) 'quasiquote '(quasiquote (list (unquote (+ 1 2)) 4)))
+(SECTION 5 2 1)
+(define add3 (lambda (x) (+ x 3)))
+(test 6 'define (add3 3))
+(define first car)
+(test 1 'define (first '(1 2)))
+(SECTION 5 2 2)
+(test 45 'define
+ (let ((x 5))
+ (define foo (lambda (y) (bar x y)))
+ (define bar (lambda (a b) (+ (* a b) a)))
+ (foo (+ x 3))))
+(define x 34)
+(define (foo) (define x 5) x)
+(test 5 foo)
+(test 34 'define x)
+(define foo (lambda () (define x 5) x))
+(test 5 foo)
+(test 34 'define x)
+(define (foo x) ((lambda () (define x 5) x)) x)
+(test 88 foo 88)
+(test 4 foo 4)
+(test 34 'define x)
+(SECTION 6 1)
+(test #f not #t)
+(test #f not 3)
+(test #f not (list 3))
+(test #t not #f)
+(test #f not '())
+(test #f not (list))
+(test #f not 'nil)
+
+(test #t boolean? #f)
+(test #f boolean? 0)
+(test #f boolean? '())
+(SECTION 6 2)
+(test #t eqv? 'a 'a)
+(test #f eqv? 'a 'b)
+(test #t eqv? 2 2)
+(test #t eqv? '() '())
+(test #t eqv? '10000 '10000)
+(test #f eqv? (cons 1 2)(cons 1 2))
+(test #f eqv? (lambda () 1) (lambda () 2))
+(test #f eqv? #f 'nil)
+(let ((p (lambda (x) x)))
+ (test #t eqv? p p))
+(define gen-counter
+ (lambda ()
+ (let ((n 0))
+ (lambda () (set! n (+ n 1)) n))))
+(let ((g (gen-counter))) (test #t eqv? g g))
+(test #f eqv? (gen-counter) (gen-counter))
+(letrec ((f (lambda () (if (eqv? f g) 'f 'both)))
+ (g (lambda () (if (eqv? f g) 'g 'both))))
+ (test #f eqv? f g))
+
+(test #t eq? 'a 'a)
+(test #f eq? (list 'a) (list 'a))
+(test #t eq? '() '())
+(test #t eq? car car)
+(let ((x '(a))) (test #t eq? x x))
+(let ((x '#())) (test #t eq? x x))
+(let ((x (lambda (x) x))) (test #t eq? x x))
+
+(test #t equal? 'a 'a)
+(test #t equal? '(a) '(a))
+(test #t equal? '(a (b) c) '(a (b) c))
+(test #t equal? "abc" "abc")
+(test #t equal? 2 2)
+(test #t equal? (make-vector 5 'a) (make-vector 5 'a))
+(SECTION 6 3)
+(test '(a b c d e) 'dot '(a . (b . (c . (d . (e . ()))))))
+(define x (list 'a 'b 'c))
+(define y x)
+(and list? (test #t list? y))
+(set-cdr! x 4)
+(test '(a . 4) 'set-cdr! x)
+(test #t eqv? x y)
+(test '(a b c . d) 'dot '(a . (b . (c . d))))
+(and list? (test #f list? y))
+(and list? (let ((x (list 'a))) (set-cdr! x x) (test #f 'list? (list? x))))
+
+(test #t pair? '(a . b))
+(test #t pair? '(a . 1))
+(test #t pair? '(a b c))
+(test #f pair? '())
+(test #f pair? '#(a b))
+
+(test '(a) cons 'a '())
+(test '((a) b c d) cons '(a) '(b c d))
+(test '("a" b c) cons "a" '(b c))
+(test '(a . 3) cons 'a 3)
+(test '((a b) . c) cons '(a b) 'c)
+
+(test 'a car '(a b c))
+(test '(a) car '((a) b c d))
+(test 1 car '(1 . 2))
+
+(test '(b c d) cdr '((a) b c d))
+(test 2 cdr '(1 . 2))
+
+(test '(a 7 c) list 'a (+ 3 4) 'c)
+(test '() list)
+
+(test 3 length '(a b c))
+(test 3 length '(a (b) (c d e)))
+(test 0 length '())
+
+(test '(x y) append '(x) '(y))
+(test '(a b c d) append '(a) '(b c d))
+(test '(a (b) (c)) append '(a (b)) '((c)))
+(test '() append)
+(test '(a b c . d) append '(a b) '(c . d))
+(test 'a append '() 'a)
+
+(test '(c b a) reverse '(a b c))
+(test '((e (f)) d (b c) a) reverse '(a (b c) d (e (f))))
+
+(test 'c list-ref '(a b c d) 2)
+
+(test '(a b c) memq 'a '(a b c))
+(test '(b c) memq 'b '(a b c))
+(test '#f memq 'a '(b c d))
+(test '#f memq (list 'a) '(b (a) c))
+(test '((a) c) member (list 'a) '(b (a) c))
+(test '(101 102) memv 101 '(100 101 102))
+
+(define e '((a 1) (b 2) (c 3)))
+(test '(a 1) assq 'a e)
+(test '(b 2) assq 'b e)
+(test #f assq 'd e)
+(test #f assq (list 'a) '(((a)) ((b)) ((c))))
+(test '((a)) assoc (list 'a) '(((a)) ((b)) ((c))))
+(test '(5 7) assv 5 '((2 3) (5 7) (11 13)))
+(SECTION 6 4)
+(test #t symbol? 'foo)
+(test #t symbol? (car '(a b)))
+(test #f symbol? "bar")
+(test #t symbol? 'nil)
+(test #f symbol? '())
+(test #f symbol? #f)
+;;; But first, what case are symbols in? Determine the standard case:
+(define char-standard-case char-upcase)
+(if (string=? (symbol->string 'A) "a")
+ (set! char-standard-case char-downcase))
+;;; Not for Guile
+;(test #t 'standard-case
+; (string=? (symbol->string 'a) (symbol->string 'A)))
+;(test #t 'standard-case
+; (or (string=? (symbol->string 'a) "A")
+; (string=? (symbol->string 'A) "a")))
+(define (str-copy s)
+ (let ((v (make-string (string-length s))))
+ (do ((i (- (string-length v) 1) (- i 1)))
+ ((< i 0) v)
+ (string-set! v i (string-ref s i)))))
+(define (string-standard-case s)
+ (set! s (str-copy s))
+ (do ((i 0 (+ 1 i))
+ (sl (string-length s)))
+ ((>= i sl) s)
+ (string-set! s i (char-standard-case (string-ref s i)))))
+;;; Not for Guile
+;(test (string-standard-case "flying-fish") symbol->string 'flying-fish)
+;(test (string-standard-case "martin") symbol->string 'Martin)
+(test "Malvina" symbol->string (string->symbol "Malvina"))
+;;; Not for Guile
+;(test #t 'standard-case (eq? 'a 'A))
+
+(define x (string #\a #\b))
+(define y (string->symbol x))
+(string-set! x 0 #\c)
+(test "cb" 'string-set! x)
+(test "ab" symbol->string y)
+(test y string->symbol "ab")
+
+;;; Not for Guile
+;(test #t eq? 'mISSISSIppi 'mississippi)
+;(test #f 'string->symbol (eq? 'bitBlt (string->symbol "bitBlt")))
+(test 'JollyWog string->symbol (symbol->string 'JollyWog))
+
+(SECTION 6 5 5)
+(test #t number? 3)
+(test #t complex? 3)
+(test #t real? 3)
+(test #t rational? 3)
+(test #t integer? 3)
+
+(test #t exact? 3)
+(test #f inexact? 3)
+
+(test #t = 22 22 22)
+(test #t = 22 22)
+(test #f = 34 34 35)
+(test #f = 34 35)
+(test #t > 3 -6246)
+(test #f > 9 9 -2424)
+(test #t >= 3 -4 -6246)
+(test #t >= 9 9)
+(test #f >= 8 9)
+(test #t < -1 2 3 4 5 6 7 8)
+(test #f < -1 2 3 4 4 5 6 7)
+(test #t <= -1 2 3 4 5 6 7 8)
+(test #t <= -1 2 3 4 4 5 6 7)
+(test #f < 1 3 2)
+(test #f >= 1 3 2)
+
+(test #t zero? 0)
+(test #f zero? 1)
+(test #f zero? -1)
+(test #f zero? -100)
+(test #t positive? 4)
+(test #f positive? -4)
+(test #f positive? 0)
+(test #f negative? 4)
+(test #t negative? -4)
+(test #f negative? 0)
+(test #t odd? 3)
+(test #f odd? 2)
+(test #f odd? -4)
+(test #t odd? -1)
+(test #f even? 3)
+(test #t even? 2)
+(test #t even? -4)
+(test #f even? -1)
+
+(test 38 max 34 5 7 38 6)
+(test -24 min 3 5 5 330 4 -24)
+
+(test 7 + 3 4)
+(test '3 + 3)
+(test 0 +)
+(test 4 * 4)
+(test 1 *)
+
+(test -1 - 3 4)
+(test -3 - 3)
+(test 7 abs -7)
+(test 7 abs 7)
+(test 0 abs 0)
+
+(test 5 quotient 35 7)
+(test -5 quotient -35 7)
+(test -5 quotient 35 -7)
+(test 5 quotient -35 -7)
+(test 1 modulo 13 4)
+(test 1 remainder 13 4)
+(test 3 modulo -13 4)
+(test -1 remainder -13 4)
+(test -3 modulo 13 -4)
+(test 1 remainder 13 -4)
+(test -1 modulo -13 -4)
+(test -1 remainder -13 -4)
+(define (divtest n1 n2)
+ (= n1 (+ (* n2 (quotient n1 n2))
+ (remainder n1 n2))))
+(test #t divtest 238 9)
+(test #t divtest -238 9)
+(test #t divtest 238 -9)
+(test #t divtest -238 -9)
+
+(test 4 gcd 0 4)
+(test 4 gcd -4 0)
+(test 4 gcd 32 -36)
+(test 0 gcd)
+(test 288 lcm 32 -36)
+(test 1 lcm)
+
+;;;;From: fred@sce.carleton.ca (Fred J Kaudel)
+;;; Modified by jaffer.
+(define (test-inexact)
+ (define f3.9 (string->number "3.9"))
+ (define f4.0 (string->number "4.0"))
+ (define f-3.25 (string->number "-3.25"))
+ (define f.25 (string->number ".25"))
+ (define f4.5 (string->number "4.5"))
+ (define f3.5 (string->number "3.5"))
+ (define f0.0 (string->number "0.0"))
+ (define f0.8 (string->number "0.8"))
+ (define f1.0 (string->number "1.0"))
+ (define wto write-test-obj)
+ (define dto display-test-obj)
+ (define lto load-test-obj)
+ (newline)
+ (display ";testing inexact numbers; ")
+ (newline)
+ (SECTION 6 5 5)
+ (test #t inexact? f3.9)
+ (test #t 'inexact? (inexact? (max f3.9 4)))
+ (test f4.0 'max (max f3.9 4))
+ (test f4.0 'exact->inexact (exact->inexact 4))
+ (test (- f4.0) round (- f4.5))
+ (test (- f4.0) round (- f3.5))
+ (test (- f4.0) round (- f3.9))
+ (test f0.0 round f0.0)
+ (test f0.0 round f.25)
+ (test f1.0 round f0.8)
+ (test f4.0 round f3.5)
+ (test f4.0 round f4.5)
+ (set! write-test-obj (list f.25 f-3.25));.25 inexact errors less likely.
+ (set! display-test-obj (list f.25 f-3.25));.3 often has such errors (~10^-13)
+ (set! load-test-obj (list 'define 'foo (list 'quote write-test-obj)))
+ (test #t call-with-output-file
+ "tmp3"
+ (lambda (test-file)
+ (write-char #\; test-file)
+ (display write-test-obj test-file)
+ (newline test-file)
+ (write load-test-obj test-file)
+ (output-port? test-file)))
+ (check-test-file "tmp3")
+ (set! write-test-obj wto)
+ (set! display-test-obj dto)
+ (set! load-test-obj lto)
+ (let ((x (string->number "4195835.0"))
+ (y (string->number "3145727.0")))
+ (test #t 'pentium-fdiv-bug (> f1.0 (- x (* (/ x y) y)))))
+ (report-errs))
+
+(define (test-bignum)
+ (define tb
+ (lambda (n1 n2)
+ (= n1 (+ (* n2 (quotient n1 n2))
+ (remainder n1 n2)))))
+ (newline)
+ (display ";testing bignums; ")
+ (newline)
+ (SECTION 6 5 5)
+ (test 0 modulo -2177452800 86400)
+ (test 0 modulo 2177452800 -86400)
+ (test 0 modulo 2177452800 86400)
+ (test 0 modulo -2177452800 -86400)
+ (test #t 'remainder (tb 281474976710655 65535))
+ (test #t 'remainder (tb 281474976710654 65535))
+ (SECTION 6 5 6)
+ (test 281474976710655 string->number "281474976710655")
+ (test "281474976710655" number->string 281474976710655)
+ (report-errs))
+
+(SECTION 6 5 6)
+(test "0" number->string 0)
+(test "100" number->string 100)
+(test "100" number->string 256 16)
+(test 100 string->number "100")
+(test 256 string->number "100" 16)
+(test #f string->number "")
+(test #f string->number ".")
+(test #f string->number "d")
+(test #f string->number "D")
+(test #f string->number "i")
+(test #f string->number "I")
+(test #f string->number "3i")
+(test #f string->number "3I")
+(test #f string->number "33i")
+(test #f string->number "33I")
+(test #f string->number "3.3i")
+(test #f string->number "3.3I")
+(test #f string->number "-")
+(test #f string->number "+")
+
+(SECTION 6 6)
+(test #t eqv? '#\space #\Space)
+(test #t eqv? #\space '#\Space)
+(test #t char? #\a)
+(test #t char? #\()
+(test #t char? #\space)
+(test #t char? '#\newline)
+
+(test #f char=? #\A #\B)
+(test #f char=? #\a #\b)
+(test #f char=? #\9 #\0)
+(test #t char=? #\A #\A)
+
+(test #t char<? #\A #\B)
+(test #t char<? #\a #\b)
+(test #f char<? #\9 #\0)
+(test #f char<? #\A #\A)
+
+(test #f char>? #\A #\B)
+(test #f char>? #\a #\b)
+(test #t char>? #\9 #\0)
+(test #f char>? #\A #\A)
+
+(test #t char<=? #\A #\B)
+(test #t char<=? #\a #\b)
+(test #f char<=? #\9 #\0)
+(test #t char<=? #\A #\A)
+
+(test #f char>=? #\A #\B)
+(test #f char>=? #\a #\b)
+(test #t char>=? #\9 #\0)
+(test #t char>=? #\A #\A)
+
+(test #f char-ci=? #\A #\B)
+(test #f char-ci=? #\a #\B)
+(test #f char-ci=? #\A #\b)
+(test #f char-ci=? #\a #\b)
+(test #f char-ci=? #\9 #\0)
+(test #t char-ci=? #\A #\A)
+(test #t char-ci=? #\A #\a)
+
+(test #t char-ci<? #\A #\B)
+(test #t char-ci<? #\a #\B)
+(test #t char-ci<? #\A #\b)
+(test #t char-ci<? #\a #\b)
+(test #f char-ci<? #\9 #\0)
+(test #f char-ci<? #\A #\A)
+(test #f char-ci<? #\A #\a)
+
+(test #f char-ci>? #\A #\B)
+(test #f char-ci>? #\a #\B)
+(test #f char-ci>? #\A #\b)
+(test #f char-ci>? #\a #\b)
+(test #t char-ci>? #\9 #\0)
+(test #f char-ci>? #\A #\A)
+(test #f char-ci>? #\A #\a)
+
+(test #t char-ci<=? #\A #\B)
+(test #t char-ci<=? #\a #\B)
+(test #t char-ci<=? #\A #\b)
+(test #t char-ci<=? #\a #\b)
+(test #f char-ci<=? #\9 #\0)
+(test #t char-ci<=? #\A #\A)
+(test #t char-ci<=? #\A #\a)
+
+(test #f char-ci>=? #\A #\B)
+(test #f char-ci>=? #\a #\B)
+(test #f char-ci>=? #\A #\b)
+(test #f char-ci>=? #\a #\b)
+(test #t char-ci>=? #\9 #\0)
+(test #t char-ci>=? #\A #\A)
+(test #t char-ci>=? #\A #\a)
+
+(test #t char-alphabetic? #\a)
+(test #t char-alphabetic? #\A)
+(test #t char-alphabetic? #\z)
+(test #t char-alphabetic? #\Z)
+(test #f char-alphabetic? #\0)
+(test #f char-alphabetic? #\9)
+(test #f char-alphabetic? #\space)
+(test #f char-alphabetic? #\;)
+
+(test #f char-numeric? #\a)
+(test #f char-numeric? #\A)
+(test #f char-numeric? #\z)
+(test #f char-numeric? #\Z)
+(test #t char-numeric? #\0)
+(test #t char-numeric? #\9)
+(test #f char-numeric? #\space)
+(test #f char-numeric? #\;)
+
+(test #f char-whitespace? #\a)
+(test #f char-whitespace? #\A)
+(test #f char-whitespace? #\z)
+(test #f char-whitespace? #\Z)
+(test #f char-whitespace? #\0)
+(test #f char-whitespace? #\9)
+(test #t char-whitespace? #\space)
+(test #f char-whitespace? #\;)
+
+(test #f char-upper-case? #\0)
+(test #f char-upper-case? #\9)
+(test #f char-upper-case? #\space)
+(test #f char-upper-case? #\;)
+
+(test #f char-lower-case? #\0)
+(test #f char-lower-case? #\9)
+(test #f char-lower-case? #\space)
+(test #f char-lower-case? #\;)
+
+(test #\. integer->char (char->integer #\.))
+(test #\A integer->char (char->integer #\A))
+(test #\a integer->char (char->integer #\a))
+(test #\A char-upcase #\A)
+(test #\A char-upcase #\a)
+(test #\a char-downcase #\A)
+(test #\a char-downcase #\a)
+(SECTION 6 7)
+(test #t string? "The word \"recursion\\\" has many meanings.")
+(test #t string? "")
+(define f (make-string 3 #\*))
+(test "?**" 'string-set! (begin (string-set! f 0 #\?) f))
+(test "abc" string #\a #\b #\c)
+(test "" string)
+(test 3 string-length "abc")
+(test #\a string-ref "abc" 0)
+(test #\c string-ref "abc" 2)
+(test 0 string-length "")
+(test "" substring "ab" 0 0)
+(test "" substring "ab" 1 1)
+(test "" substring "ab" 2 2)
+(test "a" substring "ab" 0 1)
+(test "b" substring "ab" 1 2)
+(test "ab" substring "ab" 0 2)
+(test "foobar" string-append "foo" "bar")
+(test "foo" string-append "foo")
+(test "foo" string-append "foo" "")
+(test "foo" string-append "" "foo")
+(test "" string-append)
+(test "" make-string 0)
+(test #t string=? "" "")
+(test #f string<? "" "")
+(test #f string>? "" "")
+(test #t string<=? "" "")
+(test #t string>=? "" "")
+(test #t string-ci=? "" "")
+(test #f string-ci<? "" "")
+(test #f string-ci>? "" "")
+(test #t string-ci<=? "" "")
+(test #t string-ci>=? "" "")
+
+(test #f string=? "A" "B")
+(test #f string=? "a" "b")
+(test #f string=? "9" "0")
+(test #t string=? "A" "A")
+
+(test #t string<? "A" "B")
+(test #t string<? "a" "b")
+(test #f string<? "9" "0")
+(test #f string<? "A" "A")
+
+(test #f string>? "A" "B")
+(test #f string>? "a" "b")
+(test #t string>? "9" "0")
+(test #f string>? "A" "A")
+
+(test #t string<=? "A" "B")
+(test #t string<=? "a" "b")
+(test #f string<=? "9" "0")
+(test #t string<=? "A" "A")
+
+(test #f string>=? "A" "B")
+(test #f string>=? "a" "b")
+(test #t string>=? "9" "0")
+(test #t string>=? "A" "A")
+
+(test #f string-ci=? "A" "B")
+(test #f string-ci=? "a" "B")
+(test #f string-ci=? "A" "b")
+(test #f string-ci=? "a" "b")
+(test #f string-ci=? "9" "0")
+(test #t string-ci=? "A" "A")
+(test #t string-ci=? "A" "a")
+
+(test #t string-ci<? "A" "B")
+(test #t string-ci<? "a" "B")
+(test #t string-ci<? "A" "b")
+(test #t string-ci<? "a" "b")
+(test #f string-ci<? "9" "0")
+(test #f string-ci<? "A" "A")
+(test #f string-ci<? "A" "a")
+
+(test #f string-ci>? "A" "B")
+(test #f string-ci>? "a" "B")
+(test #f string-ci>? "A" "b")
+(test #f string-ci>? "a" "b")
+(test #t string-ci>? "9" "0")
+(test #f string-ci>? "A" "A")
+(test #f string-ci>? "A" "a")
+
+(test #t string-ci<=? "A" "B")
+(test #t string-ci<=? "a" "B")
+(test #t string-ci<=? "A" "b")
+(test #t string-ci<=? "a" "b")
+(test #f string-ci<=? "9" "0")
+(test #t string-ci<=? "A" "A")
+(test #t string-ci<=? "A" "a")
+
+(test #f string-ci>=? "A" "B")
+(test #f string-ci>=? "a" "B")
+(test #f string-ci>=? "A" "b")
+(test #f string-ci>=? "a" "b")
+(test #t string-ci>=? "9" "0")
+(test #t string-ci>=? "A" "A")
+(test #t string-ci>=? "A" "a")
+(SECTION 6 8)
+(test #t vector? '#(0 (2 2 2 2) "Anna"))
+(test #t vector? '#())
+(test '#(a b c) vector 'a 'b 'c)
+(test '#() vector)
+(test 3 vector-length '#(0 (2 2 2 2) "Anna"))
+(test 0 vector-length '#())
+(test 8 vector-ref '#(1 1 2 3 5 8 13 21) 5)
+(test '#(0 ("Sue" "Sue") "Anna") 'vector-set
+ (let ((vec (vector 0 '(2 2 2 2) "Anna")))
+ (vector-set! vec 1 '("Sue" "Sue"))
+ vec))
+(test '#(hi hi) make-vector 2 'hi)
+(test '#() make-vector 0)
+(test '#() make-vector 0 'a)
+(SECTION 6 9)
+(test #t procedure? car)
+(test #f procedure? 'car)
+(test #t procedure? (lambda (x) (* x x)))
+(test #f procedure? '(lambda (x) (* x x)))
+(test #t call-with-current-continuation procedure?)
+(test 7 apply + (list 3 4))
+(test 7 apply (lambda (a b) (+ a b)) (list 3 4))
+(test 17 apply + 10 (list 3 4))
+(test '() apply list '())
+(define compose (lambda (f g) (lambda args (f (apply g args)))))
+(test 30 (compose sqt *) 12 75)
+
+(test '(b e h) map cadr '((a b) (d e) (g h)))
+(test '(5 7 9) map + '(1 2 3) '(4 5 6))
+(test '#(0 1 4 9 16) 'for-each
+ (let ((v (make-vector 5)))
+ (for-each (lambda (i) (vector-set! v i (* i i)))
+ '(0 1 2 3 4))
+ v))
+(test -3 call-with-current-continuation
+ (lambda (exit)
+ (for-each (lambda (x) (if (negative? x) (exit x)))
+ '(54 0 37 -3 245 19))
+ #t))
+(define list-length
+ (lambda (obj)
+ (call-with-current-continuation
+ (lambda (return)
+ (letrec ((r (lambda (obj) (cond ((null? obj) 0)
+ ((pair? obj) (+ (r (cdr obj)) 1))
+ (else (return #f))))))
+ (r obj))))))
+(test 4 list-length '(1 2 3 4))
+(test #f list-length '(a b . c))
+(test '() map cadr '())
+
+;;; This tests full conformance of call-with-current-continuation. It
+;;; is a separate test because some schemes do not support call/cc
+;;; other than escape procedures. I am indebted to
+;;; raja@copper.ucs.indiana.edu (Raja Sooriamurthi) for fixing this
+;;; code. The function leaf-eq? compares the leaves of 2 arbitrary
+;;; trees constructed of conses.
+(define (next-leaf-generator obj eot)
+ (letrec ((return #f)
+ (cont (lambda (x)
+ (recur obj)
+ (set! cont (lambda (x) (return eot)))
+ (cont #f)))
+ (recur (lambda (obj)
+ (if (pair? obj)
+ (for-each recur obj)
+ (call-with-current-continuation
+ (lambda (c)
+ (set! cont c)
+ (return obj)))))))
+ (lambda () (call-with-current-continuation
+ (lambda (ret) (set! return ret) (cont #f))))))
+(define (leaf-eq? x y)
+ (let* ((eot (list 'eot))
+ (xf (next-leaf-generator x eot))
+ (yf (next-leaf-generator y eot)))
+ (letrec ((loop (lambda (x y)
+ (cond ((not (eq? x y)) #f)
+ ((eq? eot x) #t)
+ (else (loop (xf) (yf)))))))
+ (loop (xf) (yf)))))
+(define (test-cont)
+ (newline)
+ (display ";testing continuations; ")
+ (newline)
+ (SECTION 6 9)
+ (test #t leaf-eq? '(a (b (c))) '((a) b c))
+ (test #f leaf-eq? '(a (b (c))) '((a) b c d))
+ (report-errs))
+
+;;; Test Optional R4RS DELAY syntax and FORCE procedure
+(define (test-delay)
+ (newline)
+ (display ";testing DELAY and FORCE; ")
+ (newline)
+ (SECTION 6 9)
+ (test 3 'delay (force (delay (+ 1 2))))
+ (test '(3 3) 'delay (let ((p (delay (+ 1 2))))
+ (list (force p) (force p))))
+ (test 2 'delay (letrec ((a-stream
+ (letrec ((next (lambda (n)
+ (cons n (delay (next (+ n 1)))))))
+ (next 0)))
+ (head car)
+ (tail (lambda (stream) (force (cdr stream)))))
+ (head (tail (tail a-stream)))))
+ (letrec ((count 0)
+ (p (delay (begin (set! count (+ count 1))
+ (if (> count x)
+ count
+ (force p)))))
+ (x 5))
+ (test 6 force p)
+ (set! x 10)
+ (test 6 force p))
+ (test 3 'force
+ (letrec ((p (delay (if c 3 (begin (set! c #t) (+ (force p) 1)))))
+ (c #f))
+ (force p)))
+ (report-errs))
+
+(SECTION 6 10 1)
+(test #t input-port? (current-input-port))
+(test #t output-port? (current-output-port))
+(test #t call-with-input-file "test.scm" input-port?)
+(define this-file (open-input-file "test.scm"))
+(test #t input-port? this-file)
+(SECTION 6 10 2)
+(test #\; peek-char this-file)
+(test #\; read-char this-file)
+(test '(define cur-section '()) read this-file)
+(test #\( peek-char this-file)
+(test '(define errs '()) read this-file)
+(close-input-port this-file)
+(close-input-port this-file)
+(define (check-test-file name)
+ (define test-file (open-input-file name))
+ (test #t 'input-port?
+ (call-with-input-file
+ name
+ (lambda (test-file)
+ (test load-test-obj read test-file)
+ (test #t eof-object? (peek-char test-file))
+ (test #t eof-object? (read-char test-file))
+ (input-port? test-file))))
+ (test #\; read-char test-file)
+ (test display-test-obj read test-file)
+ (test load-test-obj read test-file)
+ (close-input-port test-file))
+(SECTION 6 10 3)
+(define write-test-obj
+ '(#t #f #\a () 9739 -3 . #((test) "te \" \" st" "" test #() b c)))
+(define display-test-obj
+ '(#t #f a () 9739 -3 . #((test) te " " st test #() b c)))
+(define load-test-obj
+ (list 'define 'foo (list 'quote write-test-obj)))
+(test #t call-with-output-file
+ "tmp1"
+ (lambda (test-file)
+ (write-char #\; test-file)
+ (display write-test-obj test-file)
+ (newline test-file)
+ (write load-test-obj test-file)
+ (output-port? test-file)))
+(check-test-file "tmp1")
+
+(define test-file (open-output-file "tmp2"))
+(write-char #\; test-file)
+(display write-test-obj test-file)
+(newline test-file)
+(write load-test-obj test-file)
+(test #t output-port? test-file)
+(close-output-port test-file)
+(check-test-file "tmp2")
+(define (test-sc4)
+ (newline)
+ (display ";testing scheme 4 functions; ")
+ (newline)
+ (SECTION 6 7)
+ (test '(#\P #\space #\l) string->list "P l")
+ (test '() string->list "")
+ (test "1\\\"" list->string '(#\1 #\\ #\"))
+ (test "" list->string '())
+ (SECTION 6 8)
+ (test '(dah dah didah) vector->list '#(dah dah didah))
+ (test '() vector->list '#())
+ (test '#(dididit dah) list->vector '(dididit dah))
+ (test '#() list->vector '())
+ (SECTION 6 10 4)
+ (load "tmp1")
+ (test write-test-obj 'load foo)
+ (report-errs))
+
+(report-errs)
+(if (and (string->number "0.0") (inexact? (string->number "0.0")))
+ (test-inexact))
+
+(let ((n (string->number "281474976710655")))
+ (if (and n (exact? n))
+ (test-bignum)))
+(newline)
+(test-cont)
+(newline)
+(test-sc4)
+(newline)
+(test-delay)
+(newline)
+"last item in file"
+;;;; Copyright (C) 1996, 1998, 2001, 2002, 2003, 2006, 2010, 2011,
+;;;; 2012 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+;;;; ----------------------------------------------------------------
+;;;; threads.scm -- User-level interface to Guile's thread system
+;;;; 4 March 1996, Anthony Green <green@cygnus.com>
+;;;; Modified 5 October 1996, MDJ <djurfeldt@nada.kth.se>
+;;;; Modified 6 April 2001, ttn
+;;;; ----------------------------------------------------------------
+;;;;
+
+;;; Commentary:
+
+;; This module is documented in the Guile Reference Manual.
+;; Briefly, one procedure is exported: `%thread-handler';
+;; as well as four macros: `make-thread', `begin-thread',
+;; `with-mutex' and `monitor'.
+
+;;; Code:
+
+(define-module (ice-9 threads)
+ #\use-module (ice-9 futures)
+ #\use-module (ice-9 match)
+ #\export (begin-thread
+ parallel
+ letpar
+ make-thread
+ with-mutex
+ monitor
+
+ par-map
+ par-for-each
+ n-par-map
+ n-par-for-each
+ n-for-each-par-map
+ %thread-handler))
+
+
+
+;;; Macros first, so that the procedures expand correctly.
+
+(define-syntax-rule (begin-thread e0 e1 ...)
+ (call-with-new-thread
+ (lambda () e0 e1 ...)
+ %thread-handler))
+
+(define-syntax parallel
+ (lambda (x)
+ (syntax-case x ()
+ ((_ e0 ...)
+ (with-syntax (((tmp0 ...) (generate-temporaries (syntax (e0 ...)))))
+ #'(let ((tmp0 (future e0))
+ ...)
+ (values (touch tmp0) ...)))))))
+
+(define-syntax-rule (letpar ((v e) ...) b0 b1 ...)
+ (call-with-values
+ (lambda () (parallel e ...))
+ (lambda (v ...)
+ b0 b1 ...)))
+
+(define-syntax-rule (make-thread proc arg ...)
+ (call-with-new-thread
+ (lambda () (proc arg ...))
+ %thread-handler))
+
+(define-syntax-rule (with-mutex m e0 e1 ...)
+ (let ((x m))
+ (dynamic-wind
+ (lambda () (lock-mutex x))
+ (lambda () (begin e0 e1 ...))
+ (lambda () (unlock-mutex x)))))
+
+(define-syntax-rule (monitor first rest ...)
+ (with-mutex (make-mutex)
+ first rest ...))
+
+(define (par-mapper mapper cons)
+ (lambda (proc . lists)
+ (let loop ((lists lists))
+ (match lists
+ (((heads tails ...) ...)
+ (let ((tail (future (loop tails)))
+ (head (apply proc heads)))
+ (cons head (touch tail))))
+ (_
+ '())))))
+
+(define par-map (par-mapper map cons))
+(define par-for-each (par-mapper for-each (const *unspecified*)))
+
+(define (n-par-map n proc . arglists)
+ (let* ((m (make-mutex))
+ (threads '())
+ (results (make-list (length (car arglists))))
+ (result results))
+ (do ((i 0 (+ 1 i)))
+ ((= i n)
+ (for-each join-thread threads)
+ results)
+ (set! threads
+ (cons (begin-thread
+ (let loop ()
+ (lock-mutex m)
+ (if (null? result)
+ (unlock-mutex m)
+ (let ((args (map car arglists))
+ (my-result result))
+ (set! arglists (map cdr arglists))
+ (set! result (cdr result))
+ (unlock-mutex m)
+ (set-car! my-result (apply proc args))
+ (loop)))))
+ threads)))))
+
+(define (n-par-for-each n proc . arglists)
+ (let ((m (make-mutex))
+ (threads '()))
+ (do ((i 0 (+ 1 i)))
+ ((= i n)
+ (for-each join-thread threads))
+ (set! threads
+ (cons (begin-thread
+ (let loop ()
+ (lock-mutex m)
+ (if (null? (car arglists))
+ (unlock-mutex m)
+ (let ((args (map car arglists)))
+ (set! arglists (map cdr arglists))
+ (unlock-mutex m)
+ (apply proc args)
+ (loop)))))
+ threads)))))
+
+;;; The following procedure is motivated by the common and important
+;;; case where a lot of work should be done, (not too much) in parallel,
+;;; but the results need to be handled serially (for example when
+;;; writing them to a file).
+;;;
+(define (n-for-each-par-map n s-proc p-proc . arglists)
+ "Using N parallel processes, apply S-PROC in serial order on the results
+of applying P-PROC on ARGLISTS."
+ (let* ((m (make-mutex))
+ (threads '())
+ (no-result '(no-value))
+ (results (make-list (length (car arglists)) no-result))
+ (result results))
+ (do ((i 0 (+ 1 i)))
+ ((= i n)
+ (for-each join-thread threads))
+ (set! threads
+ (cons (begin-thread
+ (let loop ()
+ (lock-mutex m)
+ (cond ((null? results)
+ (unlock-mutex m))
+ ((not (eq? (car results) no-result))
+ (let ((arg (car results)))
+ ;; stop others from choosing to process results
+ (set-car! results no-result)
+ (unlock-mutex m)
+ (s-proc arg)
+ (lock-mutex m)
+ (set! results (cdr results))
+ (unlock-mutex m)
+ (loop)))
+ ((null? result)
+ (unlock-mutex m))
+ (else
+ (let ((args (map car arglists))
+ (my-result result))
+ (set! arglists (map cdr arglists))
+ (set! result (cdr result))
+ (unlock-mutex m)
+ (set-car! my-result (apply p-proc args))
+ (loop))))))
+ threads)))))
+
+(define (thread-handler tag . args)
+ (let ((n (length args))
+ (p (current-error-port)))
+ (display "In thread:" p)
+ (newline p)
+ (if (>= n 3)
+ (display-error #f
+ p
+ (car args)
+ (cadr args)
+ (caddr args)
+ (if (= n 4)
+ (cadddr args)
+ '()))
+ (begin
+ (display "uncaught throw to " p)
+ (display tag p)
+ (display ": " p)
+ (display args p)
+ (newline p)))
+ #f))
+
+;;; Set system thread handler
+(define %thread-handler thread-handler)
+
+;;; threads.scm ends here
+;;;; Copyright (C) 2001, 2004, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+
+;; This module exports a single macro: `time'.
+;; Usage: (time exp)
+;;
+;; Example:
+;; guile> (time (sleep 3))
+;; clock utime stime cutime cstime gctime
+;; 3.01 0.00 0.00 0.00 0.00 0.00
+;; 0
+
+;;; Code:
+
+(define-module (ice-9 time)
+ \:use-module (ice-9 format)
+ \:export (time))
+
+(define (time-proc proc)
+ (let* ((gc-start (gc-run-time))
+ (tms-start (times))
+ (result (proc))
+ (tms-end (times))
+ (gc-end (gc-run-time)))
+ ;; FIXME: We would probably like format ~f to accept rationals, but
+ ;; currently it doesn't so we force to a flonum with exact->inexact.
+ (define (get proc start end)
+ (exact->inexact (/ (- (proc end) (proc start)) internal-time-units-per-second)))
+ (display "clock utime stime cutime cstime gctime\n")
+ (format #t "~5,2F ~5,2F ~5,2F ~6,2F ~6,2F ~6,2F\n"
+ (get tms:clock tms-start tms-end)
+ (get tms:utime tms-start tms-end)
+ (get tms:stime tms-start tms-end)
+ (get tms:cutime tms-start tms-end)
+ (get tms:cstime tms-start tms-end)
+ (get identity gc-start gc-end))
+ result))
+
+(define-macro (time exp)
+ `((@@ (ice-9 time) time-proc) (lambda () ,exp)))
+
+;;; time.scm ends here
+;;; -*- mode: scheme; coding: utf-8; -*-
+
+;;;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
+;;;; 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+(define-module (ice-9 top-repl)
+ #\use-module (ice-9 top-repl)
+ #\use-module ((system repl repl) #\select (start-repl))
+
+ ;; #\replace, as with deprecated code enabled these will be in the root env
+ #\replace (top-repl))
+
+(define call-with-sigint
+ (if (not (provided? 'posix))
+ (lambda (thunk) (thunk))
+ (lambda (thunk)
+ (let ((handler #f))
+ (dynamic-wind
+ (lambda ()
+ (set! handler
+ (sigaction SIGINT
+ (lambda (sig)
+ (scm-error 'signal #f "User interrupt" '()
+ (list sig))))))
+ thunk
+ (lambda ()
+ (if handler
+ ;; restore Scheme handler, SIG_IGN or SIG_DFL.
+ (sigaction SIGINT (car handler) (cdr handler))
+ ;; restore original C handler.
+ (sigaction SIGINT #f))))))))
+
+(define (top-repl)
+ (let ((guile-user-module (resolve-module '(guile-user))))
+
+ ;; Use some convenient modules (in reverse order)
+
+ (set-current-module guile-user-module)
+ (process-use-modules
+ (append
+ '(((ice-9 r5rs))
+ ((ice-9 session)))
+ (if (provided? 'regex)
+ '(((ice-9 regex)))
+ '())
+ (if (provided? 'threads)
+ '(((ice-9 threads)))
+ '())))
+
+ (call-with-sigint
+ (lambda ()
+ (and (defined? 'setlocale)
+ (catch 'system-error
+ (lambda ()
+ (setlocale LC_ALL ""))
+ (lambda (key subr fmt args errno)
+ (format (current-error-port)
+ "warning: failed to install locale: ~a~%"
+ (strerror (car errno))))))
+
+ (let ((status (start-repl (current-language))))
+ (run-hook exit-hook)
+ status)))))
+;; unicode
+
+;;;; Copyright (C) 2014 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library. If not, see
+;;;; <http://www.gnu.org/licenses/>.
+;;;;
+
+(define-module (ice-9 unicode)
+ #\export (formal-name->char
+ char->formal-name))
+
+(eval-when (expand load eval)
+ (load-extension (string-append "libguile-" (effective-version))
+ "scm_init_unicode"))
+;;; -*- mode: scheme; coding: utf-8; -*-
+;;;
+;;; Copyright (C) 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (ice-9 vlist)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-9 gnu)
+ #\use-module (srfi srfi-26)
+ #\use-module (ice-9 format)
+
+ #\export (vlist? vlist-cons vlist-head vlist-tail vlist-null?
+ vlist-null list->vlist vlist-ref vlist-drop vlist-take
+ vlist-length vlist-fold vlist-fold-right vlist-map
+ vlist-unfold vlist-unfold-right vlist-append
+ vlist-reverse vlist-filter vlist-delete vlist->list
+ vlist-for-each
+ block-growth-factor
+
+ vhash? vhash-cons vhash-consq vhash-consv
+ vhash-assoc vhash-assq vhash-assv
+ vhash-delete vhash-delq vhash-delv
+ vhash-fold vhash-fold-right
+ vhash-fold* vhash-foldq* vhash-foldv*
+ alist->vhash))
+
+;;; Author: Ludovic Courtès <ludo@gnu.org>
+;;;
+;;; Commentary:
+;;;
+;;; This module provides an implementations of vlists, a functional list-like
+;;; data structure described by Phil Bagwell in "Fast Functional Lists,
+;;; Hash-Lists, Dequeues and Variable-Length Arrays", EPFL Technical Report,
+;;; 2002.
+;;;
+;;; The idea is to store vlist elements in increasingly large contiguous blocks
+;;; (implemented as vectors here). These blocks are linked to one another using
+;;; a pointer to the next block (called `block-base' here) and an offset within
+;;; that block (`block-offset' here). The size of these blocks form a geometric
+;;; series with ratio `block-growth-factor'.
+;;;
+;;; In the best case (e.g., using a vlist returned by `list->vlist'),
+;;; elements from the first half of an N-element vlist are accessed in O(1)
+;;; (assuming `block-growth-factor' is 2), and `vlist-length' takes only
+;;; O(ln(N)). Furthermore, the data structure improves data locality since
+;;; vlist elements are adjacent, which plays well with caches.
+;;;
+;;; Code:
+
+
+;;;
+;;; VList Blocks and Block Descriptors.
+;;;
+
+(define block-growth-factor
+ (make-fluid 2))
+
+(define-inlinable (make-block base offset size hash-tab?)
+ ;; Return a block (and block descriptor) of SIZE elements pointing to
+ ;; BASE at OFFSET. If HASH-TAB? is true, we also reserve space for a
+ ;; "hash table". Note: We use `next-free' instead of `last-used' as
+ ;; suggested by Bagwell.
+ (if hash-tab?
+ (vector (make-vector (* size 3) #f)
+ base offset size 0)
+ (vector (make-vector size)
+ base offset size 0)))
+
+(define-syntax-rule (define-block-accessor name index)
+ (define-inlinable (name block)
+ (vector-ref block index)))
+
+(define-block-accessor block-content 0)
+(define-block-accessor block-base 1)
+(define-block-accessor block-offset 2)
+(define-block-accessor block-size 3)
+(define-block-accessor block-next-free 4)
+
+(define-inlinable (block-hash-table? block)
+ (< (block-size block) (vector-length (block-content block))))
+
+(define-inlinable (set-block-next-free! block next-free)
+ (vector-set! block 4 next-free))
+
+(define-inlinable (block-append! block value offset)
+ ;; This is not thread-safe. To fix it, see Section 2.8 of the paper.
+ (and (< offset (block-size block))
+ (= offset (block-next-free block))
+ (begin
+ (set-block-next-free! block (1+ offset))
+ (vector-set! (block-content block) offset value)
+ #t)))
+
+;; Return the item at slot OFFSET.
+(define-inlinable (block-ref content offset)
+ (vector-ref content offset))
+
+;; Return the offset of the next item in the hash bucket, after the one
+;; at OFFSET.
+(define-inlinable (block-hash-table-next-offset content size offset)
+ (vector-ref content (+ size size offset)))
+
+;; Save the offset of the next item in the hash bucket, after the one
+;; at OFFSET.
+(define-inlinable (block-hash-table-set-next-offset! content size offset
+ next-offset)
+ (vector-set! content (+ size size offset) next-offset))
+
+;; Returns the index of the last entry stored in CONTENT with
+;; SIZE-modulo hash value KHASH.
+(define-inlinable (block-hash-table-ref content size khash)
+ (vector-ref content (+ size khash)))
+
+(define-inlinable (block-hash-table-set! content size khash offset)
+ (vector-set! content (+ size khash) offset))
+
+;; Add hash table information for the item recently added at OFFSET,
+;; with SIZE-modulo hash KHASH.
+(define-inlinable (block-hash-table-add! content size khash offset)
+ (block-hash-table-set-next-offset! content size offset
+ (block-hash-table-ref content size khash))
+ (block-hash-table-set! content size khash offset))
+
+(define block-null
+ ;; The null block.
+ (make-block #f 0 0 #f))
+
+
+;;;
+;;; VLists.
+;;;
+
+(define-record-type <vlist>
+ ;; A vlist is just a base+offset pair pointing to a block.
+
+ ;; XXX: Allocating a <vlist> record in addition to the block at each
+ ;; `vlist-cons' call is inefficient. However, Bagwell's hack to avoid it
+ ;; (Section 2.2) would require GC_ALL_INTERIOR_POINTERS, which would be a
+ ;; performance hit for everyone.
+ (make-vlist base offset)
+ vlist?
+ (base vlist-base)
+ (offset vlist-offset))
+
+(set-record-type-printer! <vlist>
+ (lambda (vl port)
+ (cond ((vlist-null? vl)
+ (format port "#<vlist ()>"))
+ ((vhash? vl)
+ (format port "#<vhash ~x ~a pairs>"
+ (object-address vl)
+ (vlist-length vl)))
+ (else
+ (format port "#<vlist ~a>"
+ (vlist->list vl))))))
+
+
+(define vlist-null
+ ;; The empty vlist.
+ (make-vlist block-null 0))
+
+;; Asserting that something is a vlist is actually a win if your next
+;; step is to call record accessors, because that causes CSE to
+;; eliminate the type checks in those accessors.
+;;
+(define-inlinable (assert-vlist val)
+ (unless (vlist? val)
+ (throw 'wrong-type-arg
+ #f
+ "Not a vlist: ~S"
+ (list val)
+ (list val))))
+
+(define-inlinable (block-cons item vlist hash-tab?)
+ (let ((base (vlist-base vlist))
+ (offset (1+ (vlist-offset vlist))))
+ (cond
+ ((block-append! base item offset)
+ ;; Fast path: We added the item directly to the block.
+ (make-vlist base offset))
+ (else
+ ;; Slow path: Allocate a new block.
+ (let* ((size (block-size base))
+ (base (make-block
+ base
+ (1- offset)
+ (cond
+ ((zero? size) 1)
+ ((< offset size) 1) ;; new vlist head
+ (else (* (fluid-ref block-growth-factor) size)))
+ hash-tab?)))
+ (set-block-next-free! base 1)
+ (vector-set! (block-content base) 0 item)
+ (make-vlist base 0))))))
+
+(define (vlist-cons item vlist)
+ "Return a new vlist with ITEM as its head and VLIST as its
+tail."
+ ;; Note: Although the result of `vlist-cons' on a vhash is a valid
+ ;; vlist, it is not a valid vhash. The new item does not get a hash
+ ;; table entry. If we allocate a new block, the new block will not
+ ;; have a hash table. Perhaps we can do something more sensible here,
+ ;; but this is a hot function, so there are performance impacts.
+ (assert-vlist vlist)
+ (block-cons item vlist #f))
+
+(define (vlist-head vlist)
+ "Return the head of VLIST."
+ (assert-vlist vlist)
+ (let ((base (vlist-base vlist))
+ (offset (vlist-offset vlist)))
+ (block-ref (block-content base) offset)))
+
+(define (vlist-tail vlist)
+ "Return the tail of VLIST."
+ (assert-vlist vlist)
+ (let ((base (vlist-base vlist))
+ (offset (vlist-offset vlist)))
+ (if (> offset 0)
+ (make-vlist base (- offset 1))
+ (make-vlist (block-base base)
+ (block-offset base)))))
+
+(define (vlist-null? vlist)
+ "Return true if VLIST is empty."
+ (assert-vlist vlist)
+ (let ((base (vlist-base vlist)))
+ (and (not (block-base base))
+ (= 0 (block-size base)))))
+
+
+;;;
+;;; VList Utilities.
+;;;
+
+(define (list->vlist lst)
+ "Return a new vlist whose contents correspond to LST."
+ (vlist-reverse (fold vlist-cons vlist-null lst)))
+
+(define (vlist-fold proc init vlist)
+ "Fold over VLIST, calling PROC for each element."
+ ;; FIXME: Handle multiple lists.
+ (assert-vlist vlist)
+ (let loop ((base (vlist-base vlist))
+ (offset (vlist-offset vlist))
+ (result init))
+ (if (eq? base block-null)
+ result
+ (let* ((next (- offset 1))
+ (done? (< next 0)))
+ (loop (if done? (block-base base) base)
+ (if done? (block-offset base) next)
+ (proc (block-ref (block-content base) offset) result))))))
+
+(define (vlist-fold-right proc init vlist)
+ "Fold over VLIST, calling PROC for each element, starting from
+the last element."
+ (assert-vlist vlist)
+ (let loop ((index (1- (vlist-length vlist)))
+ (result init))
+ (if (< index 0)
+ result
+ (loop (1- index)
+ (proc (vlist-ref vlist index) result)))))
+
+(define (vlist-reverse vlist)
+ "Return a new VLIST whose content are those of VLIST in reverse
+order."
+ (vlist-fold vlist-cons vlist-null vlist))
+
+(define (vlist-map proc vlist)
+ "Map PROC over the elements of VLIST and return a new vlist."
+ (vlist-fold (lambda (item result)
+ (vlist-cons (proc item) result))
+ vlist-null
+ (vlist-reverse vlist)))
+
+(define (vlist->list vlist)
+ "Return a new list whose contents match those of VLIST."
+ (vlist-fold-right cons '() vlist))
+
+(define (vlist-ref vlist index)
+ "Return the element at index INDEX in VLIST."
+ (assert-vlist vlist)
+ (let loop ((index index)
+ (base (vlist-base vlist))
+ (offset (vlist-offset vlist)))
+ (if (<= index offset)
+ (block-ref (block-content base) (- offset index))
+ (loop (- index offset 1)
+ (block-base base)
+ (block-offset base)))))
+
+(define (vlist-drop vlist count)
+ "Return a new vlist that does not contain the COUNT first elements of
+VLIST."
+ (assert-vlist vlist)
+ (let loop ((count count)
+ (base (vlist-base vlist))
+ (offset (vlist-offset vlist)))
+ (if (<= count offset)
+ (make-vlist base (- offset count))
+ (loop (- count offset 1)
+ (block-base base)
+ (block-offset base)))))
+
+(define (vlist-take vlist count)
+ "Return a new vlist that contains only the COUNT first elements of
+VLIST."
+ (let loop ((count count)
+ (vlist vlist)
+ (result vlist-null))
+ (if (= 0 count)
+ (vlist-reverse result)
+ (loop (- count 1)
+ (vlist-tail vlist)
+ (vlist-cons (vlist-head vlist) result)))))
+
+(define (vlist-filter pred vlist)
+ "Return a new vlist containing all the elements from VLIST that
+satisfy PRED."
+ (vlist-fold-right (lambda (e v)
+ (if (pred e)
+ (vlist-cons e v)
+ v))
+ vlist-null
+ vlist))
+
+(define* (vlist-delete x vlist #\optional (equal? equal?))
+ "Return a new vlist corresponding to VLIST without the elements
+EQUAL? to X."
+ (vlist-filter (lambda (e)
+ (not (equal? e x)))
+ vlist))
+
+(define (vlist-length vlist)
+ "Return the length of VLIST."
+ (assert-vlist vlist)
+ (let loop ((base (vlist-base vlist))
+ (len (vlist-offset vlist)))
+ (if (eq? base block-null)
+ len
+ (loop (block-base base)
+ (+ len 1 (block-offset base))))))
+
+(define* (vlist-unfold p f g seed
+ #\optional (tail-gen (lambda (x) vlist-null)))
+ "Return a new vlist. See the description of SRFI-1 `unfold' for details."
+ (let uf ((seed seed))
+ (if (p seed)
+ (tail-gen seed)
+ (vlist-cons (f seed)
+ (uf (g seed))))))
+
+(define* (vlist-unfold-right p f g seed #\optional (tail vlist-null))
+ "Return a new vlist. See the description of SRFI-1 `unfold-right' for
+details."
+ (let uf ((seed seed) (lis tail))
+ (if (p seed)
+ lis
+ (uf (g seed) (vlist-cons (f seed) lis)))))
+
+(define (vlist-append . vlists)
+ "Append the given lists."
+ (if (null? vlists)
+ vlist-null
+ (fold-right (lambda (vlist result)
+ (vlist-fold-right (lambda (e v)
+ (vlist-cons e v))
+ result
+ vlist))
+ vlist-null
+ vlists)))
+
+(define (vlist-for-each proc vlist)
+ "Call PROC on each element of VLIST. The result is unspecified."
+ (vlist-fold (lambda (item x)
+ (proc item))
+ (if #f #f)
+ vlist))
+
+
+;;;
+;;; Hash Lists, aka. `VHash'.
+;;;
+
+;; Assume keys K1 and K2, H = hash(K1) = hash(K2), and two values V1 and V2
+;; associated with K1 and K2, respectively. The resulting layout is a
+;; follows:
+;;
+;; ,--------------------.
+;; 0| ,-> (K1 . V1) | Vlist array
+;; 1| | |
+;; 2| | (K2 . V2) |
+;; 3| | |
+;; size +-|------------------+
+;; 0| | | Hash table
+;; 1| | |
+;; 2| +-- O <------------- H
+;; 3| | |
+;; size * 2 +-|------------------+
+;; 0| `-> 2 | Chain links
+;; 1| |
+;; 2| #f |
+;; 3| |
+;; size * 3 `--------------------'
+;;
+;; The backing store for the vhash is partitioned into three areas: the
+;; vlist part, the hash table part, and the chain links part. In this
+;; example we have a hash H which, when indexed into the hash table
+;; part, indicates that a value with this hash can be found at offset 0
+;; in the vlist part. The corresponding index (in this case, 0) of the
+;; chain links array holds the index of the next element in this block
+;; with this hash value, or #f if we reached the end of the chain.
+;;
+;; This API potentially requires users to repeat which hash function and
+;; which equality predicate to use. This can lead to unpredictable
+;; results if they are used in consistenly, e.g., between `vhash-cons'
+;; and `vhash-assoc', which is undesirable, as argued in
+;; http://savannah.gnu.org/bugs/?22159 . OTOH, two arguments can be
+;; made in favor of this API:
+;;
+;; - It's consistent with how alists are handled in SRFI-1.
+;;
+;; - In practice, users will probably consistenly use either the `q',
+;; the `v', or the plain variant (`vlist-cons' and `vlist-assoc'
+;; without any optional argument), i.e., they will rarely explicitly
+;; pass a hash function or equality predicate.
+
+(define (vhash? obj)
+ "Return true if OBJ is a hash list."
+ (and (vlist? obj)
+ (block-hash-table? (vlist-base obj))))
+
+(define* (vhash-cons key value vhash #\optional (hash hash))
+ "Return a new hash list based on VHASH where KEY is associated
+with VALUE. Use HASH to compute KEY's hash."
+ (assert-vlist vhash)
+ ;; We should also assert that it is a hash table. Need to check the
+ ;; performance impacts of that. Also, vlist-null is a valid hash
+ ;; table, which does not pass vhash?. A bug, perhaps.
+ (let* ((vhash (block-cons (cons key value) vhash #t))
+ (base (vlist-base vhash))
+ (offset (vlist-offset vhash))
+ (size (block-size base))
+ (khash (hash key size))
+ (content (block-content base)))
+ (block-hash-table-add! content size khash offset)
+ vhash))
+
+(define vhash-consq (cut vhash-cons <> <> <> hashq))
+(define vhash-consv (cut vhash-cons <> <> <> hashv))
+
+(define-inlinable (%vhash-fold* proc init key vhash equal? hash)
+ ;; Fold over all the values associated with KEY in VHASH.
+ (define (visit-block base max-offset result)
+ (let* ((size (block-size base))
+ (content (block-content base))
+ (khash (hash key size)))
+ (let loop ((offset (block-hash-table-ref content size khash))
+ (result result))
+ (if offset
+ (loop (block-hash-table-next-offset content size offset)
+ (if (and (<= offset max-offset)
+ (equal? key (car (block-ref content offset))))
+ (proc (cdr (block-ref content offset)) result)
+ result))
+ (let ((next-block (block-base base)))
+ (if (> (block-size next-block) 0)
+ (visit-block next-block (block-offset base) result)
+ result))))))
+
+ (assert-vlist vhash)
+ (if (> (block-size (vlist-base vhash)) 0)
+ (visit-block (vlist-base vhash)
+ (vlist-offset vhash)
+ init)
+ init))
+
+(define* (vhash-fold* proc init key vhash
+ #\optional (equal? equal?) (hash hash))
+ "Fold over all the values associated with KEY in VHASH, with each
+call to PROC having the form ‘(proc value result)’, where
+RESULT is the result of the previous call to PROC and INIT the
+value of RESULT for the first call to PROC."
+ (%vhash-fold* proc init key vhash equal? hash))
+
+(define (vhash-foldq* proc init key vhash)
+ "Same as ‘vhash-fold*’, but using ‘hashq’ and ‘eq?’."
+ (%vhash-fold* proc init key vhash eq? hashq))
+
+(define (vhash-foldv* proc init key vhash)
+ "Same as ‘vhash-fold*’, but using ‘hashv’ and ‘eqv?’."
+ (%vhash-fold* proc init key vhash eqv? hashv))
+
+(define-inlinable (%vhash-assoc key vhash equal? hash)
+ ;; A specialization of `vhash-fold*' that stops when the first value
+ ;; associated with KEY is found or when the end-of-list is reached. Inline to
+ ;; make sure `vhash-assq' gets to use the `eq?' instruction instead of calling
+ ;; the `eq?' subr.
+ (define (visit-block base max-offset)
+ (let* ((size (block-size base))
+ (content (block-content base))
+ (khash (hash key size)))
+ (let loop ((offset (block-hash-table-ref content size khash)))
+ (if offset
+ (if (and (<= offset max-offset)
+ (equal? key (car (block-ref content offset))))
+ (block-ref content offset)
+ (loop (block-hash-table-next-offset content size offset)))
+ (let ((next-block (block-base base)))
+ (and (> (block-size next-block) 0)
+ (visit-block next-block (block-offset base))))))))
+
+ (assert-vlist vhash)
+ (and (> (block-size (vlist-base vhash)) 0)
+ (visit-block (vlist-base vhash)
+ (vlist-offset vhash))))
+
+(define* (vhash-assoc key vhash #\optional (equal? equal?) (hash hash))
+ "Return the first key/value pair from VHASH whose key is equal to
+KEY according to the EQUAL? equality predicate."
+ (%vhash-assoc key vhash equal? hash))
+
+(define (vhash-assq key vhash)
+ "Return the first key/value pair from VHASH whose key is ‘eq?’ to
+KEY."
+ (%vhash-assoc key vhash eq? hashq))
+
+(define (vhash-assv key vhash)
+ "Return the first key/value pair from VHASH whose key is ‘eqv?’ to
+KEY."
+ (%vhash-assoc key vhash eqv? hashv))
+
+(define* (vhash-delete key vhash #\optional (equal? equal?) (hash hash))
+ "Remove all associations from VHASH with KEY, comparing keys
+with EQUAL?."
+ (if (vhash-assoc key vhash equal? hash)
+ (vlist-fold (lambda (k+v result)
+ (let ((k (car k+v))
+ (v (cdr k+v)))
+ (if (equal? k key)
+ result
+ (vhash-cons k v result hash))))
+ vlist-null
+ vhash)
+ vhash))
+
+(define vhash-delq (cut vhash-delete <> <> eq? hashq))
+(define vhash-delv (cut vhash-delete <> <> eqv? hashv))
+
+(define (vhash-fold proc init vhash)
+ "Fold over the key/pair elements of VHASH from left to right, with
+each call to PROC having the form ‘(PROC key value result)’,
+where RESULT is the result of the previous call to PROC and
+INIT the value of RESULT for the first call to PROC."
+ (vlist-fold (lambda (key+value result)
+ (proc (car key+value) (cdr key+value)
+ result))
+ init
+ vhash))
+
+(define (vhash-fold-right proc init vhash)
+ "Fold over the key/pair elements of VHASH from right to left, with
+each call to PROC having the form ‘(PROC key value result)’,
+where RESULT is the result of the previous call to PROC and
+INIT the value of RESULT for the first call to PROC."
+ (vlist-fold-right (lambda (key+value result)
+ (proc (car key+value) (cdr key+value)
+ result))
+ init
+ vhash))
+
+(define* (alist->vhash alist #\optional (hash hash))
+ "Return the vhash corresponding to ALIST, an association list."
+ (fold-right (lambda (pair result)
+ (vhash-cons (car pair) (cdr pair) result hash))
+ vlist-null
+ alist))
+
+;;; vlist.scm ends here
+;;; installed-scm-file
+
+;;;; Copyright (C) 2003, 2006, 2014 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (ice-9 weak-vector)
+ \:export (make-weak-vector list->weak-vector weak-vector weak-vector?
+ weak-vector-length weak-vector-ref weak-vector-set!
+ make-weak-key-alist-vector
+ make-weak-value-alist-vector
+ make-doubly-weak-alist-vector
+ weak-key-alist-vector?
+ weak-value-alist-vector?
+ doubly-weak-alist-vector?) ; C
+ )
+
+(%init-weaks-builtins) ; defined in libguile/weaks.c
+;;; Guile Virtual Machine Assembly
+
+;; Copyright (C) 2001, 2009, 2010, 2011 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language assembly)
+ #\use-module (rnrs bytevectors)
+ #\use-module (system base pmatch)
+ #\use-module (system vm instruction)
+ #\use-module ((srfi srfi-1) #\select (fold))
+ #\export (byte-length
+ addr+ align-program align-code align-block
+ assembly-pack assembly-unpack
+ object->assembly assembly->object))
+
+;; len, metalen
+(define *program-header-len* (+ 4 4))
+
+;; lengths are encoded in 3 bytes
+(define *len-len* 3)
+
+
+(define (byte-length assembly)
+ (pmatch assembly
+ ((,inst . _) (guard (>= (instruction-length inst) 0))
+ (+ 1 (instruction-length inst)))
+ ((load-number ,str)
+ (+ 1 *len-len* (string-length str)))
+ ((load-string ,str)
+ (+ 1 *len-len* (string-length str)))
+ ((load-wide-string ,str)
+ (+ 1 *len-len* (* 4 (string-length str))))
+ ((load-symbol ,str)
+ (+ 1 *len-len* (string-length str)))
+ ((load-array ,bv)
+ (+ 1 *len-len* (bytevector-length bv)))
+ ((load-program ,labels ,len ,meta . ,code)
+ (+ 1 *program-header-len* len (if meta (1- (byte-length meta)) 0)))
+ (,label (guard (not (pair? label)))
+ 0)
+ (else (error "unknown instruction" assembly))))
+
+
+(define *program-alignment* 8)
+
+(define (addr+ addr code)
+ (fold (lambda (x len) (+ (byte-length x) len))
+ addr
+ code))
+
+(define (code-alignment addr alignment header-len)
+ (make-list (modulo (- alignment
+ (modulo (+ addr header-len) alignment))
+ alignment)
+ '(nop)))
+
+(define (align-block addr)
+ '())
+
+(define (align-code code addr alignment header-len)
+ `(,@(code-alignment addr alignment header-len)
+ ,code))
+
+(define (align-program prog addr)
+ (align-code prog addr *program-alignment* 1))
+
+;;;
+;;; Code compress/decompression
+;;;
+
+(define *abbreviations*
+ '(((make-int8 0) . (make-int8:0))
+ ((make-int8 1) . (make-int8:1))))
+
+(define *expansions*
+ (map (lambda (x) (cons (cdr x) (car x))) *abbreviations*))
+
+(define (assembly-pack code)
+ (or (assoc-ref *abbreviations* code)
+ code))
+
+(define (assembly-unpack code)
+ (or (assoc-ref *expansions* code)
+ code))
+
+
+;;;
+;;; Encoder/decoder
+;;;
+
+(define (object->assembly x)
+ (cond ((eq? x #t) `(make-true))
+ ((eq? x #f) `(make-false))
+ ((eq? x #nil) `(make-nil))
+ ((null? x) `(make-eol))
+ ((and (integer? x) (exact? x))
+ (cond ((and (<= -128 x) (< x 128))
+ (assembly-pack `(make-int8 ,(modulo x 256))))
+ ((and (<= -32768 x) (< x 32768))
+ (let ((n (if (< x 0) (+ x 65536) x)))
+ `(make-int16 ,(quotient n 256) ,(modulo n 256))))
+ ((and (<= 0 x #xffffffffffffffff))
+ `(make-uint64 ,@(bytevector->u8-list
+ (let ((bv (make-bytevector 8)))
+ (bytevector-u64-set! bv 0 x (endianness big))
+ bv))))
+ ((and (<= 0 (+ x #x8000000000000000) #x7fffffffffffffff))
+ `(make-int64 ,@(bytevector->u8-list
+ (let ((bv (make-bytevector 8)))
+ (bytevector-s64-set! bv 0 x (endianness big))
+ bv))))
+ (else #f)))
+ ((char? x)
+ (cond ((<= (char->integer x) #xff)
+ `(make-char8 ,(char->integer x)))
+ (else
+ `(make-char32 ,(char->integer x)))))
+ (else #f)))
+
+(define (assembly->object code)
+ (pmatch code
+ ((make-true) #t)
+ ((make-false) #f) ;; FIXME: Same as the `else' case!
+ ((make-nil) #nil)
+ ((make-eol) '())
+ ((make-int8 ,n)
+ (if (< n 128) n (- n 256)))
+ ((make-int16 ,n1 ,n2)
+ (let ((n (+ (* n1 256) n2)))
+ (if (< n 32768) n (- n 65536))))
+ ((make-uint64 ,n1 ,n2 ,n3 ,n4 ,n5 ,n6 ,n7 ,n8)
+ (bytevector-u64-ref
+ (u8-list->bytevector (list n1 n2 n3 n4 n5 n6 n7 n8))
+ 0
+ (endianness big)))
+ ((make-int64 ,n1 ,n2 ,n3 ,n4 ,n5 ,n6 ,n7 ,n8)
+ (bytevector-s64-ref
+ (u8-list->bytevector (list n1 n2 n3 n4 n5 n6 n7 n8))
+ 0
+ (endianness big)))
+ ((make-char8 ,n)
+ (integer->char n))
+ ((make-char32 ,n1 ,n2 ,n3 ,n4)
+ (integer->char (+ (* n1 #x1000000)
+ (* n2 #x10000)
+ (* n3 #x100)
+ n4)))
+ ((load-string ,s) s)
+ ((load-symbol ,s) (string->symbol s))
+ (else #f)))
+;;; Guile VM assembler
+
+;; Copyright (C) 2001, 2009, 2010, 2011, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language assembly compile-bytecode)
+ #\use-module (system base pmatch)
+ #\use-module (system base target)
+ #\use-module (language assembly)
+ #\use-module (system vm instruction)
+ #\use-module (rnrs bytevectors)
+ #\use-module ((srfi srfi-1) #\select (fold))
+ #\export (compile-bytecode))
+
+(define (compile-bytecode assembly env . opts)
+ (define-syntax-rule (define-inline1 (proc arg) body body* ...)
+ (define-syntax proc
+ (syntax-rules ()
+ ((_ (arg-expr (... ...)))
+ (let ((x (arg-expr (... ...))))
+ (proc x)))
+ ((_ arg)
+ (begin body body* ...)))))
+
+ (define (fill-bytecode bv target-endianness)
+ (let ((pos 0))
+ (define-inline1 (write-byte b)
+ (bytevector-u8-set! bv pos b)
+ (set! pos (1+ pos)))
+ (define u32-bv (make-bytevector 4))
+ (define-inline1 (write-int24-be x)
+ (bytevector-s32-set! u32-bv 0 x (endianness big))
+ (bytevector-u8-set! bv pos (bytevector-u8-ref u32-bv 1))
+ (bytevector-u8-set! bv (+ pos 1) (bytevector-u8-ref u32-bv 2))
+ (bytevector-u8-set! bv (+ pos 2) (bytevector-u8-ref u32-bv 3))
+ (set! pos (+ pos 3)))
+ (define-inline1 (write-uint32-be x)
+ (bytevector-u32-set! bv pos x (endianness big))
+ (set! pos (+ pos 4)))
+ (define-inline1 (write-uint32 x)
+ (bytevector-u32-set! bv pos x target-endianness)
+ (set! pos (+ pos 4)))
+ (define-inline1 (write-loader-len len)
+ (bytevector-u8-set! bv pos (ash len -16))
+ (bytevector-u8-set! bv (+ pos 1) (logand (ash len -8) 255))
+ (bytevector-u8-set! bv (+ pos 2) (logand len 255))
+ (set! pos (+ pos 3)))
+ (define-inline1 (write-latin1-string s)
+ (let ((len (string-length s)))
+ (write-loader-len len)
+ (let lp ((i 0))
+ (if (< i len)
+ (begin
+ (bytevector-u8-set! bv (+ pos i)
+ (char->integer (string-ref s i)))
+ (lp (1+ i)))))
+ (set! pos (+ pos len))))
+ (define-inline1 (write-bytevector bv*)
+ (let ((len (bytevector-length bv*)))
+ (write-loader-len len)
+ (bytevector-copy! bv* 0 bv pos len)
+ (set! pos (+ pos len))))
+ (define-inline1 (write-wide-string s)
+ (write-bytevector (string->utf32 s target-endianness)))
+ (define-inline1 (write-break label)
+ (let ((offset (- (assq-ref labels label) (+ (get-addr) 3))))
+ (cond ((>= offset (ash 1 23)) (error "jump too far forward" offset))
+ ((< offset (- (ash 1 23))) (error "jump too far backwards" offset))
+ (else (write-int24-be offset)))))
+
+ (define (write-bytecode asm labels address emit-opcode?)
+ ;; Write ASM's bytecode to BV. If EMIT-OPCODE? is false, don't
+ ;; emit bytecode for the first opcode encountered. Assume code
+ ;; starts at ADDRESS (an integer). LABELS is assumed to be an
+ ;; alist mapping labels to addresses.
+ (define get-addr
+ (let ((start pos))
+ (lambda ()
+ (+ address (- pos start)))))
+ (define (write-break label)
+ (let ((offset (- (assq-ref labels label) (+ (get-addr) 3))))
+ (cond ((>= offset (ash 1 23)) (error "jump too far forward" offset))
+ ((< offset (- (ash 1 23))) (error "jump too far backwards" offset))
+ (else (write-int24-be offset)))))
+
+ (let ((inst (car asm))
+ (args (cdr asm)))
+ (let ((opcode (instruction->opcode inst))
+ (len (instruction-length inst)))
+ (if emit-opcode?
+ (write-byte opcode))
+ (pmatch asm
+ ((load-program ,labels ,length ,meta . ,code)
+ (write-uint32 length)
+ (write-uint32 (if meta (1- (byte-length meta)) 0))
+ (fold (lambda (asm address)
+ (let ((start pos))
+ (write-bytecode asm labels address #t)
+ (+ address (- pos start))))
+ 0
+ code)
+ (if meta
+ ;; Don't emit the `load-program' byte for metadata. Note that
+ ;; META's bytecode meets the alignment requirements of
+ ;; `scm_objcode', thanks to the alignment computed in `(language
+ ;; assembly)'.
+ (write-bytecode meta '() 0 #f)))
+ ((make-char32 ,x) (write-uint32-be x))
+ ((load-number ,str) (write-latin1-string str))
+ ((load-string ,str) (write-latin1-string str))
+ ((load-wide-string ,str) (write-wide-string str))
+ ((load-symbol ,str) (write-latin1-string str))
+ ((load-array ,bv) (write-bytevector bv))
+ ((br ,l) (write-break l))
+ ((br-if ,l) (write-break l))
+ ((br-if-not ,l) (write-break l))
+ ((br-if-eq ,l) (write-break l))
+ ((br-if-not-eq ,l) (write-break l))
+ ((br-if-null ,l) (write-break l))
+ ((br-if-not-null ,l) (write-break l))
+ ((br-if-nargs-ne ,hi ,lo ,l) (write-byte hi) (write-byte lo) (write-break l))
+ ((br-if-nargs-lt ,hi ,lo ,l) (write-byte hi) (write-byte lo) (write-break l))
+ ((br-if-nargs-gt ,hi ,lo ,l) (write-byte hi) (write-byte lo) (write-break l))
+ ((bind-optionals/shuffle-or-br ,nreq-hi ,nreq-lo
+ ,nreq-and-nopt-hi ,nreq-and-nopt-lo
+ ,ntotal-hi ,ntotal-lo
+ ,l)
+ (write-byte nreq-hi)
+ (write-byte nreq-lo)
+ (write-byte nreq-and-nopt-hi)
+ (write-byte nreq-and-nopt-lo)
+ (write-byte ntotal-hi)
+ (write-byte ntotal-lo)
+ (write-break l))
+ ((mv-call ,n ,l) (write-byte n) (write-break l))
+ ((prompt ,escape-only? ,l) (write-byte escape-only?) (write-break l))
+ (else
+ (cond
+ ((< len 0)
+ (error "unhanded variable-length instruction" asm))
+ ((not (= (length args) len))
+ (error "bad number of args to instruction" asm len))
+ (else
+ (for-each (lambda (x) (write-byte x)) args))))))))
+
+ ;; Don't emit the `load-program' byte.
+ (write-bytecode assembly '() 0 #f)
+ (if (= pos (bytevector-length bv))
+ (values bv env env)
+ (error "failed to fill bytevector" bv pos
+ (bytevector-length bv)))))
+
+ (pmatch assembly
+ ((load-program ,labels ,length ,meta . ,code)
+ (fill-bytecode (make-bytevector (+ 4 4 length
+ (if meta
+ (1- (byte-length meta))
+ 0)))
+ (target-endianness)))
+
+ (else (error "bad assembly" assembly))))
+;;; Guile VM code converters
+
+;; Copyright (C) 2001, 2009, 2010, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language assembly decompile-bytecode)
+ #\use-module (system vm instruction)
+ #\use-module (system base pmatch)
+ #\use-module (srfi srfi-4)
+ #\use-module (rnrs bytevectors)
+ #\use-module (language assembly)
+ #\use-module ((system vm objcode) #\select (byte-order))
+ #\export (decompile-bytecode))
+
+(define (decompile-bytecode x env opts)
+ (let ((i 0) (size (u8vector-length x)))
+ (define (pop)
+ (let ((b (cond ((< i size) (u8vector-ref x i))
+ ((= i size) #f)
+ (else (error "tried to decode too many bytes")))))
+ (if b (set! i (1+ i)))
+ b))
+ (let ((ret (decode-load-program pop)))
+ (if (= i size)
+ (values ret env)
+ (error "bad bytecode: only decoded ~a out of ~a bytes" i size)))))
+
+(define (br-instruction? x)
+ (memq x '(br br-if br-if-not br-if-eq br-if-not-eq br-if-null br-if-not-null)))
+(define (br-nargs-instruction? x)
+ (memq x '(br-if-nargs-ne br-if-nargs-lt br-if-nargs-gt br-if-nargs-lt/non-kw)))
+
+(define (bytes->s24 a b c)
+ (let ((x (+ (ash a 16) (ash b 8) c)))
+ (if (zero? (logand (ash 1 23) x))
+ x
+ (- x (ash 1 24)))))
+
+;; FIXME: this is a little-endian disassembly!!!
+(define (decode-load-program pop)
+ (let* ((a (pop)) (b (pop)) (c (pop)) (d (pop))
+ (e (pop)) (f (pop)) (g (pop)) (h (pop))
+ (len (+ a (ash b 8) (ash c 16) (ash d 24)))
+ (metalen (+ e (ash f 8) (ash g 16) (ash h 24)))
+ (labels '())
+ (i 0))
+ (define (ensure-label rel1 rel2 rel3)
+ (let ((where (+ i (bytes->s24 rel1 rel2 rel3))))
+ (or (assv-ref labels where)
+ (begin
+ (let ((l (gensym ":L")))
+ (set! labels (acons where l labels))
+ l)))))
+ (define (sub-pop) ;; ...records. ha. ha.
+ (let ((b (cond ((< i len) (pop))
+ ((= i len) #f)
+ (else (error "tried to decode too many bytes")))))
+ (if b (set! i (1+ i)))
+ b))
+ (let lp ((out '()))
+ (cond ((> i len)
+ (error "error decoding program -- read too many bytes" out))
+ ((= i len)
+ `(load-program ,(map (lambda (x) (cons (cdr x) (car x)))
+ (reverse labels))
+ ,len
+ ,(if (zero? metalen) #f (decode-load-program pop))
+ ,@(reverse! out)))
+ (else
+ (let ((exp (decode-bytecode sub-pop)))
+ (pmatch exp
+ ((,br ,rel1 ,rel2 ,rel3) (guard (br-instruction? br))
+ (lp (cons `(,br ,(ensure-label rel1 rel2 rel3)) out)))
+ ((,br ,hi ,lo ,rel1 ,rel2 ,rel3) (guard (br-nargs-instruction? br))
+ (lp (cons `(,br ,hi ,lo ,(ensure-label rel1 rel2 rel3)) out)))
+ ((bind-optionals/shuffle-or-br ,nreq-hi ,nreq-lo
+ ,nreq-and-nopt-hi ,nreq-and-nopt-lo
+ ,ntotal-hi ,ntotal-lo
+ ,rel1 ,rel2 ,rel3)
+ (lp (cons `(bind-optionals/shuffle-or-br
+ ,nreq-hi ,nreq-lo
+ ,nreq-and-nopt-hi ,nreq-and-nopt-lo
+ ,ntotal-hi ,ntotal-lo
+ ,(ensure-label rel1 rel2 rel3))
+ out)))
+ ((mv-call ,n ,rel1 ,rel2 ,rel3)
+ (lp (cons `(mv-call ,n ,(ensure-label rel1 rel2 rel3)) out)))
+ ((prompt ,n0 ,rel1 ,rel2 ,rel3)
+ (lp (cons `(prompt ,n0 ,(ensure-label rel1 rel2 rel3)) out)))
+ (else
+ (lp (cons exp out))))))))))
+
+(define (decode-bytecode pop)
+ (and=> (pop)
+ (lambda (opcode)
+ (let ((inst (opcode->instruction opcode)))
+ (cond
+ ((eq? inst 'load-program)
+ (decode-load-program pop))
+
+ ((< (instruction-length inst) 0)
+ ;; the negative length indicates a variable length
+ ;; instruction
+ (let* ((make-sequence
+ (if (or (memq inst '(load-array load-wide-string)))
+ make-bytevector
+ make-string))
+ (sequence-set!
+ (if (or (memq inst '(load-array load-wide-string)))
+ bytevector-u8-set!
+ (lambda (str pos value)
+ (string-set! str pos (integer->char value)))))
+ (len (let* ((a (pop)) (b (pop)) (c (pop)))
+ (+ (ash a 16) (ash b 8) c)))
+ (seq (make-sequence len)))
+ (let lp ((i 0))
+ (if (= i len)
+ `(,inst ,(if (eq? inst 'load-wide-string)
+ (utf32->string seq (native-endianness))
+ seq))
+ (begin
+ (sequence-set! seq i (pop))
+ (lp (1+ i)))))))
+ (else
+ ;; fixed length
+ (let lp ((n (instruction-length inst)) (out (list inst)))
+ (if (zero? n)
+ (reverse! out)
+ (lp (1- n) (cons (pop) out))))))))))
+;;; Guile VM code converters
+
+;; Copyright (C) 2001, 2009, 2010, 2012, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language assembly disassemble)
+ #\use-module (ice-9 format)
+ #\use-module (srfi srfi-1)
+ #\use-module (system vm instruction)
+ #\use-module (system vm program)
+ #\use-module (system base pmatch)
+ #\use-module (language assembly)
+ #\use-module (system base compile)
+ #\export (disassemble))
+
+(define (disassemble x)
+ (format #t "Disassembly of ~A:\n\n" x)
+ (call-with-values
+ (lambda () (decompile x #\from 'value #\to 'assembly))
+ disassemble-load-program))
+
+(define (disassemble-load-program asm env)
+ (pmatch asm
+ ((load-program ,labels ,len ,meta . ,code)
+ (let ((objs (and env (assq-ref env 'objects)))
+ (free-vars (and env (assq-ref env 'free-vars)))
+ (meta (and env (assq-ref env 'meta)))
+ (blocs (and env (assq-ref env 'blocs)))
+ (srcs (and env (assq-ref env 'sources))))
+ (let lp ((pos 0) (code code) (programs '()))
+ (cond
+ ((null? code)
+ (newline)
+ (for-each
+ (lambda (sym+asm)
+ (format #t "Embedded program ~A:\n\n" (car sym+asm))
+ (disassemble-load-program (cdr sym+asm) '()))
+ (reverse! programs)))
+ (else
+ (let* ((asm (car code))
+ (len (byte-length asm))
+ (end (+ pos len)))
+ (pmatch asm
+ ((load-program . _)
+ (let ((sym (gensym "")))
+ (print-info pos `(load-program ,sym) #f #f)
+ (lp (+ pos (byte-length asm)) (cdr code)
+ (acons sym asm programs))))
+ ((nop)
+ (lp (+ pos (byte-length asm)) (cdr code) programs))
+ (else
+ (print-info pos asm
+ ;; FIXME: code-annotation for whether it's
+ ;; an arg or not, currently passing nargs=-1
+ (code-annotation end asm objs -1 blocs
+ labels)
+ (and=> (and srcs (assq end srcs)) source->string))
+ (lp (+ pos (byte-length asm)) (cdr code) programs)))))))
+
+ (if (pair? free-vars)
+ (disassemble-free-vars free-vars))
+ (if meta
+ (disassemble-meta meta))
+
+ ;; Disassemble other bytecode in it
+ ;; FIXME: something about the module.
+ (if objs
+ (for-each
+ (lambda (x)
+ (if (program? x)
+ (begin (display "----------------------------------------\n")
+ (disassemble x))))
+ (cdr (vector->list objs))))))
+ (else
+ (error "bad load-program form" asm))))
+
+(define (disassemble-free-vars free-vars)
+ (display "Free variables:\n\n")
+ (fold (lambda (free-var i)
+ (print-info i free-var #f #f)
+ (+ 1 i))
+ 0
+ free-vars))
+
+(define-macro (unless test . body)
+ `(if (not ,test) (begin ,@body)))
+
+(define *uninteresting-props* '(name))
+
+(define (disassemble-meta meta)
+ (let ((props (filter (lambda (x)
+ (not (memq (car x) *uninteresting-props*)))
+ (cdddr meta))))
+ (unless (null? props)
+ (display "Properties:\n\n")
+ (for-each (lambda (x) (print-info #f x #f #f)) props)
+ (newline))))
+
+(define (source->string src)
+ (format #f "~a:~a:~a" (or (source:file src) "(unknown file)")
+ (source:line-for-user src) (source:column src)))
+
+(define (make-int16 byte1 byte2)
+ (+ (* byte1 256) byte2))
+
+(define (code-annotation end-addr code objs nargs blocs labels)
+ (let* ((code (assembly-unpack code))
+ (inst (car code))
+ (args (cdr code)))
+ (case inst
+ ((list vector)
+ (list "~a element~:p" (apply make-int16 args)))
+ ((br br-if br-if-eq br-if-not br-if-not-eq br-if-not-null br-if-null)
+ (list "-> ~A" (assq-ref labels (car args))))
+ ((br-if-nargs-ne br-if-nargs-lt br-if-nargs-gt)
+ (list "-> ~A" (assq-ref labels (caddr args))))
+ ((bind-optionals/shuffle-or-br)
+ (list "-> ~A" (assq-ref labels (car (last-pair args)))))
+ ((object-ref)
+ (and objs (list "~s" (vector-ref objs (car args)))))
+ ((local-ref local-boxed-ref local-set local-boxed-set)
+ (and blocs
+ (let lp ((bindings (list-ref blocs (car args))))
+ (and (pair? bindings)
+ (let ((b (car bindings)))
+ (if (and (< (binding:start (car bindings)) end-addr)
+ (>= (binding:end (car bindings)) end-addr))
+ (list "`~a'~@[ (arg)~]"
+ (binding:name b) (< (binding:index b) nargs))
+ (lp (cdr bindings))))))))
+ ((assert-nargs-ee/locals assert-nargs-ge/locals)
+ (list "~a arg~:p, ~a local~:p"
+ (logand (car args) #x7) (ash (car args) -3)))
+ ((free-ref free-boxed-ref free-boxed-set)
+ ;; FIXME: we can do better than this
+ (list "(closure variable)"))
+ ((toplevel-ref toplevel-set)
+ (and objs
+ (let ((v (vector-ref objs (car args))))
+ (if (and (variable? v) (variable-bound? v))
+ (list "~s" (variable-ref v))
+ (list "`~s'" v)))))
+ ((mv-call)
+ (list "MV -> ~A" (assq-ref labels (cadr args))))
+ ((prompt)
+ ;; the H is for handler
+ (list "H -> ~A" (assq-ref labels (cadr args))))
+ (else
+ (and=> (assembly->object code)
+ (lambda (obj) (list "~s" obj)))))))
+
+;; i am format's daddy.
+(define (print-info addr info extra src)
+ (format #t "~4@S ~32S~@[;; ~1{~@?~}~]~@[~61t at ~a~]\n" addr info extra src))
+;;; Guile Virtual Machine Assembly
+
+;; Copyright (C) 2001, 2009, 2010, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language assembly spec)
+ #\use-module (system base language)
+ #\use-module (language assembly compile-bytecode)
+ #\use-module (language assembly decompile-bytecode)
+ #\export (assembly))
+
+(define-language assembly
+ #\title "Guile Virtual Machine Assembly Language"
+ #\reader (lambda (port env) (read port))
+ #\printer write
+ #\parser read ;; fixme: make a verifier?
+ #\compilers `((bytecode . ,compile-bytecode))
+ #\decompilers `((bytecode . ,decompile-bytecode))
+ #\for-humans? #f
+ )
+;;; Brainfuck for GNU Guile
+
+;; Copyright (C) 2009, 2013 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language brainfuck compile-scheme)
+ #\export (compile-scheme))
+
+;; Compilation of Brainfuck to Scheme is pretty straight-forward. For all of
+;; brainfuck's instructions, there are basic representations in Scheme we
+;; only have to generate.
+;;
+;; Brainfuck's pointer and data-tape are stored in the variables pointer and
+;; tape, where tape is a vector of integer values initially set to zero. Pointer
+;; starts out at position 0.
+;; Our tape is thus of finite length, with an address range of 0..n for
+;; some defined upper bound n depending on the length of our tape.
+
+
+;; Define the length to use for the tape.
+
+(define tape-size 30000)
+
+
+;; This compiles a whole brainfuck program. This constructs a Scheme code like:
+;; (let ((pointer 0)
+;; (tape (make-vector tape-size 0)))
+;; (begin
+;; <body>
+;; (write-char #\newline)))
+;;
+;; So first the pointer and tape variables are set up correctly, then the
+;; program's body is executed in this context, and finally we output an
+;; additional newline character in case the program does not output one.
+;;
+;; TODO: Find out and explain the details about env, the three return values and
+;; how to use the options. Implement options to set the tape-size, maybe.
+
+(define (compile-scheme exp env opts)
+ (values
+ `(let ((pointer 0)
+ (tape (make-vector ,tape-size 0)))
+ ,@(compile-body (cdr exp))
+ (write-char #\newline))
+ env
+ env))
+
+
+;; Compile a list of instructions to get a list of Scheme codes. As we always
+;; strip off the car of the instructions-list and cons the result onto the
+;; result-list, it will get out in reversed order first; so we have to (reverse)
+;; it on return.
+
+(define (compile-body instructions)
+ (let iterate ((cur instructions)
+ (result '()))
+ (if (null? cur)
+ (reverse result)
+ (let ((compiled (compile-instruction (car cur))))
+ (iterate (cdr cur) (cons compiled result))))))
+
+
+;; Compile a single instruction to Scheme, using the direct representations
+;; all of Brainfuck's instructions have.
+
+(define (compile-instruction ins)
+ (case (car ins)
+
+ ;; Pointer moval >< is done simply by something like:
+ ;; (set! pointer (+ pointer +-1))
+ ((<bf-move>)
+ (let ((dir (cadr ins)))
+ `(set! pointer (+ pointer ,dir))))
+
+ ;; Cell increment +- is done as:
+ ;; (vector-set! tape pointer (+ (vector-ref tape pointer) +-1))
+ ((<bf-increment>)
+ (let ((inc (cadr ins)))
+ `(vector-set! tape pointer (+ (vector-ref tape pointer) ,inc))))
+
+ ;; Output . is done by converting the cell's integer value to a character
+ ;; first and then printing out this character:
+ ;; (write-char (integer->char (vector-ref tape pointer)))
+ ((<bf-print>)
+ '(write-char (integer->char (vector-ref tape pointer))))
+
+ ;; Input , is done similarly, read in a character, get its ASCII code and
+ ;; store it into the current cell:
+ ;; (vector-set! tape pointer (char->integer (read-char)))
+ ((<bf-read>)
+ '(vector-set! tape pointer (char->integer (read-char))))
+
+ ;; For loops [...] we use a named let construction to execute the body until
+ ;; the current cell gets zero. The body is compiled via a recursive call
+ ;; back to (compile-body).
+ ;; (let iterate ()
+ ;; (if (not (= (vector-ref! tape pointer) 0))
+ ;; (begin
+ ;; <body>
+ ;; (iterate))))
+ ((<bf-loop>)
+ `(let iterate ()
+ (if (not (= (vector-ref tape pointer) 0))
+ (begin
+ ,@(compile-body (cdr ins))
+ (iterate)))))
+
+ (else (error "unknown brainfuck instruction " (car ins)))))
+;;; Brainfuck for GNU Guile
+
+;; Copyright (C) 2009 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Commentary:
+
+;; Brainfuck is a simple language that mostly mimics the operations of a
+;; Turing machine. This file implements a compiler from Brainfuck to
+;; Guile's Tree-IL.
+
+;;; Code:
+
+(define-module (language brainfuck compile-tree-il)
+ #\use-module (system base pmatch)
+ #\use-module (language tree-il)
+ #\export (compile-tree-il))
+
+;; Compilation of Brainfuck is pretty straight-forward. For all of
+;; brainfuck's instructions, there are basic representations in Tree-IL
+;; we only have to generate.
+;;
+;; Brainfuck's pointer and data-tape are stored in the variables pointer and
+;; tape, where tape is a vector of integer values initially set to zero. Pointer
+;; starts out at position 0.
+;; Our tape is thus of finite length, with an address range of 0..n for
+;; some defined upper bound n depending on the length of our tape.
+
+
+;; Define the length to use for the tape.
+
+(define tape-size 30000)
+
+
+;; This compiles a whole brainfuck program. This constructs a Tree-IL
+;; code equivalent to Scheme code like this:
+;;
+;; (let ((pointer 0)
+;; (tape (make-vector tape-size 0)))
+;; (begin
+;; <body>
+;; (write-char #\newline)))
+;;
+;; So first the pointer and tape variables are set up correctly, then the
+;; program's body is executed in this context, and finally we output an
+;; additional newline character in case the program does not output one.
+;;
+;; The fact that we are compiling to Guile primitives gives this
+;; implementation a number of interesting characteristics. First, the
+;; values of the tape cells do not underflow or overflow. We could make
+;; them do otherwise via compiling calls to "modulo" at certain points.
+;;
+;; In addition, tape overruns or underruns will be detected, and will
+;; throw an error, whereas a number of Brainfuck compilers do not detect
+;; this.
+;;
+;; Note that we're generating the S-expression representation of
+;; Tree-IL, then using parse-tree-il to turn it into the actual Tree-IL
+;; data structures. This makes the compiler more pleasant to look at,
+;; but we do lose is the ability to propagate source information. Since
+;; Brainfuck is so obtuse anyway, this shouldn't matter ;-)
+;;
+;; `compile-tree-il' takes as its input the read expression, the
+;; environment, and some compile options. It returns the compiled
+;; expression, the environment appropriate for the next pass of the
+;; compiler -- in our case, just the environment unchanged -- and the
+;; continuation environment.
+;;
+;; The normal use of a continuation environment is if compiling one
+;; expression changes the environment, and that changed environment
+;; should be passed to the next compiled expression -- for example,
+;; changing the current module. But Brainfuck is incapable of that, so
+;; for us, the continuation environment is just the same environment we
+;; got in.
+;;
+;; FIXME: perhaps use options or the env to set the tape-size?
+
+(define (compile-tree-il exp env opts)
+ (values
+ (parse-tree-il
+ `(let (pointer tape) (pointer tape)
+ ((const 0)
+ (apply (primitive make-vector) (const ,tape-size) (const 0)))
+ ,(compile-body exp)))
+ env
+ env))
+
+
+;; Compile a list of instructions to a Tree-IL expression.
+
+(define (compile-body instructions)
+ (let lp ((in instructions) (out '()))
+ (define (emit x)
+ (lp (cdr in) (cons x out)))
+ (cond
+ ((null? in)
+ ;; No more input, build our output.
+ (cond
+ ((null? out) '(void)) ; no output
+ ((null? (cdr out)) (car out)) ; single expression
+ (else `(begin ,@(reverse out)))) ; sequence
+ )
+ (else
+ (pmatch (car in)
+
+ ;; Pointer moves >< are done simply by something like:
+ ;; (set! pointer (+ pointer +-1))
+ ((<bf-move> ,dir)
+ (emit `(set! (lexical pointer)
+ (apply (primitive +) (lexical pointer) (const ,dir)))))
+
+ ;; Cell increment +- is done as:
+ ;; (vector-set! tape pointer (+ (vector-ref tape pointer) +-1))
+ ((<bf-increment> ,inc)
+ (emit `(apply (primitive vector-set!) (lexical tape) (lexical pointer)
+ (apply (primitive +)
+ (apply (primitive vector-ref)
+ (lexical tape) (lexical pointer))
+ (const ,inc)))))
+
+ ;; Output . is done by converting the cell's integer value to a
+ ;; character first and then printing out this character:
+ ;; (write-char (integer->char (vector-ref tape pointer)))
+ ((<bf-print>)
+ (emit `(apply (primitive write-char)
+ (apply (primitive integer->char)
+ (apply (primitive vector-ref)
+ (lexical tape) (lexical pointer))))))
+
+ ;; Input , is done similarly, read in a character, get its ASCII
+ ;; code and store it into the current cell:
+ ;; (vector-set! tape pointer (char->integer (read-char)))
+ ((<bf-read>)
+ (emit `(apply (primitive vector-set!)
+ (lexical tape) (lexical pointer)
+ (apply (primitive char->integer)
+ (apply (primitive read-char))))))
+
+ ;; For loops [...] we use a letrec construction to execute the body until
+ ;; the current cell gets zero. The body is compiled via a recursive call
+ ;; back to (compile-body).
+ ;; (let iterate ()
+ ;; (if (not (= (vector-ref! tape pointer) 0))
+ ;; (begin
+ ;; <body>
+ ;; (iterate))))
+ ;;
+ ;; Indeed, letrec is the only way we have to loop in Tree-IL.
+ ;; Note that this does not mean that the closure must actually
+ ;; be created; later passes can compile tail-recursive letrec
+ ;; calls into inline code with gotos. Admittedly, that part of
+ ;; the compiler is not yet in place, but it will be, and in the
+ ;; meantime the code is still reasonably efficient.
+ ((<bf-loop> . ,body)
+ (let ((iterate (gensym)))
+ (emit `(letrec (iterate) (,iterate)
+ ((lambda ()
+ (lambda-case
+ ((() #f #f #f () ())
+ (if (apply (primitive =)
+ (apply (primitive vector-ref)
+ (lexical tape) (lexical pointer))
+ (const 0))
+ (void)
+ (begin ,(compile-body body)
+ (apply (lexical ,iterate)))))
+ #f)))
+ (apply (lexical ,iterate))))))
+
+ (else (error "unknown brainfuck instruction" (car in))))))))
+;;; Brainfuck for GNU Guile.
+
+;; Copyright (C) 2009, 2013 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Code:
+
+(define-module (language brainfuck parse)
+ #\export (read-brainfuck))
+
+; Purpose of the parse module is to read in brainfuck in text form and produce
+; the corresponding tree representing the brainfuck code.
+;
+; Each object (representing basically a single instruction) is structured like:
+; (<instruction> [arguments])
+; where <instruction> is a symbolic name representing the type of instruction
+; and the optional arguments represent further data (for instance, the body of
+; a [...] loop as a number of nested instructions).
+
+
+; While reading a number of instructions in sequence, all of them are cons'ed
+; onto a list of instructions; thus this list gets out in reverse order.
+; Additionally, for "comment characters" (everything not an instruction) we
+; generate <bf-nop> NOP instructions.
+;
+; This routine reverses a list of instructions and removes all <bf-nop>'s on the
+; way to fix these two issues for a read-in list.
+
+(define (reverse-without-nops lst)
+ (let iterate ((cur lst)
+ (result '()))
+ (if (null? cur)
+ result
+ (let ((head (car cur))
+ (tail (cdr cur)))
+ (if (eq? (car head) '<bf-nop>)
+ (iterate tail result)
+ (iterate tail (cons head result)))))))
+
+
+; Read in a set of instructions until a terminating ] character is found (or
+; end of file is reached). This is used both for loop bodies and whole
+; programs, so that a program has to be either terminated by EOF or an
+; additional ], too.
+;
+; For instance, the basic program so just echo one character would be:
+; ,.]
+
+(define (read-brainfuck p)
+ (let iterate ((parsed '()))
+ (let ((chr (read-char p)))
+ (cond
+ ((eof-object? chr)
+ (let ((parsed (reverse-without-nops parsed)))
+ (if (null? parsed)
+ chr ;; pass on the EOF object
+ parsed)))
+ ((eqv? chr #\])
+ (reverse-without-nops parsed))
+ (else
+ (iterate (cons (process-input-char chr p) parsed)))))))
+
+
+; This routine processes a single character of input and builds the
+; corresponding instruction. Loop bodies are read by recursively calling
+; back (read-brainfuck).
+;
+; For the poiner movement commands >< and the cell increment/decrement +-
+; commands, we only use one instruction form each and specify the direction of
+; the pointer/value increment using an argument to the instruction form.
+
+(define (process-input-char chr p)
+ (case chr
+ ((#\>) '(<bf-move> 1))
+ ((#\<) '(<bf-move> -1))
+ ((#\+) '(<bf-increment> 1))
+ ((#\-) '(<bf-increment> -1))
+ ((#\.) '(<bf-print>))
+ ((#\,) '(<bf-read>))
+ ((#\[) `(<bf-loop> ,@(read-brainfuck p)))
+ (else '(<bf-nop>))))
+;;; Brainfuck for GNU Guile.
+
+;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Code:
+
+(define-module (language brainfuck spec)
+ #\use-module (language brainfuck compile-tree-il)
+ #\use-module (language brainfuck compile-scheme)
+ #\use-module (language brainfuck parse)
+ #\use-module (system base language)
+ #\export (brainfuck))
+
+
+; The new language is integrated into Guile via this (define-language)
+; specification in the special module (language [lang] spec).
+; Provided is a parser-routine in #\reader, a output routine in #\printer
+; and one or more compiler routines (as target-language - routine pairs)
+; in #\compilers. This is the basic set of fields needed to specify a new
+; language.
+
+(define-language brainfuck
+ #\title "Brainfuck"
+ #\reader (lambda (port env) (read-brainfuck port))
+ #\compilers `((tree-il . ,compile-tree-il)
+ (scheme . ,compile-scheme))
+ #\printer write
+ )
+;;; Guile Lowlevel Intermediate Language
+
+;; Copyright (C) 2001, 2009, 2010, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language bytecode spec)
+ #\use-module (system base language)
+ #\use-module (system vm objcode)
+ #\export (bytecode))
+
+(define (compile-objcode x e opts)
+ (values (bytecode->objcode x) e e))
+
+(define (decompile-objcode x e opts)
+ (values (objcode->bytecode x) e))
+
+(define-language bytecode
+ #\title "Guile Bytecode Vectors"
+ #\reader (lambda (port env) (read port))
+ #\printer write
+ #\compilers `((objcode . ,compile-objcode))
+ #\decompilers `((objcode . ,decompile-objcode))
+ #\for-humans? #f
+ )
+;;; ECMAScript for Guile
+
+;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language ecmascript array)
+ #\use-module (oop goops)
+ #\use-module (language ecmascript base)
+ #\use-module (language ecmascript function)
+ #\export (*array-prototype* new-array))
+
+
+(define-class <js-array-object> (<js-object>)
+ (vector #\init-value #() #\accessor js-array-vector #\init-keyword #\vector))
+
+(define (new-array . vals)
+ (let ((o (make <js-array-object> #\class "Array"
+ #\prototype *array-prototype*)))
+ (pput o 'length (length vals))
+ (let ((vect (js-array-vector o)))
+ (let lp ((i 0) (vals vals))
+ (cond ((not (null? vals))
+ (vector-set! vect i (car vals))
+ (lp (1+ i) (cdr vals)))
+ (else o))))))
+
+(define *array-prototype* (make <js-object> #\class "Array"
+ #\value new-array
+ #\constructor new-array))
+
+(hashq-set! *program-wrappers* new-array *array-prototype*)
+
+(pput *array-prototype* 'prototype *array-prototype*)
+(pput *array-prototype* 'constructor new-array)
+
+(define-method (pget (o <js-array-object>) p)
+ (cond ((and (integer? p) (exact? p) (>= p 0))
+ (let ((v (js-array-vector o)))
+ (if (< p (vector-length v))
+ (vector-ref v p)
+ (next-method))))
+ ((or (and (symbol? p) (eq? p 'length))
+ (and (string? p) (string=? p "length")))
+ (vector-length (js-array-vector o)))
+ (else (next-method))))
+
+(define-method (pput (o <js-array-object>) p v)
+ (cond ((and (integer? p) (exact? p) (>= 0 p))
+ (let ((vect (js-array-vector o)))
+ (if (< p (vector-length vect))
+ (vector-set! vect p v)
+ ;; Fixme: round up to powers of 2?
+ (let ((new (make-vector (1+ p) 0)))
+ (vector-move-left! vect 0 (vector-length vect) new 0)
+ (set! (js-array-vector o) new)
+ (vector-set! new p v)))))
+ ((or (and (symbol? p) (eq? p 'length))
+ (and (string? p) (string=? p "length")))
+ (let ((vect (js-array-vector o)))
+ (let ((new (make-vector (->uint32 v) 0)))
+ (vector-move-left! vect 0 (min (vector-length vect) (->uint32 v))
+ new 0)
+ (set! (js-array-vector o) new))))
+ (else (next-method))))
+
+(define-js-method *array-prototype* (toString)
+ (format #f "~A" (js-array-vector this)))
+
+(define-js-method *array-prototype* (concat . rest)
+ (let* ((len (apply + (->uint32 (pget this 'length))
+ (map (lambda (x) (->uint32 (pget x 'length)))
+ rest)))
+ (rv (make-vector len 0)))
+ (let lp ((objs (cons this rest)) (i 0))
+ (cond ((null? objs) (make <js-array-object> #\class "Array"
+ #\prototype *array-prototype*
+ #\vector rv))
+ ((is-a? (car objs) <js-array-object>)
+ (let ((v (js-array-vector (car objs))))
+ (vector-move-left! v 0 (vector-length v)
+ rv i)
+ (lp (cdr objs) (+ i (vector-length v)))))
+ (else
+ (error "generic array concats not yet implemented"))))))
+
+(define-js-method *array-prototype* (join . separator)
+ (let lp ((i (1- (->uint32 (pget this 'length)))) (l '()))
+ (if (< i 0)
+ (string-join l (if separator (->string (car separator)) ","))
+ (lp (1+ i)
+ (cons (->string (pget this i)) l)))))
+
+(define-js-method *array-prototype* (pop)
+ (let ((len (->uint32 (pget this 'length))))
+ (if (zero? len)
+ *undefined*
+ (let ((ret (pget this (1- len))))
+ (pput this 'length (1- len))
+ ret))))
+
+(define-js-method *array-prototype* (push . args)
+ (let lp ((args args))
+ (if (null? args)
+ (->uint32 (pget this 'length))
+ (begin (pput this (->uint32 (pget this 'length)) (car args))
+ (lp (cdr args))))))
+;;; ECMAScript for Guile
+
+;; Copyright (C) 2009, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language ecmascript base)
+ #\use-module (oop goops)
+ #\export (*undefined* *this*
+ <js-object> *object-prototype*
+ js-prototype js-props js-prop-attrs js-value js-constructor js-class
+ pget prop-keys prop-attrs prop-has-attr? pput has-property? pdel
+
+ object->string object->number object->value/string
+ object->value/number object->value
+
+ ->primitive ->boolean ->number ->integer ->int32 ->uint32
+ ->uint16 ->string ->object
+
+ call/this* call/this lambda/this define-js-method
+
+ new-object new))
+
+(define *undefined* ((@@ (oop goops) make-unbound)))
+(define *this* (make-fluid))
+
+(define-class <js-object> ()
+ (prototype #\getter js-prototype #\init-keyword #\prototype
+ #\init-thunk (lambda () *object-prototype*))
+ (props #\getter js-props #\init-form (make-hash-table 7))
+ (prop-attrs #\getter js-prop-attrs #\init-value #f)
+ (value #\getter js-value #\init-value #f #\init-keyword #\value)
+ (constructor #\getter js-constructor #\init-value #f #\init-keyword #\constructor)
+ (class #\getter js-class #\init-value "Object" #\init-keyword #\class))
+
+(define-method (prop-keys (o <js-object>))
+ (hash-map->list (lambda (k v) k) (js-props o)))
+
+(define-method (pget (o <js-object>) (p <string>))
+ (pget o (string->symbol p)))
+
+(define-method (pget (o <js-object>) p)
+ (let ((h (hashq-get-handle (js-props o) p)))
+ (if h
+ (cdr h)
+ (let ((proto (js-prototype o)))
+ (if proto
+ (pget proto p)
+ *undefined*)))))
+
+(define-method (prop-attrs (o <js-object>) p)
+ (or (let ((attrs (js-prop-attrs o)))
+ (and attrs (hashq-ref (js-prop-attrs o) p)))
+ (let ((proto (js-prototype o)))
+ (if proto
+ (prop-attrs proto p)
+ '()))))
+
+(define-method (prop-has-attr? (o <js-object>) p attr)
+ (memq attr (prop-attrs o p)))
+
+(define-method (pput (o <js-object>) p v)
+ (if (prop-has-attr? o p 'ReadOnly)
+ (throw 'ReferenceError o p)
+ (hashq-set! (js-props o) p v)))
+
+(define-method (pput (o <js-object>) (p <string>) v)
+ (pput o (string->symbol p) v))
+
+(define-method (pdel (o <js-object>) p)
+ (if (prop-has-attr? o p 'DontDelete)
+ #f
+ (begin
+ (pput o p *undefined*)
+ #t)))
+
+(define-method (pdel (o <js-object>) (p <string>) v)
+ (pdel o (string->symbol p)))
+
+(define-method (has-property? (o <js-object>) p)
+ (if (hashq-get-handle (js-props o) p)
+ #t
+ (let ((proto (js-prototype o)))
+ (if proto
+ (has-property? proto p)
+ #f))))
+
+(define (call/this* this f)
+ (with-fluid* *this* this f))
+
+(define-macro (call/this this f . args)
+ `(with-fluid* *this* ,this (lambda () (,f . ,args))))
+(define-macro (lambda/this formals . body)
+ `(lambda ,formals (let ((this (fluid-ref *this*))) . ,body)))
+(define-macro (define-js-method object name-and-args . body)
+ `(pput ,object ',(car name-and-args) (lambda/this ,(cdr name-and-args) . ,body)))
+
+(define *object-prototype* #f)
+(set! *object-prototype* (make <js-object>))
+
+(define-js-method *object-prototype* (toString)
+ (format #f "[object ~A]" (js-class this)))
+(define-js-method *object-prototype* (toLocaleString . args)
+ ((pget *object-prototype* 'toString)))
+(define-js-method *object-prototype* (valueOf)
+ this)
+(define-js-method *object-prototype* (hasOwnProperty p)
+ (and (hashq-get-handle (js-props this) p) #t))
+(define-js-method *object-prototype* (isPrototypeOf v)
+ (eq? this (js-prototype v)))
+(define-js-method *object-prototype* (propertyIsEnumerable p)
+ (and (hashq-get-handle (js-props this) p)
+ (not (prop-has-attr? this p 'DontEnum))))
+
+(define (object->string o error?)
+ (let ((toString (pget o 'toString)))
+ (if (procedure? toString)
+ (let ((x (call/this o toString)))
+ (if (and error? (is-a? x <js-object>))
+ (throw 'TypeError o 'default-value)
+ x))
+ (if error?
+ (throw 'TypeError o 'default-value)
+ o))))
+
+(define (object->number o error?)
+ (let ((valueOf (pget o 'valueOf)))
+ (if (procedure? valueOf)
+ (let ((x (call/this o valueOf)))
+ (if (and error? (is-a? x <js-object>))
+ (throw 'TypeError o 'default-value)
+ x))
+ (if error?
+ (throw 'TypeError o 'default-value)
+ o))))
+
+(define (object->value/string o)
+ (if (is-a? o <js-object>)
+ (object->number o #t)
+ o))
+
+(define (object->value/number o)
+ (if (is-a? o <js-object>)
+ (object->string o #t)
+ o))
+
+(define (object->value o)
+ ;; FIXME: if it's a date, we should try numbers first
+ (object->value/string o))
+
+(define (->primitive x)
+ (if (is-a? x <js-object>)
+ (object->value x)
+ x))
+
+(define (->boolean x)
+ (not (or (not x) (null? x) (eq? x *undefined*)
+ (and (number? x) (or (zero? x) (nan? x)))
+ (and (string? x) (= (string-length x) 0)))))
+
+(define (->number x)
+ (cond ((number? x) x)
+ ((boolean? x) (if x 1 0))
+ ((null? x) 0)
+ ((eq? x *undefined*) +nan.0)
+ ((is-a? x <js-object>) (object->number x #t))
+ ((string? x) (string->number x))
+ (else (throw 'TypeError x '->number))))
+
+(define (->integer x)
+ (let ((n (->number x)))
+ (cond ((nan? n) 0)
+ ((zero? n) n)
+ ((inf? n) n)
+ (else (inexact->exact (round n))))))
+
+(define (->int32 x)
+ (let ((n (->number x)))
+ (if (or (nan? n) (zero? n) (inf? n))
+ 0
+ (let ((m (logand (1- (ash 1 32)) (inexact->exact (round n)))))
+ (if (negative? n)
+ (- m (ash 1 32))
+ m)))))
+
+(define (->uint32 x)
+ (let ((n (->number x)))
+ (if (or (nan? n) (zero? n) (inf? n))
+ 0
+ (logand (1- (ash 1 32)) (inexact->exact (round n))))))
+
+(define (->uint16 x)
+ (let ((n (->number x)))
+ (if (or (nan? n) (zero? n) (inf? n))
+ 0
+ (logand (1- (ash 1 16)) (inexact->exact (round n))))))
+
+(define (->string x)
+ (cond ((eq? x *undefined*) "undefined")
+ ((null? x) "null")
+ ((boolean? x) (if x "true" "false"))
+ ((string? x) x)
+ ((number? x)
+ (cond ((nan? x) "NaN")
+ ((zero? x) "0")
+ ((inf? x) "Infinity")
+ (else (number->string x))))
+ (else (->string (object->value/string x)))))
+
+(define (->object x)
+ (cond ((eq? x *undefined*) (throw 'TypeError x '->object))
+ ((null? x) (throw 'TypeError x '->object))
+ ((boolean? x) (make <js-object> #\prototype Boolean #\value x))
+ ((number? x) (make <js-object> #\prototype String #\value x))
+ ((string? x) (make <js-object> #\prototype Number #\value x))
+ (else x)))
+
+(define (new-object . pairs)
+ (let ((o (make <js-object>)))
+ (map (lambda (pair)
+ (pput o (car pair) (cdr pair)))
+ pairs)
+ o))
+(slot-set! *object-prototype* 'constructor new-object)
+
+(define-method (new o . initargs)
+ (let ((ctor (js-constructor o)))
+ (if (not ctor)
+ (throw 'TypeError 'new o)
+ (let ((o (make <js-object>
+ #\prototype (or (js-prototype o) *object-prototype*))))
+ (let ((new-o (call/this o apply ctor initargs)))
+ (if (is-a? new-o <js-object>)
+ new-o
+ o))))))
+;;; ECMAScript for Guile
+
+;; Copyright (C) 2009, 2011 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language ecmascript compile-tree-il)
+ #\use-module (language tree-il)
+ #\use-module (ice-9 receive)
+ #\use-module (system base pmatch)
+ #\use-module (srfi srfi-1)
+ #\export (compile-tree-il))
+
+(define-syntax-rule (-> (type arg ...))
+ `(type ,arg ...))
+
+(define-syntax-rule (@implv sym)
+ (-> (@ '(language ecmascript impl) 'sym)))
+
+(define-syntax-rule (@impl sym arg ...)
+ (-> (apply (@implv sym) arg ...)))
+
+(define (empty-lexical-environment)
+ '())
+
+(define (econs name gensym env)
+ (acons name (-> (lexical name gensym)) env))
+
+(define (lookup name env)
+ (or (assq-ref env name)
+ (-> (toplevel name))))
+
+(define (compile-tree-il exp env opts)
+ (values
+ (parse-tree-il
+ (-> (begin (@impl js-init)
+ (comp exp (empty-lexical-environment)))))
+ env
+ env))
+
+(define (location x)
+ (and (pair? x)
+ (let ((props (source-properties x)))
+ (and (not (null? props))
+ props))))
+
+;; for emacs:
+;; (put 'pmatch/source 'scheme-indent-function 1)
+
+(define-syntax-rule (pmatch/source x clause ...)
+ (let ((x x))
+ (let ((res (pmatch x
+ clause ...)))
+ (let ((loc (location x)))
+ (if loc
+ (set-source-properties! res (location x))))
+ res)))
+
+(define current-return-tag (make-parameter #f))
+
+(define (return expr)
+ (-> (abort (or (current-return-tag) (error "return outside function"))
+ (list expr)
+ (-> (const '())))))
+
+(define (with-return-prompt body-thunk)
+ (let ((tag (gensym "return")))
+ (parameterize ((current-return-tag
+ (-> (lexical 'return tag))))
+ (-> (let '(return) (list tag)
+ (list (-> (apply (-> (primitive 'make-prompt-tag)))))
+ (-> (prompt (current-return-tag)
+ (body-thunk)
+ (let ((val (gensym "val")))
+ (-> (lambda-case
+ `(((k val) #f #f #f () (,(gensym) ,val))
+ ,(-> (lexical 'val val)))))))))))))
+
+(define (comp x e)
+ (let ((l (location x)))
+ (define (let1 what proc)
+ (let ((sym (gensym)))
+ (-> (let (list sym) (list sym) (list what)
+ (proc sym)))))
+ (define (begin1 what proc)
+ (let1 what (lambda (v)
+ (-> (begin (proc v)
+ (-> (lexical v v)))))))
+ (pmatch/source x
+ (null
+ ;; FIXME, null doesn't have much relation to EOL...
+ (-> (const '())))
+ (true
+ (-> (const #t)))
+ (false
+ (-> (const #f)))
+ ((number ,num)
+ (-> (const num)))
+ ((string ,str)
+ (-> (const str)))
+ (this
+ (@impl get-this))
+ ((+ ,a)
+ (-> (apply (-> (primitive '+))
+ (@impl ->number (comp a e))
+ (-> (const 0)))))
+ ((- ,a)
+ (-> (apply (-> (primitive '-)) (-> (const 0)) (comp a e))))
+ ((~ ,a)
+ (@impl bitwise-not (comp a e)))
+ ((! ,a)
+ (@impl logical-not (comp a e)))
+ ((+ ,a ,b)
+ (-> (apply (-> (primitive '+)) (comp a e) (comp b e))))
+ ((- ,a ,b)
+ (-> (apply (-> (primitive '-)) (comp a e) (comp b e))))
+ ((/ ,a ,b)
+ (-> (apply (-> (primitive '/)) (comp a e) (comp b e))))
+ ((* ,a ,b)
+ (-> (apply (-> (primitive '*)) (comp a e) (comp b e))))
+ ((% ,a ,b)
+ (@impl mod (comp a e) (comp b e)))
+ ((<< ,a ,b)
+ (@impl shift (comp a e) (comp b e)))
+ ((>> ,a ,b)
+ (@impl shift (comp a e) (comp `(- ,b) e)))
+ ((< ,a ,b)
+ (-> (apply (-> (primitive '<)) (comp a e) (comp b e))))
+ ((<= ,a ,b)
+ (-> (apply (-> (primitive '<=)) (comp a e) (comp b e))))
+ ((> ,a ,b)
+ (-> (apply (-> (primitive '>)) (comp a e) (comp b e))))
+ ((>= ,a ,b)
+ (-> (apply (-> (primitive '>=)) (comp a e) (comp b e))))
+ ((in ,a ,b)
+ (@impl has-property? (comp a e) (comp b e)))
+ ((== ,a ,b)
+ (-> (apply (-> (primitive 'equal?)) (comp a e) (comp b e))))
+ ((!= ,a ,b)
+ (-> (apply (-> (primitive 'not))
+ (-> (apply (-> (primitive 'equal?))
+ (comp a e) (comp b e))))))
+ ((=== ,a ,b)
+ (-> (apply (-> (primitive 'eqv?)) (comp a e) (comp b e))))
+ ((!== ,a ,b)
+ (-> (apply (-> (primitive 'not))
+ (-> (apply (-> (primitive 'eqv?))
+ (comp a e) (comp b e))))))
+ ((& ,a ,b)
+ (@impl band (comp a e) (comp b e)))
+ ((^ ,a ,b)
+ (@impl bxor (comp a e) (comp b e)))
+ ((bor ,a ,b)
+ (@impl bior (comp a e) (comp b e)))
+ ((and ,a ,b)
+ (-> (if (@impl ->boolean (comp a e))
+ (comp b e)
+ (-> (const #f)))))
+ ((or ,a ,b)
+ (let1 (comp a e)
+ (lambda (v)
+ (-> (if (@impl ->boolean (-> (lexical v v)))
+ (-> (lexical v v))
+ (comp b e))))))
+ ((if ,test ,then ,else)
+ (-> (if (@impl ->boolean (comp test e))
+ (comp then e)
+ (comp else e))))
+ ((if ,test ,then)
+ (-> (if (@impl ->boolean (comp test e))
+ (comp then e)
+ (@implv *undefined*))))
+ ((postinc (ref ,foo))
+ (begin1 (comp `(ref ,foo) e)
+ (lambda (var)
+ (-> (set! (lookup foo e)
+ (-> (apply (-> (primitive '+))
+ (-> (lexical var var))
+ (-> (const 1)))))))))
+ ((postinc (pref ,obj ,prop))
+ (let1 (comp obj e)
+ (lambda (objvar)
+ (begin1 (@impl pget
+ (-> (lexical objvar objvar))
+ (-> (const prop)))
+ (lambda (tmpvar)
+ (@impl pput
+ (-> (lexical objvar objvar))
+ (-> (const prop))
+ (-> (apply (-> (primitive '+))
+ (-> (lexical tmpvar tmpvar))
+ (-> (const 1))))))))))
+ ((postinc (aref ,obj ,prop))
+ (let1 (comp obj e)
+ (lambda (objvar)
+ (let1 (comp prop e)
+ (lambda (propvar)
+ (begin1 (@impl pget
+ (-> (lexical objvar objvar))
+ (-> (lexical propvar propvar)))
+ (lambda (tmpvar)
+ (@impl pput
+ (-> (lexical objvar objvar))
+ (-> (lexical propvar propvar))
+ (-> (apply (-> (primitive '+))
+ (-> (lexical tmpvar tmpvar))
+ (-> (const 1))))))))))))
+ ((postdec (ref ,foo))
+ (begin1 (comp `(ref ,foo) e)
+ (lambda (var)
+ (-> (set (lookup foo e)
+ (-> (apply (-> (primitive '-))
+ (-> (lexical var var))
+ (-> (const 1)))))))))
+ ((postdec (pref ,obj ,prop))
+ (let1 (comp obj e)
+ (lambda (objvar)
+ (begin1 (@impl pget
+ (-> (lexical objvar objvar))
+ (-> (const prop)))
+ (lambda (tmpvar)
+ (@impl pput
+ (-> (lexical objvar objvar))
+ (-> (const prop))
+ (-> (apply (-> (primitive '-))
+ (-> (lexical tmpvar tmpvar))
+ (-> (const 1))))))))))
+ ((postdec (aref ,obj ,prop))
+ (let1 (comp obj e)
+ (lambda (objvar)
+ (let1 (comp prop e)
+ (lambda (propvar)
+ (begin1 (@impl pget
+ (-> (lexical objvar objvar))
+ (-> (lexical propvar propvar)))
+ (lambda (tmpvar)
+ (@impl pput
+ (-> (lexical objvar objvar))
+ (-> (lexical propvar propvar))
+ (-> (inline
+ '- (-> (lexical tmpvar tmpvar))
+ (-> (const 1))))))))))))
+ ((preinc (ref ,foo))
+ (let ((v (lookup foo e)))
+ (-> (begin
+ (-> (set! v
+ (-> (apply (-> (primitive '+))
+ v
+ (-> (const 1))))))
+ v))))
+ ((preinc (pref ,obj ,prop))
+ (let1 (comp obj e)
+ (lambda (objvar)
+ (begin1 (-> (apply (-> (primitive '+))
+ (@impl pget
+ (-> (lexical objvar objvar))
+ (-> (const prop)))
+ (-> (const 1))))
+ (lambda (tmpvar)
+ (@impl pput (-> (lexical objvar objvar))
+ (-> (const prop))
+ (-> (lexical tmpvar tmpvar))))))))
+ ((preinc (aref ,obj ,prop))
+ (let1 (comp obj e)
+ (lambda (objvar)
+ (let1 (comp prop e)
+ (lambda (propvar)
+ (begin1 (-> (apply (-> (primitive '+))
+ (@impl pget
+ (-> (lexical objvar objvar))
+ (-> (lexical propvar propvar)))
+ (-> (const 1))))
+ (lambda (tmpvar)
+ (@impl pput
+ (-> (lexical objvar objvar))
+ (-> (lexical propvar propvar))
+ (-> (lexical tmpvar tmpvar))))))))))
+ ((predec (ref ,foo))
+ (let ((v (lookup foo e)))
+ (-> (begin
+ (-> (set! v
+ (-> (apply (-> (primitive '-))
+ v
+ (-> (const 1))))))
+ v))))
+ ((predec (pref ,obj ,prop))
+ (let1 (comp obj e)
+ (lambda (objvar)
+ (begin1 (-> (apply (-> (primitive '-))
+ (@impl pget
+ (-> (lexical objvar objvar))
+ (-> (const prop)))
+ (-> (const 1))))
+ (lambda (tmpvar)
+ (@impl pput
+ (-> (lexical objvar objvar))
+ (-> (const prop))
+ (-> (lexical tmpvar tmpvar))))))))
+ ((predec (aref ,obj ,prop))
+ (let1 (comp obj e)
+ (lambda (objvar)
+ (let1 (comp prop e)
+ (lambda (propvar)
+ (begin1 (-> (apply (-> (primitive '-))
+ (@impl pget
+ (-> (lexical objvar objvar))
+ (-> (lexical propvar propvar)))
+ (-> (const 1))))
+ (lambda (tmpvar)
+ (@impl pput
+ (-> (lexical objvar objvar))
+ (-> (lexical propvar propvar))
+ (-> (lexical tmpvar tmpvar))))))))))
+ ((ref ,id)
+ (lookup id e))
+ ((var . ,forms)
+ `(begin
+ ,@(map (lambda (form)
+ (pmatch form
+ ((,x ,y)
+ (-> (define x (comp y e))))
+ ((,x)
+ (-> (define x (@implv *undefined*))))
+ (else (error "bad var form" form))))
+ forms)))
+ ((begin)
+ (-> (void)))
+ ((begin ,form)
+ (comp form e))
+ ((begin . ,forms)
+ `(begin ,@(map (lambda (x) (comp x e)) forms)))
+ ((lambda ,formals ,body)
+ (let ((syms (map (lambda (x)
+ (gensym (string-append (symbol->string x) " ")))
+ formals)))
+ `(lambda ()
+ (lambda-case
+ ((() ,formals #f #f ,(map (lambda (x) (@implv *undefined*)) formals) ,syms)
+ ,(with-return-prompt
+ (lambda ()
+ (comp-body e body formals syms))))))))
+ ((call/this ,obj ,prop . ,args)
+ (@impl call/this*
+ obj
+ (-> (lambda '()
+ `(lambda-case
+ ((() #f #f #f () ())
+ (apply ,(@impl pget obj prop) ,@args)))))))
+ ((call (pref ,obj ,prop) ,args)
+ (comp `(call/this ,(comp obj e)
+ ,(-> (const prop))
+ ,@(map (lambda (x) (comp x e)) args))
+ e))
+ ((call (aref ,obj ,prop) ,args)
+ (comp `(call/this ,(comp obj e)
+ ,(comp prop e)
+ ,@(map (lambda (x) (comp x e)) args))
+ e))
+ ((call ,proc ,args)
+ `(apply ,(comp proc e)
+ ,@(map (lambda (x) (comp x e)) args)))
+ ((return ,expr)
+ (return (comp expr e)))
+ ((array . ,args)
+ `(apply ,(@implv new-array)
+ ,@(map (lambda (x) (comp x e)) args)))
+ ((object . ,args)
+ `(apply ,(@implv new-object)
+ ,@(map (lambda (x)
+ (pmatch x
+ ((,prop ,val)
+ (-> (apply (-> (primitive 'cons))
+ (-> (const prop))
+ (comp val e))))
+ (else
+ (error "bad prop-val pair" x))))
+ args)))
+ ((pref ,obj ,prop)
+ (@impl pget
+ (comp obj e)
+ (-> (const prop))))
+ ((aref ,obj ,index)
+ (@impl pget
+ (comp obj e)
+ (comp index e)))
+ ((= (ref ,name) ,val)
+ (let ((v (lookup name e)))
+ (-> (begin
+ (-> (set! v (comp val e)))
+ v))))
+ ((= (pref ,obj ,prop) ,val)
+ (@impl pput
+ (comp obj e)
+ (-> (const prop))
+ (comp val e)))
+ ((= (aref ,obj ,prop) ,val)
+ (@impl pput
+ (comp obj e)
+ (comp prop e)
+ (comp val e)))
+ ((+= ,what ,val)
+ (comp `(= ,what (+ ,what ,val)) e))
+ ((-= ,what ,val)
+ (comp `(= ,what (- ,what ,val)) e))
+ ((/= ,what ,val)
+ (comp `(= ,what (/ ,what ,val)) e))
+ ((*= ,what ,val)
+ (comp `(= ,what (* ,what ,val)) e))
+ ((%= ,what ,val)
+ (comp `(= ,what (% ,what ,val)) e))
+ ((>>= ,what ,val)
+ (comp `(= ,what (>> ,what ,val)) e))
+ ((<<= ,what ,val)
+ (comp `(= ,what (<< ,what ,val)) e))
+ ((>>>= ,what ,val)
+ (comp `(= ,what (>>> ,what ,val)) e))
+ ((&= ,what ,val)
+ (comp `(= ,what (& ,what ,val)) e))
+ ((bor= ,what ,val)
+ (comp `(= ,what (bor ,what ,val)) e))
+ ((^= ,what ,val)
+ (comp `(= ,what (^ ,what ,val)) e))
+ ((new ,what ,args)
+ (@impl new
+ (map (lambda (x) (comp x e))
+ (cons what args))))
+ ((delete (pref ,obj ,prop))
+ (@impl pdel
+ (comp obj e)
+ (-> (const prop))))
+ ((delete (aref ,obj ,prop))
+ (@impl pdel
+ (comp obj e)
+ (comp prop e)))
+ ((void ,expr)
+ (-> (begin
+ (comp expr e)
+ (@implv *undefined*))))
+ ((typeof ,expr)
+ (@impl typeof
+ (comp expr e)))
+ ((do ,statement ,test)
+ (let ((%loop (gensym "%loop "))
+ (%continue (gensym "%continue ")))
+ (let ((e (econs '%loop %loop (econs '%continue %continue e))))
+ (-> (letrec '(%loop %continue) (list %loop %continue)
+ (list (-> (lambda '()
+ (-> (lambda-case
+ `((() #f #f #f () ())
+ ,(-> (begin
+ (comp statement e)
+ (-> (apply (-> (lexical '%continue %continue)))))))))))
+ (-> (lambda '()
+ (-> (lambda-case
+ `((() #f #f #f () ())
+ ,(-> (if (@impl ->boolean (comp test e))
+ (-> (apply (-> (lexical '%loop %loop))))
+ (@implv *undefined*)))))))))
+ (-> (apply (-> (lexical '%loop %loop)))))))))
+ ((while ,test ,statement)
+ (let ((%continue (gensym "%continue ")))
+ (let ((e (econs '%continue %continue e)))
+ (-> (letrec '(%continue) (list %continue)
+ (list (-> (lambda '()
+ (-> (lambda-case
+ `((() #f #f #f () ())
+ ,(-> (if (@impl ->boolean (comp test e))
+ (-> (begin (comp statement e)
+ (-> (apply (-> (lexical '%continue %continue))))))
+ (@implv *undefined*)))))))))
+ (-> (apply (-> (lexical '%continue %continue)))))))))
+
+ ((for ,init ,test ,inc ,statement)
+ (let ((%continue (gensym "%continue ")))
+ (let ((e (econs '%continue %continue e)))
+ (-> (letrec '(%continue) (list %continue)
+ (list (-> (lambda '()
+ (-> (lambda-case
+ `((() #f #f #f () ())
+ ,(-> (if (if test
+ (@impl ->boolean (comp test e))
+ (comp 'true e))
+ (-> (begin (comp statement e)
+ (comp (or inc '(begin)) e)
+ (-> (apply (-> (lexical '%continue %continue))))))
+ (@implv *undefined*)))))))))
+ (-> (begin (comp (or init '(begin)) e)
+ (-> (apply (-> (lexical '%continue %continue)))))))))))
+
+ ((for-in ,var ,object ,statement)
+ (let ((%enum (gensym "%enum "))
+ (%continue (gensym "%continue ")))
+ (let ((e (econs '%enum %enum (econs '%continue %continue e))))
+ (-> (letrec '(%enum %continue) (list %enum %continue)
+ (list (@impl make-enumerator (comp object e))
+ (-> (lambda '()
+ (-> (lambda-case
+ `((() #f #f #f () ())
+ (-> (if (@impl ->boolean
+ (@impl pget
+ (-> (lexical '%enum %enum))
+ (-> (const 'length))))
+ (-> (begin
+ (comp `(= ,var (call/this ,(-> (lexical '%enum %enum))
+ ,(-> (const 'pop))))
+ e)
+ (comp statement e)
+ (-> (apply (-> (lexical '%continue %continue))))))
+ (@implv *undefined*)))))))))
+ (-> (apply (-> (lexical '%continue %continue)))))))))
+
+ ((block ,x)
+ (comp x e))
+ (else
+ (error "compilation not yet implemented:" x)))))
+
+(define (comp-body e body formals formal-syms)
+ (define (process)
+ (let lp ((in body) (out '()) (rvars '()))
+ (pmatch in
+ (((var (,x) . ,morevars) . ,rest)
+ (lp `((var . ,morevars) . ,rest)
+ out
+ (if (or (memq x rvars) (memq x formals))
+ rvars
+ (cons x rvars))))
+ (((var (,x ,y) . ,morevars) . ,rest)
+ (lp `((var . ,morevars) . ,rest)
+ `((= (ref ,x) ,y) . ,out)
+ (if (or (memq x rvars) (memq x formals))
+ rvars
+ (cons x rvars))))
+ (((var) . ,rest)
+ (lp rest out rvars))
+ ((,x . ,rest) (guard (and (pair? x) (eq? (car x) 'lambda)))
+ (lp rest
+ (cons x out)
+ rvars))
+ ((,x . ,rest) (guard (pair? x))
+ (receive (sub-out rvars)
+ (lp x '() rvars)
+ (lp rest
+ (cons sub-out out)
+ rvars)))
+ ((,x . ,rest)
+ (lp rest
+ (cons x out)
+ rvars))
+ (()
+ (values (reverse! out)
+ rvars)))))
+ (receive (out rvars)
+ (process)
+ (let* ((names (reverse rvars))
+ (syms (map (lambda (x)
+ (gensym (string-append (symbol->string x) " ")))
+ names))
+ (e (fold econs (fold econs e formals formal-syms) names syms)))
+ (-> (let names syms (map (lambda (x) (@implv *undefined*)) names)
+ (comp out e))))))
+;;; ECMAScript for Guile
+
+;; Copyright (C) 2009 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language ecmascript function)
+ #\use-module (oop goops)
+ #\use-module (language ecmascript base)
+ #\export (*function-prototype* *program-wrappers*))
+
+
+(define-class <js-program-wrapper> (<js-object>))
+
+(define *program-wrappers* (make-doubly-weak-hash-table 31))
+
+(define *function-prototype* (make <js-object> #\class "Function"
+ #\value (lambda args *undefined*)))
+
+(define-js-method *function-prototype* (toString)
+ (format #f "~A" (js-value this)))
+
+(define-js-method *function-prototype* (apply this-arg array)
+ (cond ((or (null? array) (eq? array *undefined*))
+ (call/this this-arg (js-value this)))
+ ((is-a? array <js-array-object>)
+ (call/this this-arg
+ (lambda ()
+ (apply (js-value this)
+ (vector->list (js-array-vector array))))))
+ (else
+ (throw 'TypeError 'apply array))))
+
+(define-js-method *function-prototype* (call this-arg . args)
+ (call/this this-arg
+ (lambda ()
+ (apply (js-value this) args))))
+
+(define-method (pget (o <applicable>) p)
+ (let ((wrapper (hashq-ref *program-wrappers* o)))
+ (if wrapper
+ (pget wrapper p)
+ (pget *function-prototype* p))))
+
+(define-method (pput (o <applicable>) p v)
+ (let ((wrapper (hashq-ref *program-wrappers* o)))
+ (if wrapper
+ (pput wrapper p v)
+ (let ((wrapper (make <js-program-wrapper> #\value o #\class "Function"
+ #\prototype *function-prototype*)))
+ (hashq-set! *program-wrappers* o wrapper)
+ (pput wrapper p v)))))
+
+(define-method (js-prototype (o <applicable>))
+ (let ((wrapper (hashq-ref *program-wrappers* o)))
+ (if wrapper
+ (js-prototype wrapper)
+ #f)))
+
+(define-method (js-constructor (o <applicable>))
+ (let ((wrapper (hashq-ref *program-wrappers* o)))
+ (if wrapper
+ (js-constructor wrapper)
+ #f)))
+;;; ECMAScript for Guile
+
+;; Copyright (C) 2009 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language ecmascript impl)
+ #\use-module (oop goops)
+ #\use-module (language ecmascript base)
+ #\use-module (language ecmascript function)
+ #\use-module (language ecmascript array)
+ #\re-export (*undefined* *this* call/this*
+ pget pput pdel has-property?
+ ->boolean ->number
+ new-object new new-array)
+ #\export (js-init get-this
+ typeof
+ bitwise-not logical-not
+ shift
+ mod
+ band bxor bior
+ make-enumerator))
+
+
+(define-class <js-module-object> (<js-object>)
+ (module #\init-form (current-module) #\init-keyword #\module
+ #\getter js-module))
+(define-method (pget (o <js-module-object>) (p <string>))
+ (pget o (string->symbol p)))
+(define-method (pget (o <js-module-object>) (p <symbol>))
+ (let ((v (module-variable (js-module o) p)))
+ (if v
+ (variable-ref v)
+ (next-method))))
+(define-method (pput (o <js-module-object>) (p <string>) v)
+ (pput o (string->symbol p) v))
+(define-method (pput (o <js-module-object>) (p <symbol>) v)
+ (module-define! (js-module o) p v))
+(define-method (prop-attrs (o <js-module-object>) (p <symbol>))
+ (cond ((module-local-variable (js-module o) p) '())
+ ((module-variable (js-module o) p) '(DontDelete ReadOnly))
+ (else (next-method))))
+(define-method (prop-attrs (o <js-module-object>) (p <string>))
+ (prop-attrs o (string->symbol p)))
+(define-method (prop-keys (o <js-module-object>))
+ (append (hash-map->list (lambda (k v) k) (module-obarray (js-module o)))
+ (next-method)))
+
+;; we could make a renamer, but having obj['foo-bar'] should be enough
+(define (js-require modstr)
+ (make <js-module-object> #\module
+ (resolve-interface (map string->symbol (string-split modstr #\.)))))
+
+(define-class <js-global-object> (<js-module-object>))
+(define-method (js-module (o <js-global-object>))
+ (current-module))
+
+(define (init-js-bindings! mod)
+ (module-define! mod 'NaN +nan.0)
+ (module-define! mod 'Infinity +inf.0)
+ (module-define! mod 'undefined *undefined*)
+ (module-define! mod 'require js-require)
+ ;; isNAN, isFinite, parseFloat, parseInt, eval
+ ;; decodeURI, decodeURIComponent, encodeURI, encodeURIComponent
+ ;; Object Function Array String Boolean Number Date RegExp Error EvalError
+ ;; RangeError ReferenceError SyntaxError TypeError URIError
+ (module-define! mod 'Object *object-prototype*)
+ (module-define! mod 'Array *array-prototype*))
+
+(define (js-init)
+ (cond ((get-this))
+ (else
+ (fluid-set! *this* (make <js-global-object>))
+ (init-js-bindings! (current-module)))))
+
+(define (get-this)
+ (fluid-ref *this*))
+
+(define (typeof x)
+ (cond ((eq? x *undefined*) "undefined")
+ ((null? x) "object")
+ ((boolean? x) "boolean")
+ ((number? x) "number")
+ ((string? x) "string")
+ ((procedure? x) "function")
+ ((is-a? x <js-object>) "object")
+ (else "scm")))
+
+(define bitwise-not lognot)
+(define (logical-not x)
+ (not (->boolean (->primitive x))))
+
+(define shift ash)
+
+(define band logand)
+(define bxor logxor)
+(define bior logior)
+
+(define mod modulo)
+
+(define-method (+ (a <string>) (b <string>))
+ (string-append a b))
+
+(define-method (+ (a <string>) b)
+ (string-append a (->string b)))
+
+(define-method (+ a (b <string>))
+ (string-append (->string a) b))
+
+(define-method (+ a b)
+ (+ (->number a) (->number b)))
+
+(define-method (- a b)
+ (- (->number a) (->number b)))
+
+(define-method (* a b)
+ (* (->number a) (->number b)))
+
+(define-method (/ a b)
+ (/ (->number a) (->number b)))
+
+(define-method (< a b)
+ (< (->number a) (->number b)))
+(define-method (< (a <string>) (b <string>))
+ (string< a b))
+
+(define-method (<= a b)
+ (<= (->number a) (->number b)))
+(define-method (<= (a <string>) (b <string>))
+ (string<= a b))
+
+(define-method (>= a b)
+ (>= (->number a) (->number b)))
+(define-method (>= (a <string>) (b <string>))
+ (string>= a b))
+
+(define-method (> a b)
+ (> (->number a) (->number b)))
+(define-method (> (a <string>) (b <string>))
+ (string> a b))
+
+(define (obj-and-prototypes o)
+ (if o
+ (cons o (obj-and-prototypes (js-prototype o)))
+ '()))
+
+(define (make-enumerator obj)
+ (let ((props (make-hash-table 23)))
+ (for-each (lambda (o)
+ (for-each (lambda (k) (hashq-set! props k #t))
+ (prop-keys o)))
+ (obj-and-prototypes obj))
+ (apply new-array (filter (lambda (p)
+ (not (prop-has-attr? obj p 'DontEnum)))
+ (hash-map->list (lambda (k v) k) props)))))
+;;; ECMAScript for Guile
+
+;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language ecmascript parse)
+ #\use-module (system base lalr)
+ #\use-module (language ecmascript tokenize)
+ #\export (read-ecmascript read-ecmascript/1 make-parser))
+
+(define* (syntax-error message #\optional token)
+ (if (lexical-token? token)
+ (throw 'syntax-error #f message
+ (and=> (lexical-token-source token)
+ source-location->source-properties)
+ (or (lexical-token-value token)
+ (lexical-token-category token))
+ #f)
+ (throw 'syntax-error #f message #f token #f)))
+
+(define (read-ecmascript port)
+ (let ((parse (make-parser)))
+ (parse (make-tokenizer port) syntax-error)))
+
+(define (read-ecmascript/1 port)
+ (let ((parse (make-parser)))
+ (parse (make-tokenizer/1 port) syntax-error)))
+
+(define *eof-object*
+ (call-with-input-string "" read-char))
+
+(define (make-parser)
+ ;; Return a fresh ECMAScript parser. Parsers produced by `lalr-scm' are now
+ ;; stateful (e.g., they won't invoke the tokenizer any more once it has
+ ;; returned `*eoi*'), hence the need to instantiate new parsers.
+
+ (lalr-parser
+ ;; terminal (i.e. input) token types
+ (lbrace rbrace lparen rparen lbracket rbracket dot semicolon comma <
+ > <= >= == != === !== + - * % ++ -- << >> >>> & bor ^ ! ~ && or ?
+ colon = += -= *= %= <<= >>= >>>= &= bor= ^= / /=
+
+ break else new var case finally return void catch for switch while
+ continue function this with default if throw delete in try do
+ instanceof typeof null true false
+
+ Identifier StringLiteral NumericLiteral RegexpLiteral)
+
+
+ (Program (SourceElements) \: $1
+ (*eoi*) \: *eof-object*)
+
+ ;;
+ ;; Verily, here we define statements. Expressions are defined
+ ;; afterwards.
+ ;;
+
+ (SourceElement (Statement) \: $1
+ (FunctionDeclaration) \: $1)
+
+ (FunctionDeclaration (function Identifier lparen rparen lbrace FunctionBody rbrace) \: `(var (,$2 (lambda () ,$6)))
+ (function Identifier lparen FormalParameterList rparen lbrace FunctionBody rbrace) \: `(var (,$2 (lambda ,$4 ,$7))))
+ (FunctionExpression (function lparen rparen lbrace FunctionBody rbrace) \: `(lambda () ,$5)
+ (function Identifier lparen rparen lbrace FunctionBody rbrace) \: `(lambda () ,$6)
+ (function lparen FormalParameterList rparen lbrace FunctionBody rbrace) \: `(lambda ,$3 ,$6)
+ (function Identifier lparen FormalParameterList rparen lbrace FunctionBody rbrace) \: `(lambda ,$4 ,$7))
+ (FormalParameterList (Identifier) \: `(,$1)
+ (FormalParameterList comma Identifier) \: `(,@$1 ,$3))
+ (SourceElements (SourceElement) \: $1
+ (SourceElements SourceElement) \: (if (and (pair? $1) (eq? (car $1) 'begin))
+ `(begin ,@(cdr $1) ,$2)
+ `(begin ,$1 ,$2)))
+ (FunctionBody (SourceElements) \: $1
+ () \: '(begin))
+
+ (Statement (Block) \: $1
+ (VariableStatement) \: $1
+ (EmptyStatement) \: $1
+ (ExpressionStatement) \: $1
+ (IfStatement) \: $1
+ (IterationStatement) \: $1
+ (ContinueStatement) \: $1
+ (BreakStatement) \: $1
+ (ReturnStatement) \: $1
+ (WithStatement) \: $1
+ (LabelledStatement) \: $1
+ (SwitchStatement) \: $1
+ (ThrowStatement) \: $1
+ (TryStatement) \: $1)
+
+ (Block (lbrace StatementList rbrace) \: `(block ,$2))
+ (StatementList (Statement) \: $1
+ (StatementList Statement) \: (if (and (pair? $1) (eq? (car $1) 'begin))
+ `(begin ,@(cdr $1) ,$2)
+ `(begin ,$1 ,$2)))
+
+ (VariableStatement (var VariableDeclarationList) \: `(var ,@$2))
+ (VariableDeclarationList (VariableDeclaration) \: `(,$1)
+ (VariableDeclarationList comma VariableDeclaration) \: `(,@$1 ,$2))
+ (VariableDeclarationListNoIn (VariableDeclarationNoIn) \: `(,$1)
+ (VariableDeclarationListNoIn comma VariableDeclarationNoIn) \: `(,@$1 ,$2))
+ (VariableDeclaration (Identifier) \: `(,$1)
+ (Identifier Initialiser) \: `(,$1 ,$2))
+ (VariableDeclarationNoIn (Identifier) \: `(,$1)
+ (Identifier Initialiser) \: `(,$1 ,$2))
+ (Initialiser (= AssignmentExpression) \: $2)
+ (InitialiserNoIn (= AssignmentExpressionNoIn) \: $2)
+
+ (EmptyStatement (semicolon) \: '(begin))
+
+ (ExpressionStatement (Expression semicolon) \: $1)
+
+ (IfStatement (if lparen Expression rparen Statement else Statement) \: `(if ,$3 ,$5 ,$7)
+ (if lparen Expression rparen Statement) \: `(if ,$3 ,$5))
+
+ (IterationStatement (do Statement while lparen Expression rparen semicolon) \: `(do ,$2 ,$5)
+
+ (while lparen Expression rparen Statement) \: `(while ,$3 ,$5)
+
+ (for lparen semicolon semicolon rparen Statement) \: `(for #f #f #f ,$6)
+ (for lparen semicolon semicolon Expression rparen Statement) \: `(for #f #f ,$5 ,$7)
+ (for lparen semicolon Expression semicolon rparen Statement) \: `(for #f ,$4 #f ,$7)
+ (for lparen semicolon Expression semicolon Expression rparen Statement) \: `(for #f ,$4 ,$6 ,$8)
+
+ (for lparen ExpressionNoIn semicolon semicolon rparen Statement) \: `(for ,$3 #f #f ,$7)
+ (for lparen ExpressionNoIn semicolon semicolon Expression rparen Statement) \: `(for ,$3 #f ,$6 ,$8)
+ (for lparen ExpressionNoIn semicolon Expression semicolon rparen Statement) \: `(for ,$3 ,$5 #f ,$8)
+ (for lparen ExpressionNoIn semicolon Expression semicolon Expression rparen Statement) \: `(for ,$3 ,$5 ,$7 ,$9)
+
+ (for lparen var VariableDeclarationListNoIn semicolon semicolon rparen Statement) \: `(for (var ,@$4) #f #f ,$8)
+ (for lparen var VariableDeclarationListNoIn semicolon semicolon Expression rparen Statement) \: `(for (var ,@$4) #f ,$7 ,$9)
+ (for lparen var VariableDeclarationListNoIn semicolon Expression semicolon rparen Statement) \: `(for (var ,@$4) ,$6 #f ,$9)
+ (for lparen var VariableDeclarationListNoIn semicolon Expression semicolon Expression rparen Statement) \: `(for (var ,@$4) ,$6 ,$8 ,$10)
+
+ (for lparen LeftHandSideExpression in Expression rparen Statement) \: `(for-in ,$3 ,$5 ,$7)
+ (for lparen var VariableDeclarationNoIn in Expression rparen Statement) \: `(begin (var ,$4) (for-in (ref ,@$4) ,$6 ,$8)))
+
+ (ContinueStatement (continue Identifier semicolon) \: `(continue ,$2)
+ (continue semicolon) \: `(continue))
+
+ (BreakStatement (break Identifier semicolon) \: `(break ,$2)
+ (break semicolon) \: `(break))
+
+ (ReturnStatement (return Expression semicolon) \: `(return ,$2)
+ (return semicolon) \: `(return))
+
+ (WithStatement (with lparen Expression rparen Statement) \: `(with ,$3 ,$5))
+
+ (SwitchStatement (switch lparen Expression rparen CaseBlock) \: `(switch ,$3 ,@$5))
+ (CaseBlock (lbrace rbrace) \: '()
+ (lbrace CaseClauses rbrace) \: $2
+ (lbrace CaseClauses DefaultClause rbrace) \: `(,@$2 ,@$3)
+ (lbrace DefaultClause rbrace) \: `(,$2)
+ (lbrace DefaultClause CaseClauses rbrace) \: `(,@$2 ,@$3))
+ (CaseClauses (CaseClause) \: `(,$1)
+ (CaseClauses CaseClause) \: `(,@$1 ,$2))
+ (CaseClause (case Expression colon) \: `(case ,$2)
+ (case Expression colon StatementList) \: `(case ,$2 ,$4))
+ (DefaultClause (default colon) \: `(default)
+ (default colon StatementList) \: `(default ,$3))
+
+ (LabelledStatement (Identifier colon Statement) \: `(label ,$1 ,$3))
+
+ (ThrowStatement (throw Expression semicolon) \: `(throw ,$2))
+
+ (TryStatement (try Block Catch) \: `(try ,$2 ,$3 #f)
+ (try Block Finally) \: `(try ,$2 #f ,$3)
+ (try Block Catch Finally) \: `(try ,$2 ,$3 ,$4))
+ (Catch (catch lparen Identifier rparen Block) \: `(catch ,$3 ,$5))
+ (Finally (finally Block) \: `(finally ,$2))
+
+ ;;
+ ;; As promised, expressions. We build up to Expression bottom-up, so
+ ;; as to get operator precedence right.
+ ;;
+
+ (PrimaryExpression (this) \: 'this
+ (null) \: 'null
+ (true) \: 'true
+ (false) \: 'false
+ (Identifier) \: `(ref ,$1)
+ (StringLiteral) \: `(string ,$1)
+ (RegexpLiteral) \: `(regexp ,$1)
+ (NumericLiteral) \: `(number ,$1)
+ (dot NumericLiteral) \: `(number ,(string->number (string-append "." (number->string $2))))
+ (ArrayLiteral) \: $1
+ (ObjectLiteral) \: $1
+ (lparen Expression rparen) \: $2)
+
+ (ArrayLiteral (lbracket rbracket) \: '(array)
+ (lbracket Elision rbracket) \: '(array ,@$2)
+ (lbracket ElementList rbracket) \: `(array ,@$2)
+ (lbracket ElementList comma rbracket) \: `(array ,@$2)
+ (lbracket ElementList comma Elision rbracket) \: `(array ,@$2))
+ (ElementList (AssignmentExpression) \: `(,$1)
+ (Elision AssignmentExpression) \: `(,@$1 ,$2)
+ (ElementList comma AssignmentExpression) \: `(,@$1 ,$3)
+ (ElementList comma Elision AssignmentExpression) \: `(,@$1 ,@$3 ,$4))
+ (Elision (comma) \: '((number 0))
+ (Elision comma) \: `(,@$1 (number 0)))
+
+ (ObjectLiteral (lbrace rbrace) \: `(object)
+ (lbrace PropertyNameAndValueList rbrace) \: `(object ,@$2))
+ (PropertyNameAndValueList (PropertyName colon AssignmentExpression) \: `((,$1 ,$3))
+ (PropertyNameAndValueList comma PropertyName colon AssignmentExpression) \: `(,@$1 (,$3 ,$5)))
+ (PropertyName (Identifier) \: $1
+ (StringLiteral) \: (string->symbol $1)
+ (NumericLiteral) \: $1)
+
+ (MemberExpression (PrimaryExpression) \: $1
+ (FunctionExpression) \: $1
+ (MemberExpression lbracket Expression rbracket) \: `(aref ,$1 ,$3)
+ (MemberExpression dot Identifier) \: `(pref ,$1 ,$3)
+ (new MemberExpression Arguments) \: `(new ,$2 ,$3))
+
+ (NewExpression (MemberExpression) \: $1
+ (new NewExpression) \: `(new ,$2 ()))
+
+ (CallExpression (MemberExpression Arguments) \: `(call ,$1 ,$2)
+ (CallExpression Arguments) \: `(call ,$1 ,$2)
+ (CallExpression lbracket Expression rbracket) \: `(aref ,$1 ,$3)
+ (CallExpression dot Identifier) \: `(pref ,$1 ,$3))
+ (Arguments (lparen rparen) \: '()
+ (lparen ArgumentList rparen) \: $2)
+ (ArgumentList (AssignmentExpression) \: `(,$1)
+ (ArgumentList comma AssignmentExpression) \: `(,@$1 ,$3))
+
+ (LeftHandSideExpression (NewExpression) \: $1
+ (CallExpression) \: $1)
+
+ (PostfixExpression (LeftHandSideExpression) \: $1
+ (LeftHandSideExpression ++) \: `(postinc ,$1)
+ (LeftHandSideExpression --) \: `(postdec ,$1))
+
+ (UnaryExpression (PostfixExpression) \: $1
+ (delete UnaryExpression) \: `(delete ,$2)
+ (void UnaryExpression) \: `(void ,$2)
+ (typeof UnaryExpression) \: `(typeof ,$2)
+ (++ UnaryExpression) \: `(preinc ,$2)
+ (-- UnaryExpression) \: `(predec ,$2)
+ (+ UnaryExpression) \: `(+ ,$2)
+ (- UnaryExpression) \: `(- ,$2)
+ (~ UnaryExpression) \: `(~ ,$2)
+ (! UnaryExpression) \: `(! ,$2))
+
+ (MultiplicativeExpression (UnaryExpression) \: $1
+ (MultiplicativeExpression * UnaryExpression) \: `(* ,$1 ,$3)
+ (MultiplicativeExpression / UnaryExpression) \: `(/ ,$1 ,$3)
+ (MultiplicativeExpression % UnaryExpression) \: `(% ,$1 ,$3))
+
+ (AdditiveExpression (MultiplicativeExpression) \: $1
+ (AdditiveExpression + MultiplicativeExpression) \: `(+ ,$1 ,$3)
+ (AdditiveExpression - MultiplicativeExpression) \: `(- ,$1 ,$3))
+
+ (ShiftExpression (AdditiveExpression) \: $1
+ (ShiftExpression << MultiplicativeExpression) \: `(<< ,$1 ,$3)
+ (ShiftExpression >> MultiplicativeExpression) \: `(>> ,$1 ,$3)
+ (ShiftExpression >>> MultiplicativeExpression) \: `(>>> ,$1 ,$3))
+
+ (RelationalExpression (ShiftExpression) \: $1
+ (RelationalExpression < ShiftExpression) \: `(< ,$1 ,$3)
+ (RelationalExpression > ShiftExpression) \: `(> ,$1 ,$3)
+ (RelationalExpression <= ShiftExpression) \: `(<= ,$1 ,$3)
+ (RelationalExpression >= ShiftExpression) \: `(>= ,$1 ,$3)
+ (RelationalExpression instanceof ShiftExpression) \: `(instanceof ,$1 ,$3)
+ (RelationalExpression in ShiftExpression) \: `(in ,$1 ,$3))
+
+ (RelationalExpressionNoIn (ShiftExpression) \: $1
+ (RelationalExpressionNoIn < ShiftExpression) \: `(< ,$1 ,$3)
+ (RelationalExpressionNoIn > ShiftExpression) \: `(> ,$1 ,$3)
+ (RelationalExpressionNoIn <= ShiftExpression) \: `(<= ,$1 ,$3)
+ (RelationalExpressionNoIn >= ShiftExpression) \: `(>= ,$1 ,$3)
+ (RelationalExpressionNoIn instanceof ShiftExpression) \: `(instanceof ,$1 ,$3))
+
+ (EqualityExpression (RelationalExpression) \: $1
+ (EqualityExpression == RelationalExpression) \: `(== ,$1 ,$3)
+ (EqualityExpression != RelationalExpression) \: `(!= ,$1 ,$3)
+ (EqualityExpression === RelationalExpression) \: `(=== ,$1 ,$3)
+ (EqualityExpression !== RelationalExpression) \: `(!== ,$1 ,$3))
+
+ (EqualityExpressionNoIn (RelationalExpressionNoIn) \: $1
+ (EqualityExpressionNoIn == RelationalExpressionNoIn) \: `(== ,$1 ,$3)
+ (EqualityExpressionNoIn != RelationalExpressionNoIn) \: `(!= ,$1 ,$3)
+ (EqualityExpressionNoIn === RelationalExpressionNoIn) \: `(=== ,$1 ,$3)
+ (EqualityExpressionNoIn !== RelationalExpressionNoIn) \: `(!== ,$1 ,$3))
+
+ (BitwiseANDExpression (EqualityExpression) \: $1
+ (BitwiseANDExpression & EqualityExpression) \: `(& ,$1 ,$3))
+ (BitwiseANDExpressionNoIn (EqualityExpressionNoIn) \: $1
+ (BitwiseANDExpressionNoIn & EqualityExpressionNoIn) \: `(& ,$1 ,$3))
+
+ (BitwiseXORExpression (BitwiseANDExpression) \: $1
+ (BitwiseXORExpression ^ BitwiseANDExpression) \: `(^ ,$1 ,$3))
+ (BitwiseXORExpressionNoIn (BitwiseANDExpressionNoIn) \: $1
+ (BitwiseXORExpressionNoIn ^ BitwiseANDExpressionNoIn) \: `(^ ,$1 ,$3))
+
+ (BitwiseORExpression (BitwiseXORExpression) \: $1
+ (BitwiseORExpression bor BitwiseXORExpression) \: `(bor ,$1 ,$3))
+ (BitwiseORExpressionNoIn (BitwiseXORExpressionNoIn) \: $1
+ (BitwiseORExpressionNoIn bor BitwiseXORExpressionNoIn) \: `(bor ,$1 ,$3))
+
+ (LogicalANDExpression (BitwiseORExpression) \: $1
+ (LogicalANDExpression && BitwiseORExpression) \: `(and ,$1 ,$3))
+ (LogicalANDExpressionNoIn (BitwiseORExpressionNoIn) \: $1
+ (LogicalANDExpressionNoIn && BitwiseORExpressionNoIn) \: `(and ,$1 ,$3))
+
+ (LogicalORExpression (LogicalANDExpression) \: $1
+ (LogicalORExpression or LogicalANDExpression) \: `(or ,$1 ,$3))
+ (LogicalORExpressionNoIn (LogicalANDExpressionNoIn) \: $1
+ (LogicalORExpressionNoIn or LogicalANDExpressionNoIn) \: `(or ,$1 ,$3))
+
+ (ConditionalExpression (LogicalORExpression) \: $1
+ (LogicalORExpression ? AssignmentExpression colon AssignmentExpression) \: `(if ,$1 ,$3 ,$5))
+ (ConditionalExpressionNoIn (LogicalORExpressionNoIn) \: $1
+ (LogicalORExpressionNoIn ? AssignmentExpressionNoIn colon AssignmentExpressionNoIn) \: `(if ,$1 ,$3 ,$5))
+
+ (AssignmentExpression (ConditionalExpression) \: $1
+ (LeftHandSideExpression AssignmentOperator AssignmentExpression) \: `(,$2 ,$1 ,$3))
+ (AssignmentExpressionNoIn (ConditionalExpressionNoIn) \: $1
+ (LeftHandSideExpression AssignmentOperator AssignmentExpressionNoIn) \: `(,$2 ,$1 ,$3))
+ (AssignmentOperator (=) \: '=
+ (*=) \: '*=
+ (/=) \: '/=
+ (%=) \: '%=
+ (+=) \: '+=
+ (-=) \: '-=
+ (<<=) \: '<<=
+ (>>=) \: '>>=
+ (>>>=) \: '>>>=
+ (&=) \: '&=
+ (^=) \: '^=
+ (bor=) \: 'bor=)
+
+ (Expression (AssignmentExpression) \: $1
+ (Expression comma AssignmentExpression) \: `(begin ,$1 ,$3))
+ (ExpressionNoIn (AssignmentExpressionNoIn) \: $1
+ (ExpressionNoIn comma AssignmentExpressionNoIn) \: `(begin ,$1 ,$3))))
+;;; ECMAScript specification for Guile
+
+;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language ecmascript spec)
+ #\use-module (system base language)
+ #\use-module (language ecmascript parse)
+ #\use-module (language ecmascript compile-tree-il)
+ #\export (ecmascript))
+
+;;;
+;;; Language definition
+;;;
+
+(define-language ecmascript
+ #\title "ECMAScript"
+ #\reader (lambda (port env) (read-ecmascript/1 port))
+ #\compilers `((tree-il . ,compile-tree-il))
+ ;; a pretty-printer would be interesting.
+ #\printer write
+ )
+;;; ECMAScript for Guile
+
+;; Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language ecmascript tokenize)
+ #\use-module (ice-9 rdelim)
+ #\use-module ((srfi srfi-1) #\select (unfold-right))
+ #\use-module (system base lalr)
+ #\export (next-token make-tokenizer make-tokenizer/1 tokenize tokenize/1))
+
+(define (syntax-error what loc form . args)
+ (throw 'syntax-error #f what
+ (and=> loc source-location->source-properties)
+ form #f args))
+
+(define (port-source-location port)
+ (make-source-location (port-filename port)
+ (port-line port)
+ (port-column port)
+ (false-if-exception (ftell port))
+ #f))
+
+;; taken from SSAX, sorta
+(define (read-until delims port loc)
+ (if (eof-object? (peek-char port))
+ (syntax-error "EOF while reading a token" loc #f)
+ (let ((token (read-delimited delims port 'peek)))
+ (if (eof-object? (peek-char port))
+ (syntax-error "EOF while reading a token" loc token)
+ token))))
+
+(define (char-hex? c)
+ (and (not (eof-object? c))
+ (or (char-numeric? c)
+ (memv c '(#\a #\b #\c #\d #\e #\f))
+ (memv c '(#\A #\B #\C #\D #\E #\F)))))
+
+(define (digit->number c)
+ (- (char->integer c) (char->integer #\0)))
+
+(define (hex->number c)
+ (if (char-numeric? c)
+ (digit->number c)
+ (+ 10 (- (char->integer (char-downcase c)) (char->integer #\a)))))
+
+(define (read-slash port loc div?)
+ (let ((c1 (begin
+ (read-char port)
+ (peek-char port))))
+ (cond
+ ((eof-object? c1)
+ ;; hmm. error if we're not looking for a div? ?
+ (make-lexical-token '/ loc #f))
+ ((char=? c1 #\/)
+ (read-line port)
+ (next-token port div?))
+ ((char=? c1 #\*)
+ (read-char port)
+ (let lp ((c (read-char port)))
+ (cond
+ ((eof-object? c)
+ (syntax-error "EOF while in multi-line comment" loc #f))
+ ((char=? c #\*)
+ (if (eqv? (peek-char port) #\/)
+ (begin
+ (read-char port)
+ (next-token port div?))
+ (lp (read-char port))))
+ (else
+ (lp (read-char port))))))
+ (div?
+ (case c1
+ ((#\=) (read-char port) (make-lexical-token '/= loc #f))
+ (else (make-lexical-token '/ loc #f))))
+ (else
+ (read-regexp port loc)))))
+
+(define (read-regexp port loc)
+ ;; first slash already read
+ (let ((terms (string #\/ #\\ #\nl #\cr)))
+ (let lp ((str (read-until terms port loc)) (head ""))
+ (let ((terminator (peek-char port)))
+ (cond
+ ((char=? terminator #\/)
+ (read-char port)
+ ;; flags
+ (let lp ((c (peek-char port)) (flags '()))
+ (if (or (eof-object? c)
+ (not (or (char-alphabetic? c)
+ (char-numeric? c)
+ (char=? c #\$)
+ (char=? c #\_))))
+ (make-lexical-token 'RegexpLiteral loc
+ (cons (string-append head str)
+ (reverse flags)))
+ (begin (read-char port)
+ (lp (peek-char port) (cons c flags))))))
+ ((char=? terminator #\\)
+ (read-char port)
+ (let ((echar (read-char port)))
+ (lp (read-until terms port loc)
+ (string-append head str (string #\\ echar)))))
+ (else
+ (syntax-error "regexp literals may not contain newlines"
+ loc str)))))))
+
+(define (read-string port loc)
+ (let ((c (read-char port)))
+ (let ((terms (string c #\\ #\nl #\cr)))
+ (define (read-escape port)
+ (let ((c (read-char port)))
+ (case c
+ ((#\' #\" #\\) c)
+ ((#\b) #\bs)
+ ((#\f) #\np)
+ ((#\n) #\nl)
+ ((#\r) #\cr)
+ ((#\t) #\tab)
+ ((#\v) #\vt)
+ ((#\0)
+ (let ((next (peek-char port)))
+ (cond
+ ((eof-object? next) #\nul)
+ ((char-numeric? next)
+ (syntax-error "octal escape sequences are not supported"
+ loc #f))
+ (else #\nul))))
+ ((#\x)
+ (let* ((a (read-char port))
+ (b (read-char port)))
+ (cond
+ ((and (char-hex? a) (char-hex? b))
+ (integer->char (+ (* 16 (hex->number a)) (hex->number b))))
+ (else
+ (syntax-error "bad hex character escape" loc (string a b))))))
+ ((#\u)
+ (let* ((a (read-char port))
+ (b (read-char port))
+ (c (read-char port))
+ (d (read-char port)))
+ (integer->char (string->number (string a b c d) 16))))
+ (else
+ c))))
+ (let lp ((str (read-until terms port loc)))
+ (let ((terminator (peek-char port)))
+ (cond
+ ((char=? terminator c)
+ (read-char port)
+ (make-lexical-token 'StringLiteral loc str))
+ ((char=? terminator #\\)
+ (read-char port)
+ (let ((echar (read-escape port)))
+ (lp (string-append str (string echar)
+ (read-until terms port loc)))))
+ (else
+ (syntax-error "string literals may not contain newlines"
+ loc str))))))))
+
+(define *keywords*
+ '(("break" . break)
+ ("else" . else)
+ ("new" . new)
+ ("var" . var)
+ ("case" . case)
+ ("finally" . finally)
+ ("return" . return)
+ ("void" . void)
+ ("catch" . catch)
+ ("for" . for)
+ ("switch" . switch)
+ ("while" . while)
+ ("continue" . continue)
+ ("function" . function)
+ ("this" . this)
+ ("with" . with)
+ ("default" . default)
+ ("if" . if)
+ ("throw" . throw)
+ ("delete" . delete)
+ ("in" . in)
+ ("try" . try)
+ ("do" . do)
+ ("instanceof" . instanceof)
+ ("typeof" . typeof)
+
+ ;; these aren't exactly keywords, but hey
+ ("null" . null)
+ ("true" . true)
+ ("false" . false)))
+
+(define *future-reserved-words*
+ '(("abstract" . abstract)
+ ("enum" . enum)
+ ("int" . int)
+ ("short" . short)
+ ("boolean" . boolean)
+ ("export" . export)
+ ("interface" . interface)
+ ("static" . static)
+ ("byte" . byte)
+ ("extends" . extends)
+ ("long" . long)
+ ("super" . super)
+ ("char" . char)
+ ("final" . final)
+ ("native" . native)
+ ("synchronized" . synchronized)
+ ("class" . class)
+ ("float" . float)
+ ("package" . package)
+ ("throws" . throws)
+ ("const" . const)
+ ("goto" . goto)
+ ("private" . private)
+ ("transient" . transient)
+ ("debugger" . debugger)
+ ("implements" . implements)
+ ("protected" . protected)
+ ("volatile" . volatile)
+ ("double" . double)
+ ("import" . import)
+ ("public" . public)))
+
+(define (read-identifier port loc)
+ (let lp ((c (peek-char port)) (chars '()))
+ (if (or (eof-object? c)
+ (not (or (char-alphabetic? c)
+ (char-numeric? c)
+ (char=? c #\$)
+ (char=? c #\_))))
+ (let ((word (list->string (reverse chars))))
+ (cond ((assoc-ref *keywords* word)
+ => (lambda (x) (make-lexical-token x loc #f)))
+ ((assoc-ref *future-reserved-words* word)
+ (syntax-error "word is reserved for the future, dude."
+ loc word))
+ (else (make-lexical-token 'Identifier loc
+ (string->symbol word)))))
+ (begin (read-char port)
+ (lp (peek-char port) (cons c chars))))))
+
+(define (read-numeric port loc)
+ (let* ((c0 (if (char=? (peek-char port) #\.)
+ #\0
+ (read-char port)))
+ (c1 (peek-char port)))
+ (cond
+ ((eof-object? c1) (digit->number c0))
+ ((and (char=? c0 #\0) (or (char=? c1 #\x) (char=? c1 #\X)))
+ (read-char port)
+ (let ((c (peek-char port)))
+ (if (not (char-hex? c))
+ (syntax-error "bad digit reading hexadecimal number"
+ loc c))
+ (let lp ((c c) (acc 0))
+ (cond ((char-hex? c)
+ (read-char port)
+ (lp (peek-char port)
+ (+ (* 16 acc) (hex->number c))))
+ (else
+ acc)))))
+ ((and (char=? c0 #\0) (char-numeric? c1))
+ (let lp ((c c1) (acc 0))
+ (cond ((eof-object? c) acc)
+ ((char-numeric? c)
+ (if (or (char=? c #\8) (char=? c #\9))
+ (syntax-error "invalid digit in octal sequence"
+ loc c))
+ (read-char port)
+ (lp (peek-char port)
+ (+ (* 8 acc) (digit->number c))))
+ (else
+ acc))))
+ (else
+ (let lp ((c1 c1) (acc (digit->number c0)))
+ (cond
+ ((eof-object? c1) acc)
+ ((char-numeric? c1)
+ (read-char port)
+ (lp (peek-char port)
+ (+ (* 10 acc) (digit->number c1))))
+ ((or (char=? c1 #\e) (char=? c1 #\E))
+ (read-char port)
+ (let ((add (let ((c (peek-char port)))
+ (cond ((eof-object? c)
+ (syntax-error "error reading exponent: EOF"
+ loc #f))
+ ((char=? c #\+) (read-char port) +)
+ ((char=? c #\-) (read-char port) -)
+ ((char-numeric? c) +)
+ (else
+ (syntax-error "error reading exponent: non-digit"
+ loc c))))))
+ (let lp ((c (peek-char port)) (e 0))
+ (cond ((and (not (eof-object? c)) (char-numeric? c))
+ (read-char port)
+ (lp (peek-char port) (add (* 10 e) (digit->number c))))
+ (else
+ (* (if (negative? e) (* acc 1.0) acc) (expt 10 e)))))))
+ ((char=? c1 #\.)
+ (read-char port)
+ (let lp2 ((c (peek-char port)) (dec 0.0) (n -1))
+ (cond ((and (not (eof-object? c)) (char-numeric? c))
+ (read-char port)
+ (lp2 (peek-char port)
+ (+ dec (* (digit->number c) (expt 10 n)))
+ (1- n)))
+ (else
+ ;; loop back to catch an exponential part
+ (lp c (+ acc dec))))))
+ (else
+ acc)))))))
+
+(define *punctuation*
+ '(("{" . lbrace)
+ ("}" . rbrace)
+ ("(" . lparen)
+ (")" . rparen)
+ ("[" . lbracket)
+ ("]" . rbracket)
+ ("." . dot)
+ (";" . semicolon)
+ ("," . comma)
+ ("<" . <)
+ (">" . >)
+ ("<=" . <=)
+ (">=" . >=)
+ ("==" . ==)
+ ("!=" . !=)
+ ("===" . ===)
+ ("!==" . !==)
+ ("+" . +)
+ ("-" . -)
+ ("*" . *)
+ ("%" . %)
+ ("++" . ++)
+ ("--" . --)
+ ("<<" . <<)
+ (">>" . >>)
+ (">>>" . >>>)
+ ("&" . &)
+ ("|" . bor)
+ ("^" . ^)
+ ("!" . !)
+ ("~" . ~)
+ ("&&" . &&)
+ ("||" . or)
+ ("?" . ?)
+ (":" . colon)
+ ("=" . =)
+ ("+=" . +=)
+ ("-=" . -=)
+ ("*=" . *=)
+ ("%=" . %=)
+ ("<<=" . <<=)
+ (">>=" . >>=)
+ (">>>=" . >>>=)
+ ("&=" . &=)
+ ("|=" . bor=)
+ ("^=" . ^=)))
+
+(define *div-punctuation*
+ '(("/" . /)
+ ("/=" . /=)))
+
+;; node ::= (char (symbol | #f) node*)
+(define read-punctuation
+ (let ((punc-tree (let lp ((nodes '()) (puncs *punctuation*))
+ (cond ((null? puncs)
+ nodes)
+ ((assv-ref nodes (string-ref (caar puncs) 0))
+ => (lambda (node-tail)
+ (if (= (string-length (caar puncs)) 1)
+ (set-car! node-tail (cdar puncs))
+ (set-cdr! node-tail
+ (lp (cdr node-tail)
+ `((,(substring (caar puncs) 1)
+ . ,(cdar puncs))))))
+ (lp nodes (cdr puncs))))
+ (else
+ (lp (cons (list (string-ref (caar puncs) 0) #f) nodes)
+ puncs))))))
+ (lambda (port loc)
+ (let lp ((c (peek-char port)) (tree punc-tree) (candidate #f))
+ (cond
+ ((assv-ref tree c)
+ => (lambda (node-tail)
+ (read-char port)
+ (lp (peek-char port) (cdr node-tail) (car node-tail))))
+ (candidate
+ (make-lexical-token candidate loc #f))
+ (else
+ (syntax-error "bad syntax: character not allowed" loc c)))))))
+
+(define (next-token port div?)
+ (let ((c (peek-char port))
+ (loc (port-source-location port)))
+ (case c
+ ((#\ht #\vt #\np #\space #\x00A0) ; whitespace
+ (read-char port)
+ (next-token port div?))
+ ((#\newline #\cr) ; line break
+ (read-char port)
+ (next-token port div?))
+ ((#\/)
+ ;; division, single comment, double comment, or regexp
+ (read-slash port loc div?))
+ ((#\" #\') ; string literal
+ (read-string port loc))
+ (else
+ (cond
+ ((eof-object? c)
+ '*eoi*)
+ ((or (char-alphabetic? c)
+ (char=? c #\$)
+ (char=? c #\_))
+ ;; reserved word or identifier
+ (read-identifier port loc))
+ ((char-numeric? c)
+ ;; numeric -- also accept . FIXME, requires lookahead
+ (make-lexical-token 'NumericLiteral loc (read-numeric port loc)))
+ (else
+ ;; punctuation
+ (read-punctuation port loc)))))))
+
+(define (make-tokenizer port)
+ (let ((div? #f))
+ (lambda ()
+ (let ((tok (next-token port div?)))
+ (set! div? (and (lexical-token? tok)
+ (let ((cat (lexical-token-category tok)))
+ (or (eq? cat 'Identifier)
+ (eq? cat 'NumericLiteral)
+ (eq? cat 'StringLiteral)))))
+ tok))))
+
+(define (make-tokenizer/1 port)
+ (let ((div? #f)
+ (eoi? #f)
+ (stack '()))
+ (lambda ()
+ (if eoi?
+ '*eoi*
+ (let ((tok (next-token port div?)))
+ (case (if (lexical-token? tok) (lexical-token-category tok) tok)
+ ((lparen)
+ (set! stack (cons tok stack)))
+ ((rparen)
+ (if (and (pair? stack)
+ (eq? (lexical-token-category (car stack)) 'lparen))
+ (set! stack (cdr stack))
+ (syntax-error "unexpected right parenthesis"
+ (lexical-token-source tok)
+ #f)))
+ ((lbracket)
+ (set! stack (cons tok stack)))
+ ((rbracket)
+ (if (and (pair? stack)
+ (eq? (lexical-token-category (car stack)) 'lbracket))
+ (set! stack (cdr stack))
+ (syntax-error "unexpected right bracket"
+ (lexical-token-source tok)
+ #f)))
+ ((lbrace)
+ (set! stack (cons tok stack)))
+ ((rbrace)
+ (if (and (pair? stack)
+ (eq? (lexical-token-category (car stack)) 'lbrace))
+ (set! stack (cdr stack))
+ (syntax-error "unexpected right brace"
+ (lexical-token-source tok)
+ #f)))
+ ((semicolon)
+ (set! eoi? (null? stack))))
+ (set! div? (and (lexical-token? tok)
+ (let ((cat (lexical-token-category tok)))
+ (or (eq? cat 'Identifier)
+ (eq? cat 'NumericLiteral)
+ (eq? cat 'StringLiteral)))))
+ tok)))))
+
+(define (tokenize port)
+ (let ((next (make-tokenizer port)))
+ (let lp ((out '()))
+ (let ((tok (next)))
+ (if (eq? tok '*eoi*)
+ (reverse! out)
+ (lp (cons tok out)))))))
+
+(define (tokenize/1 port)
+ (let ((next (make-tokenizer/1 port)))
+ (let lp ((out '()))
+ (let ((tok (next)))
+ (if (eq? tok '*eoi*)
+ (reverse! out)
+ (lp (cons tok out)))))))
+
+;;; Guile Emacs Lisp
+
+;;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language elisp bindings)
+ #\export (make-bindings
+ mark-global-needed!
+ map-globals-needed
+ with-lexical-bindings
+ with-dynamic-bindings
+ get-lexical-binding))
+
+;;; This module defines routines to handle analysis of symbol bindings
+;;; used during elisp compilation. This data allows to collect the
+;;; symbols, for which globals need to be created, or mark certain
+;;; symbols as lexically bound.
+;;;
+;;; Needed globals are stored in an association-list that stores a list
+;;; of symbols for each module they are needed in.
+;;;
+;;; The lexical bindings of symbols are stored in a hash-table that
+;;; associates symbols to fluids; those fluids are used in the
+;;; with-lexical-binding and with-dynamic-binding routines to associate
+;;; symbols to different bindings over a dynamic extent.
+
+;;; Record type used to hold the data necessary.
+
+(define bindings-type
+ (make-record-type 'bindings '(needed-globals lexical-bindings)))
+
+;;; Construct an 'empty' instance of the bindings data structure to be
+;;; used at the start of a fresh compilation.
+
+(define (make-bindings)
+ ((record-constructor bindings-type) '() (make-hash-table)))
+
+;;; Mark that a given symbol is needed as global in the specified
+;;; slot-module.
+
+(define (mark-global-needed! bindings sym module)
+ (let* ((old-needed ((record-accessor bindings-type 'needed-globals)
+ bindings))
+ (old-in-module (or (assoc-ref old-needed module) '()))
+ (new-in-module (if (memq sym old-in-module)
+ old-in-module
+ (cons sym old-in-module)))
+ (new-needed (assoc-set! old-needed module new-in-module)))
+ ((record-modifier bindings-type 'needed-globals)
+ bindings
+ new-needed)))
+
+;;; Cycle through all globals needed in order to generate the code for
+;;; their creation or some other analysis.
+
+(define (map-globals-needed bindings proc)
+ (let ((needed ((record-accessor bindings-type 'needed-globals)
+ bindings)))
+ (let iterate-modules ((mod-tail needed)
+ (mod-result '()))
+ (if (null? mod-tail)
+ mod-result
+ (iterate-modules
+ (cdr mod-tail)
+ (let* ((aentry (car mod-tail))
+ (module (car aentry))
+ (symbols (cdr aentry)))
+ (let iterate-symbols ((sym-tail symbols)
+ (sym-result mod-result))
+ (if (null? sym-tail)
+ sym-result
+ (iterate-symbols (cdr sym-tail)
+ (cons (proc module (car sym-tail))
+ sym-result))))))))))
+
+;;; Get the current lexical binding (gensym it should refer to in the
+;;; current scope) for a symbol or #f if it is dynamically bound.
+
+(define (get-lexical-binding bindings sym)
+ (let* ((lex ((record-accessor bindings-type 'lexical-bindings)
+ bindings))
+ (slot (hash-ref lex sym #f)))
+ (if slot
+ (fluid-ref slot)
+ #f)))
+
+;;; Establish a binding or mark a symbol as dynamically bound for the
+;;; extent of calling proc.
+
+(define (with-symbol-bindings bindings syms targets proc)
+ (if (or (not (list? syms))
+ (not (and-map symbol? syms)))
+ (error "can't bind non-symbols" syms))
+ (let ((lex ((record-accessor bindings-type 'lexical-bindings)
+ bindings)))
+ (for-each (lambda (sym)
+ (if (not (hash-ref lex sym))
+ (hash-set! lex sym (make-fluid))))
+ syms)
+ (with-fluids* (map (lambda (sym) (hash-ref lex sym)) syms)
+ targets
+ proc)))
+
+(define (with-lexical-bindings bindings syms targets proc)
+ (if (or (not (list? targets))
+ (not (and-map symbol? targets)))
+ (error "invalid targets for lexical binding" targets)
+ (with-symbol-bindings bindings syms targets proc)))
+
+(define (with-dynamic-bindings bindings syms proc)
+ (with-symbol-bindings bindings
+ syms
+ (map (lambda (el) #f) syms)
+ proc))
+;;; Guile Emacs Lisp
+
+;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+
+;; 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, 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; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Code:
+
+(define-module (language elisp compile-tree-il)
+ #\use-module (language elisp bindings)
+ #\use-module (language elisp runtime)
+ #\use-module (language tree-il)
+ #\use-module (system base pmatch)
+ #\use-module (system base compile)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-8)
+ #\use-module (srfi srfi-11)
+ #\use-module (srfi srfi-26)
+ #\export (compile-tree-il
+ compile-progn
+ compile-if
+ compile-defconst
+ compile-defvar
+ compile-setq
+ compile-let
+ compile-lexical-let
+ compile-flet
+ compile-let*
+ compile-lexical-let*
+ compile-flet*
+ compile-without-void-checks
+ compile-with-always-lexical
+ compile-guile-ref
+ compile-guile-primitive
+ compile-while
+ compile-function
+ compile-defmacro
+ compile-defun
+ #{compile-\`}
+ compile-quote))
+
+;;; Certain common parameters (like the bindings data structure or
+;;; compiler options) are not always passed around but accessed using
+;;; fluids to simulate dynamic binding (hey, this is about elisp).
+
+;;; The bindings data structure to keep track of symbol binding related
+;;; data.
+
+(define bindings-data (make-fluid))
+
+;;; Store for which symbols (or all/none) void checks are disabled.
+
+(define disable-void-check (make-fluid))
+
+;;; Store which symbols (or all/none) should always be bound lexically,
+;;; even with ordinary let and as lambda arguments.
+
+(define always-lexical (make-fluid))
+
+;;; Find the source properties of some parsed expression if there are
+;;; any associated with it.
+
+(define (location x)
+ (and (pair? x)
+ (let ((props (source-properties x)))
+ (and (not (null? props))
+ props))))
+
+;;; Values to use for Elisp's nil and t.
+
+(define (nil-value loc)
+ (make-const loc (@ (language elisp runtime) nil-value)))
+
+(define (t-value loc)
+ (make-const loc (@ (language elisp runtime) t-value)))
+
+;;; Modules that contain the value and function slot bindings.
+
+(define runtime '(language elisp runtime))
+
+(define value-slot (@ (language elisp runtime) value-slot-module))
+
+(define function-slot (@ (language elisp runtime) function-slot-module))
+
+;;; The backquoting works the same as quasiquotes in Scheme, but the
+;;; forms are named differently; to make easy adaptions, we define these
+;;; predicates checking for a symbol being the car of an
+;;; unquote/unquote-splicing/backquote form.
+
+(define (unquote? sym)
+ (and (symbol? sym) (eq? sym '#{\,})))
+
+(define (unquote-splicing? sym)
+ (and (symbol? sym) (eq? sym '#{\,\@})))
+
+;;; Build a call to a primitive procedure nicely.
+
+(define (call-primitive loc sym . args)
+ (make-application loc (make-primitive-ref loc sym) args))
+
+;;; Error reporting routine for syntax/compilation problems or build
+;;; code for a runtime-error output.
+
+(define (report-error loc . args)
+ (apply error args))
+
+(define (runtime-error loc msg . args)
+ (make-application loc
+ (make-primitive-ref loc 'error)
+ (cons (make-const loc msg) args)))
+
+;;; Generate code to ensure a global symbol is there for further use of
+;;; a given symbol. In general during the compilation, those needed are
+;;; only tracked with the bindings data structure. Afterwards, however,
+;;; for all those needed symbols the globals are really generated with
+;;; this routine.
+
+(define (generate-ensure-global loc sym module)
+ (make-application loc
+ (make-module-ref loc runtime 'ensure-fluid! #t)
+ (list (make-const loc module)
+ (make-const loc sym))))
+
+(define (ensuring-globals loc bindings body)
+ (make-sequence
+ loc
+ `(,@(map-globals-needed (fluid-ref bindings)
+ (lambda (mod sym)
+ (generate-ensure-global loc sym mod)))
+ ,body)))
+
+;;; Build a construct that establishes dynamic bindings for certain
+;;; variables. We may want to choose between binding with fluids and
+;;; with-fluids* and using just ordinary module symbols and
+;;; setting/reverting their values with a dynamic-wind.
+
+(define (let-dynamic loc syms module vals body)
+ (call-primitive
+ loc
+ 'with-fluids*
+ (make-application loc
+ (make-primitive-ref loc 'list)
+ (map (lambda (sym)
+ (make-module-ref loc module sym #t))
+ syms))
+ (make-application loc (make-primitive-ref loc 'list) vals)
+ (make-lambda loc
+ '()
+ (make-lambda-case #f '() #f #f #f '() '() body #f))))
+
+;;; Handle access to a variable (reference/setting) correctly depending
+;;; on whether it is currently lexically or dynamically bound. lexical
+;;; access is done only for references to the value-slot module!
+
+(define (access-variable loc
+ sym
+ module
+ handle-global
+ handle-lexical
+ handle-dynamic)
+ (let ((lexical (get-lexical-binding (fluid-ref bindings-data) sym)))
+ (cond
+ (lexical (handle-lexical lexical))
+ ((equal? module function-slot) (handle-global))
+ (else (handle-dynamic)))))
+
+;;; Generate code to reference a variable. For references in the
+;;; value-slot module, we may want to generate a lexical reference
+;;; instead if the variable has a lexical binding.
+
+(define (reference-variable loc sym module)
+ (access-variable
+ loc
+ sym
+ module
+ (lambda () (make-module-ref loc module sym #t))
+ (lambda (lexical) (make-lexical-ref loc lexical lexical))
+ (lambda ()
+ (mark-global-needed! (fluid-ref bindings-data) sym module)
+ (call-primitive loc
+ 'fluid-ref
+ (make-module-ref loc module sym #t)))))
+
+;;; Generate code to set a variable. Just as with reference-variable, in
+;;; case of a reference to value-slot, we want to generate a lexical set
+;;; when the variable has a lexical binding.
+
+(define (set-variable! loc sym module value)
+ (access-variable
+ loc
+ sym
+ module
+ (lambda ()
+ (make-application
+ loc
+ (make-module-ref loc runtime 'set-variable! #t)
+ (list (make-const loc module) (make-const loc sym) value)))
+ (lambda (lexical) (make-lexical-set loc lexical lexical value))
+ (lambda ()
+ (mark-global-needed! (fluid-ref bindings-data) sym module)
+ (call-primitive loc
+ 'fluid-set!
+ (make-module-ref loc module sym #t)
+ value))))
+
+;;; Process the bindings part of a let or let* expression; that is,
+;;; check for correctness and bring it to the form ((sym1 . val1) (sym2
+;;; . val2) ...).
+
+(define (process-let-bindings loc bindings)
+ (map
+ (lambda (b)
+ (if (symbol? b)
+ (cons b 'nil)
+ (if (or (not (list? b))
+ (not (= (length b) 2)))
+ (report-error
+ loc
+ "expected symbol or list of 2 elements in let")
+ (if (not (symbol? (car b)))
+ (report-error loc "expected symbol in let")
+ (cons (car b) (cadr b))))))
+ bindings))
+
+;;; Split the let bindings into a list to be done lexically and one
+;;; dynamically. A symbol will be bound lexically if and only if: We're
+;;; processing a lexical-let (i.e. module is 'lexical), OR we're
+;;; processing a value-slot binding AND the symbol is already lexically
+;;; bound or is always lexical, OR we're processing a function-slot
+;;; binding.
+
+(define (bind-lexically? sym module)
+ (or (eq? module 'lexical)
+ (eq? module function-slot)
+ (and (equal? module value-slot)
+ (let ((always (fluid-ref always-lexical)))
+ (or (eq? always 'all)
+ (memq sym always)
+ (get-lexical-binding (fluid-ref bindings-data) sym))))))
+
+(define (split-let-bindings bindings module)
+ (let iterate ((tail bindings)
+ (lexical '())
+ (dynamic '()))
+ (if (null? tail)
+ (values (reverse lexical) (reverse dynamic))
+ (if (bind-lexically? (caar tail) module)
+ (iterate (cdr tail) (cons (car tail) lexical) dynamic)
+ (iterate (cdr tail) lexical (cons (car tail) dynamic))))))
+
+;;; Compile let and let* expressions. The code here is used both for
+;;; let/let* and flet/flet*, just with a different bindings module.
+;;;
+;;; A special module value 'lexical means that we're doing a lexical-let
+;;; instead and the bindings should not be saved to globals at all but
+;;; be done with the lexical framework instead.
+
+;;; Let is done with a single call to let-dynamic binding them locally
+;;; to new values all "at once". If there is at least one variable to
+;;; bind lexically among the bindings, we first do a let for all of them
+;;; to evaluate all values before any bindings take place, and then call
+;;; let-dynamic for the variables to bind dynamically.
+
+(define (generate-let loc module bindings body)
+ (let ((bind (process-let-bindings loc bindings)))
+ (call-with-values
+ (lambda () (split-let-bindings bind module))
+ (lambda (lexical dynamic)
+ (for-each (lambda (sym)
+ (mark-global-needed! (fluid-ref bindings-data)
+ sym
+ module))
+ (map car dynamic))
+ (let ((make-values (lambda (for)
+ (map (lambda (el) (compile-expr (cdr el)))
+ for)))
+ (make-body (lambda ()
+ (make-sequence loc (map compile-expr body)))))
+ (if (null? lexical)
+ (let-dynamic loc (map car dynamic) module
+ (make-values dynamic) (make-body))
+ (let* ((lexical-syms (map (lambda (el) (gensym)) lexical))
+ (dynamic-syms (map (lambda (el) (gensym)) dynamic))
+ (all-syms (append lexical-syms dynamic-syms))
+ (vals (append (make-values lexical)
+ (make-values dynamic))))
+ (make-let loc
+ all-syms
+ all-syms
+ vals
+ (with-lexical-bindings
+ (fluid-ref bindings-data)
+ (map car lexical) lexical-syms
+ (lambda ()
+ (if (null? dynamic)
+ (make-body)
+ (let-dynamic loc
+ (map car dynamic)
+ module
+ (map
+ (lambda (sym)
+ (make-lexical-ref loc
+ sym
+ sym))
+ dynamic-syms)
+ (make-body)))))))))))))
+
+;;; Let* is compiled to a cascaded set of "small lets" for each binding
+;;; in turn so that each one already sees the preceding bindings.
+
+(define (generate-let* loc module bindings body)
+ (let ((bind (process-let-bindings loc bindings)))
+ (begin
+ (for-each (lambda (sym)
+ (if (not (bind-lexically? sym module))
+ (mark-global-needed! (fluid-ref bindings-data)
+ sym
+ module)))
+ (map car bind))
+ (let iterate ((tail bind))
+ (if (null? tail)
+ (make-sequence loc (map compile-expr body))
+ (let ((sym (caar tail))
+ (value (compile-expr (cdar tail))))
+ (if (bind-lexically? sym module)
+ (let ((target (gensym)))
+ (make-let loc
+ `(,target)
+ `(,target)
+ `(,value)
+ (with-lexical-bindings
+ (fluid-ref bindings-data)
+ `(,sym)
+ `(,target)
+ (lambda () (iterate (cdr tail))))))
+ (let-dynamic loc
+ `(,(caar tail))
+ module
+ `(,value)
+ (iterate (cdr tail))))))))))
+
+;;; Split the argument list of a lambda expression into required,
+;;; optional and rest arguments and also check it is actually valid.
+;;; Additionally, we create a list of all "local variables" (that is,
+;;; required, optional and rest arguments together) and also this one
+;;; split into those to be bound lexically and dynamically. Returned is
+;;; as multiple values: required optional rest lexical dynamic
+
+(define (bind-arg-lexical? arg)
+ (let ((always (fluid-ref always-lexical)))
+ (or (eq? always 'all)
+ (memq arg always))))
+
+(define (split-lambda-arguments loc args)
+ (let iterate ((tail args)
+ (mode 'required)
+ (required '())
+ (optional '())
+ (lexical '())
+ (dynamic '()))
+ (cond
+ ((null? tail)
+ (let ((final-required (reverse required))
+ (final-optional (reverse optional))
+ (final-lexical (reverse lexical))
+ (final-dynamic (reverse dynamic)))
+ (values final-required
+ final-optional
+ #f
+ final-lexical
+ final-dynamic)))
+ ((and (eq? mode 'required)
+ (eq? (car tail) '&optional))
+ (iterate (cdr tail) 'optional required optional lexical dynamic))
+ ((eq? (car tail) '&rest)
+ (if (or (null? (cdr tail))
+ (not (null? (cddr tail))))
+ (report-error loc "expected exactly one symbol after &rest")
+ (let* ((rest (cadr tail))
+ (rest-lexical (bind-arg-lexical? rest))
+ (final-required (reverse required))
+ (final-optional (reverse optional))
+ (final-lexical (reverse (if rest-lexical
+ (cons rest lexical)
+ lexical)))
+ (final-dynamic (reverse (if rest-lexical
+ dynamic
+ (cons rest dynamic)))))
+ (values final-required
+ final-optional
+ rest
+ final-lexical
+ final-dynamic))))
+ (else
+ (if (not (symbol? (car tail)))
+ (report-error loc
+ "expected symbol in argument list, got"
+ (car tail))
+ (let* ((arg (car tail))
+ (bind-lexical (bind-arg-lexical? arg))
+ (new-lexical (if bind-lexical
+ (cons arg lexical)
+ lexical))
+ (new-dynamic (if bind-lexical
+ dynamic
+ (cons arg dynamic))))
+ (case mode
+ ((required) (iterate (cdr tail) mode
+ (cons arg required) optional
+ new-lexical new-dynamic))
+ ((optional) (iterate (cdr tail) mode
+ required (cons arg optional)
+ new-lexical new-dynamic))
+ (else
+ (error "invalid mode in split-lambda-arguments"
+ mode)))))))))
+
+;;; Compile a lambda expression. One thing we have to be aware of is
+;;; that lambda arguments are usually dynamically bound, even when a
+;;; lexical binding is intact for a symbol. For symbols that are marked
+;;; as 'always lexical,' however, we lexically bind here as well, and
+;;; thus we get them out of the let-dynamic call and register a lexical
+;;; binding for them (the lexical target variable is already there,
+;;; namely the real lambda argument from TreeIL).
+
+(define (compile-lambda loc args body)
+ (if (not (list? args))
+ (report-error loc "expected list for argument-list" args))
+ (if (null? body)
+ (report-error loc "function body must not be empty"))
+ (receive (required optional rest lexical dynamic)
+ (split-lambda-arguments loc args)
+ (define (process-args args)
+ (define (find-pairs pairs filter)
+ (lset-intersection (lambda (name+sym x)
+ (eq? (car name+sym) x))
+ pairs
+ filter))
+ (let* ((syms (map (lambda (x) (gensym)) args))
+ (pairs (map cons args syms))
+ (lexical-pairs (find-pairs pairs lexical))
+ (dynamic-pairs (find-pairs pairs dynamic)))
+ (values syms pairs lexical-pairs dynamic-pairs)))
+ (let*-values (((required-syms
+ required-pairs
+ required-lex-pairs
+ required-dyn-pairs)
+ (process-args required))
+ ((optional-syms
+ optional-pairs
+ optional-lex-pairs
+ optional-dyn-pairs)
+ (process-args optional))
+ ((rest-syms rest-pairs rest-lex-pairs rest-dyn-pairs)
+ (process-args (if rest (list rest) '())))
+ ((the-rest-sym) (if rest (car rest-syms) #f))
+ ((all-syms) (append required-syms
+ optional-syms
+ rest-syms))
+ ((all-lex-pairs) (append required-lex-pairs
+ optional-lex-pairs
+ rest-lex-pairs))
+ ((all-dyn-pairs) (append required-dyn-pairs
+ optional-dyn-pairs
+ rest-dyn-pairs)))
+ (for-each (lambda (sym)
+ (mark-global-needed! (fluid-ref bindings-data)
+ sym
+ value-slot))
+ dynamic)
+ (with-dynamic-bindings
+ (fluid-ref bindings-data)
+ dynamic
+ (lambda ()
+ (with-lexical-bindings
+ (fluid-ref bindings-data)
+ (map car all-lex-pairs)
+ (map cdr all-lex-pairs)
+ (lambda ()
+ (make-lambda
+ loc
+ '()
+ (make-lambda-case
+ #f
+ required
+ optional
+ rest
+ #f
+ (map (lambda (x) (nil-value loc)) optional)
+ all-syms
+ (let ((compiled-body
+ (make-sequence loc (map compile-expr body))))
+ (make-sequence
+ loc
+ (list
+ (if rest
+ (make-conditional
+ loc
+ (call-primitive loc
+ 'null?
+ (make-lexical-ref loc
+ rest
+ the-rest-sym))
+ (make-lexical-set loc
+ rest
+ the-rest-sym
+ (nil-value loc))
+ (make-void loc))
+ (make-void loc))
+ (if (null? dynamic)
+ compiled-body
+ (let-dynamic loc
+ dynamic
+ value-slot
+ (map (lambda (name-sym)
+ (make-lexical-ref
+ loc
+ (car name-sym)
+ (cdr name-sym)))
+ all-dyn-pairs)
+ compiled-body)))))
+ #f)))))))))
+
+;;; Handle the common part of defconst and defvar, that is, checking for
+;;; a correct doc string and arguments as well as maybe in the future
+;;; handling the docstring somehow.
+
+(define (handle-var-def loc sym doc)
+ (cond
+ ((not (symbol? sym)) (report-error loc "expected symbol, got" sym))
+ ((> (length doc) 1) (report-error loc "too many arguments to defvar"))
+ ((and (not (null? doc)) (not (string? (car doc))))
+ (report-error loc "expected string as third argument of defvar, got"
+ (car doc)))
+ ;; TODO: Handle doc string if present.
+ (else #t)))
+
+;;; Handle macro and special operator bindings.
+
+(define (find-operator sym type)
+ (and
+ (symbol? sym)
+ (module-defined? (resolve-interface function-slot) sym)
+ (let* ((op (module-ref (resolve-module function-slot) sym))
+ (op (if (fluid? op) (fluid-ref op) op)))
+ (if (and (pair? op) (eq? (car op) type))
+ (cdr op)
+ #f))))
+
+;;; See if a (backquoted) expression contains any unquotes.
+
+(define (contains-unquotes? expr)
+ (if (pair? expr)
+ (if (or (unquote? (car expr)) (unquote-splicing? (car expr)))
+ #t
+ (or (contains-unquotes? (car expr))
+ (contains-unquotes? (cdr expr))))
+ #f))
+
+;;; Process a backquoted expression by building up the needed
+;;; cons/append calls. For splicing, it is assumed that the expression
+;;; spliced in evaluates to a list. The emacs manual does not really
+;;; state either it has to or what to do if it does not, but Scheme
+;;; explicitly forbids it and this seems reasonable also for elisp.
+
+(define (unquote-cell? expr)
+ (and (list? expr) (= (length expr) 2) (unquote? (car expr))))
+
+(define (unquote-splicing-cell? expr)
+ (and (list? expr) (= (length expr) 2) (unquote-splicing? (car expr))))
+
+(define (process-backquote loc expr)
+ (if (contains-unquotes? expr)
+ (if (pair? expr)
+ (if (or (unquote-cell? expr) (unquote-splicing-cell? expr))
+ (compile-expr (cadr expr))
+ (let* ((head (car expr))
+ (processed-tail (process-backquote loc (cdr expr)))
+ (head-is-list-2 (and (list? head)
+ (= (length head) 2)))
+ (head-unquote (and head-is-list-2
+ (unquote? (car head))))
+ (head-unquote-splicing (and head-is-list-2
+ (unquote-splicing?
+ (car head)))))
+ (if head-unquote-splicing
+ (call-primitive loc
+ 'append
+ (compile-expr (cadr head))
+ processed-tail)
+ (call-primitive loc 'cons
+ (if head-unquote
+ (compile-expr (cadr head))
+ (process-backquote loc head))
+ processed-tail))))
+ (report-error loc
+ "non-pair expression contains unquotes"
+ expr))
+ (make-const loc expr)))
+
+;;; Temporarily update a list of symbols that are handled specially
+;;; (disabled void check or always lexical) for compiling body. We need
+;;; to handle special cases for already all / set to all and the like.
+
+(define (with-added-symbols loc fluid syms body)
+ (if (null? body)
+ (report-error loc "symbol-list construct has empty body"))
+ (if (not (or (eq? syms 'all)
+ (and (list? syms) (and-map symbol? syms))))
+ (report-error loc "invalid symbol list" syms))
+ (let ((old (fluid-ref fluid))
+ (make-body (lambda ()
+ (make-sequence loc (map compile-expr body)))))
+ (if (eq? old 'all)
+ (make-body)
+ (let ((new (if (eq? syms 'all)
+ 'all
+ (append syms old))))
+ (with-fluids ((fluid new))
+ (make-body))))))
+
+;;; Special operators
+
+(defspecial progn (loc args)
+ (make-sequence loc (map compile-expr args)))
+
+(defspecial if (loc args)
+ (pmatch args
+ ((,cond ,then . ,else)
+ (make-conditional loc
+ (compile-expr cond)
+ (compile-expr then)
+ (if (null? else)
+ (nil-value loc)
+ (make-sequence loc
+ (map compile-expr else)))))))
+
+(defspecial defconst (loc args)
+ (pmatch args
+ ((,sym ,value . ,doc)
+ (if (handle-var-def loc sym doc)
+ (make-sequence loc
+ (list (set-variable! loc
+ sym
+ value-slot
+ (compile-expr value))
+ (make-const loc sym)))))))
+
+(defspecial defvar (loc args)
+ (pmatch args
+ ((,sym) (make-const loc sym))
+ ((,sym ,value . ,doc)
+ (if (handle-var-def loc sym doc)
+ (make-sequence
+ loc
+ (list
+ (make-conditional
+ loc
+ (make-conditional
+ loc
+ (call-primitive
+ loc
+ 'module-bound?
+ (call-primitive loc
+ 'resolve-interface
+ (make-const loc value-slot))
+ (make-const loc sym))
+ (call-primitive loc
+ 'fluid-bound?
+ (make-module-ref loc value-slot sym #t))
+ (make-const loc #f))
+ (make-void loc)
+ (set-variable! loc sym value-slot (compile-expr value)))
+ (make-const loc sym)))))))
+
+(defspecial setq (loc args)
+ (define (car* x) (if (null? x) '() (car x)))
+ (define (cdr* x) (if (null? x) '() (cdr x)))
+ (define (cadr* x) (car* (cdr* x)))
+ (define (cddr* x) (cdr* (cdr* x)))
+ (make-sequence
+ loc
+ (let loop ((args args) (last (nil-value loc)))
+ (if (null? args)
+ (list last)
+ (let ((sym (car args))
+ (val (compile-expr (cadr* args))))
+ (if (not (symbol? sym))
+ (report-error loc "expected symbol in setq")
+ (cons
+ (set-variable! loc sym value-slot val)
+ (loop (cddr* args)
+ (reference-variable loc sym value-slot)))))))))
+
+(defspecial let (loc args)
+ (pmatch args
+ ((,bindings . ,body)
+ (generate-let loc value-slot bindings body))))
+
+(defspecial lexical-let (loc args)
+ (pmatch args
+ ((,bindings . ,body)
+ (generate-let loc 'lexical bindings body))))
+
+(defspecial flet (loc args)
+ (pmatch args
+ ((,bindings . ,body)
+ (generate-let loc function-slot bindings body))))
+
+(defspecial let* (loc args)
+ (pmatch args
+ ((,bindings . ,body)
+ (generate-let* loc value-slot bindings body))))
+
+(defspecial lexical-let* (loc args)
+ (pmatch args
+ ((,bindings . ,body)
+ (generate-let* loc 'lexical bindings body))))
+
+(defspecial flet* (loc args)
+ (pmatch args
+ ((,bindings . ,body)
+ (generate-let* loc function-slot bindings body))))
+
+;;; Temporarily set symbols as always lexical only for the lexical scope
+;;; of a construct.
+
+(defspecial with-always-lexical (loc args)
+ (pmatch args
+ ((,syms . ,body)
+ (with-added-symbols loc always-lexical syms body))))
+
+;;; guile-ref allows building TreeIL's module references from within
+;;; elisp as a way to access data within the Guile universe. The module
+;;; and symbol referenced are static values, just like (@ module symbol)
+;;; does!
+
+(defspecial guile-ref (loc args)
+ (pmatch args
+ ((,module ,sym) (guard (and (list? module) (symbol? sym)))
+ (make-module-ref loc module sym #t))))
+
+;;; guile-primitive allows to create primitive references, which are
+;;; still a little faster.
+
+(defspecial guile-primitive (loc args)
+ (pmatch args
+ ((,sym)
+ (make-primitive-ref loc sym))))
+
+;;; A while construct is transformed into a tail-recursive loop like
+;;; this:
+;;;
+;;; (letrec ((iterate (lambda ()
+;;; (if condition
+;;; (begin body
+;;; (iterate))
+;;; #nil))))
+;;; (iterate))
+;;;
+;;; As letrec is not directly accessible from elisp, while is
+;;; implemented here instead of with a macro.
+
+(defspecial while (loc args)
+ (pmatch args
+ ((,condition . ,body)
+ (let* ((itersym (gensym))
+ (compiled-body (map compile-expr body))
+ (iter-call (make-application loc
+ (make-lexical-ref loc
+ 'iterate
+ itersym)
+ (list)))
+ (full-body (make-sequence loc
+ `(,@compiled-body ,iter-call)))
+ (lambda-body (make-conditional loc
+ (compile-expr condition)
+ full-body
+ (nil-value loc)))
+ (iter-thunk (make-lambda loc
+ '()
+ (make-lambda-case #f
+ '()
+ #f
+ #f
+ #f
+ '()
+ '()
+ lambda-body
+ #f))))
+ (make-letrec loc
+ #f
+ '(iterate)
+ (list itersym)
+ (list iter-thunk)
+ iter-call)))))
+
+(defspecial function (loc args)
+ (pmatch args
+ (((lambda ,args . ,body))
+ (compile-lambda loc args body))
+ ((,sym) (guard (symbol? sym))
+ (reference-variable loc sym function-slot))))
+
+(defspecial defmacro (loc args)
+ (pmatch args
+ ((,name ,args . ,body)
+ (if (not (symbol? name))
+ (report-error loc "expected symbol as macro name" name)
+ (let* ((tree-il
+ (make-sequence
+ loc
+ (list
+ (set-variable!
+ loc
+ name
+ function-slot
+ (make-application
+ loc
+ (make-module-ref loc '(guile) 'cons #t)
+ (list (make-const loc 'macro)
+ (compile-lambda loc args body))))
+ (make-const loc name)))))
+ (compile (ensuring-globals loc bindings-data tree-il)
+ #\from 'tree-il
+ #\to 'value)
+ tree-il)))))
+
+(defspecial defun (loc args)
+ (pmatch args
+ ((,name ,args . ,body)
+ (if (not (symbol? name))
+ (report-error loc "expected symbol as function name" name)
+ (make-sequence loc
+ (list (set-variable! loc
+ name
+ function-slot
+ (compile-lambda loc
+ args
+ body))
+ (make-const loc name)))))))
+
+(defspecial #{\`} (loc args)
+ (pmatch args
+ ((,val)
+ (process-backquote loc val))))
+
+(defspecial quote (loc args)
+ (pmatch args
+ ((,val)
+ (make-const loc val))))
+
+;;; Compile a compound expression to Tree-IL.
+
+(define (compile-pair loc expr)
+ (let ((operator (car expr))
+ (arguments (cdr expr)))
+ (cond
+ ((find-operator operator 'special-operator)
+ => (lambda (special-operator-function)
+ (special-operator-function loc arguments)))
+ ((find-operator operator 'macro)
+ => (lambda (macro-function)
+ (compile-expr (apply macro-function arguments))))
+ (else
+ (make-application loc
+ (if (symbol? operator)
+ (reference-variable loc
+ operator
+ function-slot)
+ (compile-expr operator))
+ (map compile-expr arguments))))))
+
+;;; Compile a symbol expression. This is a variable reference or maybe
+;;; some special value like nil.
+
+(define (compile-symbol loc sym)
+ (case sym
+ ((nil) (nil-value loc))
+ ((t) (t-value loc))
+ (else (reference-variable loc sym value-slot))))
+
+;;; Compile a single expression to TreeIL.
+
+(define (compile-expr expr)
+ (let ((loc (location expr)))
+ (cond
+ ((symbol? expr)
+ (compile-symbol loc expr))
+ ((pair? expr)
+ (compile-pair loc expr))
+ (else (make-const loc expr)))))
+
+;;; Process the compiler options.
+;;; FIXME: Why is '(()) passed as options by the REPL?
+
+(define (valid-symbol-list-arg? value)
+ (or (eq? value 'all)
+ (and (list? value) (and-map symbol? value))))
+
+(define (process-options! opt)
+ (if (and (not (null? opt))
+ (not (equal? opt '(()))))
+ (if (null? (cdr opt))
+ (report-error #f "Invalid compiler options" opt)
+ (let ((key (car opt))
+ (value (cadr opt)))
+ (case key
+ ((#\warnings) ; ignore
+ #f)
+ ((#\always-lexical)
+ (if (valid-symbol-list-arg? value)
+ (fluid-set! always-lexical value)
+ (report-error #f
+ "Invalid value for #\always-lexical"
+ value)))
+ (else (report-error #f
+ "Invalid compiler option"
+ key)))))))
+
+;;; Entry point for compilation to TreeIL. This creates the bindings
+;;; data structure, and after compiling the main expression we need to
+;;; make sure all globals for symbols used during the compilation are
+;;; created using the generate-ensure-global function.
+
+(define (compile-tree-il expr env opts)
+ (values
+ (with-fluids ((bindings-data (make-bindings))
+ (disable-void-check '())
+ (always-lexical '()))
+ (process-options! opts)
+ (let ((compiled (compile-expr expr)))
+ (ensuring-globals (location expr) bindings-data compiled)))
+ env
+ env))
+;;; Guile Emacs Lisp
+
+;;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language elisp lexer)
+ #\use-module (ice-9 regex)
+ #\export (get-lexer get-lexer/1))
+
+;;; This is the lexical analyzer for the elisp reader. It is
+;;; hand-written instead of using some generator. I think this is the
+;;; best solution because of all that fancy escape sequence handling and
+;;; the like.
+;;;
+;;; Characters are handled internally as integers representing their
+;;; code value. This is necessary because elisp allows a lot of fancy
+;;; modifiers that set certain high-range bits and the resulting values
+;;; would not fit into a real Scheme character range. Additionally,
+;;; elisp wants characters as integers, so we just do the right thing...
+;;;
+;;; TODO: #@count comments
+
+;;; Report an error from the lexer (that is, invalid input given).
+
+(define (lexer-error port msg . args)
+ (apply error msg args))
+
+;;; In a character, set a given bit. This is just some bit-wise or'ing
+;;; on the characters integer code and converting back to character.
+
+(define (set-char-bit chr bit)
+ (logior chr (ash 1 bit)))
+
+;;; Check if a character equals some other. This is just like char=?
+;;; except that the tested one could be EOF in which case it simply
+;;; isn't equal.
+
+(define (is-char? tested should-be)
+ (and (not (eof-object? tested))
+ (char=? tested should-be)))
+
+;;; For a character (as integer code), find the real character it
+;;; represents or #\nul if out of range. This is used to work with
+;;; Scheme character functions like char-numeric?.
+
+(define (real-character chr)
+ (if (< chr 256)
+ (integer->char chr)
+ #\nul))
+
+;;; Return the control modified version of a character. This is not
+;;; just setting a modifier bit, because ASCII conrol characters must be
+;;; handled as such, and in elisp C-? is the delete character for
+;;; historical reasons. Otherwise, we set bit 26.
+
+(define (add-control chr)
+ (let ((real (real-character chr)))
+ (if (char-alphabetic? real)
+ (- (char->integer (char-upcase real)) (char->integer #\@))
+ (case real
+ ((#\?) 127)
+ ((#\@) 0)
+ (else (set-char-bit chr 26))))))
+
+;;; Parse a charcode given in some base, basically octal or hexadecimal
+;;; are needed. A requested number of digits can be given (#f means it
+;;; does not matter and arbitrary many are allowed), and additionally
+;;; early return allowed (if fewer valid digits are found). These
+;;; options are all we need to handle the \u, \U, \x and \ddd (octal
+;;; digits) escape sequences.
+
+(define (charcode-escape port base digits early-return)
+ (let iterate ((result 0)
+ (procdigs 0))
+ (if (and digits (>= procdigs digits))
+ result
+ (let* ((cur (read-char port))
+ (value (cond
+ ((char-numeric? cur)
+ (- (char->integer cur) (char->integer #\0)))
+ ((char-alphabetic? cur)
+ (let ((code (- (char->integer (char-upcase cur))
+ (char->integer #\A))))
+ (if (< code 0)
+ #f
+ (+ code 10))))
+ (else #f)))
+ (valid (and value (< value base))))
+ (if (not valid)
+ (if (or (not digits) early-return)
+ (begin
+ (unread-char cur port)
+ result)
+ (lexer-error port
+ "invalid digit in escape-code"
+ base
+ cur))
+ (iterate (+ (* result base) value) (1+ procdigs)))))))
+
+;;; Read a character and process escape-sequences when necessary. The
+;;; special in-string argument defines if this character is part of a
+;;; string literal or a single character literal, the difference being
+;;; that in strings the meta modifier sets bit 7, while it is bit 27 for
+;;; characters.
+
+(define basic-escape-codes
+ '((#\a . 7)
+ (#\b . 8)
+ (#\t . 9)
+ (#\n . 10)
+ (#\v . 11)
+ (#\f . 12)
+ (#\r . 13)
+ (#\e . 27)
+ (#\s . 32)
+ (#\d . 127)))
+
+(define (get-character port in-string)
+ (let ((meta-bits `((#\A . 22)
+ (#\s . 23)
+ (#\H . 24)
+ (#\S . 25)
+ (#\M . ,(if in-string 7 27))))
+ (cur (read-char port)))
+ (if (char=? cur #\\)
+ ;; Handle an escape-sequence.
+ (let* ((escaped (read-char port))
+ (esc-code (assq-ref basic-escape-codes escaped))
+ (meta (assq-ref meta-bits escaped)))
+ (cond
+ ;; Meta-check must be before esc-code check because \s- must
+ ;; be recognized as the super-meta modifier if a - follows.
+ ;; If not, it will be caught as \s -> space escape code.
+ ((and meta (is-char? (peek-char port) #\-))
+ (if (not (char=? (read-char port) #\-))
+ (error "expected - after control sequence"))
+ (set-char-bit (get-character port in-string) meta))
+ ;; One of the basic control character escape names?
+ (esc-code esc-code)
+ ;; Handle \ddd octal code if it is one.
+ ((and (char>=? escaped #\0) (char<? escaped #\8))
+ (begin
+ (unread-char escaped port)
+ (charcode-escape port 8 3 #t)))
+ ;; Check for some escape-codes directly or otherwise use the
+ ;; escaped character literally.
+ (else
+ (case escaped
+ ((#\^) (add-control (get-character port in-string)))
+ ((#\C)
+ (if (is-char? (peek-char port) #\-)
+ (begin
+ (if (not (char=? (read-char port) #\-))
+ (error "expected - after control sequence"))
+ (add-control (get-character port in-string)))
+ escaped))
+ ((#\x) (charcode-escape port 16 #f #t))
+ ((#\u) (charcode-escape port 16 4 #f))
+ ((#\U) (charcode-escape port 16 8 #f))
+ (else (char->integer escaped))))))
+ ;; No escape-sequence, just the literal character. But remember
+ ;; to get the code instead!
+ (char->integer cur))))
+
+;;; Read a symbol or number from a port until something follows that
+;;; marks the start of a new token (like whitespace or parentheses).
+;;; The data read is returned as a string for further conversion to the
+;;; correct type, but we also return what this is
+;;; (integer/float/symbol). If any escaped character is found, it must
+;;; be a symbol. Otherwise we at the end check the result-string
+;;; against regular expressions to determine if it is possibly an
+;;; integer or a float.
+
+(define integer-regex (make-regexp "^[+-]?[0-9]+\\.?$"))
+
+(define float-regex
+ (make-regexp
+ "^[+-]?([0-9]+\\.?[0-9]*|[0-9]*\\.?[0-9]+)(e[+-]?[0-9]+)?$"))
+
+;;; A dot is also allowed literally, only a single dort alone is parsed
+;;; as the 'dot' terminal for dotted lists.
+
+(define no-escape-punctuation (string->char-set "-+=*/_~!@$%^&:<>{}?."))
+
+(define (get-symbol-or-number port)
+ (let iterate ((result-chars '())
+ (had-escape #f))
+ (let* ((c (read-char port))
+ (finish (lambda ()
+ (let ((result (list->string
+ (reverse result-chars))))
+ (values
+ (cond
+ ((and (not had-escape)
+ (regexp-exec integer-regex result))
+ 'integer)
+ ((and (not had-escape)
+ (regexp-exec float-regex result))
+ 'float)
+ (else 'symbol))
+ result))))
+ (need-no-escape? (lambda (c)
+ (or (char-numeric? c)
+ (char-alphabetic? c)
+ (char-set-contains?
+ no-escape-punctuation
+ c)))))
+ (cond
+ ((eof-object? c) (finish))
+ ((need-no-escape? c) (iterate (cons c result-chars) had-escape))
+ ((char=? c #\\) (iterate (cons (read-char port) result-chars) #t))
+ (else
+ (unread-char c port)
+ (finish))))))
+
+;;; Parse a circular structure marker without the leading # (which was
+;;; already read and recognized), that is, a number as identifier and
+;;; then either = or #.
+
+(define (get-circular-marker port)
+ (call-with-values
+ (lambda ()
+ (let iterate ((result 0))
+ (let ((cur (read-char port)))
+ (if (char-numeric? cur)
+ (let ((val (- (char->integer cur) (char->integer #\0))))
+ (iterate (+ (* result 10) val)))
+ (values result cur)))))
+ (lambda (id type)
+ (case type
+ ((#\#) `(circular-ref . ,id))
+ ((#\=) `(circular-def . ,id))
+ (else (lexer-error port
+ "invalid circular marker character"
+ type))))))
+
+;;; Main lexer routine, which is given a port and does look for the next
+;;; token.
+
+(define (lex port)
+ (let ((return (let ((file (if (file-port? port)
+ (port-filename port)
+ #f))
+ (line (1+ (port-line port)))
+ (column (1+ (port-column port))))
+ (lambda (token value)
+ (let ((obj (cons token value)))
+ (set-source-property! obj 'filename file)
+ (set-source-property! obj 'line line)
+ (set-source-property! obj 'column column)
+ obj))))
+ ;; Read afterwards so the source-properties are correct above
+ ;; and actually point to the very character to be read.
+ (c (read-char port)))
+ (cond
+ ;; End of input must be specially marked to the parser.
+ ((eof-object? c) (return 'eof c))
+ ;; Whitespace, just skip it.
+ ((char-whitespace? c) (lex port))
+ ;; The dot is only the one for dotted lists if followed by
+ ;; whitespace. Otherwise it is considered part of a number of
+ ;; symbol.
+ ((and (char=? c #\.)
+ (char-whitespace? (peek-char port)))
+ (return 'dot #f))
+ ;; Continue checking for literal character values.
+ (else
+ (case c
+ ;; A line comment, skip until end-of-line is found.
+ ((#\;)
+ (let iterate ()
+ (let ((cur (read-char port)))
+ (if (or (eof-object? cur) (char=? cur #\newline))
+ (lex port)
+ (iterate)))))
+ ;; A character literal.
+ ((#\?)
+ (return 'character (get-character port #f)))
+ ;; A literal string. This is mainly a sequence of characters
+ ;; just as in the character literals, the only difference is
+ ;; that escaped newline and space are to be completely ignored
+ ;; and that meta-escapes set bit 7 rather than bit 27.
+ ((#\")
+ (let iterate ((result-chars '()))
+ (let ((cur (read-char port)))
+ (case cur
+ ((#\")
+ (return 'string (list->string (reverse result-chars))))
+ ((#\\)
+ (let ((escaped (read-char port)))
+ (case escaped
+ ((#\newline #\space)
+ (iterate result-chars))
+ (else
+ (unread-char escaped port)
+ (unread-char cur port)
+ (iterate
+ (cons (integer->char (get-character port #t))
+ result-chars))))))
+ (else (iterate (cons cur result-chars)))))))
+ ((#\#)
+ (let ((c (read-char port)))
+ (case c
+ ((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)
+ (unread-char c port)
+ (let ((mark (get-circular-marker port)))
+ (return (car mark) (cdr mark))))
+ ((#\')
+ (return 'function #f)))))
+ ;; Parentheses and other special-meaning single characters.
+ ((#\() (return 'paren-open #f))
+ ((#\)) (return 'paren-close #f))
+ ((#\[) (return 'square-open #f))
+ ((#\]) (return 'square-close #f))
+ ((#\') (return 'quote #f))
+ ((#\`) (return 'backquote #f))
+ ;; Unquote and unquote-splicing.
+ ((#\,)
+ (if (is-char? (peek-char port) #\@)
+ (if (not (char=? (read-char port) #\@))
+ (error "expected @ in unquote-splicing")
+ (return 'unquote-splicing #f))
+ (return 'unquote #f)))
+ ;; Remaining are numbers and symbols. Process input until next
+ ;; whitespace is found, and see if it looks like a number
+ ;; (float/integer) or symbol and return accordingly.
+ (else
+ (unread-char c port)
+ (call-with-values
+ (lambda () (get-symbol-or-number port))
+ (lambda (type str)
+ (case type
+ ((symbol)
+ ;; str could be empty if the first character is already
+ ;; something not allowed in a symbol (and not escaped)!
+ ;; Take care about that, it is an error because that
+ ;; character should have been handled elsewhere or is
+ ;; invalid in the input.
+ (if (zero? (string-length str))
+ (begin
+ ;; Take it out so the REPL might not get into an
+ ;; infinite loop with further reading attempts.
+ (read-char port)
+ (error "invalid character in input" c))
+ (return 'symbol (string->symbol str))))
+ ((integer)
+ ;; In elisp, something like "1." is an integer, while
+ ;; string->number returns an inexact real. Thus we need
+ ;; a conversion here, but it should always result in an
+ ;; integer!
+ (return
+ 'integer
+ (let ((num (inexact->exact (string->number str))))
+ (if (not (integer? num))
+ (error "expected integer" str num))
+ num)))
+ ((float)
+ (return 'float (let ((num (string->number str)))
+ (if (exact? num)
+ (error "expected inexact float"
+ str
+ num))
+ num)))
+ (else (error "wrong number/symbol type" type)))))))))))
+
+;;; Build a lexer thunk for a port. This is the exported routine which
+;;; can be used to create a lexer for the parser to use.
+
+(define (get-lexer port)
+ (lambda () (lex port)))
+
+;;; Build a special lexer that will only read enough for one expression
+;;; and then always return end-of-input. If we find one of the quotation
+;;; stuff, one more expression is needed in any case.
+
+(define (get-lexer/1 port)
+ (let ((lex (get-lexer port))
+ (finished #f)
+ (paren-level 0))
+ (lambda ()
+ (if finished
+ (cons 'eof ((@ (ice-9 binary-ports) eof-object)))
+ (let ((next (lex))
+ (quotation #f))
+ (case (car next)
+ ((paren-open square-open)
+ (set! paren-level (1+ paren-level)))
+ ((paren-close square-close)
+ (set! paren-level (1- paren-level)))
+ ((quote backquote unquote unquote-splicing circular-def)
+ (set! quotation #t)))
+ (if (and (not quotation) (<= paren-level 0))
+ (set! finished #t))
+ next)))))
+;;; Guile Emacs Lisp
+
+;;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language elisp parser)
+ #\use-module (language elisp lexer)
+ #\export (read-elisp))
+
+;;; The parser (reader) for elisp expressions.
+;;;
+;;; It is hand-written (just as the lexer is) instead of using some
+;;; parser generator because this allows easier transfer of source
+;;; properties from the lexer ((text parse-lalr) seems not to allow
+;;; access to the original lexer token-pair) and is easy enough anyways.
+
+;;; Report a parse error. The first argument is some current lexer
+;;; token where source information is available should it be useful.
+
+(define (parse-error token msg . args)
+ (apply error msg args))
+
+;;; For parsing circular structures, we keep track of definitions in a
+;;; hash-map that maps the id's to their values. When defining a new
+;;; id, though, we immediatly fill the slot with a promise before
+;;; parsing and setting the real value, because it must already be
+;;; available at that time in case of a circular reference. The promise
+;;; refers to a local variable that will be set when the real value is
+;;; available through a closure. After parsing the expression is
+;;; completed, we work through it again and force all promises we find.
+;;; The definitions themselves are stored in a fluid and their scope is
+;;; one call to read-elisp (but not only the currently parsed
+;;; expression!).
+
+(define circular-definitions (make-fluid))
+
+(define (make-circular-definitions)
+ (make-hash-table))
+
+(define (circular-ref token)
+ (if (not (eq? (car token) 'circular-ref))
+ (error "invalid token for circular-ref" token))
+ (let* ((id (cdr token))
+ (value (hashq-ref (fluid-ref circular-definitions) id)))
+ (if value
+ value
+ (parse-error token "undefined circular reference" id))))
+
+;;; Returned is a closure that, when invoked, will set the final value.
+;;; This means both the variable the promise will return and the
+;;; hash-table slot so we don't generate promises any longer.
+
+(define (circular-define! token)
+ (if (not (eq? (car token) 'circular-def))
+ (error "invalid token for circular-define!" token))
+ (let ((value #f)
+ (table (fluid-ref circular-definitions))
+ (id (cdr token)))
+ (hashq-set! table id (delay value))
+ (lambda (real-value)
+ (set! value real-value)
+ (hashq-set! table id real-value))))
+
+;;; Work through a parsed data structure and force the promises there.
+;;; After a promise is forced, the resulting value must not be recursed
+;;; on; this may lead to infinite recursion with a circular structure,
+;;; and additionally this value was already processed when it was
+;;; defined. All deep data structures that can be parsed must be
+;;; handled here!
+
+(define (force-promises! data)
+ (cond
+ ((pair? data)
+ (begin
+ (if (promise? (car data))
+ (set-car! data (force (car data)))
+ (force-promises! (car data)))
+ (if (promise? (cdr data))
+ (set-cdr! data (force (cdr data)))
+ (force-promises! (cdr data)))))
+ ((vector? data)
+ (let ((len (vector-length data)))
+ (let iterate ((i 0))
+ (if (< i len)
+ (let ((el (vector-ref data i)))
+ (if (promise? el)
+ (vector-set! data i (force el))
+ (force-promises! el))
+ (iterate (1+ i)))))))
+ ;; Else nothing needs to be done.
+ ))
+
+;;; We need peek-functionality for the next lexer token, this is done
+;;; with some single token look-ahead storage. This is handled by a
+;;; closure which allows getting or peeking the next token. When one
+;;; expression is fully parsed, we don't want a look-ahead stored here
+;;; because it would miss from future parsing. This is verified by the
+;;; finish action.
+
+(define (make-lexer-buffer lex)
+ (let ((look-ahead #f))
+ (lambda (action)
+ (if (eq? action 'finish)
+ (if look-ahead
+ (error "lexer-buffer is not empty when finished")
+ #f)
+ (begin
+ (if (not look-ahead)
+ (set! look-ahead (lex)))
+ (case action
+ ((peek) look-ahead)
+ ((get)
+ (let ((result look-ahead))
+ (set! look-ahead #f)
+ result))
+ (else (error "invalid lexer-buffer action" action))))))))
+
+;;; Get the contents of a list, where the opening parentheses has
+;;; already been found. The same code is used for vectors and lists,
+;;; where lists allow the dotted tail syntax and vectors not;
+;;; additionally, the closing parenthesis must of course match. The
+;;; implementation here is not tail-recursive, but I think it is clearer
+;;; and simpler this way.
+
+(define (get-list lex allow-dot close-square)
+ (let* ((next (lex 'peek))
+ (type (car next)))
+ (cond
+ ((eq? type (if close-square 'square-close 'paren-close))
+ (begin
+ (if (not (eq? (car (lex 'get)) type))
+ (error "got different token than peeked"))
+ '()))
+ ((and allow-dot (eq? type 'dot))
+ (begin
+ (if (not (eq? (car (lex 'get)) type))
+ (error "got different token than peeked"))
+ (let ((tail (get-list lex #f close-square)))
+ (if (not (= (length tail) 1))
+ (parse-error next
+ "expected exactly one element after dot"))
+ (car tail))))
+ (else
+ ;; Do both parses in exactly this sequence!
+ (let* ((head (get-expression lex))
+ (tail (get-list lex allow-dot close-square)))
+ (cons head tail))))))
+
+;;; Parse a single expression from a lexer-buffer. This is the main
+;;; routine in our recursive-descent parser.
+
+(define quotation-symbols '((quote . quote)
+ (backquote . #\`)
+ (unquote . #\,)
+ (unquote-splicing . #\,\@)))
+
+(define (get-expression lex)
+ (let* ((token (lex 'get))
+ (type (car token))
+ (return (lambda (result)
+ (if (pair? result)
+ (set-source-properties!
+ result
+ (source-properties token)))
+ result)))
+ (case type
+ ((eof)
+ (parse-error token "end of file during parsing"))
+ ((integer float symbol character string)
+ (return (cdr token)))
+ ((function)
+ (return `(function ,(get-expression lex))))
+ ((quote backquote unquote unquote-splicing)
+ (return (list (assq-ref quotation-symbols type)
+ (get-expression lex))))
+ ((paren-open)
+ (return (get-list lex #t #f)))
+ ((square-open)
+ (return (list->vector (get-list lex #f #t))))
+ ((circular-ref)
+ (circular-ref token))
+ ((circular-def)
+ ;; The order of definitions is important!
+ (let* ((setter (circular-define! token))
+ (expr (get-expression lex)))
+ (setter expr)
+ (force-promises! expr)
+ expr))
+ (else
+ (parse-error token "expected expression, got" token)))))
+
+;;; Define the reader function based on this; build a lexer, a
+;;; lexer-buffer, and then parse a single expression to return. We also
+;;; define a circular-definitions data structure to use.
+
+(define (read-elisp port)
+ (with-fluids ((circular-definitions (make-circular-definitions)))
+ (let* ((lexer (get-lexer port))
+ (lexbuf (make-lexer-buffer lexer))
+ (next (lexbuf 'peek)))
+ (if (eq? (car next) 'eof)
+ (cdr next)
+ (let ((result (get-expression lexbuf)))
+ (lexbuf 'finish)
+ result)))))
+;;; Guile Emacs Lisp
+
+;;; Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language elisp runtime)
+ #\export (nil-value
+ t-value
+ value-slot-module
+ function-slot-module
+ elisp-bool
+ ensure-fluid!
+ reference-variable
+ set-variable!
+ runtime-error
+ macro-error)
+ #\export-syntax (built-in-func built-in-macro defspecial prim))
+
+;;; This module provides runtime support for the Elisp front-end.
+
+;;; Values for t and nil. (FIXME remove this abstraction)
+
+(define nil-value #nil)
+
+(define t-value #t)
+
+;;; Modules for the binding slots.
+;;; Note: Naming those value-slot and/or function-slot clashes with the
+;;; submodules of these names!
+
+(define value-slot-module '(language elisp runtime value-slot))
+
+(define function-slot-module '(language elisp runtime function-slot))
+
+;;; Report an error during macro compilation, that means some special
+;;; compilation (syntax) error; or report a simple runtime-error from a
+;;; built-in function.
+
+(define (macro-error msg . args)
+ (apply error msg args))
+
+(define runtime-error macro-error)
+
+;;; Convert a scheme boolean to Elisp.
+
+(define (elisp-bool b)
+ (if b
+ t-value
+ nil-value))
+
+;;; Routines for access to elisp dynamically bound symbols. This is
+;;; used for runtime access using functions like symbol-value or set,
+;;; where the symbol accessed might not be known at compile-time. These
+;;; always access the dynamic binding and can not be used for the
+;;; lexical!
+
+(define (ensure-fluid! module sym)
+ (let ((intf (resolve-interface module))
+ (resolved (resolve-module module)))
+ (if (not (module-defined? intf sym))
+ (let ((fluid (make-unbound-fluid)))
+ (module-define! resolved sym fluid)
+ (module-export! resolved `(,sym))))))
+
+(define (reference-variable module sym)
+ (let ((resolved (resolve-module module)))
+ (cond
+ ((equal? module function-slot-module)
+ (module-ref resolved sym))
+ (else
+ (ensure-fluid! module sym)
+ (fluid-ref (module-ref resolved sym))))))
+
+(define (set-variable! module sym value)
+ (let ((intf (resolve-interface module))
+ (resolved (resolve-module module)))
+ (cond
+ ((equal? module function-slot-module)
+ (cond
+ ((module-defined? intf sym)
+ (module-set! resolved sym value))
+ (else
+ (module-define! resolved sym value)
+ (module-export! resolved `(,sym)))))
+ (else
+ (ensure-fluid! module sym)
+ (fluid-set! (module-ref resolved sym) value))))
+ value)
+
+;;; Define a predefined function or predefined macro for use in the
+;;; function-slot and macro-slot modules, respectively.
+
+(define-syntax built-in-func
+ (syntax-rules ()
+ ((_ name value)
+ (begin
+ (define-public name value)))))
+
+(define (make-id template-id . data)
+ (let ((append-symbols
+ (lambda (symbols)
+ (string->symbol
+ (apply string-append (map symbol->string symbols))))))
+ (datum->syntax template-id
+ (append-symbols
+ (map (lambda (datum)
+ ((if (identifier? datum)
+ syntax->datum
+ identity)
+ datum))
+ data)))))
+
+(define-syntax built-in-macro
+ (lambda (x)
+ (syntax-case x ()
+ ((_ name value)
+ (with-syntax ((scheme-name (make-id #'name 'macro- #'name)))
+ #'(begin
+ (define-public scheme-name
+ (make-fluid (cons 'macro value)))))))))
+
+(define-syntax defspecial
+ (lambda (x)
+ (syntax-case x ()
+ ((_ name args body ...)
+ (with-syntax ((scheme-name (make-id #'name 'compile- #'name)))
+ #'(begin
+ (define scheme-name
+ (make-fluid
+ (cons 'special-operator
+ (lambda args body ...))))))))))
+
+;;; Call a guile-primitive that may be rebound for elisp and thus needs
+;;; absolute addressing.
+
+(define-syntax prim
+ (syntax-rules ()
+ ((_ sym args ...)
+ ((@ (guile) sym) args ...))))
+;;; Guile Emacs Lisp
+
+;;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (language elisp runtime function-slot)
+ #\use-module (language elisp runtime subrs)
+ #\use-module ((language elisp runtime macros)
+ #\select
+ ((macro-lambda . lambda)
+ (macro-prog1 . prog1)
+ (macro-prog2 . prog2)
+ (macro-when . when)
+ (macro-unless . unless)
+ (macro-cond . cond)
+ (macro-and . and)
+ (macro-or . or)
+ (macro-dotimes . dotimes)
+ (macro-dolist . dolist)
+ (macro-catch . catch)
+ (macro-unwind-protect . unwind-protect)
+ (macro-pop . pop)
+ (macro-push . push)))
+ #\use-module ((language elisp compile-tree-il)
+ #\select
+ ((compile-progn . progn)
+ (compile-if . if)
+ (compile-defconst . defconst)
+ (compile-defvar . defvar)
+ (compile-setq . setq)
+ (compile-let . let)
+ (compile-lexical-let . lexical-let)
+ (compile-flet . flet)
+ (compile-let* . let*)
+ (compile-lexical-let* . lexical-let*)
+ (compile-flet* . flet*)
+ (compile-with-always-lexical . with-always-lexical)
+ (compile-guile-ref . guile-ref)
+ (compile-guile-primitive . guile-primitive)
+ (compile-while . while)
+ (compile-function . function)
+ (compile-defun . defun)
+ (compile-defmacro . defmacro)
+ (#{compile-\`} . #\`)
+ (compile-quote . quote)))
+ #\duplicates (last)
+ ;; special operators
+ #\re-export (progn
+ if
+ defconst
+ defvar
+ setq
+ let
+ lexical-let
+ flet
+ let*
+ lexical-let*
+ flet*
+ with-always-lexical
+ guile-ref
+ guile-primitive
+ while
+ function
+ defun
+ defmacro
+ #\`
+ quote)
+ ;; macros
+ #\re-export (lambda
+ prog1
+ prog2
+ when
+ unless
+ cond
+ and
+ or
+ dotimes
+ dolist
+ catch
+ unwind-protect
+ pop
+ push)
+ ;; functions
+ #\re-export (eq
+ equal
+ floatp
+ integerp
+ numberp
+ wholenump
+ zerop
+ =
+ /=
+ <
+ <=
+ >
+ >=
+ max
+ min
+ abs
+ float
+ 1+
+ 1-
+ +
+ -
+ *
+ %
+ ffloor
+ fceiling
+ ftruncate
+ fround
+ consp
+ atomp
+ listp
+ nlistp
+ null
+ car
+ cdr
+ car-safe
+ cdr-safe
+ nth
+ nthcdr
+ length
+ cons
+ list
+ make-list
+ append
+ reverse
+ copy-tree
+ number-sequence
+ setcar
+ setcdr
+ symbol-value
+ symbol-function
+ set
+ fset
+ makunbound
+ fmakunbound
+ boundp
+ fboundp
+ apply
+ funcall
+ throw
+ not
+ eval
+ load))
+;;; Guile Emacs Lisp
+
+;;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language elisp runtime macros)
+ #\use-module (language elisp runtime))
+
+;;; This module contains the macro definitions of elisp symbols. In
+;;; contrast to the other runtime modules, those are used directly
+;;; during compilation, of course, so not really in runtime. But I
+;;; think it fits well to the others here.
+
+(built-in-macro lambda
+ (lambda cdr
+ `(function (lambda ,@cdr))))
+
+;;; The prog1 and prog2 constructs can easily be defined as macros using
+;;; progn and some lexical-let's to save the intermediate value to
+;;; return at the end.
+
+(built-in-macro prog1
+ (lambda (form1 . rest)
+ (let ((temp (gensym)))
+ `(lexical-let ((,temp ,form1))
+ ,@rest
+ ,temp))))
+
+(built-in-macro prog2
+ (lambda (form1 form2 . rest)
+ `(progn ,form1 (prog1 ,form2 ,@rest))))
+
+;;; Define the conditionals when and unless as macros.
+
+(built-in-macro when
+ (lambda (condition . thens)
+ `(if ,condition (progn ,@thens) nil)))
+
+(built-in-macro unless
+ (lambda (condition . elses)
+ `(if ,condition nil (progn ,@elses))))
+
+;;; Impement the cond form as nested if's. A special case is a
+;;; (condition) subform, in which case we need to return the condition
+;;; itself if it is true and thus save it in a local variable before
+;;; testing it.
+
+(built-in-macro cond
+ (lambda (. clauses)
+ (let iterate ((tail clauses))
+ (if (null? tail)
+ 'nil
+ (let ((cur (car tail))
+ (rest (iterate (cdr tail))))
+ (prim cond
+ ((prim or (not (list? cur)) (null? cur))
+ (macro-error "invalid clause in cond" cur))
+ ((null? (cdr cur))
+ (let ((var (gensym)))
+ `(lexical-let ((,var ,(car cur)))
+ (if ,var
+ ,var
+ ,rest))))
+ (else
+ `(if ,(car cur)
+ (progn ,@(cdr cur))
+ ,rest))))))))
+
+;;; The `and' and `or' forms can also be easily defined with macros.
+
+(built-in-macro and
+ (case-lambda
+ (() 't)
+ ((x) x)
+ ((x . args)
+ (let iterate ((x x) (tail args))
+ (if (null? tail)
+ x
+ `(if ,x
+ ,(iterate (car tail) (cdr tail))
+ nil))))))
+
+(built-in-macro or
+ (case-lambda
+ (() 'nil)
+ ((x) x)
+ ((x . args)
+ (let iterate ((x x) (tail args))
+ (if (null? tail)
+ x
+ (let ((var (gensym)))
+ `(lexical-let ((,var ,x))
+ (if ,var
+ ,var
+ ,(iterate (car tail) (cdr tail))))))))))
+
+;;; Define the dotimes and dolist iteration macros.
+
+(built-in-macro dotimes
+ (lambda (args . body)
+ (if (prim or
+ (not (list? args))
+ (< (length args) 2)
+ (> (length args) 3))
+ (macro-error "invalid dotimes arguments" args)
+ (let ((var (car args))
+ (count (cadr args)))
+ (if (not (symbol? var))
+ (macro-error "expected symbol as dotimes variable"))
+ `(let ((,var 0))
+ (while ((guile-primitive <) ,var ,count)
+ ,@body
+ (setq ,var ((guile-primitive 1+) ,var)))
+ ,@(if (= (length args) 3)
+ (list (caddr args))
+ '()))))))
+
+(built-in-macro dolist
+ (lambda (args . body)
+ (if (prim or
+ (not (list? args))
+ (< (length args) 2)
+ (> (length args) 3))
+ (macro-error "invalid dolist arguments" args)
+ (let ((var (car args))
+ (iter-list (cadr args))
+ (tailvar (gensym)))
+ (if (not (symbol? var))
+ (macro-error "expected symbol as dolist variable")
+ `(let (,var)
+ (lexical-let ((,tailvar ,iter-list))
+ (while ((guile-primitive not)
+ ((guile-primitive null?) ,tailvar))
+ (setq ,var ((guile-primitive car) ,tailvar))
+ ,@body
+ (setq ,tailvar ((guile-primitive cdr) ,tailvar)))
+ ,@(if (= (length args) 3)
+ (list (caddr args))
+ '()))))))))
+
+;;; Exception handling. unwind-protect and catch are implemented as
+;;; macros (throw is a built-in function).
+
+;;; catch and throw can mainly be implemented directly using Guile's
+;;; primitives for exceptions, the only difficulty is that the keys used
+;;; within Guile must be symbols, while elisp allows any value and
+;;; checks for matches using eq (eq?). We handle this by using always #t
+;;; as key for the Guile primitives and check for matches inside the
+;;; handler; if the elisp keys are not eq?, we rethrow the exception.
+
+(built-in-macro catch
+ (lambda (tag . body)
+ (if (null? body)
+ (macro-error "catch with empty body"))
+ (let ((tagsym (gensym)))
+ `(lexical-let ((,tagsym ,tag))
+ ((guile-primitive catch)
+ #t
+ (lambda () ,@body)
+ ,(let* ((dummy-key (gensym))
+ (elisp-key (gensym))
+ (value (gensym))
+ (arglist `(,dummy-key ,elisp-key ,value)))
+ `(with-always-lexical
+ ,arglist
+ (lambda ,arglist
+ (if (eq ,elisp-key ,tagsym)
+ ,value
+ ((guile-primitive throw) ,dummy-key ,elisp-key
+ ,value))))))))))
+
+;;; unwind-protect is just some weaker construct as dynamic-wind, so
+;;; straight-forward to implement.
+
+(built-in-macro unwind-protect
+ (lambda (body . clean-ups)
+ (if (null? clean-ups)
+ (macro-error "unwind-protect without cleanup code"))
+ `((guile-primitive dynamic-wind)
+ (lambda () nil)
+ (lambda () ,body)
+ (lambda () ,@clean-ups))))
+
+;;; Pop off the first element from a list or push one to it.
+
+(built-in-macro pop
+ (lambda (list-name)
+ `(prog1 (car ,list-name)
+ (setq ,list-name (cdr ,list-name)))))
+
+(built-in-macro push
+ (lambda (new-el list-name)
+ `(setq ,list-name (cons ,new-el ,list-name))))
+;;; Guile Emacs Lisp
+
+;;; Copyright (C) 2009 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;;; 02110-1301 USA
+
+;;; Code:
+
+(define-module (language elisp runtime subrs)
+ #\use-module (language elisp runtime)
+ #\use-module (system base compile))
+
+;;; This module contains the function-slots of elisp symbols. Elisp
+;;; built-in functions are implemented as predefined function bindings
+;;; here.
+
+;;; Equivalence and equalness predicates.
+
+(built-in-func eq
+ (lambda (a b)
+ (elisp-bool (eq? a b))))
+
+(built-in-func equal
+ (lambda (a b)
+ (elisp-bool (equal? a b))))
+
+;;; Number predicates.
+
+(built-in-func floatp
+ (lambda (num)
+ (elisp-bool (and (real? num)
+ (or (inexact? num)
+ (prim not (integer? num)))))))
+
+(built-in-func integerp
+ (lambda (num)
+ (elisp-bool (and (exact? num)
+ (integer? num)))))
+
+(built-in-func numberp
+ (lambda (num)
+ (elisp-bool (real? num))))
+
+(built-in-func wholenump
+ (lambda (num)
+ (elisp-bool (and (exact? num)
+ (integer? num)
+ (prim >= num 0)))))
+
+(built-in-func zerop
+ (lambda (num)
+ (elisp-bool (prim = num 0))))
+
+;;; Number comparisons.
+
+(built-in-func =
+ (lambda (num1 num2)
+ (elisp-bool (prim = num1 num2))))
+
+(built-in-func /=
+ (lambda (num1 num2)
+ (elisp-bool (prim not (prim = num1 num2)))))
+
+(built-in-func <
+ (lambda (num1 num2)
+ (elisp-bool (prim < num1 num2))))
+
+(built-in-func <=
+ (lambda (num1 num2)
+ (elisp-bool (prim <= num1 num2))))
+
+(built-in-func >
+ (lambda (num1 num2)
+ (elisp-bool (prim > num1 num2))))
+
+(built-in-func >=
+ (lambda (num1 num2)
+ (elisp-bool (prim >= num1 num2))))
+
+(built-in-func max
+ (lambda (. nums)
+ (prim apply (@ (guile) max) nums)))
+
+(built-in-func min
+ (lambda (. nums)
+ (prim apply (@ (guile) min) nums)))
+
+(built-in-func abs
+ (@ (guile) abs))
+
+;;; Number conversion.
+
+(built-in-func float
+ (lambda (num)
+ (if (exact? num)
+ (exact->inexact num)
+ num)))
+
+;;; TODO: truncate, floor, ceiling, round.
+
+;;; Arithmetic functions.
+
+(built-in-func 1+ (@ (guile) 1+))
+
+(built-in-func 1- (@ (guile) 1-))
+
+(built-in-func + (@ (guile) +))
+
+(built-in-func - (@ (guile) -))
+
+(built-in-func * (@ (guile) *))
+
+(built-in-func % (@ (guile) modulo))
+
+;;; TODO: / with correct integer/real behaviour, mod (for floating-piont
+;;; values).
+
+;;; Floating-point rounding operations.
+
+(built-in-func ffloor (@ (guile) floor))
+
+(built-in-func fceiling (@ (guile) ceiling))
+
+(built-in-func ftruncate (@ (guile) truncate))
+
+(built-in-func fround (@ (guile) round))
+
+;;; List predicates.
+
+(built-in-func consp
+ (lambda (el)
+ (elisp-bool (pair? el))))
+
+(built-in-func atomp
+ (lambda (el)
+ (elisp-bool (prim not (pair? el)))))
+
+(built-in-func listp
+ (lambda (el)
+ (elisp-bool (or (pair? el) (null? el)))))
+
+(built-in-func nlistp
+ (lambda (el)
+ (elisp-bool (and (prim not (pair? el))
+ (prim not (null? el))))))
+
+(built-in-func null
+ (lambda (el)
+ (elisp-bool (null? el))))
+
+;;; Accessing list elements.
+
+(built-in-func car
+ (lambda (el)
+ (if (null? el)
+ nil-value
+ (prim car el))))
+
+(built-in-func cdr
+ (lambda (el)
+ (if (null? el)
+ nil-value
+ (prim cdr el))))
+
+(built-in-func car-safe
+ (lambda (el)
+ (if (pair? el)
+ (prim car el)
+ nil-value)))
+
+(built-in-func cdr-safe
+ (lambda (el)
+ (if (pair? el)
+ (prim cdr el)
+ nil-value)))
+
+(built-in-func nth
+ (lambda (n lst)
+ (if (negative? n)
+ (prim car lst)
+ (let iterate ((i n)
+ (tail lst))
+ (cond
+ ((null? tail) nil-value)
+ ((zero? i) (prim car tail))
+ (else (iterate (prim 1- i) (prim cdr tail))))))))
+
+(built-in-func nthcdr
+ (lambda (n lst)
+ (if (negative? n)
+ lst
+ (let iterate ((i n)
+ (tail lst))
+ (cond
+ ((null? tail) nil-value)
+ ((zero? i) tail)
+ (else (iterate (prim 1- i) (prim cdr tail))))))))
+
+(built-in-func length (@ (guile) length))
+
+;;; Building lists.
+
+(built-in-func cons (@ (guile) cons))
+
+(built-in-func list (@ (guile) list))
+
+(built-in-func make-list
+ (lambda (len obj)
+ (prim make-list len obj)))
+
+(built-in-func append (@ (guile) append))
+
+(built-in-func reverse (@ (guile) reverse))
+
+(built-in-func copy-tree (@ (guile) copy-tree))
+
+(built-in-func number-sequence
+ (lambda (from . rest)
+ (if (prim > (prim length rest) 2)
+ (runtime-error "too many arguments for number-sequence"
+ (prim cdddr rest))
+ (if (null? rest)
+ `(,from)
+ (let ((to (prim car rest))
+ (sep (if (or (null? (prim cdr rest))
+ (eq? nil-value (prim cadr rest)))
+ 1
+ (prim cadr rest))))
+ (cond
+ ((or (eq? nil-value to) (prim = to from)) `(,from))
+ ((and (zero? sep) (prim not (prim = from to)))
+ (runtime-error "infinite list in number-sequence"))
+ ((prim < (prim * to sep) (prim * from sep)) '())
+ (else
+ (let iterate ((i (prim +
+ from
+ (prim *
+ sep
+ (prim quotient
+ (prim abs
+ (prim -
+ to
+ from))
+ (prim abs sep)))))
+ (result '()))
+ (if (prim = i from)
+ (prim cons i result)
+ (iterate (prim - i sep)
+ (prim cons i result)))))))))))
+
+;;; Changing lists.
+
+(built-in-func setcar
+ (lambda (cell val)
+ (if (and (null? cell) (null? val))
+ #nil
+ (prim set-car! cell val))
+ val))
+
+(built-in-func setcdr
+ (lambda (cell val)
+ (if (and (null? cell) (null? val))
+ #nil
+ (prim set-cdr! cell val))
+ val))
+
+;;; Accessing symbol bindings for symbols known only at runtime.
+
+(built-in-func symbol-value
+ (lambda (sym)
+ (reference-variable value-slot-module sym)))
+
+(built-in-func symbol-function
+ (lambda (sym)
+ (reference-variable function-slot-module sym)))
+
+(built-in-func set
+ (lambda (sym value)
+ (set-variable! value-slot-module sym value)))
+
+(built-in-func fset
+ (lambda (sym value)
+ (set-variable! function-slot-module sym value)))
+
+(built-in-func makunbound
+ (lambda (sym)
+ (if (module-bound? (resolve-interface value-slot-module) sym)
+ (let ((var (module-variable (resolve-module value-slot-module)
+ sym)))
+ (if (and (variable-bound? var) (fluid? (variable-ref var)))
+ (fluid-unset! (variable-ref var))
+ (variable-unset! var))))
+ sym))
+
+(built-in-func fmakunbound
+ (lambda (sym)
+ (if (module-bound? (resolve-interface function-slot-module) sym)
+ (let ((var (module-variable
+ (resolve-module function-slot-module)
+ sym)))
+ (if (and (variable-bound? var) (fluid? (variable-ref var)))
+ (fluid-unset! (variable-ref var))
+ (variable-unset! var))))
+ sym))
+
+(built-in-func boundp
+ (lambda (sym)
+ (elisp-bool
+ (and
+ (module-bound? (resolve-interface value-slot-module) sym)
+ (let ((var (module-variable (resolve-module value-slot-module)
+ sym)))
+ (and (variable-bound? var)
+ (if (fluid? (variable-ref var))
+ (fluid-bound? (variable-ref var))
+ #t)))))))
+
+(built-in-func fboundp
+ (lambda (sym)
+ (elisp-bool
+ (and
+ (module-bound? (resolve-interface function-slot-module) sym)
+ (let* ((var (module-variable (resolve-module function-slot-module)
+ sym)))
+ (and (variable-bound? var)
+ (if (fluid? (variable-ref var))
+ (fluid-bound? (variable-ref var))
+ #t)))))))
+
+;;; Function calls. These must take care of special cases, like using
+;;; symbols or raw lambda-lists as functions!
+
+(built-in-func apply
+ (lambda (func . args)
+ (let ((real-func (cond
+ ((symbol? func)
+ (reference-variable function-slot-module func))
+ ((list? func)
+ (if (and (prim not (null? func))
+ (eq? (prim car func) 'lambda))
+ (compile func #\from 'elisp #\to 'value)
+ (runtime-error "list is not a function"
+ func)))
+ (else func))))
+ (prim apply (@ (guile) apply) real-func args))))
+
+(built-in-func funcall
+ (lambda (func . args)
+ (apply func args)))
+
+;;; Throw can be implemented as built-in function.
+
+(built-in-func throw
+ (lambda (tag value)
+ (prim throw 'elisp-exception tag value)))
+
+;;; Miscellaneous.
+
+(built-in-func not
+ (lambda (x)
+ (if x nil-value t-value)))
+
+(built-in-func eval
+ (lambda (form)
+ (compile form #\from 'elisp #\to 'value)))
+
+(built-in-func load
+ (lambda* (file)
+ (compile-file file #\from 'elisp #\to 'value)
+ #t))
+;;; Guile Emacs Lisp
+
+;;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language elisp runtime value-slot))
+
+;;; This module contains the value-slots of elisp symbols.
+;;; Guile Emac Lisp
+
+;; Copyright (C) 2001, 2009, 2010 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language elisp spec)
+ #\use-module (language elisp compile-tree-il)
+ #\use-module (language elisp parser)
+ #\use-module (system base language)
+ #\export (elisp))
+
+(define-language elisp
+ #\title "Emacs Lisp"
+ #\reader (lambda (port env) (read-elisp port))
+ #\printer write
+ #\compilers `((tree-il . ,compile-tree-il)))
+;;; Guile Low Intermediate Language
+
+;; Copyright (C) 2001, 2009, 2010 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language glil)
+ #\use-module (system base syntax)
+ #\use-module (system base pmatch)
+ #\use-module ((srfi srfi-1) #\select (fold))
+ #\export
+ (<glil-program> make-glil-program glil-program?
+ glil-program-meta glil-program-body
+
+ <glil-std-prelude> make-glil-std-prelude glil-std-prelude?
+ glil-std-prelude-nreq glil-std-prelude-nlocs glil-std-prelude-else-label
+
+ <glil-opt-prelude> make-glil-opt-prelude glil-opt-prelude?
+ glil-opt-prelude-nreq glil-opt-prelude-nopt glil-opt-prelude-rest
+ glil-opt-prelude-nlocs glil-opt-prelude-else-label
+
+ <glil-kw-prelude> make-glil-kw-prelude glil-kw-prelude?
+ glil-kw-prelude-nreq glil-kw-prelude-nopt glil-kw-prelude-kw
+ glil-kw-prelude-allow-other-keys? glil-kw-prelude-rest
+ glil-kw-prelude-nlocs glil-kw-prelude-else-label
+
+ <glil-bind> make-glil-bind glil-bind?
+ glil-bind-vars
+
+ <glil-mv-bind> make-glil-mv-bind glil-mv-bind?
+ glil-mv-bind-vars glil-mv-bind-rest
+
+ <glil-unbind> make-glil-unbind glil-unbind?
+
+ <glil-source> make-glil-source glil-source?
+ glil-source-props
+
+ <glil-void> make-glil-void glil-void?
+
+ <glil-const> make-glil-const glil-const?
+ glil-const-obj
+
+ <glil-lexical> make-glil-lexical glil-lexical?
+ glil-lexical-local? glil-lexical-boxed? glil-lexical-op glil-lexical-index
+
+ <glil-toplevel> make-glil-toplevel glil-toplevel?
+ glil-toplevel-op glil-toplevel-name
+
+ <glil-module> make-glil-module glil-module?
+ glil-module-op glil-module-mod glil-module-name glil-module-public?
+
+ <glil-label> make-glil-label glil-label?
+ glil-label-label
+
+ <glil-branch> make-glil-branch glil-branch?
+ glil-branch-inst glil-branch-label
+
+ <glil-call> make-glil-call glil-call?
+ glil-call-inst glil-call-nargs
+
+ <glil-mv-call> make-glil-mv-call glil-mv-call?
+ glil-mv-call-nargs glil-mv-call-ra
+
+ <glil-prompt> make-glil-prompt glil-prompt? glil-prompt-label glil-prompt-escape-only?
+
+ parse-glil unparse-glil))
+
+(define (print-glil x port)
+ (format port "#<glil ~s>" (unparse-glil x)))
+
+(define-type (<glil> #\printer print-glil)
+ ;; Meta operations
+ (<glil-program> meta body)
+ (<glil-std-prelude> nreq nlocs else-label)
+ (<glil-opt-prelude> nreq nopt rest nlocs else-label)
+ (<glil-kw-prelude> nreq nopt rest kw allow-other-keys? nlocs else-label)
+ (<glil-bind> vars)
+ (<glil-mv-bind> vars rest)
+ (<glil-unbind>)
+ (<glil-source> props)
+ ;; Objects
+ (<glil-void>)
+ (<glil-const> obj)
+ ;; Variables
+ (<glil-lexical> local? boxed? op index)
+ (<glil-toplevel> op name)
+ (<glil-module> op mod name public?)
+ ;; Controls
+ (<glil-label> label)
+ (<glil-branch> inst label)
+ (<glil-call> inst nargs)
+ (<glil-mv-call> nargs ra)
+ (<glil-prompt> label escape-only?))
+
+
+
+(define (parse-glil x)
+ (pmatch x
+ ((program ,meta . ,body)
+ (make-glil-program meta (map parse-glil body)))
+ ((std-prelude ,nreq ,nlocs ,else-label)
+ (make-glil-std-prelude nreq nlocs else-label))
+ ((opt-prelude ,nreq ,nopt ,rest ,nlocs ,else-label)
+ (make-glil-opt-prelude nreq nopt rest nlocs else-label))
+ ((kw-prelude ,nreq ,nopt ,rest ,kw ,allow-other-keys? ,nlocs ,else-label)
+ (make-glil-kw-prelude nreq nopt rest kw allow-other-keys? nlocs else-label))
+ ((bind . ,vars) (make-glil-bind vars))
+ ((mv-bind ,vars ,rest) (make-glil-mv-bind vars rest))
+ ((unbind) (make-glil-unbind))
+ ((source ,props) (make-glil-source props))
+ ((void) (make-glil-void))
+ ((const ,obj) (make-glil-const obj))
+ ((lexical ,local? ,boxed? ,op ,index) (make-glil-lexical local? boxed? op index))
+ ((toplevel ,op ,name) (make-glil-toplevel op name))
+ ((module public ,op ,mod ,name) (make-glil-module op mod name #t))
+ ((module private ,op ,mod ,name) (make-glil-module op mod name #f))
+ ((label ,label) (make-glil-label label))
+ ((branch ,inst ,label) (make-glil-branch inst label))
+ ((call ,inst ,nargs) (make-glil-call inst nargs))
+ ((mv-call ,nargs ,ra) (make-glil-mv-call nargs ra))
+ ((prompt ,label ,escape-only?)
+ (make-glil-prompt label escape-only?))
+ (else (error "invalid glil" x))))
+
+(define (unparse-glil glil)
+ (record-case glil
+ ;; meta
+ ((<glil-program> meta body)
+ `(program ,meta ,@(map unparse-glil body)))
+ ((<glil-std-prelude> nreq nlocs else-label)
+ `(std-prelude ,nreq ,nlocs ,else-label))
+ ((<glil-opt-prelude> nreq nopt rest nlocs else-label)
+ `(opt-prelude ,nreq ,nopt ,rest ,nlocs ,else-label))
+ ((<glil-kw-prelude> nreq nopt rest kw allow-other-keys? nlocs else-label)
+ `(kw-prelude ,nreq ,nopt ,rest ,kw ,allow-other-keys? ,nlocs ,else-label))
+ ((<glil-bind> vars) `(bind ,@vars))
+ ((<glil-mv-bind> vars rest) `(mv-bind ,vars ,rest))
+ ((<glil-unbind>) `(unbind))
+ ((<glil-source> props) `(source ,props))
+ ;; constants
+ ((<glil-void>) `(void))
+ ((<glil-const> obj) `(const ,obj))
+ ;; variables
+ ((<glil-lexical> local? boxed? op index)
+ `(lexical ,local? ,boxed? ,op ,index))
+ ((<glil-toplevel> op name)
+ `(toplevel ,op ,name))
+ ((<glil-module> op mod name public?)
+ `(module ,(if public? 'public 'private) ,op ,mod ,name))
+ ;; controls
+ ((<glil-label> label) `(label ,label))
+ ((<glil-branch> inst label) `(branch ,inst ,label))
+ ((<glil-call> inst nargs) `(call ,inst ,nargs))
+ ((<glil-mv-call> nargs ra) `(mv-call ,nargs ,ra))
+ ((<glil-prompt> label escape-only?)
+ `(prompt ,label escape-only?))))
+;;; Guile VM assembler
+
+;; Copyright (C) 2001, 2009, 2010, 2011, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language glil compile-assembly)
+ #\use-module (system base syntax)
+ #\use-module (system base pmatch)
+ #\use-module (language glil)
+ #\use-module (language assembly)
+ #\use-module (system vm instruction)
+ #\use-module ((system vm program) #\select (make-binding))
+ #\use-module (ice-9 receive)
+ #\use-module (ice-9 vlist)
+ #\use-module ((srfi srfi-1) #\select (fold))
+ #\use-module (rnrs bytevectors)
+ #\export (compile-assembly))
+
+;; Traversal helpers
+;;
+(define (vhash-fold-right2 proc vhash s0 s1)
+ (let lp ((i (vlist-length vhash)) (s0 s0) (s1 s1))
+ (if (zero? i)
+ (values s0 s1)
+ (receive (s0 s1) (let ((pair (vlist-ref vhash (1- i))))
+ (proc (car pair) (cdr pair) s0 s1))
+ (lp (1- i) s0 s1)))))
+
+(define (fold2 proc ls s0 s1)
+ (let lp ((ls ls) (s0 s0) (s1 s1))
+ (if (null? ls)
+ (values s0 s1)
+ (receive (s0 s1) (proc (car ls) s0 s1)
+ (lp (cdr ls) s0 s1)))))
+
+(define (vector-fold2 proc vect s0 s1)
+ (let ((len (vector-length vect)))
+ (let lp ((i 0) (s0 s0) (s1 s1))
+ (if (< i len)
+ (receive (s0 s1) (proc (vector-ref vect i) s0 s1)
+ (lp (1+ i) s0 s1))
+ (values s0 s1)))))
+
+;; Variable cache cells go in the object table, and serialize as their
+;; keys. The reason we wrap the keys in these records is so they don't
+;; compare as `equal?' to other objects in the object table.
+;;
+;; `key' is either a symbol or the list (MODNAME SYM PUBLIC?)
+
+(define-record <variable-cache-cell> key)
+
+(define (limn-sources sources)
+ (let lp ((in sources) (out '()) (filename #f))
+ (if (null? in)
+ (reverse! out)
+ (let ((addr (caar in))
+ (new-filename (assq-ref (cdar in ) 'filename))
+ (line (assq-ref (cdar in) 'line))
+ (column (assq-ref (cdar in) 'column)))
+ (cond
+ ((not (equal? new-filename filename))
+ (lp (cdr in)
+ `((,addr . (,line . ,column))
+ (filename . ,new-filename)
+ . ,out)
+ new-filename))
+ ((or (null? out) (not (equal? (cdar out) `(,line . ,column))))
+ (lp (cdr in)
+ `((,addr . (,line . ,column))
+ . ,out)
+ filename))
+ (else
+ (lp (cdr in) out filename)))))))
+
+
+;; Avoid going through the compiler so as to avoid adding to the
+;; constant store.
+(define (make-meta bindings sources arities tail)
+ (let ((body `(,@(dump-object `(,bindings ,sources ,arities ,@tail) 0)
+ (return))))
+ `(load-program ()
+ ,(addr+ 0 body)
+ #f
+ ,@body)))
+
+;; If this is true, the object doesn't need to go in a constant table.
+;;
+(define (immediate? x)
+ (object->assembly x))
+
+;; This tests for a proper scheme list whose last cdr is '(), not #nil.
+;;
+(define (scheme-list? x)
+ (and (list? x)
+ (or (eq? x '())
+ (let ((p (last-pair x)))
+ (and (pair? p)
+ (eq? (cdr p) '()))))))
+
+;; Note: in all of these procedures that build up constant tables, the
+;; first (zeroth) index is reserved. At runtime it is replaced with the
+;; procedure's module. Hence all of this 1+ length business.
+
+;; Build up a vhash of constant -> index, allowing us to build up a
+;; constant table for a whole compilation unit.
+;;
+(define (build-constant-store x)
+ (define (add-to-store store x)
+ (define (add-to-end store x)
+ (vhash-cons x (1+ (vlist-length store)) store))
+ (cond
+ ((vhash-assoc x store)
+ ;; Already in the store.
+ store)
+ ((immediate? x)
+ ;; Immediates don't need to go in the constant table.
+ store)
+ ((or (number? x)
+ (string? x)
+ (symbol? x)
+ (keyword? x))
+ ;; Atoms.
+ (add-to-end store x))
+ ((variable-cache-cell? x)
+ ;; Variable cache cells (see below).
+ (add-to-end (add-to-store store (variable-cache-cell-key x))
+ x))
+ ((list? x)
+ ;; Add the elements to the store, then the list itself. We could
+ ;; try hashing the cdrs as well, but that seems a bit overkill, and
+ ;; this way we do compress the bytecode a bit by allowing the use of
+ ;; the `list' opcode.
+ (let ((store (fold (lambda (x store)
+ (add-to-store store x))
+ store
+ x)))
+ (add-to-end store x)))
+ ((pair? x)
+ ;; Non-lists get caching on both fields.
+ (let ((store (add-to-store (add-to-store store (car x))
+ (cdr x))))
+ (add-to-end store x)))
+ ((and (vector? x)
+ (equal? (array-shape x) (list (list 0 (1- (vector-length x))))))
+ ;; Likewise, add the elements to the store, then the vector itself.
+ ;; Important for the vectors produced by the psyntax expansion
+ ;; process.
+ (let ((store (fold (lambda (x store)
+ (add-to-store store x))
+ store
+ (vector->list x))))
+ (add-to-end store x)))
+ ((array? x)
+ ;; Naive assumption that if folks are using arrays, that perhaps
+ ;; there's not much more duplication.
+ (add-to-end store x))
+ (else
+ (error "build-constant-store: unrecognized object" x))))
+
+ (let walk ((x x) (store vlist-null))
+ (record-case x
+ ((<glil-program> meta body)
+ (fold walk store body))
+ ((<glil-const> obj)
+ (add-to-store store obj))
+ ((<glil-kw-prelude> kw)
+ (add-to-store store kw))
+ ((<glil-toplevel> op name)
+ ;; We don't add toplevel variable cache cells to the global
+ ;; constant table, because they are sensitive to changes in
+ ;; modules as the toplevel expressions are evaluated. So we just
+ ;; add the name.
+ (add-to-store store name))
+ ((<glil-module> op mod name public?)
+ ;; However, it is fine add module variable cache cells to the
+ ;; global table, as their bindings are not dependent on the
+ ;; current module.
+ (add-to-store store
+ (make-variable-cache-cell (list mod name public?))))
+ (else store))))
+
+;; Analyze one <glil-program> to determine its object table. Produces a
+;; vhash of constant to index.
+;;
+(define (build-object-table x)
+ (define (add store x)
+ (if (vhash-assoc x store)
+ store
+ (vhash-cons x (1+ (vlist-length store)) store)))
+ (record-case x
+ ((<glil-program> meta body)
+ (fold (lambda (x table)
+ (record-case x
+ ((<glil-program> meta body)
+ ;; Add the GLIL itself to the table.
+ (add table x))
+ ((<glil-const> obj)
+ (if (immediate? obj)
+ table
+ (add table obj)))
+ ((<glil-kw-prelude> kw)
+ (add table kw))
+ ((<glil-toplevel> op name)
+ (add table (make-variable-cache-cell name)))
+ ((<glil-module> op mod name public?)
+ (add table (make-variable-cache-cell (list mod name public?))))
+ (else table)))
+ vlist-null
+ body))))
+
+;; A functional stack of names of live variables.
+(define (make-open-binding name boxed? index)
+ (list name boxed? index))
+(define (make-closed-binding open-binding start end)
+ (make-binding (car open-binding) (cadr open-binding)
+ (caddr open-binding) start end))
+(define (open-binding bindings vars start)
+ (cons
+ (acons start
+ (map
+ (lambda (v)
+ (pmatch v
+ ((,name ,boxed? ,i)
+ (make-open-binding name boxed? i))
+ (else (error "unknown binding type" v))))
+ vars)
+ (car bindings))
+ (cdr bindings)))
+(define (close-binding bindings end)
+ (pmatch bindings
+ ((((,start . ,closing) . ,open) . ,closed)
+ (cons open
+ (fold (lambda (o tail)
+ ;; the cons is for dsu sort
+ (acons start (make-closed-binding o start end)
+ tail))
+ closed
+ closing)))
+ (else (error "broken bindings" bindings))))
+(define (close-all-bindings bindings end)
+ (if (null? (car bindings))
+ (map cdr
+ (stable-sort (reverse (cdr bindings))
+ (lambda (x y) (< (car x) (car y)))))
+ (close-all-bindings (close-binding bindings end) end)))
+
+
+;; A functional arities thingamajiggy.
+;; arities := ((ip nreq [[nopt] [[rest] [kw]]]]) ...)
+(define (open-arity addr nreq nopt rest kw arities)
+ (cons
+ (cond
+ (kw (list addr nreq nopt rest kw))
+ (rest (list addr nreq nopt rest))
+ (nopt (list addr nreq nopt))
+ (nreq (list addr nreq))
+ (else (list addr)))
+ arities))
+(define (close-arity addr arities)
+ (pmatch arities
+ (() '())
+ (((,start . ,tail) . ,rest)
+ `((,start ,addr . ,tail) . ,rest))
+ (else (error "bad arities" arities))))
+(define (begin-arity end start nreq nopt rest kw arities)
+ (open-arity start nreq nopt rest kw (close-arity end arities)))
+
+(define (compile-assembly glil)
+ (let* ((all-constants (build-constant-store glil))
+ (prog (compile-program glil all-constants))
+ (len (byte-length prog)))
+ ;; The top objcode thunk. We're going to wrap this thunk in
+ ;; a thunk -- yo dawgs -- with the goal being to lift all
+ ;; constants up to the top level. The store forms a DAG, so
+ ;; we can actually build up later elements in terms of
+ ;; earlier ones.
+ ;;
+ (cond
+ ((vlist-null? all-constants)
+ ;; No constants: just emit the inner thunk.
+ prog)
+ (else
+ ;; We have an object store, so write it out, attach it
+ ;; to the inner thunk, and tail call.
+ (receive (tablecode addr) (dump-constants all-constants)
+ (let ((prog (align-program prog addr)))
+ ;; Outer thunk.
+ `(load-program ()
+ ,(+ (addr+ addr prog)
+ 2 ; for (tail-call 0)
+ )
+ #f
+ ;; Load the table, build the inner
+ ;; thunk, then tail call.
+ ,@tablecode
+ ,@prog
+ (tail-call 0))))))))
+
+(define (compile-program glil constants)
+ (record-case glil
+ ((<glil-program> meta body)
+ (let lp ((body body) (code '()) (bindings '(())) (source-alist '())
+ (label-alist '()) (arities '()) (addr 0))
+ (cond
+ ((null? body)
+ (let ((code (fold append '() code))
+ (bindings (close-all-bindings bindings addr))
+ (sources (limn-sources (reverse! source-alist)))
+ (labels (reverse label-alist))
+ (arities (reverse (close-arity addr arities)))
+ (len addr))
+ (let* ((meta (make-meta bindings sources arities meta))
+ (meta-pad (if meta (modulo (- 8 (modulo len 8)) 8) 0)))
+ `(load-program ,labels
+ ,(+ len meta-pad)
+ ,meta
+ ,@code
+ ,@(if meta
+ (make-list meta-pad '(nop))
+ '())))))
+ (else
+ (receive (subcode bindings source-alist label-alist arities)
+ (glil->assembly (car body) bindings
+ source-alist label-alist
+ constants arities addr)
+ (lp (cdr body) (cons subcode code)
+ bindings source-alist label-alist arities
+ (addr+ addr subcode)))))))))
+
+(define (compile-objtable constants table addr)
+ (define (load-constant idx)
+ (if (< idx 256)
+ (values `((object-ref ,idx))
+ 2)
+ (values `((long-object-ref
+ ,(quotient idx 256) ,(modulo idx 256)))
+ 3)))
+ (cond
+ ((vlist-null? table)
+ ;; Empty table; just return #f.
+ (values '((make-false))
+ (1+ addr)))
+ (else
+ (call-with-values
+ (lambda ()
+ (vhash-fold-right2
+ (lambda (obj idx codes addr)
+ (cond
+ ((vhash-assoc obj constants)
+ => (lambda (pair)
+ (receive (load len) (load-constant (cdr pair))
+ (values (cons load codes)
+ (+ addr len)))))
+ ((variable-cache-cell? obj)
+ (cond
+ ((vhash-assoc (variable-cache-cell-key obj) constants)
+ => (lambda (pair)
+ (receive (load len) (load-constant (cdr pair))
+ (values (cons load codes)
+ (+ addr len)))))
+ (else (error "vcache cell key not in table" obj))))
+ ((glil-program? obj)
+ ;; Programs are not cached in the global constants
+ ;; table because when a program is loaded, its module
+ ;; is bound, and we want to do that only after any
+ ;; preceding effectful statements.
+ (let* ((table (build-object-table obj))
+ (prog (compile-program obj table)))
+ (receive (tablecode addr)
+ (compile-objtable constants table addr)
+ (let ((prog (align-program prog addr)))
+ (values (cons `(,@tablecode ,@prog)
+ codes)
+ (addr+ addr prog))))))
+ (else
+ (error "unrecognized constant" obj))))
+ table
+ '(((make-false))) (1+ addr)))
+ (lambda (elts addr)
+ (let ((len (1+ (vlist-length table))))
+ (values
+ (fold append
+ `((vector ,(quotient len 256) ,(modulo len 256)))
+ elts)
+ (+ addr 3))))))))
+
+(define (glil->assembly glil bindings source-alist label-alist
+ constants arities addr)
+ (define (emit-code x)
+ (values x bindings source-alist label-alist arities))
+ (define (emit-object-ref i)
+ (values (if (< i 256)
+ `((object-ref ,i))
+ `((long-object-ref ,(quotient i 256) ,(modulo i 256))))
+ bindings source-alist label-alist arities))
+ (define (emit-code/arity x nreq nopt rest kw)
+ (values x bindings source-alist label-alist
+ (begin-arity addr (addr+ addr x) nreq nopt rest kw arities)))
+
+ (record-case glil
+ ((<glil-program> meta body)
+ (cond
+ ((vhash-assoc glil constants)
+ ;; We are cached in someone's objtable; just emit a load.
+ => (lambda (pair)
+ (emit-object-ref (cdr pair))))
+ (else
+ ;; Otherwise, build an objtable for the program, compile it, and
+ ;; emit a load-program.
+ (let* ((table (build-object-table glil))
+ (prog (compile-program glil table)))
+ (receive (tablecode addr) (compile-objtable constants table addr)
+ (emit-code `(,@tablecode ,@(align-program prog addr))))))))
+
+ ((<glil-std-prelude> nreq nlocs else-label)
+ (emit-code/arity
+ (if (and (< nreq 8) (< nlocs (+ nreq 32)) (not else-label))
+ `((assert-nargs-ee/locals ,(logior nreq (ash (- nlocs nreq) 3))))
+ `(,(if else-label
+ `(br-if-nargs-ne ,(quotient nreq 256)
+ ,(modulo nreq 256)
+ ,else-label)
+ `(assert-nargs-ee ,(quotient nreq 256)
+ ,(modulo nreq 256)))
+ (reserve-locals ,(quotient nlocs 256)
+ ,(modulo nlocs 256))))
+ nreq #f #f #f))
+
+ ((<glil-opt-prelude> nreq nopt rest nlocs else-label)
+ (let ((bind-required
+ (if else-label
+ `((br-if-nargs-lt ,(quotient nreq 256)
+ ,(modulo nreq 256)
+ ,else-label))
+ `((assert-nargs-ge ,(quotient nreq 256)
+ ,(modulo nreq 256)))))
+ (bind-optionals
+ (if (zero? nopt)
+ '()
+ `((bind-optionals ,(quotient (+ nopt nreq) 256)
+ ,(modulo (+ nreq nopt) 256)))))
+ (bind-rest
+ (cond
+ (rest
+ `((push-rest ,(quotient (+ nreq nopt) 256)
+ ,(modulo (+ nreq nopt) 256))))
+ (else
+ (if else-label
+ `((br-if-nargs-gt ,(quotient (+ nreq nopt) 256)
+ ,(modulo (+ nreq nopt) 256)
+ ,else-label))
+ `((assert-nargs-ee ,(quotient (+ nreq nopt) 256)
+ ,(modulo (+ nreq nopt) 256))))))))
+ (emit-code/arity
+ `(,@bind-required
+ ,@bind-optionals
+ ,@bind-rest
+ (reserve-locals ,(quotient nlocs 256)
+ ,(modulo nlocs 256)))
+ nreq nopt rest #f)))
+
+ ((<glil-kw-prelude> nreq nopt rest kw allow-other-keys? nlocs else-label)
+ (let* ((kw-idx (or (and=> (vhash-assoc kw constants) cdr)
+ (error "kw not in objtable")))
+ (bind-required
+ (if else-label
+ `((br-if-nargs-lt ,(quotient nreq 256)
+ ,(modulo nreq 256)
+ ,else-label))
+ `((assert-nargs-ge ,(quotient nreq 256)
+ ,(modulo nreq 256)))))
+ (ntotal (apply max (+ nreq nopt) (map 1+ (map cdr kw))))
+ (bind-optionals-and-shuffle
+ `((,(if (and else-label (not rest))
+ 'bind-optionals/shuffle-or-br
+ 'bind-optionals/shuffle)
+ ,(quotient nreq 256)
+ ,(modulo nreq 256)
+ ,(quotient (+ nreq nopt) 256)
+ ,(modulo (+ nreq nopt) 256)
+ ,(quotient ntotal 256)
+ ,(modulo ntotal 256)
+ ,@(if (and else-label (not rest))
+ `(,else-label)
+ '()))))
+ (bind-kw
+ ;; when this code gets called, all optionals are filled
+ ;; in, space has been made for kwargs, and the kwargs
+ ;; themselves have been shuffled above the slots for all
+ ;; req/opt/kwargs locals.
+ `((bind-kwargs
+ ,(quotient kw-idx 256)
+ ,(modulo kw-idx 256)
+ ,(quotient ntotal 256)
+ ,(modulo ntotal 256)
+ ,(logior (if rest 2 0)
+ (if allow-other-keys? 1 0)))))
+ (bind-rest
+ (if rest
+ `((bind-rest ,(quotient ntotal 256)
+ ,(modulo ntotal 256)
+ ,(quotient rest 256)
+ ,(modulo rest 256)))
+ '())))
+
+ (let ((code `(,@bind-required
+ ,@bind-optionals-and-shuffle
+ ,@bind-kw
+ ,@bind-rest
+ (reserve-locals ,(quotient nlocs 256)
+ ,(modulo nlocs 256)))))
+ (values code bindings source-alist label-alist
+ (begin-arity addr (addr+ addr code) nreq nopt rest
+ (and kw (cons allow-other-keys? kw))
+ arities)))))
+
+ ((<glil-bind> vars)
+ (values '()
+ (open-binding bindings vars addr)
+ source-alist
+ label-alist
+ arities))
+
+ ((<glil-mv-bind> vars rest)
+ (if (integer? vars)
+ (values `((truncate-values ,vars ,(if rest 1 0)))
+ bindings
+ source-alist
+ label-alist
+ arities)
+ (values `((truncate-values ,(length vars) ,(if rest 1 0)))
+ (open-binding bindings vars addr)
+ source-alist
+ label-alist
+ arities)))
+
+ ((<glil-unbind>)
+ (values '()
+ (close-binding bindings addr)
+ source-alist
+ label-alist
+ arities))
+
+ ((<glil-source> props)
+ (values '()
+ bindings
+ (acons addr props source-alist)
+ label-alist
+ arities))
+
+ ((<glil-void>)
+ (emit-code '((void))))
+
+ ((<glil-const> obj)
+ (cond
+ ((object->assembly obj)
+ => (lambda (code)
+ (emit-code (list code))))
+ ((vhash-assoc obj constants)
+ => (lambda (pair)
+ (emit-object-ref (cdr pair))))
+ (else (error "const not in table" obj))))
+
+ ((<glil-lexical> local? boxed? op index)
+ (emit-code
+ (if local?
+ (if (< index 256)
+ (case op
+ ((ref) (if boxed?
+ `((local-boxed-ref ,index))
+ `((local-ref ,index))))
+ ((set) (if boxed?
+ `((local-boxed-set ,index))
+ `((local-set ,index))))
+ ((box) `((box ,index)))
+ ((empty-box) `((empty-box ,index)))
+ ((fix) `((fix-closure 0 ,index)))
+ ((bound?) (if boxed?
+ `((local-ref ,index)
+ (variable-bound?))
+ `((local-bound? ,index))))
+ (else (error "what" op)))
+ (let ((a (quotient index 256))
+ (b (modulo index 256)))
+ (case op
+ ((ref)
+ (if boxed?
+ `((long-local-ref ,a ,b)
+ (variable-ref))
+ `((long-local-ref ,a ,b))))
+ ((set)
+ (if boxed?
+ `((long-local-ref ,a ,b)
+ (variable-set))
+ `((long-local-set ,a ,b))))
+ ((box)
+ `((make-variable)
+ (variable-set)
+ (long-local-set ,a ,b)))
+ ((empty-box)
+ `((make-variable)
+ (long-local-set ,a ,b)))
+ ((fix)
+ `((fix-closure ,a ,b)))
+ ((bound?)
+ (if boxed?
+ `((long-local-ref ,a ,b)
+ (variable-bound?))
+ `((long-local-bound? ,a ,b))))
+ (else (error "what" op)))))
+ `((,(case op
+ ((ref) (if boxed? 'free-boxed-ref 'free-ref))
+ ((set) (if boxed? 'free-boxed-set (error "what." glil)))
+ (else (error "what" op)))
+ ,index)))))
+
+ ((<glil-toplevel> op name)
+ (case op
+ ((ref set)
+ (cond
+ ((and=> (vhash-assoc (make-variable-cache-cell name) constants)
+ cdr)
+ => (lambda (i)
+ (emit-code (if (< i 256)
+ `((,(case op
+ ((ref) 'toplevel-ref)
+ ((set) 'toplevel-set))
+ ,i))
+ `((,(case op
+ ((ref) 'long-toplevel-ref)
+ ((set) 'long-toplevel-set))
+ ,(quotient i 256)
+ ,(modulo i 256)))))))
+ (else
+ (let ((i (or (and=> (vhash-assoc name constants) cdr)
+ (error "toplevel name not in objtable" name))))
+ (emit-code `(,(if (< i 256)
+ `(object-ref ,i)
+ `(long-object-ref ,(quotient i 256)
+ ,(modulo i 256)))
+ (link-now)
+ ,(case op
+ ((ref) '(variable-ref))
+ ((set) '(variable-set)))))))))
+ ((define)
+ (let ((i (or (and=> (vhash-assoc name constants) cdr)
+ (error "toplevel name not in objtable" name))))
+ (emit-code `(,(if (< i 256)
+ `(object-ref ,i)
+ `(long-object-ref ,(quotient i 256)
+ ,(modulo i 256)))
+ (define)))))
+ (else
+ (error "unknown toplevel var kind" op name))))
+
+ ((<glil-module> op mod name public?)
+ (let ((key (list mod name public?)))
+ (case op
+ ((ref set)
+ (let ((i (or (and=> (vhash-assoc (make-variable-cache-cell key)
+ constants) cdr)
+ (error "module vcache not in objtable" key))))
+ (emit-code (if (< i 256)
+ `((,(case op
+ ((ref) 'toplevel-ref)
+ ((set) 'toplevel-set))
+ ,i))
+ `((,(case op
+ ((ref) 'long-toplevel-ref)
+ ((set) 'long-toplevel-set))
+ ,(quotient i 256)
+ ,(modulo i 256)))))))
+ (else
+ (error "unknown module var kind" op key)))))
+
+ ((<glil-label> label)
+ (let ((code (align-block addr)))
+ (values code
+ bindings
+ source-alist
+ (acons label (addr+ addr code) label-alist)
+ arities)))
+
+ ((<glil-branch> inst label)
+ (emit-code `((,inst ,label))))
+
+ ;; nargs is number of stack args to insn. probably should rename.
+ ((<glil-call> inst nargs)
+ (if (not (instruction? inst))
+ (error "Unknown instruction:" inst))
+ (let ((pops (instruction-pops inst)))
+ (cond ((< pops 0)
+ (case (instruction-length inst)
+ ((1) (emit-code `((,inst ,nargs))))
+ ((2) (emit-code `((,inst ,(quotient nargs 256)
+ ,(modulo nargs 256)))))
+ (else (error "Unknown length for variable-arg instruction:"
+ inst (instruction-length inst)))))
+ ((= pops nargs)
+ (emit-code `((,inst))))
+ (else
+ (error "Wrong number of stack arguments to instruction:" inst nargs)))))
+
+ ((<glil-mv-call> nargs ra)
+ (emit-code `((mv-call ,nargs ,ra))))
+
+ ((<glil-prompt> label escape-only?)
+ (emit-code `((prompt ,(if escape-only? 1 0) ,label))))))
+
+(define (dump-object x addr)
+ (define (too-long x)
+ (error (string-append x " too long")))
+
+ (cond
+ ((object->assembly x) => list)
+ ((variable-cache-cell? x) (dump-object (variable-cache-cell-key x) addr))
+ ((number? x)
+ `((load-number ,(number->string x))))
+ ((string? x)
+ (case (string-bytes-per-char x)
+ ((1) `((load-string ,x)))
+ ((4) (align-code `(load-wide-string ,x) addr 4 4))
+ (else (error "bad string bytes per char" x))))
+ ((symbol? x)
+ (let ((str (symbol->string x)))
+ (case (string-bytes-per-char str)
+ ((1) `((load-symbol ,str)))
+ ((4) `(,@(dump-object str addr)
+ (make-symbol)))
+ (else (error "bad string bytes per char" str)))))
+ ((keyword? x)
+ `(,@(dump-object (keyword->symbol x) addr)
+ (make-keyword)))
+ ((scheme-list? x)
+ (let ((tail (let ((len (length x)))
+ (if (>= len 65536) (too-long "list"))
+ `((list ,(quotient len 256) ,(modulo len 256))))))
+ (let dump-objects ((objects x) (codes '()) (addr addr))
+ (if (null? objects)
+ (fold append tail codes)
+ (let ((code (dump-object (car objects) addr)))
+ (dump-objects (cdr objects) (cons code codes)
+ (addr+ addr code)))))))
+ ((pair? x)
+ (let ((kar (dump-object (car x) addr)))
+ `(,@kar
+ ,@(dump-object (cdr x) (addr+ addr kar))
+ (cons))))
+ ((and (vector? x)
+ (equal? (array-shape x) (list (list 0 (1- (vector-length x))))))
+ (let* ((len (vector-length x))
+ (tail (if (>= len 65536)
+ (too-long "vector")
+ `((vector ,(quotient len 256) ,(modulo len 256))))))
+ (let dump-objects ((i 0) (codes '()) (addr addr))
+ (if (>= i len)
+ (fold append tail codes)
+ (let ((code (dump-object (vector-ref x i) addr)))
+ (dump-objects (1+ i) (cons code codes)
+ (addr+ addr code)))))))
+ ((and (array? x) (symbol? (array-type x)))
+ (let* ((type (dump-object (array-type x) addr))
+ (shape (dump-object (array-shape x) (addr+ addr type))))
+ `(,@type
+ ,@shape
+ ,@(align-code
+ `(load-array ,(uniform-array->bytevector x))
+ (addr+ (addr+ addr type) shape)
+ 8
+ 4))))
+ ((array? x)
+ ;; an array of generic scheme values
+ (let* ((contents (array-contents x))
+ (len (vector-length contents)))
+ (let dump-objects ((i 0) (codes '()) (addr addr))
+ (if (< i len)
+ (let ((code (dump-object (vector-ref contents i) addr)))
+ (dump-objects (1+ i) (cons code codes)
+ (addr+ addr code)))
+ (fold append
+ `(,@(dump-object (array-shape x) addr)
+ (make-array ,(quotient (ash len -16) 256)
+ ,(logand #xff (ash len -8))
+ ,(logand #xff len)))
+ codes)))))
+ (else
+ (error "dump-object: unrecognized object" x))))
+
+(define (dump-constants constants)
+ (define (ref-or-dump x i addr)
+ (let ((pair (vhash-assoc x constants)))
+ (if (and pair (< (cdr pair) i))
+ (let ((idx (cdr pair)))
+ (if (< idx 256)
+ (values `((object-ref ,idx))
+ (+ addr 2))
+ (values `((long-object-ref ,(quotient idx 256)
+ ,(modulo idx 256)))
+ (+ addr 3))))
+ (dump1 x i addr))))
+ (define (dump1 x i addr)
+ (cond
+ ((object->assembly x)
+ => (lambda (code)
+ (values (list code)
+ (+ (byte-length code) addr))))
+ ((or (number? x)
+ (string? x)
+ (symbol? x)
+ (keyword? x))
+ ;; Atoms.
+ (let ((code (dump-object x addr)))
+ (values code (addr+ addr code))))
+ ((variable-cache-cell? x)
+ (dump1 (variable-cache-cell-key x) i addr))
+ ((scheme-list? x)
+ (receive (codes addr)
+ (fold2 (lambda (x codes addr)
+ (receive (subcode addr) (ref-or-dump x i addr)
+ (values (cons subcode codes) addr)))
+ x '() addr)
+ (values (fold append
+ (let ((len (length x)))
+ `((list ,(quotient len 256) ,(modulo len 256))))
+ codes)
+ (+ addr 3))))
+ ((pair? x)
+ (receive (car-code addr) (ref-or-dump (car x) i addr)
+ (receive (cdr-code addr) (ref-or-dump (cdr x) i addr)
+ (values `(,@car-code ,@cdr-code (cons))
+ (1+ addr)))))
+ ((and (vector? x)
+ (<= (vector-length x) #xffff)
+ (equal? (array-shape x) (list (list 0 (1- (vector-length x))))))
+ (receive (codes addr)
+ (vector-fold2 (lambda (x codes addr)
+ (receive (subcode addr) (ref-or-dump x i addr)
+ (values (cons subcode codes) addr)))
+ x '() addr)
+ (values (fold append
+ (let ((len (vector-length x)))
+ `((vector ,(quotient len 256) ,(modulo len 256))))
+ codes)
+ (+ addr 3))))
+ ((and (array? x) (symbol? (array-type x)))
+ (receive (type addr) (ref-or-dump (array-type x) i addr)
+ (receive (shape addr) (ref-or-dump (array-shape x) i addr)
+ (let ((bv (align-code `(load-array ,(uniform-array->bytevector x))
+ addr 8 4)))
+ (values `(,@type ,@shape ,@bv)
+ (addr+ addr bv))))))
+ ((array? x)
+ (let ((contents (array-contents x)))
+ (receive (codes addr)
+ (vector-fold2 (lambda (x codes addr)
+ (receive (subcode addr) (ref-or-dump x i addr)
+ (values (cons subcode codes) addr)))
+ contents '() addr)
+ (receive (shape addr) (ref-or-dump (array-shape x) i addr)
+ (values (fold append
+ (let ((len (vector-length contents)))
+ `(,@shape
+ (make-array ,(quotient (ash len -16) 256)
+ ,(logand #xff (ash len -8))
+ ,(logand #xff len))))
+ codes)
+ (+ addr 4))))))
+ (else
+ (error "write-table: unrecognized object" x))))
+
+ (receive (codes addr)
+ (vhash-fold-right2 (lambda (obj idx code addr)
+ ;; The vector is on the stack. Dup it, push
+ ;; the index, push the val, then vector-set.
+ (let ((pre `((dup)
+ ,(object->assembly idx))))
+ (receive (valcode addr) (dump1 obj idx
+ (addr+ addr pre))
+ (values (cons* '((vector-set))
+ valcode
+ pre
+ code)
+ (1+ addr)))))
+ constants
+ '(((assert-nargs-ee/locals 1)
+ ;; Push the vector.
+ (local-ref 0)))
+ 4)
+ (let* ((len (1+ (vlist-length constants)))
+ (pre-prog-addr (+ 2 ; reserve-locals
+ len 3 ; empty vector
+ 2 ; local-set
+ 1 ; new-frame
+ 2 ; local-ref
+ ))
+ (prog (align-program
+ `(load-program ()
+ ,(+ addr 1)
+ #f
+ ;; The `return' will be at the tail of the
+ ;; program. The vector is already pushed
+ ;; on the stack.
+ . ,(fold append '((return)) codes))
+ pre-prog-addr)))
+ (values `(;; Reserve storage for the vector.
+ (assert-nargs-ee/locals ,(logior 0 (ash 1 3)))
+ ;; Push the vector, and store it in slot 0.
+ ,@(make-list len '(make-false))
+ (vector ,(quotient len 256) ,(modulo len 256))
+ (local-set 0)
+ ;; Now we open the call frame.
+ ;;
+ (new-frame)
+ ;; Now build a thunk to init the constants. It will
+ ;; have the unfinished constant table both as its
+ ;; argument and as its objtable. The former allows it
+ ;; to update the objtable, with vector-set!, and the
+ ;; latter allows init code to refer to previously set
+ ;; values.
+ ;;
+ ;; Grab the vector, to be the objtable.
+ (local-ref 0)
+ ;; Now the load-program, properly aligned. Pops the vector.
+ ,@prog
+ ;; Grab the vector, as an argument this time.
+ (local-ref 0)
+ ;; Call the init thunk with the vector as an arg.
+ (call 1)
+ ;; The thunk also returns the vector. Leave it on the
+ ;; stack for compile-assembly to use.
+ )
+ ;; The byte length of the init code, which we can
+ ;; determine without folding over the code again.
+ (+ (addr+ pre-prog-addr prog) ; aligned program
+ 2 ; local-ref
+ 2 ; call
+ )))))
+;;; Guile Lowlevel Intermediate Language
+
+;; Copyright (C) 2001, 2009, 2010, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language glil spec)
+ #\use-module (system base language)
+ #\use-module (language glil)
+ #\use-module (language glil compile-assembly)
+ #\export (glil))
+
+(define (write-glil exp . port)
+ (apply write (unparse-glil exp) port))
+
+(define (compile-asm x e opts)
+ (values (compile-assembly x) e e))
+
+(define-language glil
+ #\title "Guile Lowlevel Intermediate Language (GLIL)"
+ #\reader (lambda (port env) (read port))
+ #\printer write-glil
+ #\parser parse-glil
+ #\compilers `((assembly . ,compile-asm))
+ #\for-humans? #f
+ )
+;;; Guile Virtual Machine Object Code
+
+;; Copyright (C) 2001 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language objcode)
+ #\export (encode-length decode-length))
+
+
+;;;
+;;; Variable-length interface
+;;;
+
+;; NOTE: decoded in vm_fetch_length in vm.c as well.
+
+(define (encode-length len)
+ (cond ((< len 254) (u8vector len))
+ ((< len (* 256 256))
+ (u8vector 254 (quotient len 256) (modulo len 256)))
+ ((< len most-positive-fixnum)
+ (u8vector 255
+ (quotient len (* 256 256 256))
+ (modulo (quotient len (* 256 256)) 256)
+ (modulo (quotient len 256) 256)
+ (modulo len 256)))
+ (else (error "Too long code length:" len))))
+
+(define (decode-length pop)
+ (let ((x (pop)))
+ (cond ((< x 254) x)
+ ((= x 254) (+ (ash x 8) (pop)))
+ (else
+ (let* ((b2 (pop))
+ (b3 (pop))
+ (b4 (pop)))
+ (+ (ash x 24) (ash b2 16) (ash b3 8) b4))))))
+;;; Guile Lowlevel Intermediate Language
+
+;; Copyright (C) 2001, 2009, 2010, 2011, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language objcode spec)
+ #\use-module (system base language)
+ #\use-module (system vm objcode)
+ #\use-module (system vm program)
+ #\export (objcode))
+
+(define (objcode->value x e opts)
+ (let ((thunk (make-program x #f #f)))
+ (if (eq? e (current-module))
+ ;; save a cons in this case
+ (values (thunk) e e)
+ (save-module-excursion
+ (lambda ()
+ (set-current-module e)
+ (values (thunk) e e))))))
+
+;; since locals are allocated on the stack and can have limited scope,
+;; in many cases we use one local for more than one lexical variable. so
+;; the returned locals set is a list, where element N of the list is
+;; itself a list of bindings for local variable N.
+(define (collapse-locals locs)
+ (let lp ((ret '()) (locs locs))
+ (if (null? locs)
+ (map cdr (sort! ret
+ (lambda (x y) (< (car x) (car y)))))
+ (let ((b (car locs)))
+ (cond
+ ((assv-ref ret (binding:index b))
+ => (lambda (bindings)
+ (append! bindings (list b))
+ (lp ret (cdr locs))))
+ (else
+ (lp (acons (binding:index b) (list b) ret)
+ (cdr locs))))))))
+
+(define (decompile-value x env opts)
+ (cond
+ ((program? x)
+ (let ((objs (program-objects x))
+ (meta (program-meta x))
+ (free-vars (program-free-variables x))
+ (binds (program-bindings x))
+ (srcs (program-sources x)))
+ (let ((blocs (and binds (collapse-locals binds))))
+ (values (program-objcode x)
+ `((objects . ,objs)
+ (meta . ,(and meta (meta)))
+ (free-vars . ,free-vars)
+ (blocs . ,blocs)
+ (sources . ,srcs))))))
+ ((objcode? x)
+ (values x #f))
+ (else
+ (error "Object for disassembly not a program or objcode" x))))
+
+(define-language objcode
+ #\title "Guile Object Code"
+ #\reader #f
+ #\printer write-objcode
+ #\compilers `((value . ,objcode->value))
+ #\decompilers `((value . ,decompile-value))
+ #\for-humans? #f
+ )
+;;; Guile Scheme specification
+
+;; Copyright (C) 2001, 2009, 2010 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language scheme compile-tree-il)
+ #\use-module (language tree-il)
+ #\export (compile-tree-il))
+
+;;; environment := MODULE
+
+(define (compile-tree-il x e opts)
+ (save-module-excursion
+ (lambda ()
+ (set-current-module e)
+ (let* ((x (macroexpand x 'c '(compile load eval)))
+ (cenv (current-module)))
+ (values x cenv cenv)))))
+;;; Guile VM code converters
+
+;; Copyright (C) 2001, 2009, 2012, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language scheme decompile-tree-il)
+ #\use-module (language tree-il)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-26)
+ #\use-module (ice-9 receive)
+ #\use-module (ice-9 vlist)
+ #\use-module (ice-9 match)
+ #\use-module (system base syntax)
+ #\export (decompile-tree-il))
+
+(define (decompile-tree-il e env opts)
+ (apply do-decompile e env opts))
+
+(define* (do-decompile e env
+ #\key
+ (use-derived-syntax? #t)
+ (avoid-lambda? #t)
+ (use-case? #t)
+ (strip-numeric-suffixes? #f)
+ #\allow-other-keys)
+
+ (receive (output-name-table occurrence-count-table)
+ (choose-output-names e use-derived-syntax? strip-numeric-suffixes?)
+
+ (define (output-name s) (hashq-ref output-name-table s))
+ (define (occurrence-count s) (hashq-ref occurrence-count-table s))
+
+ (define (const x) (lambda (_) x))
+ (define (atom? x) (not (or (pair? x) (vector? x))))
+
+ (define (build-void) '(if #f #f))
+
+ (define (build-begin es)
+ (match es
+ (() (build-void))
+ ((e) e)
+ (_ `(begin ,@es))))
+
+ (define (build-lambda-body e)
+ (match e
+ (('let () body ...) body)
+ (('begin es ...) es)
+ (_ (list e))))
+
+ (define (build-begin-body e)
+ (match e
+ (('begin es ...) es)
+ (_ (list e))))
+
+ (define (build-define name e)
+ (match e
+ ((? (const avoid-lambda?)
+ ('lambda formals body ...))
+ `(define (,name ,@formals) ,@body))
+ ((? (const avoid-lambda?)
+ ('lambda* formals body ...))
+ `(define* (,name ,@formals) ,@body))
+ (_ `(define ,name ,e))))
+
+ (define (build-let names vals body)
+ (match `(let ,(map list names vals)
+ ,@(build-lambda-body body))
+ ((_ () e) e)
+ ((_ (b) ('let* (bs ...) body ...))
+ `(let* (,b ,@bs) ,@body))
+ ((? (const use-derived-syntax?)
+ (_ (b1) ('let (b2) body ...)))
+ `(let* (,b1 ,b2) ,@body))
+ (e e)))
+
+ (define (build-letrec in-order? names vals body)
+ (match `(,(if in-order? 'letrec* 'letrec)
+ ,(map list names vals)
+ ,@(build-lambda-body body))
+ ((_ () e) e)
+ ((_ () body ...) `(let () ,@body))
+ ((_ ((name ('lambda (formals ...) body ...)))
+ (name args ...))
+ (=> failure)
+ (if (= (length formals) (length args))
+ `(let ,name ,(map list formals args) ,@body)
+ (failure)))
+ ((? (const avoid-lambda?)
+ ('letrec* _ body ...))
+ `(let ()
+ ,@(map build-define names vals)
+ ,@body))
+ (e e)))
+
+ (define (build-if test consequent alternate)
+ (match alternate
+ (('if #f _) `(if ,test ,consequent))
+ (_ `(if ,test ,consequent ,alternate))))
+
+ (define (build-and xs)
+ (match xs
+ (() #t)
+ ((x) x)
+ (_ `(and ,@xs))))
+
+ (define (build-or xs)
+ (match xs
+ (() #f)
+ ((x) x)
+ (_ `(or ,@xs))))
+
+ (define (case-test-var test)
+ (match test
+ (('memv (? atom? v) ('quote (datums ...)))
+ v)
+ (('eqv? (? atom? v) ('quote datum))
+ v)
+ (_ #f)))
+
+ (define (test->datums v test)
+ (match (cons v test)
+ ((v 'memv v ('quote (xs ...)))
+ xs)
+ ((v 'eqv? v ('quote x))
+ (list x))
+ (_ #f)))
+
+ (define (build-else-tail e)
+ (match e
+ (('if #f _) '())
+ (('and xs ... x) `((,(build-and xs) ,@(build-begin-body x))
+ (else #f)))
+ (_ `((else ,@(build-begin-body e))))))
+
+ (define (build-cond-else-tail e)
+ (match e
+ (('cond clauses ...) clauses)
+ (_ (build-else-tail e))))
+
+ (define (build-case-else-tail v e)
+ (match (cons v e)
+ ((v 'case v clauses ...)
+ clauses)
+ ((v 'if ('memv v ('quote (xs ...))) consequent . alternate*)
+ `((,xs ,@(build-begin-body consequent))
+ ,@(build-case-else-tail v (build-begin alternate*))))
+ ((v 'if ('eqv? v ('quote x)) consequent . alternate*)
+ `(((,x) ,@(build-begin-body consequent))
+ ,@(build-case-else-tail v (build-begin alternate*))))
+ (_ (build-else-tail e))))
+
+ (define (clauses+tail clauses)
+ (match clauses
+ ((cs ... (and c ('else . _))) (values cs (list c)))
+ (_ (values clauses '()))))
+
+ (define (build-cond tests consequents alternate)
+ (case (length tests)
+ ((0) alternate)
+ ((1) (build-if (car tests) (car consequents) alternate))
+ (else `(cond ,@(map (lambda (test consequent)
+ `(,test ,@(build-begin-body consequent)))
+ tests consequents)
+ ,@(build-cond-else-tail alternate)))))
+
+ (define (build-cond-or-case tests consequents alternate)
+ (if (not use-case?)
+ (build-cond tests consequents alternate)
+ (let* ((v (and (not (null? tests))
+ (case-test-var (car tests))))
+ (datum-lists (take-while identity
+ (map (cut test->datums v <>)
+ tests)))
+ (n (length datum-lists))
+ (tail (build-case-else-tail v (build-cond
+ (drop tests n)
+ (drop consequents n)
+ alternate))))
+ (receive (clauses tail) (clauses+tail tail)
+ (let ((n (+ n (length clauses)))
+ (datum-lists (append datum-lists
+ (map car clauses)))
+ (consequents (append consequents
+ (map build-begin
+ (map cdr clauses)))))
+ (if (< n 2)
+ (build-cond tests consequents alternate)
+ `(case ,v
+ ,@(map cons datum-lists (map build-begin-body
+ (take consequents n)))
+ ,@tail)))))))
+
+ (define (recurse e)
+
+ (define (recurse-body e)
+ (build-lambda-body (recurse e)))
+
+ (record-case e
+ ((<void>)
+ (build-void))
+
+ ((<const> exp)
+ (if (and (self-evaluating? exp) (not (vector? exp)))
+ exp
+ `(quote ,exp)))
+
+ ((<sequence> exps)
+ (build-begin (map recurse exps)))
+
+ ((<application> proc args)
+ (match `(,(recurse proc) ,@(map recurse args))
+ ((('lambda (formals ...) body ...) args ...)
+ (=> failure)
+ (if (= (length formals) (length args))
+ (build-let formals args (build-begin body))
+ (failure)))
+ (e e)))
+
+ ((<primitive-ref> name)
+ name)
+
+ ((<lexical-ref> gensym)
+ (output-name gensym))
+
+ ((<lexical-set> gensym exp)
+ `(set! ,(output-name gensym) ,(recurse exp)))
+
+ ((<module-ref> mod name public?)
+ `(,(if public? '@ '@@) ,mod ,name))
+
+ ((<module-set> mod name public? exp)
+ `(set! (,(if public? '@ '@@) ,mod ,name) ,(recurse exp)))
+
+ ((<toplevel-ref> name)
+ name)
+
+ ((<toplevel-set> name exp)
+ `(set! ,name ,(recurse exp)))
+
+ ((<toplevel-define> name exp)
+ (build-define name (recurse exp)))
+
+ ((<lambda> meta body)
+ (if body
+ (let ((body (recurse body))
+ (doc (assq-ref meta 'documentation)))
+ (if (not doc)
+ body
+ (match body
+ (('lambda formals body ...)
+ `(lambda ,formals ,doc ,@body))
+ (('lambda* formals body ...)
+ `(lambda* ,formals ,doc ,@body))
+ (('case-lambda (formals body ...) clauses ...)
+ `(case-lambda (,formals ,doc ,@body) ,@clauses))
+ (('case-lambda* (formals body ...) clauses ...)
+ `(case-lambda* (,formals ,doc ,@body) ,@clauses))
+ (e e))))
+ '(case-lambda)))
+
+ ((<lambda-case> req opt rest kw inits gensyms body alternate)
+ (let ((names (map output-name gensyms)))
+ (cond
+ ((and (not opt) (not kw) (not alternate))
+ `(lambda ,(if rest (apply cons* names) names)
+ ,@(recurse-body body)))
+ ((and (not opt) (not kw))
+ (let ((alt-expansion (recurse alternate))
+ (formals (if rest (apply cons* names) names)))
+ (case (car alt-expansion)
+ ((lambda)
+ `(case-lambda (,formals ,@(recurse-body body))
+ ,(cdr alt-expansion)))
+ ((lambda*)
+ `(case-lambda* (,formals ,@(recurse-body body))
+ ,(cdr alt-expansion)))
+ ((case-lambda)
+ `(case-lambda (,formals ,@(recurse-body body))
+ ,@(cdr alt-expansion)))
+ ((case-lambda*)
+ `(case-lambda* (,formals ,@(recurse-body body))
+ ,@(cdr alt-expansion))))))
+ (else
+ (let* ((alt-expansion (and alternate (recurse alternate)))
+ (nreq (length req))
+ (nopt (if opt (length opt) 0))
+ (restargs (if rest (list-ref names (+ nreq nopt)) '()))
+ (reqargs (list-head names nreq))
+ (optargs (if opt
+ `(#\optional
+ ,@(map list
+ (list-head (list-tail names nreq) nopt)
+ (map recurse
+ (list-head inits nopt))))
+ '()))
+ (kwargs (if kw
+ `(#\key
+ ,@(map list
+ (map output-name (map caddr (cdr kw)))
+ (map recurse
+ (list-tail inits nopt))
+ (map car (cdr kw)))
+ ,@(if (car kw)
+ '(#\allow-other-keys)
+ '()))
+ '()))
+ (formals `(,@reqargs ,@optargs ,@kwargs . ,restargs)))
+ (if (not alt-expansion)
+ `(lambda* ,formals ,@(recurse-body body))
+ (case (car alt-expansion)
+ ((lambda lambda*)
+ `(case-lambda* (,formals ,@(recurse-body body))
+ ,(cdr alt-expansion)))
+ ((case-lambda case-lambda*)
+ `(case-lambda* (,formals ,@(recurse-body body))
+ ,@(cdr alt-expansion))))))))))
+
+ ((<conditional> test consequent alternate)
+ (define (simplify-test e)
+ (match e
+ (('if ('eqv? (? atom? v) ('quote a)) #t ('eqv? v ('quote b)))
+ `(memv ,v '(,a ,b)))
+ (('if ('eqv? (? atom? v) ('quote a)) #t ('memv v ('quote (bs ...))))
+ `(memv ,v '(,a ,@bs)))
+ (('case (? atom? v)
+ ((datum) #t) ...
+ ('else ('eqv? v ('quote last-datum))))
+ `(memv ,v '(,@datum ,last-datum)))
+ (_ e)))
+ (match `(if ,(simplify-test (recurse test))
+ ,(recurse consequent)
+ ,@(if (void? alternate) '()
+ (list (recurse alternate))))
+ (('if test ('if ('and xs ...) consequent))
+ (build-if (build-and (cons test xs))
+ consequent
+ (build-void)))
+ ((? (const use-derived-syntax?)
+ ('if test1 ('if test2 consequent)))
+ (build-if (build-and (list test1 test2))
+ consequent
+ (build-void)))
+ (('if (? atom? x) x ('or ys ...))
+ (build-or (cons x ys)))
+ ((? (const use-derived-syntax?)
+ ('if (? atom? x) x y))
+ (build-or (list x y)))
+ (('if test consequent)
+ `(if ,test ,consequent))
+ (('if test ('and xs ...) #f)
+ (build-and (cons test xs)))
+ ((? (const use-derived-syntax?)
+ ('if test consequent #f))
+ (build-and (list test consequent)))
+ ((? (const use-derived-syntax?)
+ ('if test1 consequent1
+ ('if test2 consequent2 . alternate*)))
+ (build-cond-or-case (list test1 test2)
+ (list consequent1 consequent2)
+ (build-begin alternate*)))
+ (('if test consequent ('cond clauses ...))
+ `(cond (,test ,@(build-begin-body consequent))
+ ,@clauses))
+ (('if ('memv (? atom? v) ('quote (xs ...))) consequent
+ ('case v clauses ...))
+ `(case ,v (,xs ,@(build-begin-body consequent))
+ ,@clauses))
+ (('if ('eqv? (? atom? v) ('quote x)) consequent
+ ('case v clauses ...))
+ `(case ,v ((,x) ,@(build-begin-body consequent))
+ ,@clauses))
+ (e e)))
+
+ ((<let> gensyms vals body)
+ (match (build-let (map output-name gensyms)
+ (map recurse vals)
+ (recurse body))
+ (('let ((v e)) ('or v xs ...))
+ (=> failure)
+ (if (and (not (null? gensyms))
+ (= 3 (occurrence-count (car gensyms))))
+ `(or ,e ,@xs)
+ (failure)))
+ (('let ((v e)) ('case v clauses ...))
+ (=> failure)
+ (if (and (not (null? gensyms))
+ ;; FIXME: This fails if any of the 'memv's were
+ ;; optimized into multiple 'eqv?'s, because the
+ ;; occurrence count will be higher than we expect.
+ (= (occurrence-count (car gensyms))
+ (1+ (length (clauses+tail clauses)))))
+ `(case ,e ,@clauses)
+ (failure)))
+ (e e)))
+
+ ((<letrec> in-order? gensyms vals body)
+ (build-letrec in-order?
+ (map output-name gensyms)
+ (map recurse vals)
+ (recurse body)))
+
+ ((<fix> gensyms vals body)
+ ;; not a typo, we really do translate back to letrec. use letrec* since it
+ ;; doesn't matter, and the naive letrec* transformation does not require an
+ ;; inner let.
+ (build-letrec #t
+ (map output-name gensyms)
+ (map recurse vals)
+ (recurse body)))
+
+ ((<let-values> exp body)
+ `(call-with-values (lambda () ,@(recurse-body exp))
+ ,(recurse (make-lambda #f '() body))))
+
+ ((<dynwind> body winder unwinder)
+ `(dynamic-wind ,(recurse winder)
+ (lambda () ,@(recurse-body body))
+ ,(recurse unwinder)))
+
+ ((<dynlet> fluids vals body)
+ `(with-fluids ,(map list
+ (map recurse fluids)
+ (map recurse vals))
+ ,@(recurse-body body)))
+
+ ((<dynref> fluid)
+ `(fluid-ref ,(recurse fluid)))
+
+ ((<dynset> fluid exp)
+ `(fluid-set! ,(recurse fluid) ,(recurse exp)))
+
+ ((<prompt> tag body handler)
+ `(call-with-prompt
+ ,(recurse tag)
+ (lambda () ,@(recurse-body body))
+ ,(recurse handler)))
+
+
+ ((<abort> tag args tail)
+ `(apply abort ,(recurse tag) ,@(map recurse args)
+ ,(recurse tail)))))
+ (values (recurse e) env)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Algorithm for choosing better variable names
+;; ============================================
+;;
+;; First we perform an analysis pass, collecting the following
+;; information:
+;;
+;; * For each gensym: how many occurrences will occur in the output?
+;;
+;; * For each gensym A: which gensyms does A conflict with? Gensym A
+;; and gensym B conflict if they have the same base name (usually the
+;; same as the source name, but see below), and if giving them the
+;; same name would cause a bad variable reference due to unintentional
+;; variable capture.
+;;
+;; The occurrence counter is indexed by gensym and is global (within each
+;; invocation of the algorithm), implemented using a hash table. We also
+;; keep a global mapping from gensym to source name as provided by the
+;; binding construct (we prefer not to trust the source names in the
+;; lexical ref or set).
+;;
+;; As we recurse down into lexical binding forms, we keep track of a
+;; mapping from base name to an ordered list of bindings, innermost
+;; first. When we encounter a variable occurrence, we increment the
+;; counter, look up the base name (preferring not to trust the 'name' in
+;; the lexical ref or set), and then look up the bindings currently in
+;; effect for that base name. Hopefully our gensym will be the first
+;; (innermost) binding. If not, we register a conflict between the
+;; referenced gensym and the other bound gensyms with the same base name
+;; that shadow the binding we want. These are simply the gensyms on the
+;; binding list that come before our gensym.
+;;
+;; Top-level bindings are treated specially. Whenever top-level
+;; references are found, they conflict with every lexical binding
+;; currently in effect with the same base name. They are guaranteed to
+;; be assigned to their source names. For purposes of recording
+;; conflicts (which are normally keyed on gensyms) top-level identifiers
+;; are assigned a pseudo-gensym that is an interned pair of the form
+;; (top-level . <name>). This allows them to be compared using 'eq?'
+;; like other gensyms.
+;;
+;; The base name is normally just the source name. However, if the
+;; source name has a suffix of the form "-N" (where N is a positive
+;; integer without leading zeroes), then we strip that suffix (multiple
+;; times if necessary) to form the base name. We must do this because
+;; we add suffixes of that form in order to resolve conflicts, and we
+;; must ensure that only identifiers with the same base name can
+;; possibly conflict with each other.
+;;
+;; XXX FIXME: Currently, primitives are treated exactly like top-level
+;; bindings. This handles conflicting lexical bindings properly, but
+;; does _not_ handle the case where top-level bindings conflict with the
+;; needed primitives.
+;;
+;; Also note that this requires that 'choose-output-names' be kept in
+;; sync with 'tree-il->scheme'. Primitives that are introduced by
+;; 'tree-il->scheme' must be anticipated by 'choose-output-name'.
+;;
+;; We also ensure that lexically-bound identifiers found in operator
+;; position will never be assigned one of the standard primitive names.
+;; This is needed because 'tree-il->scheme' recognizes primitive names
+;; in operator position and assumes that they have the standard
+;; bindings.
+;;
+;;
+;; How we assign an output name to each gensym
+;; ===========================================
+;;
+;; We process the gensyms in order of decreasing occurrence count, with
+;; each gensym choosing the best output name possible, as long as it
+;; isn't the same name as any of the previously-chosen output names of
+;; conflicting gensyms.
+;;
+
+
+;;
+;; 'choose-output-names' analyzes the top-level form e, chooses good
+;; variable names that are as close as possible to the source names,
+;; and returns two values:
+;;
+;; * a hash table mapping gensym to output name
+;; * a hash table mapping gensym to number of occurrences
+;;
+(define choose-output-names
+ (let ()
+ (define primitive?
+ ;; This is a list of primitives that 'tree-il->scheme' assumes
+ ;; will have the standard bindings when found in operator
+ ;; position.
+ (let* ((primitives '(if quote @ @@ set! define define*
+ begin let let* letrec letrec*
+ and or cond case
+ lambda lambda* case-lambda case-lambda*
+ apply call-with-values dynamic-wind
+ with-fluids fluid-ref fluid-set!
+ call-with-prompt abort memv eqv?))
+ (table (make-hash-table (length primitives))))
+ (for-each (cut hashq-set! table <> #t) primitives)
+ (lambda (name) (hashq-ref table name))))
+
+ ;; Repeatedly strip suffix of the form "-N", where N is a string
+ ;; that could be produced by number->string given a positive
+ ;; integer. In other words, the first digit of N may not be 0.
+ (define compute-base-name
+ (let ((digits (string->char-set "0123456789")))
+ (define (base-name-string str)
+ (let ((i (string-skip-right str digits)))
+ (if (and i (< (1+ i) (string-length str))
+ (eq? #\- (string-ref str i))
+ (not (eq? #\0 (string-ref str (1+ i)))))
+ (base-name-string (substring str 0 i))
+ str)))
+ (lambda (sym)
+ (string->symbol (base-name-string (symbol->string sym))))))
+
+ ;; choose-output-names
+ (lambda (e use-derived-syntax? strip-numeric-suffixes?)
+
+ (define lexical-gensyms '())
+
+ (define top-level-intern!
+ (let ((table (make-hash-table)))
+ (lambda (name)
+ (let ((h (hashq-create-handle! table name #f)))
+ (or (cdr h) (begin (set-cdr! h (cons 'top-level name))
+ (cdr h)))))))
+ (define (top-level? s) (pair? s))
+ (define (top-level-name s) (cdr s))
+
+ (define occurrence-count-table (make-hash-table))
+ (define (occurrence-count s) (or (hashq-ref occurrence-count-table s) 0))
+ (define (increment-occurrence-count! s)
+ (let ((h (hashq-create-handle! occurrence-count-table s 0)))
+ (if (zero? (cdr h))
+ (set! lexical-gensyms (cons s lexical-gensyms)))
+ (set-cdr! h (1+ (cdr h)))))
+
+ (define base-name
+ (let ((table (make-hash-table)))
+ (lambda (name)
+ (let ((h (hashq-create-handle! table name #f)))
+ (or (cdr h) (begin (set-cdr! h (compute-base-name name))
+ (cdr h)))))))
+
+ (define source-name-table (make-hash-table))
+ (define (set-source-name! s name)
+ (if (not (top-level? s))
+ (let ((name (if strip-numeric-suffixes?
+ (base-name name)
+ name)))
+ (hashq-set! source-name-table s name))))
+ (define (source-name s)
+ (if (top-level? s)
+ (top-level-name s)
+ (hashq-ref source-name-table s)))
+
+ (define conflict-table (make-hash-table))
+ (define (conflicts s) (or (hashq-ref conflict-table s) '()))
+ (define (add-conflict! a b)
+ (define (add! a b)
+ (if (not (top-level? a))
+ (let ((h (hashq-create-handle! conflict-table a '())))
+ (if (not (memq b (cdr h)))
+ (set-cdr! h (cons b (cdr h)))))))
+ (add! a b)
+ (add! b a))
+
+ (let recurse-with-bindings ((e e) (bindings vlist-null))
+ (let recurse ((e e))
+
+ ;; We call this whenever we encounter a top-level ref or set
+ (define (top-level name)
+ (let ((bname (base-name name)))
+ (let ((s (top-level-intern! name))
+ (conflicts (vhash-foldq* cons '() bname bindings)))
+ (for-each (cut add-conflict! s <>) conflicts))))
+
+ ;; We call this whenever we encounter a primitive reference.
+ ;; We must also call it for every primitive that might be
+ ;; inserted by 'tree-il->scheme'. It is okay to call this
+ ;; even when 'tree-il->scheme' will not insert the named
+ ;; primitive; the worst that will happen is for a lexical
+ ;; variable of the same name to be renamed unnecessarily.
+ (define (primitive name) (top-level name))
+
+ ;; We call this whenever we encounter a lexical ref or set.
+ (define (lexical s)
+ (increment-occurrence-count! s)
+ (let ((conflicts
+ (take-while
+ (lambda (s*) (not (eq? s s*)))
+ (reverse! (vhash-foldq* cons
+ '()
+ (base-name (source-name s))
+ bindings)))))
+ (for-each (cut add-conflict! s <>) conflicts)))
+
+ (record-case e
+ ((<void>) (primitive 'if)) ; (if #f #f)
+ ((<const>) (primitive 'quote))
+
+ ((<application> proc args)
+ (if (lexical-ref? proc)
+ (let* ((gensym (lexical-ref-gensym proc))
+ (name (source-name gensym)))
+ ;; If the operator position contains a bare variable
+ ;; reference with the same source name as a standard
+ ;; primitive, we must ensure that it will be given a
+ ;; different name, so that 'tree-il->scheme' will not
+ ;; misinterpret the resulting expression.
+ (if (primitive? name)
+ (add-conflict! gensym (top-level-intern! name)))))
+ (recurse proc)
+ (for-each recurse args))
+
+ ((<primitive-ref> name) (primitive name))
+
+ ((<lexical-ref> gensym) (lexical gensym))
+ ((<lexical-set> gensym exp)
+ (primitive 'set!) (lexical gensym) (recurse exp))
+
+ ((<module-ref> public?) (primitive (if public? '@ '@@)))
+ ((<module-set> public? exp)
+ (primitive 'set!) (primitive (if public? '@ '@@)) (recurse exp))
+
+ ((<toplevel-ref> name) (top-level name))
+ ((<toplevel-set> name exp)
+ (primitive 'set!) (top-level name) (recurse exp))
+ ((<toplevel-define> name exp) (top-level name) (recurse exp))
+
+ ((<conditional> test consequent alternate)
+ (cond (use-derived-syntax?
+ (primitive 'and) (primitive 'or)
+ (primitive 'cond) (primitive 'case)
+ (primitive 'else) (primitive '=>)))
+ (primitive 'if)
+ (recurse test) (recurse consequent) (recurse alternate))
+
+ ((<sequence> exps) (primitive 'begin) (for-each recurse exps))
+ ((<lambda> body)
+ (if body (recurse body) (primitive 'case-lambda)))
+
+ ((<lambda-case> req opt rest kw inits gensyms body alternate)
+ (primitive 'lambda)
+ (cond ((or opt kw alternate)
+ (primitive 'lambda*)
+ (primitive 'case-lambda)
+ (primitive 'case-lambda*)))
+ (primitive 'let)
+ (if use-derived-syntax? (primitive 'let*))
+ (let* ((names (append req (or opt '()) (if rest (list rest) '())
+ (map cadr (if kw (cdr kw) '()))))
+ (base-names (map base-name names))
+ (body-bindings
+ (fold vhash-consq bindings base-names gensyms)))
+ (for-each increment-occurrence-count! gensyms)
+ (for-each set-source-name! gensyms names)
+ (for-each recurse inits)
+ (recurse-with-bindings body body-bindings)
+ (if alternate (recurse alternate))))
+
+ ((<let> names gensyms vals body)
+ (primitive 'let)
+ (cond (use-derived-syntax? (primitive 'let*) (primitive 'or)))
+ (for-each increment-occurrence-count! gensyms)
+ (for-each set-source-name! gensyms names)
+ (for-each recurse vals)
+ (recurse-with-bindings
+ body (fold vhash-consq bindings (map base-name names) gensyms)))
+
+ ((<letrec> in-order? names gensyms vals body)
+ (primitive 'let)
+ (cond (use-derived-syntax? (primitive 'let*) (primitive 'or)))
+ (primitive (if in-order? 'letrec* 'letrec))
+ (for-each increment-occurrence-count! gensyms)
+ (for-each set-source-name! gensyms names)
+ (let* ((base-names (map base-name names))
+ (bindings (fold vhash-consq bindings base-names gensyms)))
+ (for-each (cut recurse-with-bindings <> bindings) vals)
+ (recurse-with-bindings body bindings)))
+
+ ((<fix> names gensyms vals body)
+ (primitive 'let)
+ (primitive 'letrec*)
+ (cond (use-derived-syntax? (primitive 'let*) (primitive 'or)))
+ (for-each increment-occurrence-count! gensyms)
+ (for-each set-source-name! gensyms names)
+ (let* ((base-names (map base-name names))
+ (bindings (fold vhash-consq bindings base-names gensyms)))
+ (for-each (cut recurse-with-bindings <> bindings) vals)
+ (recurse-with-bindings body bindings)))
+
+ ((<let-values> exp body)
+ (primitive 'call-with-values)
+ (recurse exp) (recurse body))
+
+ ((<dynwind> winder body unwinder)
+ (primitive 'dynamic-wind)
+ (recurse winder) (recurse body) (recurse unwinder))
+
+ ((<dynlet> fluids vals body)
+ (primitive 'with-fluids)
+ (for-each recurse fluids)
+ (for-each recurse vals)
+ (recurse body))
+
+ ((<dynref> fluid) (primitive 'fluid-ref) (recurse fluid))
+ ((<dynset> fluid exp)
+ (primitive 'fluid-set!) (recurse fluid) (recurse exp))
+
+ ((<prompt> tag body handler)
+ (primitive 'call-with-prompt)
+ (primitive 'lambda)
+ (recurse tag) (recurse body) (recurse handler))
+
+ ((<abort> tag args tail)
+ (primitive 'apply)
+ (primitive 'abort)
+ (recurse tag) (for-each recurse args) (recurse tail)))))
+
+ (let ()
+ (define output-name-table (make-hash-table))
+ (define (set-output-name! s name)
+ (hashq-set! output-name-table s name))
+ (define (output-name s)
+ (if (top-level? s)
+ (top-level-name s)
+ (hashq-ref output-name-table s)))
+
+ (define sorted-lexical-gensyms
+ (sort-list lexical-gensyms
+ (lambda (a b) (> (occurrence-count a)
+ (occurrence-count b)))))
+
+ (for-each (lambda (s)
+ (set-output-name!
+ s
+ (let ((the-conflicts (conflicts s))
+ (the-source-name (source-name s)))
+ (define (not-yet-taken? name)
+ (not (any (lambda (s*)
+ (and=> (output-name s*)
+ (cut eq? name <>)))
+ the-conflicts)))
+ (if (not-yet-taken? the-source-name)
+ the-source-name
+ (let ((prefix (string-append
+ (symbol->string the-source-name)
+ "-")))
+ (let loop ((i 1) (name the-source-name))
+ (if (not-yet-taken? name)
+ name
+ (loop (+ i 1)
+ (string->symbol
+ (string-append
+ prefix
+ (number->string i)))))))))))
+ sorted-lexical-gensyms)
+ (values output-name-table occurrence-count-table)))))
+;;; Guile Scheme specification
+
+;; Copyright (C) 2001, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language scheme spec)
+ #\use-module (system base compile)
+ #\use-module (system base language)
+ #\use-module (language scheme compile-tree-il)
+ #\use-module (language scheme decompile-tree-il)
+ #\export (scheme))
+
+;;;
+;;; Language definition
+;;;
+
+(define-language scheme
+ #\title "Scheme"
+ #\reader (lambda (port env)
+ ;; Use the binding of current-reader from the environment.
+ ;; FIXME: Handle `read-options' as well?
+ ((or (and=> (and=> (module-variable env 'current-reader)
+ variable-ref)
+ fluid-ref)
+ read)
+ port))
+
+ #\compilers `((tree-il . ,compile-tree-il))
+ #\decompilers `((tree-il . ,decompile-tree-il))
+ #\evaluator (lambda (x module) (primitive-eval x))
+ #\printer write
+ #\make-default-environment
+ (lambda ()
+ ;; Ideally we'd duplicate the whole module hierarchy so that `set!',
+ ;; `fluid-set!', etc. don't have any effect in the current environment.
+ (let ((m (make-fresh-user-module)))
+ ;; Provide a separate `current-reader' fluid so that
+ ;; compile-time changes to `current-reader' are
+ ;; limited to the current compilation unit.
+ (module-define! m 'current-reader (make-fluid))
+
+ ;; Default to `simple-format', as is the case until
+ ;; (ice-9 format) is loaded. This allows
+ ;; compile-time warnings to be emitted when using
+ ;; unsupported options.
+ (module-set! m 'format simple-format)
+
+ m)))
+;;;; Copyright (C) 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (language tree-il)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-11)
+ #\use-module (system base pmatch)
+ #\use-module (system base syntax)
+ #\export (tree-il-src
+
+ <void> void? make-void void-src
+ <const> const? make-const const-src const-exp
+ <primitive-ref> primitive-ref? make-primitive-ref primitive-ref-src primitive-ref-name
+ <lexical-ref> lexical-ref? make-lexical-ref lexical-ref-src lexical-ref-name lexical-ref-gensym
+ <lexical-set> lexical-set? make-lexical-set lexical-set-src lexical-set-name lexical-set-gensym lexical-set-exp
+ <module-ref> module-ref? make-module-ref module-ref-src module-ref-mod module-ref-name module-ref-public?
+ <module-set> module-set? make-module-set module-set-src module-set-mod module-set-name module-set-public? module-set-exp
+ <toplevel-ref> toplevel-ref? make-toplevel-ref toplevel-ref-src toplevel-ref-name
+ <toplevel-set> toplevel-set? make-toplevel-set toplevel-set-src toplevel-set-name toplevel-set-exp
+ <toplevel-define> toplevel-define? make-toplevel-define toplevel-define-src toplevel-define-name toplevel-define-exp
+ <conditional> conditional? make-conditional conditional-src conditional-test conditional-consequent conditional-alternate
+ <application> application? make-application application-src application-proc application-args
+ <sequence> sequence? make-sequence sequence-src sequence-exps
+ <lambda> lambda? make-lambda lambda-src lambda-meta lambda-body
+ <lambda-case> lambda-case? make-lambda-case lambda-case-src
+ lambda-case-req lambda-case-opt lambda-case-rest lambda-case-kw
+ lambda-case-inits lambda-case-gensyms
+ lambda-case-body lambda-case-alternate
+ <let> let? make-let let-src let-names let-gensyms let-vals let-body
+ <letrec> letrec? make-letrec letrec-src letrec-in-order? letrec-names letrec-gensyms letrec-vals letrec-body
+ <fix> fix? make-fix fix-src fix-names fix-gensyms fix-vals fix-body
+ <let-values> let-values? make-let-values let-values-src let-values-exp let-values-body
+ <dynwind> dynwind? make-dynwind dynwind-src dynwind-winder dynwind-body dynwind-unwinder
+ <dynlet> dynlet? make-dynlet dynlet-src dynlet-fluids dynlet-vals dynlet-body
+ <dynref> dynref? make-dynref dynref-src dynref-fluid
+ <dynset> dynset? make-dynset dynset-src dynset-fluid dynset-exp
+ <prompt> prompt? make-prompt prompt-src prompt-tag prompt-body prompt-handler
+ <abort> abort? make-abort abort-src abort-tag abort-args abort-tail
+
+ parse-tree-il
+ unparse-tree-il
+ tree-il->scheme
+
+ tree-il-fold
+ make-tree-il-folder
+ post-order!
+ pre-order!
+
+ tree-il=?
+ tree-il-hash))
+
+(define (print-tree-il exp port)
+ (format port "#<tree-il ~S>" (unparse-tree-il exp)))
+
+(define-syntax borrow-core-vtables
+ (lambda (x)
+ (syntax-case x ()
+ ((_)
+ (let lp ((n 0) (out '()))
+ (if (< n (vector-length %expanded-vtables))
+ (lp (1+ n)
+ (let* ((vtable (vector-ref %expanded-vtables n))
+ (stem (struct-ref vtable (+ vtable-offset-user 0)))
+ (fields (struct-ref vtable (+ vtable-offset-user 2)))
+ (sfields (map
+ (lambda (f) (datum->syntax x f))
+ fields))
+ (type (datum->syntax x (symbol-append '< stem '>)))
+ (ctor (datum->syntax x (symbol-append 'make- stem)))
+ (pred (datum->syntax x (symbol-append stem '?))))
+ (let lp ((n 0) (fields fields)
+ (out (cons*
+ #`(define (#,ctor #,@sfields)
+ (make-struct #,type 0 #,@sfields))
+ #`(define (#,pred x)
+ (and (struct? x)
+ (eq? (struct-vtable x) #,type)))
+ #`(struct-set! #,type vtable-index-printer
+ print-tree-il)
+ #`(define #,type
+ (vector-ref %expanded-vtables #,n))
+ out)))
+ (if (null? fields)
+ out
+ (lp (1+ n)
+ (cdr fields)
+ (let ((acc (datum->syntax
+ x (symbol-append stem '- (car fields)))))
+ (cons #`(define #,acc
+ (make-procedure-with-setter
+ (lambda (x) (struct-ref x #,n))
+ (lambda (x v) (struct-set! x #,n v))))
+ out)))))))
+ #`(begin #,@(reverse out))))))))
+
+(borrow-core-vtables)
+
+ ;; (<void>)
+ ;; (<const> exp)
+ ;; (<primitive-ref> name)
+ ;; (<lexical-ref> name gensym)
+ ;; (<lexical-set> name gensym exp)
+ ;; (<module-ref> mod name public?)
+ ;; (<module-set> mod name public? exp)
+ ;; (<toplevel-ref> name)
+ ;; (<toplevel-set> name exp)
+ ;; (<toplevel-define> name exp)
+ ;; (<conditional> test consequent alternate)
+ ;; (<application> proc args)
+ ;; (<sequence> exps)
+ ;; (<lambda> meta body)
+ ;; (<lambda-case> req opt rest kw inits gensyms body alternate)
+ ;; (<let> names gensyms vals body)
+ ;; (<letrec> in-order? names gensyms vals body)
+ ;; (<dynlet> fluids vals body)
+
+(define-type (<tree-il> #\common-slots (src) #\printer print-tree-il)
+ (<fix> names gensyms vals body)
+ (<let-values> exp body)
+ (<dynwind> winder body unwinder)
+ (<dynref> fluid)
+ (<dynset> fluid exp)
+ (<prompt> tag body handler)
+ (<abort> tag args tail))
+
+
+
+(define (location x)
+ (and (pair? x)
+ (let ((props (source-properties x)))
+ (and (pair? props) props))))
+
+(define (parse-tree-il exp)
+ (let ((loc (location exp))
+ (retrans (lambda (x) (parse-tree-il x))))
+ (pmatch exp
+ ((void)
+ (make-void loc))
+
+ ((apply ,proc . ,args)
+ (make-application loc (retrans proc) (map retrans args)))
+
+ ((if ,test ,consequent ,alternate)
+ (make-conditional loc (retrans test) (retrans consequent) (retrans alternate)))
+
+ ((primitive ,name) (guard (symbol? name))
+ (make-primitive-ref loc name))
+
+ ((lexical ,name) (guard (symbol? name))
+ (make-lexical-ref loc name name))
+
+ ((lexical ,name ,sym) (guard (symbol? name) (symbol? sym))
+ (make-lexical-ref loc name sym))
+
+ ((set! (lexical ,name) ,exp) (guard (symbol? name))
+ (make-lexical-set loc name name (retrans exp)))
+
+ ((set! (lexical ,name ,sym) ,exp) (guard (symbol? name) (symbol? sym))
+ (make-lexical-set loc name sym (retrans exp)))
+
+ ((@ ,mod ,name) (guard (and-map symbol? mod) (symbol? name))
+ (make-module-ref loc mod name #t))
+
+ ((set! (@ ,mod ,name) ,exp) (guard (and-map symbol? mod) (symbol? name))
+ (make-module-set loc mod name #t (retrans exp)))
+
+ ((@@ ,mod ,name) (guard (and-map symbol? mod) (symbol? name))
+ (make-module-ref loc mod name #f))
+
+ ((set! (@@ ,mod ,name) ,exp) (guard (and-map symbol? mod) (symbol? name))
+ (make-module-set loc mod name #f (retrans exp)))
+
+ ((toplevel ,name) (guard (symbol? name))
+ (make-toplevel-ref loc name))
+
+ ((set! (toplevel ,name) ,exp) (guard (symbol? name))
+ (make-toplevel-set loc name (retrans exp)))
+
+ ((define ,name ,exp) (guard (symbol? name))
+ (make-toplevel-define loc name (retrans exp)))
+
+ ((lambda ,meta ,body)
+ (make-lambda loc meta (retrans body)))
+
+ ((lambda-case ((,req ,opt ,rest ,kw ,inits ,gensyms) ,body) ,alternate)
+ (make-lambda-case loc req opt rest kw
+ (map retrans inits) gensyms
+ (retrans body)
+ (and=> alternate retrans)))
+
+ ((lambda-case ((,req ,opt ,rest ,kw ,inits ,gensyms) ,body))
+ (make-lambda-case loc req opt rest kw
+ (map retrans inits) gensyms
+ (retrans body)
+ #f))
+
+ ((const ,exp)
+ (make-const loc exp))
+
+ ((begin . ,exps)
+ (make-sequence loc (map retrans exps)))
+
+ ((let ,names ,gensyms ,vals ,body)
+ (make-let loc names gensyms (map retrans vals) (retrans body)))
+
+ ((letrec ,names ,gensyms ,vals ,body)
+ (make-letrec loc #f names gensyms (map retrans vals) (retrans body)))
+
+ ((letrec* ,names ,gensyms ,vals ,body)
+ (make-letrec loc #t names gensyms (map retrans vals) (retrans body)))
+
+ ((fix ,names ,gensyms ,vals ,body)
+ (make-fix loc names gensyms (map retrans vals) (retrans body)))
+
+ ((let-values ,exp ,body)
+ (make-let-values loc (retrans exp) (retrans body)))
+
+ ((dynwind ,winder ,body ,unwinder)
+ (make-dynwind loc (retrans winder) (retrans body) (retrans unwinder)))
+
+ ((dynlet ,fluids ,vals ,body)
+ (make-dynlet loc (map retrans fluids) (map retrans vals) (retrans body)))
+
+ ((dynref ,fluid)
+ (make-dynref loc (retrans fluid)))
+
+ ((dynset ,fluid ,exp)
+ (make-dynset loc (retrans fluid) (retrans exp)))
+
+ ((prompt ,tag ,body ,handler)
+ (make-prompt loc (retrans tag) (retrans body) (retrans handler)))
+
+ ((abort ,tag ,args ,tail)
+ (make-abort loc (retrans tag) (map retrans args) (retrans tail)))
+
+ (else
+ (error "unrecognized tree-il" exp)))))
+
+(define (unparse-tree-il tree-il)
+ (record-case tree-il
+ ((<void>)
+ '(void))
+
+ ((<application> proc args)
+ `(apply ,(unparse-tree-il proc) ,@(map unparse-tree-il args)))
+
+ ((<conditional> test consequent alternate)
+ `(if ,(unparse-tree-il test) ,(unparse-tree-il consequent) ,(unparse-tree-il alternate)))
+
+ ((<primitive-ref> name)
+ `(primitive ,name))
+
+ ((<lexical-ref> name gensym)
+ `(lexical ,name ,gensym))
+
+ ((<lexical-set> name gensym exp)
+ `(set! (lexical ,name ,gensym) ,(unparse-tree-il exp)))
+
+ ((<module-ref> mod name public?)
+ `(,(if public? '@ '@@) ,mod ,name))
+
+ ((<module-set> mod name public? exp)
+ `(set! (,(if public? '@ '@@) ,mod ,name) ,(unparse-tree-il exp)))
+
+ ((<toplevel-ref> name)
+ `(toplevel ,name))
+
+ ((<toplevel-set> name exp)
+ `(set! (toplevel ,name) ,(unparse-tree-il exp)))
+
+ ((<toplevel-define> name exp)
+ `(define ,name ,(unparse-tree-il exp)))
+
+ ((<lambda> meta body)
+ (if body
+ `(lambda ,meta ,(unparse-tree-il body))
+ `(lambda ,meta (lambda-case))))
+
+ ((<lambda-case> req opt rest kw inits gensyms body alternate)
+ `(lambda-case ((,req ,opt ,rest ,kw ,(map unparse-tree-il inits) ,gensyms)
+ ,(unparse-tree-il body))
+ . ,(if alternate (list (unparse-tree-il alternate)) '())))
+
+ ((<const> exp)
+ `(const ,exp))
+
+ ((<sequence> exps)
+ `(begin ,@(map unparse-tree-il exps)))
+
+ ((<let> names gensyms vals body)
+ `(let ,names ,gensyms ,(map unparse-tree-il vals) ,(unparse-tree-il body)))
+
+ ((<letrec> in-order? names gensyms vals body)
+ `(,(if in-order? 'letrec* 'letrec) ,names ,gensyms
+ ,(map unparse-tree-il vals) ,(unparse-tree-il body)))
+
+ ((<fix> names gensyms vals body)
+ `(fix ,names ,gensyms ,(map unparse-tree-il vals) ,(unparse-tree-il body)))
+
+ ((<let-values> exp body)
+ `(let-values ,(unparse-tree-il exp) ,(unparse-tree-il body)))
+
+ ((<dynwind> winder body unwinder)
+ `(dynwind ,(unparse-tree-il winder) ,(unparse-tree-il body)
+ ,(unparse-tree-il unwinder)))
+
+ ((<dynlet> fluids vals body)
+ `(dynlet ,(map unparse-tree-il fluids) ,(map unparse-tree-il vals)
+ ,(unparse-tree-il body)))
+
+ ((<dynref> fluid)
+ `(dynref ,(unparse-tree-il fluid)))
+
+ ((<dynset> fluid exp)
+ `(dynset ,(unparse-tree-il fluid) ,(unparse-tree-il exp)))
+
+ ((<prompt> tag body handler)
+ `(prompt ,(unparse-tree-il tag) ,(unparse-tree-il body) ,(unparse-tree-il handler)))
+
+ ((<abort> tag args tail)
+ `(abort ,(unparse-tree-il tag) ,(map unparse-tree-il args)
+ ,(unparse-tree-il tail)))))
+
+(define* (tree-il->scheme e #\optional (env #f) (opts '()))
+ (values ((@ (language scheme decompile-tree-il)
+ decompile-tree-il)
+ e env opts)))
+
+
+(define (tree-il-fold leaf down up seed tree)
+ "Traverse TREE, calling LEAF on each leaf encountered, DOWN upon descent
+into a sub-tree, and UP when leaving a sub-tree. Each of these procedures is
+invoked as `(PROC TREE SEED)', where TREE is the sub-tree or leaf considered
+and SEED is the current result, intially seeded with SEED.
+
+This is an implementation of `foldts' as described by Andy Wingo in
+``Applications of fold to XML transformation''."
+ (let loop ((tree tree)
+ (result seed))
+ (if (or (null? tree) (pair? tree))
+ (fold loop result tree)
+ (record-case tree
+ ((<lexical-set> exp)
+ (up tree (loop exp (down tree result))))
+ ((<module-set> exp)
+ (up tree (loop exp (down tree result))))
+ ((<toplevel-set> exp)
+ (up tree (loop exp (down tree result))))
+ ((<toplevel-define> exp)
+ (up tree (loop exp (down tree result))))
+ ((<conditional> test consequent alternate)
+ (up tree (loop alternate
+ (loop consequent
+ (loop test (down tree result))))))
+ ((<application> proc args)
+ (up tree (loop (cons proc args) (down tree result))))
+ ((<sequence> exps)
+ (up tree (loop exps (down tree result))))
+ ((<lambda> body)
+ (let ((result (down tree result)))
+ (up tree
+ (if body
+ (loop body result)
+ result))))
+ ((<lambda-case> inits body alternate)
+ (up tree (if alternate
+ (loop alternate
+ (loop body (loop inits (down tree result))))
+ (loop body (loop inits (down tree result))))))
+ ((<let> vals body)
+ (up tree (loop body
+ (loop vals
+ (down tree result)))))
+ ((<letrec> vals body)
+ (up tree (loop body
+ (loop vals
+ (down tree result)))))
+ ((<fix> vals body)
+ (up tree (loop body
+ (loop vals
+ (down tree result)))))
+ ((<let-values> exp body)
+ (up tree (loop body (loop exp (down tree result)))))
+ ((<dynwind> body winder unwinder)
+ (up tree (loop unwinder
+ (loop winder
+ (loop body (down tree result))))))
+ ((<dynlet> fluids vals body)
+ (up tree (loop body
+ (loop vals
+ (loop fluids (down tree result))))))
+ ((<dynref> fluid)
+ (up tree (loop fluid (down tree result))))
+ ((<dynset> fluid exp)
+ (up tree (loop exp (loop fluid (down tree result)))))
+ ((<prompt> tag body handler)
+ (up tree
+ (loop tag (loop body (loop handler
+ (down tree result))))))
+ ((<abort> tag args tail)
+ (up tree (loop tail (loop args (loop tag (down tree result))))))
+ (else
+ (leaf tree result))))))
+
+
+(define-syntax-rule (make-tree-il-folder seed ...)
+ (lambda (tree down up seed ...)
+ (define (fold-values proc exps seed ...)
+ (if (null? exps)
+ (values seed ...)
+ (let-values (((seed ...) (proc (car exps) seed ...)))
+ (fold-values proc (cdr exps) seed ...))))
+ (let foldts ((tree tree) (seed seed) ...)
+ (let*-values
+ (((seed ...) (down tree seed ...))
+ ((seed ...)
+ (record-case tree
+ ((<lexical-set> exp)
+ (foldts exp seed ...))
+ ((<module-set> exp)
+ (foldts exp seed ...))
+ ((<toplevel-set> exp)
+ (foldts exp seed ...))
+ ((<toplevel-define> exp)
+ (foldts exp seed ...))
+ ((<conditional> test consequent alternate)
+ (let*-values (((seed ...) (foldts test seed ...))
+ ((seed ...) (foldts consequent seed ...)))
+ (foldts alternate seed ...)))
+ ((<application> proc args)
+ (let-values (((seed ...) (foldts proc seed ...)))
+ (fold-values foldts args seed ...)))
+ ((<sequence> exps)
+ (fold-values foldts exps seed ...))
+ ((<lambda> body)
+ (if body
+ (foldts body seed ...)
+ (values seed ...)))
+ ((<lambda-case> inits body alternate)
+ (let-values (((seed ...) (fold-values foldts inits seed ...)))
+ (if alternate
+ (let-values (((seed ...) (foldts body seed ...)))
+ (foldts alternate seed ...))
+ (foldts body seed ...))))
+ ((<let> vals body)
+ (let*-values (((seed ...) (fold-values foldts vals seed ...)))
+ (foldts body seed ...)))
+ ((<letrec> vals body)
+ (let*-values (((seed ...) (fold-values foldts vals seed ...)))
+ (foldts body seed ...)))
+ ((<fix> vals body)
+ (let*-values (((seed ...) (fold-values foldts vals seed ...)))
+ (foldts body seed ...)))
+ ((<let-values> exp body)
+ (let*-values (((seed ...) (foldts exp seed ...)))
+ (foldts body seed ...)))
+ ((<dynwind> body winder unwinder)
+ (let*-values (((seed ...) (foldts body seed ...))
+ ((seed ...) (foldts winder seed ...)))
+ (foldts unwinder seed ...)))
+ ((<dynlet> fluids vals body)
+ (let*-values (((seed ...) (fold-values foldts fluids seed ...))
+ ((seed ...) (fold-values foldts vals seed ...)))
+ (foldts body seed ...)))
+ ((<dynref> fluid)
+ (foldts fluid seed ...))
+ ((<dynset> fluid exp)
+ (let*-values (((seed ...) (foldts fluid seed ...)))
+ (foldts exp seed ...)))
+ ((<prompt> tag body handler)
+ (let*-values (((seed ...) (foldts tag seed ...))
+ ((seed ...) (foldts body seed ...)))
+ (foldts handler seed ...)))
+ ((<abort> tag args tail)
+ (let*-values (((seed ...) (foldts tag seed ...))
+ ((seed ...) (fold-values foldts args seed ...)))
+ (foldts tail seed ...)))
+ (else
+ (values seed ...)))))
+ (up tree seed ...)))))
+
+(define (post-order! f x)
+ (let lp ((x x))
+ (record-case x
+ ((<application> proc args)
+ (set! (application-proc x) (lp proc))
+ (set! (application-args x) (map lp args)))
+
+ ((<conditional> test consequent alternate)
+ (set! (conditional-test x) (lp test))
+ (set! (conditional-consequent x) (lp consequent))
+ (set! (conditional-alternate x) (lp alternate)))
+
+ ((<lexical-set> name gensym exp)
+ (set! (lexical-set-exp x) (lp exp)))
+
+ ((<module-set> mod name public? exp)
+ (set! (module-set-exp x) (lp exp)))
+
+ ((<toplevel-set> name exp)
+ (set! (toplevel-set-exp x) (lp exp)))
+
+ ((<toplevel-define> name exp)
+ (set! (toplevel-define-exp x) (lp exp)))
+
+ ((<lambda> body)
+ (if body
+ (set! (lambda-body x) (lp body))))
+
+ ((<lambda-case> inits body alternate)
+ (set! inits (map lp inits))
+ (set! (lambda-case-body x) (lp body))
+ (if alternate
+ (set! (lambda-case-alternate x) (lp alternate))))
+
+ ((<sequence> exps)
+ (set! (sequence-exps x) (map lp exps)))
+
+ ((<let> gensyms vals body)
+ (set! (let-vals x) (map lp vals))
+ (set! (let-body x) (lp body)))
+
+ ((<letrec> gensyms vals body)
+ (set! (letrec-vals x) (map lp vals))
+ (set! (letrec-body x) (lp body)))
+
+ ((<fix> gensyms vals body)
+ (set! (fix-vals x) (map lp vals))
+ (set! (fix-body x) (lp body)))
+
+ ((<let-values> exp body)
+ (set! (let-values-exp x) (lp exp))
+ (set! (let-values-body x) (lp body)))
+
+ ((<dynwind> body winder unwinder)
+ (set! (dynwind-body x) (lp body))
+ (set! (dynwind-winder x) (lp winder))
+ (set! (dynwind-unwinder x) (lp unwinder)))
+
+ ((<dynlet> fluids vals body)
+ (set! (dynlet-fluids x) (map lp fluids))
+ (set! (dynlet-vals x) (map lp vals))
+ (set! (dynlet-body x) (lp body)))
+
+ ((<dynref> fluid)
+ (set! (dynref-fluid x) (lp fluid)))
+
+ ((<dynset> fluid exp)
+ (set! (dynset-fluid x) (lp fluid))
+ (set! (dynset-exp x) (lp exp)))
+
+ ((<prompt> tag body handler)
+ (set! (prompt-tag x) (lp tag))
+ (set! (prompt-body x) (lp body))
+ (set! (prompt-handler x) (lp handler)))
+
+ ((<abort> tag args tail)
+ (set! (abort-tag x) (lp tag))
+ (set! (abort-args x) (map lp args))
+ (set! (abort-tail x) (lp tail)))
+
+ (else #f))
+
+ (or (f x) x)))
+
+(define (pre-order! f x)
+ (let lp ((x x))
+ (let ((x (or (f x) x)))
+ (record-case x
+ ((<application> proc args)
+ (set! (application-proc x) (lp proc))
+ (set! (application-args x) (map lp args)))
+
+ ((<conditional> test consequent alternate)
+ (set! (conditional-test x) (lp test))
+ (set! (conditional-consequent x) (lp consequent))
+ (set! (conditional-alternate x) (lp alternate)))
+
+ ((<lexical-set> exp)
+ (set! (lexical-set-exp x) (lp exp)))
+
+ ((<module-set> exp)
+ (set! (module-set-exp x) (lp exp)))
+
+ ((<toplevel-set> exp)
+ (set! (toplevel-set-exp x) (lp exp)))
+
+ ((<toplevel-define> exp)
+ (set! (toplevel-define-exp x) (lp exp)))
+
+ ((<lambda> body)
+ (if body
+ (set! (lambda-body x) (lp body))))
+
+ ((<lambda-case> inits body alternate)
+ (set! inits (map lp inits))
+ (set! (lambda-case-body x) (lp body))
+ (if alternate (set! (lambda-case-alternate x) (lp alternate))))
+
+ ((<sequence> exps)
+ (set! (sequence-exps x) (map lp exps)))
+
+ ((<let> vals body)
+ (set! (let-vals x) (map lp vals))
+ (set! (let-body x) (lp body)))
+
+ ((<letrec> vals body)
+ (set! (letrec-vals x) (map lp vals))
+ (set! (letrec-body x) (lp body)))
+
+ ((<fix> vals body)
+ (set! (fix-vals x) (map lp vals))
+ (set! (fix-body x) (lp body)))
+
+ ((<let-values> exp body)
+ (set! (let-values-exp x) (lp exp))
+ (set! (let-values-body x) (lp body)))
+
+ ((<dynwind> body winder unwinder)
+ (set! (dynwind-body x) (lp body))
+ (set! (dynwind-winder x) (lp winder))
+ (set! (dynwind-unwinder x) (lp unwinder)))
+
+ ((<dynlet> fluids vals body)
+ (set! (dynlet-fluids x) (map lp fluids))
+ (set! (dynlet-vals x) (map lp vals))
+ (set! (dynlet-body x) (lp body)))
+
+ ((<dynref> fluid)
+ (set! (dynref-fluid x) (lp fluid)))
+
+ ((<dynset> fluid exp)
+ (set! (dynset-fluid x) (lp fluid))
+ (set! (dynset-exp x) (lp exp)))
+
+ ((<prompt> tag body handler)
+ (set! (prompt-tag x) (lp tag))
+ (set! (prompt-body x) (lp body))
+ (set! (prompt-handler x) (lp handler)))
+
+ ((<abort> tag args tail)
+ (set! (abort-tag x) (lp tag))
+ (set! (abort-args x) (map lp args))
+ (set! (abort-tail x) (lp tail)))
+
+ (else #f))
+ x)))
+
+;; FIXME: We should have a better primitive than this.
+(define (struct-nfields x)
+ (/ (string-length (symbol->string (struct-layout x))) 2))
+
+(define (tree-il=? a b)
+ (cond
+ ((struct? a)
+ (and (struct? b)
+ (eq? (struct-vtable a) (struct-vtable b))
+ ;; Assume that all structs are tree-il, so we skip over the
+ ;; src slot.
+ (let lp ((n (1- (struct-nfields a))))
+ (or (zero? n)
+ (and (tree-il=? (struct-ref a n) (struct-ref b n))
+ (lp (1- n)))))))
+ ((pair? a)
+ (and (pair? b)
+ (tree-il=? (car a) (car b))
+ (tree-il=? (cdr a) (cdr b))))
+ (else
+ (equal? a b))))
+
+(define-syntax hash-bits
+ (make-variable-transformer
+ (lambda (x)
+ (syntax-case x ()
+ (var
+ (identifier? #'var)
+ (logcount most-positive-fixnum))))))
+
+(define (tree-il-hash exp)
+ (let ((hash-depth 4)
+ (hash-width 3))
+ (define (hash-exp exp depth)
+ (define (rotate x bits)
+ (logior (ash x (- bits))
+ (ash (logand x (1- (ash 1 bits))) (- hash-bits bits))))
+ (define (mix h1 h2)
+ (logxor h1 (rotate h2 8)))
+ (define (hash-struct s)
+ (let ((len (struct-nfields s))
+ (h (hashq (struct-vtable s) most-positive-fixnum)))
+ (if (zero? depth)
+ h
+ (let lp ((i (max (- len hash-width) 1)) (h h))
+ (if (< i len)
+ (lp (1+ i) (mix (hash-exp (struct-ref s i) (1+ depth)) h))
+ h)))))
+ (define (hash-list l)
+ (let ((h (hashq 'list most-positive-fixnum)))
+ (if (zero? depth)
+ h
+ (let lp ((l l) (width 0) (h h))
+ (if (< width hash-width)
+ (lp (cdr l) (1+ width)
+ (mix (hash-exp (car l) (1+ depth)) h))
+ h)))))
+ (cond
+ ((struct? exp) (hash-struct exp))
+ ((list? exp) (hash-list exp))
+ (else (hash exp most-positive-fixnum))))
+
+ (hash-exp exp 0)))
+;;; TREE-IL -> GLIL compiler
+
+;; Copyright (C) 2001, 2008, 2009, 2010, 2011, 2012,
+;; 2014 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language tree-il analyze)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-11)
+ #\use-module (srfi srfi-26)
+ #\use-module (ice-9 vlist)
+ #\use-module (ice-9 match)
+ #\use-module (system base syntax)
+ #\use-module (system base message)
+ #\use-module (system vm program)
+ #\use-module (language tree-il)
+ #\use-module (system base pmatch)
+ #\export (analyze-lexicals
+ analyze-tree
+ unused-variable-analysis
+ unused-toplevel-analysis
+ unbound-variable-analysis
+ arity-analysis
+ format-analysis))
+
+;; Allocation is the process of assigning storage locations for lexical
+;; variables. A lexical variable has a distinct "address", or storage
+;; location, for each procedure in which it is referenced.
+;;
+;; A variable is "local", i.e., allocated on the stack, if it is
+;; referenced from within the procedure that defined it. Otherwise it is
+;; a "closure" variable. For example:
+;;
+;; (lambda (a) a) ; a will be local
+;; `a' is local to the procedure.
+;;
+;; (lambda (a) (lambda () a))
+;; `a' is local to the outer procedure, but a closure variable with
+;; respect to the inner procedure.
+;;
+;; If a variable is ever assigned, it needs to be heap-allocated
+;; ("boxed"). This is so that closures and continuations capture the
+;; variable's identity, not just one of the values it may have over the
+;; course of program execution. If the variable is never assigned, there
+;; is no distinction between value and identity, so closing over its
+;; identity (whether through closures or continuations) can make a copy
+;; of its value instead.
+;;
+;; Local variables are stored on the stack within a procedure's call
+;; frame. Their index into the stack is determined from their linear
+;; postion within a procedure's binding path:
+;; (let (0 1)
+;; (let (2 3) ...)
+;; (let (2) ...))
+;; (let (2 3 4) ...))
+;; etc.
+;;
+;; This algorithm has the problem that variables are only allocated
+;; indices at the end of the binding path. If variables bound early in
+;; the path are not used in later portions of the path, their indices
+;; will not be recycled. This problem is particularly egregious in the
+;; expansion of `or':
+;;
+;; (or x y z)
+;; -> (let ((a x)) (if a a (let ((b y)) (if b b z))))
+;;
+;; As you can see, the `a' binding is only used in the ephemeral
+;; `consequent' clause of the first `if', but its index would be
+;; reserved for the whole of the `or' expansion. So we have a hack for
+;; this specific case. A proper solution would be some sort of liveness
+;; analysis, and not our linear allocation algorithm.
+;;
+;; Closure variables are captured when a closure is created, and stored in a
+;; vector inline to the closure object itself. Each closure variable has a
+;; unique index into that vector.
+;;
+;; There is one more complication. Procedures bound by <fix> may, in
+;; some cases, be rendered inline to their parent procedure. That is to
+;; say,
+;;
+;; (letrec ((lp (lambda () (lp)))) (lp))
+;; => (fix ((lp (lambda () (lp)))) (lp))
+;; => goto FIX-BODY; LP: goto LP; FIX-BODY: goto LP;
+;; ^ jump over the loop ^ the fixpoint lp ^ starting off the loop
+;;
+;; The upshot is that we don't have to allocate any space for the `lp'
+;; closure at all, as it can be rendered inline as a loop. So there is
+;; another kind of allocation, "label allocation", in which the
+;; procedure is simply a label, placed at the start of the lambda body.
+;; The label is the gensym under which the lambda expression is bound.
+;;
+;; The analyzer checks to see that the label is called with the correct
+;; number of arguments. Calls to labels compile to rename + goto.
+;; Lambda, the ultimate goto!
+;;
+;;
+;; The return value of `analyze-lexicals' is a hash table, the
+;; "allocation".
+;;
+;; The allocation maps gensyms -- recall that each lexically bound
+;; variable has a unique gensym -- to storage locations ("addresses").
+;; Since one gensym may have many storage locations, if it is referenced
+;; in many procedures, it is a two-level map.
+;;
+;; The allocation also stored information on how many local variables
+;; need to be allocated for each procedure, lexicals that have been
+;; translated into labels, and information on what free variables to
+;; capture from its lexical parent procedure.
+;;
+;; In addition, we have a conflation: while we're traversing the code,
+;; recording information to pass to the compiler, we take the
+;; opportunity to generate labels for each lambda-case clause, so that
+;; generated code can skip argument checks at runtime if they match at
+;; compile-time.
+;;
+;; Also, while we're a-traversing and an-allocating, we check prompt
+;; handlers to see if the "continuation" argument is used. If not, we
+;; mark the prompt as being "escape-only". This allows us to implement
+;; `catch' and `throw' using `prompt' and `control', but without causing
+;; a continuation to be reified. Heh heh.
+;;
+;; That is:
+;;
+;; sym -> {lambda -> address}
+;; lambda -> (labels . free-locs)
+;; lambda-case -> (gensym . nlocs)
+;; prompt -> escape-only?
+;;
+;; address ::= (local? boxed? . index)
+;; labels ::= ((sym . lambda) ...)
+;; free-locs ::= ((sym0 . address0) (sym1 . address1) ...)
+;; free variable addresses are relative to parent proc.
+
+(define (make-hashq k v)
+ (let ((res (make-hash-table)))
+ (hashq-set! res k v)
+ res))
+
+(define (analyze-lexicals x)
+ ;; bound-vars: lambda -> (sym ...)
+ ;; all identifiers bound within a lambda
+ (define bound-vars (make-hash-table))
+ ;; free-vars: lambda -> (sym ...)
+ ;; all identifiers referenced in a lambda, but not bound
+ ;; NB, this includes identifiers referenced by contained lambdas
+ (define free-vars (make-hash-table))
+ ;; assigned: sym -> #t
+ ;; variables that are assigned
+ (define assigned (make-hash-table))
+ ;; refcounts: sym -> count
+ ;; allows us to detect the or-expansion in O(1) time
+ (define refcounts (make-hash-table))
+ ;; labels: sym -> lambda
+ ;; for determining if fixed-point procedures can be rendered as
+ ;; labels.
+ (define labels (make-hash-table))
+
+ ;; returns variables referenced in expr
+ (define (analyze! x proc labels-in-proc tail? tail-call-args)
+ (define (step y) (analyze! y proc '() #f #f))
+ (define (step-tail y) (analyze! y proc labels-in-proc tail? #f))
+ (define (step-tail-call y args) (analyze! y proc labels-in-proc #f
+ (and tail? args)))
+ (define (recur/labels x new-proc labels)
+ (analyze! x new-proc (append labels labels-in-proc) #t #f))
+ (define (recur x new-proc) (analyze! x new-proc '() tail? #f))
+ (record-case x
+ ((<application> proc args)
+ (apply lset-union eq? (step-tail-call proc args)
+ (map step args)))
+
+ ((<conditional> test consequent alternate)
+ (lset-union eq? (step test) (step-tail consequent) (step-tail alternate)))
+
+ ((<lexical-ref> gensym)
+ (hashq-set! refcounts gensym (1+ (hashq-ref refcounts gensym 0)))
+ (if (not (and tail-call-args
+ (memq gensym labels-in-proc)
+ (let ((p (hashq-ref labels gensym)))
+ (and p
+ (let lp ((c (lambda-body p)))
+ (and c (lambda-case? c)
+ (or
+ ;; for now prohibit optional &
+ ;; keyword arguments; can relax this
+ ;; restriction later
+ (and (= (length (lambda-case-req c))
+ (length tail-call-args))
+ (not (lambda-case-opt c))
+ (not (lambda-case-kw c))
+ (not (lambda-case-rest c)))
+ (lp (lambda-case-alternate c)))))))))
+ (hashq-set! labels gensym #f))
+ (list gensym))
+
+ ((<lexical-set> gensym exp)
+ (hashq-set! assigned gensym #t)
+ (hashq-set! labels gensym #f)
+ (lset-adjoin eq? (step exp) gensym))
+
+ ((<module-set> exp)
+ (step exp))
+
+ ((<toplevel-set> exp)
+ (step exp))
+
+ ((<toplevel-define> exp)
+ (step exp))
+
+ ((<sequence> exps)
+ (let lp ((exps exps) (ret '()))
+ (cond ((null? exps) '())
+ ((null? (cdr exps))
+ (lset-union eq? ret (step-tail (car exps))))
+ (else
+ (lp (cdr exps) (lset-union eq? ret (step (car exps))))))))
+
+ ((<lambda> body)
+ ;; order is important here
+ (hashq-set! bound-vars x '())
+ (let ((free (recur body x)))
+ (hashq-set! bound-vars x (reverse! (hashq-ref bound-vars x)))
+ (hashq-set! free-vars x free)
+ free))
+
+ ((<lambda-case> opt kw inits gensyms body alternate)
+ (hashq-set! bound-vars proc
+ (append (reverse gensyms) (hashq-ref bound-vars proc)))
+ (lset-union
+ eq?
+ (lset-difference eq?
+ (lset-union eq?
+ (apply lset-union eq? (map step inits))
+ (step-tail body))
+ gensyms)
+ (if alternate (step-tail alternate) '())))
+
+ ((<let> gensyms vals body)
+ (hashq-set! bound-vars proc
+ (append (reverse gensyms) (hashq-ref bound-vars proc)))
+ (lset-difference eq?
+ (apply lset-union eq? (step-tail body) (map step vals))
+ gensyms))
+
+ ((<letrec> gensyms vals body)
+ (hashq-set! bound-vars proc
+ (append (reverse gensyms) (hashq-ref bound-vars proc)))
+ (for-each (lambda (sym) (hashq-set! assigned sym #t)) gensyms)
+ (lset-difference eq?
+ (apply lset-union eq? (step-tail body) (map step vals))
+ gensyms))
+
+ ((<fix> gensyms vals body)
+ ;; Try to allocate these procedures as labels.
+ (for-each (lambda (sym val) (hashq-set! labels sym val))
+ gensyms vals)
+ (hashq-set! bound-vars proc
+ (append (reverse gensyms) (hashq-ref bound-vars proc)))
+ ;; Step into subexpressions.
+ (let* ((var-refs
+ (map
+ ;; Since we're trying to label-allocate the lambda,
+ ;; pretend it's not a closure, and just recurse into its
+ ;; body directly. (Otherwise, recursing on a closure
+ ;; that references one of the fix's bound vars would
+ ;; prevent label allocation.)
+ (lambda (x)
+ (record-case x
+ ((<lambda> body)
+ ;; just like the closure case, except here we use
+ ;; recur/labels instead of recur
+ (hashq-set! bound-vars x '())
+ (let ((free (recur/labels body x gensyms)))
+ (hashq-set! bound-vars x (reverse! (hashq-ref bound-vars x)))
+ (hashq-set! free-vars x free)
+ free))))
+ vals))
+ (vars-with-refs (map cons gensyms var-refs))
+ (body-refs (recur/labels body proc gensyms)))
+ (define (delabel-dependents! sym)
+ (let ((refs (assq-ref vars-with-refs sym)))
+ (if refs
+ (for-each (lambda (sym)
+ (if (hashq-ref labels sym)
+ (begin
+ (hashq-set! labels sym #f)
+ (delabel-dependents! sym))))
+ refs))))
+ ;; Stepping into the lambdas and the body might have made some
+ ;; procedures not label-allocatable -- which might have
+ ;; knock-on effects. For example:
+ ;; (fix ((a (lambda () (b)))
+ ;; (b (lambda () a)))
+ ;; (a))
+ ;; As far as `a' is concerned, both `a' and `b' are
+ ;; label-allocatable. But `b' references `a' not in a proc-tail
+ ;; position, which makes `a' not label-allocatable. The
+ ;; knock-on effect is that, when back-propagating this
+ ;; information to `a', `b' will also become not
+ ;; label-allocatable, as it is referenced within `a', which is
+ ;; allocated as a closure. This is a transitive relationship.
+ (for-each (lambda (sym)
+ (if (not (hashq-ref labels sym))
+ (delabel-dependents! sym)))
+ gensyms)
+ ;; Now lift bound variables with label-allocated lambdas to the
+ ;; parent procedure.
+ (for-each
+ (lambda (sym val)
+ (if (hashq-ref labels sym)
+ ;; Remove traces of the label-bound lambda. The free
+ ;; vars will propagate up via the return val.
+ (begin
+ (hashq-set! bound-vars proc
+ (append (hashq-ref bound-vars val)
+ (hashq-ref bound-vars proc)))
+ (hashq-remove! bound-vars val)
+ (hashq-remove! free-vars val))))
+ gensyms vals)
+ (lset-difference eq?
+ (apply lset-union eq? body-refs var-refs)
+ gensyms)))
+
+ ((<let-values> exp body)
+ (lset-union eq? (step exp) (step body)))
+
+ ((<dynwind> body winder unwinder)
+ (lset-union eq? (step body) (step winder) (step unwinder)))
+
+ ((<dynlet> fluids vals body)
+ (apply lset-union eq? (step body) (map step (append fluids vals))))
+
+ ((<dynref> fluid)
+ (step fluid))
+
+ ((<dynset> fluid exp)
+ (lset-union eq? (step fluid) (step exp)))
+
+ ((<prompt> tag body handler)
+ (lset-union eq? (step tag) (step body) (step-tail handler)))
+
+ ((<abort> tag args tail)
+ (apply lset-union eq? (step tag) (step tail) (map step args)))
+
+ (else '())))
+
+ ;; allocation: sym -> {lambda -> address}
+ ;; lambda -> (labels . free-locs)
+ ;; lambda-case -> (gensym . nlocs)
+ (define allocation (make-hash-table))
+
+ (define (allocate! x proc n)
+ (define (recur y) (allocate! y proc n))
+ (record-case x
+ ((<application> proc args)
+ (apply max (recur proc) (map recur args)))
+
+ ((<conditional> test consequent alternate)
+ (max (recur test) (recur consequent) (recur alternate)))
+
+ ((<lexical-set> exp)
+ (recur exp))
+
+ ((<module-set> exp)
+ (recur exp))
+
+ ((<toplevel-set> exp)
+ (recur exp))
+
+ ((<toplevel-define> exp)
+ (recur exp))
+
+ ((<sequence> exps)
+ (apply max (map recur exps)))
+
+ ((<lambda> body)
+ ;; allocate closure vars in order
+ (let lp ((c (hashq-ref free-vars x)) (n 0))
+ (if (pair? c)
+ (begin
+ (hashq-set! (hashq-ref allocation (car c))
+ x
+ `(#f ,(hashq-ref assigned (car c)) . ,n))
+ (lp (cdr c) (1+ n)))))
+
+ (let ((nlocs (allocate! body x 0))
+ (free-addresses
+ (map (lambda (v)
+ (hashq-ref (hashq-ref allocation v) proc))
+ (hashq-ref free-vars x)))
+ (labels (filter cdr
+ (map (lambda (sym)
+ (cons sym (hashq-ref labels sym)))
+ (hashq-ref bound-vars x)))))
+ ;; set procedure allocations
+ (hashq-set! allocation x (cons labels free-addresses)))
+ n)
+
+ ((<lambda-case> opt kw inits gensyms body alternate)
+ (max
+ (let lp ((gensyms gensyms) (n n))
+ (if (null? gensyms)
+ (let ((nlocs (apply
+ max
+ (allocate! body proc n)
+ ;; inits not logically at the end, but they
+ ;; are the list...
+ (map (lambda (x) (allocate! x proc n)) inits))))
+ ;; label and nlocs for the case
+ (hashq-set! allocation x (cons (gensym ":LCASE") nlocs))
+ nlocs)
+ (begin
+ (hashq-set! allocation (car gensyms)
+ (make-hashq
+ proc `(#t ,(hashq-ref assigned (car gensyms)) . ,n)))
+ (lp (cdr gensyms) (1+ n)))))
+ (if alternate (allocate! alternate proc n) n)))
+
+ ((<let> gensyms vals body)
+ (let ((nmax (apply max (map recur vals))))
+ (cond
+ ;; the `or' hack
+ ((and (conditional? body)
+ (= (length gensyms) 1)
+ (let ((v (car gensyms)))
+ (and (not (hashq-ref assigned v))
+ (= (hashq-ref refcounts v 0) 2)
+ (lexical-ref? (conditional-test body))
+ (eq? (lexical-ref-gensym (conditional-test body)) v)
+ (lexical-ref? (conditional-consequent body))
+ (eq? (lexical-ref-gensym (conditional-consequent body)) v))))
+ (hashq-set! allocation (car gensyms)
+ (make-hashq proc `(#t #f . ,n)))
+ ;; the 1+ for this var
+ (max nmax (1+ n) (allocate! (conditional-alternate body) proc n)))
+ (else
+ (let lp ((gensyms gensyms) (n n))
+ (if (null? gensyms)
+ (max nmax (allocate! body proc n))
+ (let ((v (car gensyms)))
+ (hashq-set!
+ allocation v
+ (make-hashq proc
+ `(#t ,(hashq-ref assigned v) . ,n)))
+ (lp (cdr gensyms) (1+ n)))))))))
+
+ ((<letrec> gensyms vals body)
+ (let lp ((gensyms gensyms) (n n))
+ (if (null? gensyms)
+ (let ((nmax (apply max
+ (map (lambda (x)
+ (allocate! x proc n))
+ vals))))
+ (max nmax (allocate! body proc n)))
+ (let ((v (car gensyms)))
+ (hashq-set!
+ allocation v
+ (make-hashq proc
+ `(#t ,(hashq-ref assigned v) . ,n)))
+ (lp (cdr gensyms) (1+ n))))))
+
+ ((<fix> gensyms vals body)
+ (let lp ((in gensyms) (n n))
+ (if (null? in)
+ (let lp ((gensyms gensyms) (vals vals) (nmax n))
+ (cond
+ ((null? gensyms)
+ (max nmax (allocate! body proc n)))
+ ((hashq-ref labels (car gensyms))
+ ;; allocate lambda body inline to proc
+ (lp (cdr gensyms)
+ (cdr vals)
+ (record-case (car vals)
+ ((<lambda> body)
+ (max nmax (allocate! body proc n))))))
+ (else
+ ;; allocate closure
+ (lp (cdr gensyms)
+ (cdr vals)
+ (max nmax (allocate! (car vals) proc n))))))
+
+ (let ((v (car in)))
+ (cond
+ ((hashq-ref assigned v)
+ (error "fixpoint procedures may not be assigned" x))
+ ((hashq-ref labels v)
+ ;; no binding, it's a label
+ (lp (cdr in) n))
+ (else
+ ;; allocate closure binding
+ (hashq-set! allocation v (make-hashq proc `(#t #f . ,n)))
+ (lp (cdr in) (1+ n))))))))
+
+ ((<let-values> exp body)
+ (max (recur exp) (recur body)))
+
+ ((<dynwind> body winder unwinder)
+ (max (recur body) (recur winder) (recur unwinder)))
+
+ ((<dynlet> fluids vals body)
+ (apply max (recur body) (map recur (append fluids vals))))
+
+ ((<dynref> fluid)
+ (recur fluid))
+
+ ((<dynset> fluid exp)
+ (max (recur fluid) (recur exp)))
+
+ ((<prompt> tag body handler)
+ (let ((cont-var (and (lambda-case? handler)
+ (pair? (lambda-case-gensyms handler))
+ (car (lambda-case-gensyms handler)))))
+ (hashq-set! allocation x
+ (and cont-var (zero? (hashq-ref refcounts cont-var 0))))
+ (max (recur tag) (recur body) (recur handler))))
+
+ ((<abort> tag args tail)
+ (apply max (recur tag) (recur tail) (map recur args)))
+
+ (else n)))
+
+ (analyze! x #f '() #t #f)
+ (allocate! x #f 0)
+
+ allocation)
+
+
+;;;
+;;; Tree analyses for warnings.
+;;;
+
+(define-record-type <tree-analysis>
+ (make-tree-analysis leaf down up post init)
+ tree-analysis?
+ (leaf tree-analysis-leaf) ;; (lambda (x result env locs) ...)
+ (down tree-analysis-down) ;; (lambda (x result env locs) ...)
+ (up tree-analysis-up) ;; (lambda (x result env locs) ...)
+ (post tree-analysis-post) ;; (lambda (result env) ...)
+ (init tree-analysis-init)) ;; arbitrary value
+
+(define (analyze-tree analyses tree env)
+ "Run all tree analyses listed in ANALYSES on TREE for ENV, using
+`tree-il-fold'. Return TREE. The leaf/down/up procedures of each analysis are
+passed a ``location stack', which is the stack of `tree-il-src' values for each
+parent tree (a list); it can be used to approximate source location when
+accurate information is missing from a given `tree-il' element."
+
+ (define (traverse proc update-locs)
+ ;; Return a tree traversing procedure that returns a list of analysis
+ ;; results prepended by the location stack.
+ (lambda (x results)
+ (let ((locs (update-locs x (car results))))
+ (cons locs ;; the location stack
+ (map (lambda (analysis result)
+ ((proc analysis) x result env locs))
+ analyses
+ (cdr results))))))
+
+ ;; Keeping/extending/shrinking the location stack.
+ (define (keep-locs x locs) locs)
+ (define (extend-locs x locs) (cons (tree-il-src x) locs))
+ (define (shrink-locs x locs) (cdr locs))
+
+ (let ((results
+ (tree-il-fold (traverse tree-analysis-leaf keep-locs)
+ (traverse tree-analysis-down extend-locs)
+ (traverse tree-analysis-up shrink-locs)
+ (cons '() ;; empty location stack
+ (map tree-analysis-init analyses))
+ tree)))
+
+ (for-each (lambda (analysis result)
+ ((tree-analysis-post analysis) result env))
+ analyses
+ (cdr results)))
+
+ tree)
+
+
+;;;
+;;; Unused variable analysis.
+;;;
+
+;; <binding-info> records are used during tree traversals in
+;; `unused-variable-analysis'. They contain a list of the local vars
+;; currently in scope, and a list of locals vars that have been referenced.
+(define-record-type <binding-info>
+ (make-binding-info vars refs)
+ binding-info?
+ (vars binding-info-vars) ;; ((GENSYM NAME LOCATION) ...)
+ (refs binding-info-refs)) ;; (GENSYM ...)
+
+(define (gensym? sym)
+ ;; Return #t if SYM is (likely) a generated symbol.
+ (string-any #\space (symbol->string sym)))
+
+(define unused-variable-analysis
+ ;; Report unused variables in the given tree.
+ (make-tree-analysis
+ (lambda (x info env locs)
+ ;; X is a leaf: extend INFO's refs accordingly.
+ (let ((refs (binding-info-refs info))
+ (vars (binding-info-vars info)))
+ (record-case x
+ ((<lexical-ref> gensym)
+ (make-binding-info vars (vhash-consq gensym #t refs)))
+ (else info))))
+
+ (lambda (x info env locs)
+ ;; Going down into X: extend INFO's variable list
+ ;; accordingly.
+ (let ((refs (binding-info-refs info))
+ (vars (binding-info-vars info))
+ (src (tree-il-src x)))
+ (define (extend inner-vars inner-names)
+ (fold (lambda (var name vars)
+ (vhash-consq var (list name src) vars))
+ vars
+ inner-vars
+ inner-names))
+
+ (record-case x
+ ((<lexical-set> gensym)
+ (make-binding-info vars (vhash-consq gensym #t refs)))
+ ((<lambda-case> req opt inits rest kw gensyms)
+ (let ((names `(,@req
+ ,@(or opt '())
+ ,@(if rest (list rest) '())
+ ,@(if kw (map cadr (cdr kw)) '()))))
+ (make-binding-info (extend gensyms names) refs)))
+ ((<let> gensyms names)
+ (make-binding-info (extend gensyms names) refs))
+ ((<letrec> gensyms names)
+ (make-binding-info (extend gensyms names) refs))
+ ((<fix> gensyms names)
+ (make-binding-info (extend gensyms names) refs))
+ (else info))))
+
+ (lambda (x info env locs)
+ ;; Leaving X's scope: shrink INFO's variable list
+ ;; accordingly and reported unused nested variables.
+ (let ((refs (binding-info-refs info))
+ (vars (binding-info-vars info)))
+ (define (shrink inner-vars refs)
+ (vlist-for-each
+ (lambda (var)
+ (let ((gensym (car var)))
+ ;; Don't report lambda parameters as unused.
+ (if (and (memq gensym inner-vars)
+ (not (vhash-assq gensym refs))
+ (not (lambda-case? x)))
+ (let ((name (cadr var))
+ ;; We can get approximate source location by going up
+ ;; the LOCS location stack.
+ (loc (or (caddr var)
+ (find pair? locs))))
+ (if (and (not (gensym? name))
+ (not (eq? name '_)))
+ (warning 'unused-variable loc name))))))
+ vars)
+ (vlist-drop vars (length inner-vars)))
+
+ ;; For simplicity, we leave REFS untouched, i.e., with
+ ;; names of variables that are now going out of scope.
+ ;; It doesn't hurt as these are unique names, it just
+ ;; makes REFS unnecessarily fat.
+ (record-case x
+ ((<lambda-case> gensyms)
+ (make-binding-info (shrink gensyms refs) refs))
+ ((<let> gensyms)
+ (make-binding-info (shrink gensyms refs) refs))
+ ((<letrec> gensyms)
+ (make-binding-info (shrink gensyms refs) refs))
+ ((<fix> gensyms)
+ (make-binding-info (shrink gensyms refs) refs))
+ (else info))))
+
+ (lambda (result env) #t)
+ (make-binding-info vlist-null vlist-null)))
+
+
+;;;
+;;; Unused top-level variable analysis.
+;;;
+
+;; <reference-graph> record top-level definitions that are made, references to
+;; top-level definitions and their context (the top-level definition in which
+;; the reference appears), as well as the current context (the top-level
+;; definition we're currently in). The second part (`refs' below) is
+;; effectively a graph from which we can determine unused top-level definitions.
+(define-record-type <reference-graph>
+ (make-reference-graph refs defs toplevel-context)
+ reference-graph?
+ (defs reference-graph-defs) ;; ((NAME . LOC) ...)
+ (refs reference-graph-refs) ;; ((REF-CONTEXT REF ...) ...)
+ (toplevel-context reference-graph-toplevel-context)) ;; NAME | #f
+
+(define (graph-reachable-nodes root refs reachable)
+ ;; Add to REACHABLE the nodes reachable from ROOT in graph REFS. REFS is a
+ ;; vhash mapping nodes to the list of their children: for instance,
+ ;; ((A -> (B C)) (B -> (A)) (C -> ())) corresponds to
+ ;;
+ ;; ,-------.
+ ;; v |
+ ;; A ----> B
+ ;; |
+ ;; v
+ ;; C
+ ;;
+ ;; REACHABLE is a vhash of nodes known to be otherwise reachable.
+
+ (let loop ((root root)
+ (path vlist-null)
+ (result reachable))
+ (if (or (vhash-assq root path)
+ (vhash-assq root result))
+ result
+ (let* ((children (or (and=> (vhash-assq root refs) cdr) '()))
+ (path (vhash-consq root #t path))
+ (result (fold (lambda (kid result)
+ (loop kid path result))
+ result
+ children)))
+ (fold (lambda (kid result)
+ (vhash-consq kid #t result))
+ result
+ children)))))
+
+(define (graph-reachable-nodes* roots refs)
+ ;; Return the list of nodes in REFS reachable from the nodes listed in ROOTS.
+ (vlist-fold (lambda (root+true result)
+ (let* ((root (car root+true))
+ (reachable (graph-reachable-nodes root refs result)))
+ (vhash-consq root #t reachable)))
+ vlist-null
+ roots))
+
+(define (partition* pred vhash)
+ ;; Partition VHASH according to PRED. Return the two resulting vhashes.
+ (let ((result
+ (vlist-fold (lambda (k+v result)
+ (let ((k (car k+v))
+ (v (cdr k+v))
+ (r1 (car result))
+ (r2 (cdr result)))
+ (if (pred k)
+ (cons (vhash-consq k v r1) r2)
+ (cons r1 (vhash-consq k v r2)))))
+ (cons vlist-null vlist-null)
+ vhash)))
+ (values (car result) (cdr result))))
+
+(define unused-toplevel-analysis
+ ;; Report unused top-level definitions that are not exported.
+ (let ((add-ref-from-context
+ (lambda (graph name)
+ ;; Add an edge CTX -> NAME in GRAPH.
+ (let* ((refs (reference-graph-refs graph))
+ (defs (reference-graph-defs graph))
+ (ctx (reference-graph-toplevel-context graph))
+ (ctx-refs (or (and=> (vhash-assq ctx refs) cdr) '())))
+ (make-reference-graph (vhash-consq ctx (cons name ctx-refs) refs)
+ defs ctx)))))
+ (define (macro-variable? name env)
+ (and (module? env)
+ (let ((var (module-variable env name)))
+ (and var (variable-bound? var)
+ (macro? (variable-ref var))))))
+
+ (make-tree-analysis
+ (lambda (x graph env locs)
+ ;; X is a leaf.
+ (let ((ctx (reference-graph-toplevel-context graph)))
+ (record-case x
+ ((<toplevel-ref> name src)
+ (add-ref-from-context graph name))
+ (else graph))))
+
+ (lambda (x graph env locs)
+ ;; Going down into X.
+ (let ((ctx (reference-graph-toplevel-context graph))
+ (refs (reference-graph-refs graph))
+ (defs (reference-graph-defs graph)))
+ (record-case x
+ ((<toplevel-define> name src)
+ (let ((refs refs)
+ (defs (vhash-consq name (or src (find pair? locs))
+ defs)))
+ (make-reference-graph refs defs name)))
+ ((<toplevel-set> name src)
+ (add-ref-from-context graph name))
+ (else graph))))
+
+ (lambda (x graph env locs)
+ ;; Leaving X's scope.
+ (record-case x
+ ((<toplevel-define>)
+ (let ((refs (reference-graph-refs graph))
+ (defs (reference-graph-defs graph)))
+ (make-reference-graph refs defs #f)))
+ (else graph)))
+
+ (lambda (graph env)
+ ;; Process the resulting reference graph: determine all private definitions
+ ;; not reachable from any public definition. Macros
+ ;; (syntax-transformers), which are globally bound, never considered
+ ;; unused since we can't tell whether a macro is actually used; in
+ ;; addition, macros are considered roots of the graph since they may use
+ ;; private bindings. FIXME: The `make-syntax-transformer' calls don't
+ ;; contain any literal `toplevel-ref' of the global bindings they use so
+ ;; this strategy fails.
+ (define (exported? name)
+ (if (module? env)
+ (module-variable (module-public-interface env) name)
+ #t))
+
+ (let-values (((public-defs private-defs)
+ (partition* (lambda (name)
+ (or (exported? name)
+ (macro-variable? name env)))
+ (reference-graph-defs graph))))
+ (let* ((roots (vhash-consq #f #t public-defs))
+ (refs (reference-graph-refs graph))
+ (reachable (graph-reachable-nodes* roots refs))
+ (unused (vlist-filter (lambda (name+src)
+ (not (vhash-assq (car name+src)
+ reachable)))
+ private-defs)))
+ (vlist-for-each (lambda (name+loc)
+ (let ((name (car name+loc))
+ (loc (cdr name+loc)))
+ (if (not (gensym? name))
+ (warning 'unused-toplevel loc name))))
+ unused))))
+
+ (make-reference-graph vlist-null vlist-null #f))))
+
+
+;;;
+;;; Unbound variable analysis.
+;;;
+
+;; <toplevel-info> records are used during tree traversal in search of
+;; possibly unbound variable. They contain a list of references to
+;; potentially unbound top-level variables, and a list of the top-level
+;; defines that have been encountered.
+(define-record-type <toplevel-info>
+ (make-toplevel-info refs defs)
+ toplevel-info?
+ (refs toplevel-info-refs) ;; ((VARIABLE-NAME . LOCATION) ...)
+ (defs toplevel-info-defs)) ;; (VARIABLE-NAME ...)
+
+(define (goops-toplevel-definition proc args env)
+ ;; If application of PROC to ARGS is a GOOPS top-level definition, return
+ ;; the name of the variable being defined; otherwise return #f. This
+ ;; assumes knowledge of the current implementation of `define-class' et al.
+ (define (toplevel-define-arg args)
+ (match args
+ ((($ <const> _ (and (? symbol?) exp)) _)
+ exp)
+ (_ #f)))
+
+ (match proc
+ (($ <module-ref> _ '(oop goops) 'toplevel-define! #f)
+ (toplevel-define-arg args))
+ (($ <toplevel-ref> _ 'toplevel-define!)
+ ;; This may be the result of expanding one of the GOOPS macros within
+ ;; `oop/goops.scm'.
+ (and (eq? env (resolve-module '(oop goops)))
+ (toplevel-define-arg args)))
+ (_ #f)))
+
+(define unbound-variable-analysis
+ ;; Report possibly unbound variables in the given tree.
+ (make-tree-analysis
+ (lambda (x info env locs)
+ ;; X is a leaf: extend INFO's refs accordingly.
+ (let ((refs (toplevel-info-refs info))
+ (defs (toplevel-info-defs info)))
+ (define (bound? name)
+ (or (and (module? env)
+ (module-variable env name))
+ (vhash-assq name defs)))
+
+ (record-case x
+ ((<toplevel-ref> name src)
+ (if (bound? name)
+ info
+ (let ((src (or src (find pair? locs))))
+ (make-toplevel-info (vhash-consq name src refs)
+ defs))))
+ (else info))))
+
+ (lambda (x info env locs)
+ ;; Going down into X.
+ (let* ((refs (toplevel-info-refs info))
+ (defs (toplevel-info-defs info))
+ (src (tree-il-src x)))
+ (define (bound? name)
+ (or (and (module? env)
+ (module-variable env name))
+ (vhash-assq name defs)))
+
+ (record-case x
+ ((<toplevel-set> name src)
+ (if (bound? name)
+ (make-toplevel-info refs defs)
+ (let ((src (find pair? locs)))
+ (make-toplevel-info (vhash-consq name src refs)
+ defs))))
+ ((<toplevel-define> name)
+ (make-toplevel-info (vhash-delq name refs)
+ (vhash-consq name #t defs)))
+
+ ((<application> proc args)
+ ;; Check for a dynamic top-level definition, as is
+ ;; done by code expanded from GOOPS macros.
+ (let ((name (goops-toplevel-definition proc args
+ env)))
+ (if (symbol? name)
+ (make-toplevel-info (vhash-delq name refs)
+ (vhash-consq name #t defs))
+ (make-toplevel-info refs defs))))
+ (else
+ (make-toplevel-info refs defs)))))
+
+ (lambda (x info env locs)
+ ;; Leaving X's scope.
+ info)
+
+ (lambda (toplevel env)
+ ;; Post-process the result.
+ (vlist-for-each (lambda (name+loc)
+ (let ((name (car name+loc))
+ (loc (cdr name+loc)))
+ (warning 'unbound-variable loc name)))
+ (vlist-reverse (toplevel-info-refs toplevel))))
+
+ (make-toplevel-info vlist-null vlist-null)))
+
+
+;;;
+;;; Arity analysis.
+;;;
+
+;; <arity-info> records contain information about lexical definitions of
+;; procedures currently in scope, top-level procedure definitions that have
+;; been encountered, and calls to top-level procedures that have been
+;; encountered.
+(define-record-type <arity-info>
+ (make-arity-info toplevel-calls lexical-lambdas toplevel-lambdas)
+ arity-info?
+ (toplevel-calls toplevel-procedure-calls) ;; ((NAME . APPLICATION) ...)
+ (lexical-lambdas lexical-lambdas) ;; ((GENSYM . DEFINITION) ...)
+ (toplevel-lambdas toplevel-lambdas)) ;; ((NAME . DEFINITION) ...)
+
+(define (validate-arity proc application lexical?)
+ ;; Validate the argument count of APPLICATION, a tree-il application of
+ ;; PROC, emitting a warning in case of argument count mismatch.
+
+ (define (filter-keyword-args keywords allow-other-keys? args)
+ ;; Filter keyword arguments from ARGS and return the resulting list.
+ ;; KEYWORDS is the list of allowed keywords, and ALLOW-OTHER-KEYS?
+ ;; specified whethere keywords not listed in KEYWORDS are allowed.
+ (let loop ((args args)
+ (result '()))
+ (if (null? args)
+ (reverse result)
+ (let ((arg (car args)))
+ (if (and (const? arg)
+ (or (memq (const-exp arg) keywords)
+ (and allow-other-keys?
+ (keyword? (const-exp arg)))))
+ (loop (if (pair? (cdr args))
+ (cddr args)
+ '())
+ result)
+ (loop (cdr args)
+ (cons arg result)))))))
+
+ (define (arities proc)
+ ;; Return the arities of PROC, which can be either a tree-il or a
+ ;; procedure.
+ (define (len x)
+ (or (and (or (null? x) (pair? x))
+ (length x))
+ 0))
+ (cond ((program? proc)
+ (values (procedure-name proc)
+ (map (lambda (a)
+ (list (arity:nreq a) (arity:nopt a) (arity:rest? a)
+ (map car (arity:kw a))
+ (arity:allow-other-keys? a)))
+ (program-arities proc))))
+ ((procedure? proc)
+ (if (struct? proc)
+ ;; An applicable struct.
+ (arities (struct-ref proc 0))
+ ;; An applicable smob.
+ (let ((arity (procedure-minimum-arity proc)))
+ (values (procedure-name proc)
+ (list (list (car arity) (cadr arity) (caddr arity)
+ #f #f))))))
+ (else
+ (let loop ((name #f)
+ (proc proc)
+ (arities '()))
+ (if (not proc)
+ (values name (reverse arities))
+ (record-case proc
+ ((<lambda-case> req opt rest kw alternate)
+ (loop name alternate
+ (cons (list (len req) (len opt) rest
+ (and (pair? kw) (map car (cdr kw)))
+ (and (pair? kw) (car kw)))
+ arities)))
+ ((<lambda> meta body)
+ (loop (assoc-ref meta 'name) body arities))
+ (else
+ (values #f #f))))))))
+
+ (let ((args (application-args application))
+ (src (tree-il-src application)))
+ (call-with-values (lambda () (arities proc))
+ (lambda (name arities)
+ (define matches?
+ (find (lambda (arity)
+ (pmatch arity
+ ((,req ,opt ,rest? ,kw ,aok?)
+ (let ((args (if (pair? kw)
+ (filter-keyword-args kw aok? args)
+ args)))
+ (if (and req opt)
+ (let ((count (length args)))
+ (and (>= count req)
+ (or rest?
+ (<= count (+ req opt)))))
+ #t)))
+ (else #t)))
+ arities))
+
+ (if (not matches?)
+ (warning 'arity-mismatch src
+ (or name (with-output-to-string (lambda () (write proc))))
+ lexical?)))))
+ #t)
+
+(define arity-analysis
+ ;; Report arity mismatches in the given tree.
+ (make-tree-analysis
+ (lambda (x info env locs)
+ ;; X is a leaf.
+ info)
+ (lambda (x info env locs)
+ ;; Down into X.
+ (define (extend lexical-name val info)
+ ;; If VAL is a lambda, add NAME to the lexical-lambdas of INFO.
+ (let ((toplevel-calls (toplevel-procedure-calls info))
+ (lexical-lambdas (lexical-lambdas info))
+ (toplevel-lambdas (toplevel-lambdas info)))
+ (record-case val
+ ((<lambda> body)
+ (make-arity-info toplevel-calls
+ (vhash-consq lexical-name val
+ lexical-lambdas)
+ toplevel-lambdas))
+ ((<lexical-ref> gensym)
+ ;; lexical alias
+ (let ((val* (vhash-assq gensym lexical-lambdas)))
+ (if (pair? val*)
+ (extend lexical-name (cdr val*) info)
+ info)))
+ ((<toplevel-ref> name)
+ ;; top-level alias
+ (make-arity-info toplevel-calls
+ (vhash-consq lexical-name val
+ lexical-lambdas)
+ toplevel-lambdas))
+ (else info))))
+
+ (let ((toplevel-calls (toplevel-procedure-calls info))
+ (lexical-lambdas (lexical-lambdas info))
+ (toplevel-lambdas (toplevel-lambdas info)))
+
+ (record-case x
+ ((<toplevel-define> name exp)
+ (record-case exp
+ ((<lambda> body)
+ (make-arity-info toplevel-calls
+ lexical-lambdas
+ (vhash-consq name exp toplevel-lambdas)))
+ ((<toplevel-ref> name)
+ ;; alias for another toplevel
+ (let ((proc (vhash-assq name toplevel-lambdas)))
+ (make-arity-info toplevel-calls
+ lexical-lambdas
+ (vhash-consq (toplevel-define-name x)
+ (if (pair? proc)
+ (cdr proc)
+ exp)
+ toplevel-lambdas))))
+ (else info)))
+ ((<let> gensyms vals)
+ (fold extend info gensyms vals))
+ ((<letrec> gensyms vals)
+ (fold extend info gensyms vals))
+ ((<fix> gensyms vals)
+ (fold extend info gensyms vals))
+
+ ((<application> proc args src)
+ (record-case proc
+ ((<lambda> body)
+ (validate-arity proc x #t)
+ info)
+ ((<toplevel-ref> name)
+ (make-arity-info (vhash-consq name x toplevel-calls)
+ lexical-lambdas
+ toplevel-lambdas))
+ ((<lexical-ref> gensym)
+ (let ((proc (vhash-assq gensym lexical-lambdas)))
+ (if (pair? proc)
+ (record-case (cdr proc)
+ ((<toplevel-ref> name)
+ ;; alias to toplevel
+ (make-arity-info (vhash-consq name x toplevel-calls)
+ lexical-lambdas
+ toplevel-lambdas))
+ (else
+ (validate-arity (cdr proc) x #t)
+ info))
+
+ ;; If GENSYM wasn't found, it may be because it's an
+ ;; argument of the procedure being compiled.
+ info)))
+ (else info)))
+ (else info))))
+
+ (lambda (x info env locs)
+ ;; Up from X.
+ (define (shrink name val info)
+ ;; Remove NAME from the lexical-lambdas of INFO.
+ (let ((toplevel-calls (toplevel-procedure-calls info))
+ (lexical-lambdas (lexical-lambdas info))
+ (toplevel-lambdas (toplevel-lambdas info)))
+ (make-arity-info toplevel-calls
+ (if (vhash-assq name lexical-lambdas)
+ (vlist-tail lexical-lambdas)
+ lexical-lambdas)
+ toplevel-lambdas)))
+
+ (let ((toplevel-calls (toplevel-procedure-calls info))
+ (lexical-lambdas (lexical-lambdas info))
+ (toplevel-lambdas (toplevel-lambdas info)))
+ (record-case x
+ ((<let> gensyms vals)
+ (fold shrink info gensyms vals))
+ ((<letrec> gensyms vals)
+ (fold shrink info gensyms vals))
+ ((<fix> gensyms vals)
+ (fold shrink info gensyms vals))
+
+ (else info))))
+
+ (lambda (result env)
+ ;; Post-processing: check all top-level procedure calls that have been
+ ;; encountered.
+ (let ((toplevel-calls (toplevel-procedure-calls result))
+ (toplevel-lambdas (toplevel-lambdas result)))
+ (vlist-for-each
+ (lambda (name+application)
+ (let* ((name (car name+application))
+ (application (cdr name+application))
+ (proc
+ (or (and=> (vhash-assq name toplevel-lambdas) cdr)
+ (and (module? env)
+ (false-if-exception
+ (module-ref env name)))))
+ (proc*
+ ;; handle toplevel aliases
+ (if (toplevel-ref? proc)
+ (let ((name (toplevel-ref-name proc)))
+ (and (module? env)
+ (false-if-exception
+ (module-ref env name))))
+ proc)))
+ (cond ((lambda? proc*)
+ (validate-arity proc* application #t))
+ ((procedure? proc*)
+ (validate-arity proc* application #f)))))
+ toplevel-calls)))
+
+ (make-arity-info vlist-null vlist-null vlist-null)))
+
+
+;;;
+;;; `format' argument analysis.
+;;;
+
+(define &syntax-error
+ ;; The `throw' key for syntax errors.
+ (gensym "format-string-syntax-error"))
+
+(define (format-string-argument-count fmt)
+ ;; Return the minimum and maxium number of arguments that should
+ ;; follow format string FMT (or, ahem, a good estimate thereof) or
+ ;; `any' if the format string can be followed by any number of
+ ;; arguments.
+
+ (define (drop-group chars end)
+ ;; Drop characters from CHARS until "~END" is encountered.
+ (let loop ((chars chars)
+ (tilde? #f))
+ (if (null? chars)
+ (throw &syntax-error 'unterminated-iteration)
+ (if tilde?
+ (if (eq? (car chars) end)
+ (cdr chars)
+ (loop (cdr chars) #f))
+ (if (eq? (car chars) #\~)
+ (loop (cdr chars) #t)
+ (loop (cdr chars) #f))))))
+
+ (define (digit? char)
+ ;; Return true if CHAR is a digit, #f otherwise.
+ (memq char '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)))
+
+ (define (previous-number chars)
+ ;; Return the previous series of digits found in CHARS.
+ (let ((numbers (take-while digit? chars)))
+ (and (not (null? numbers))
+ (string->number (list->string (reverse numbers))))))
+
+ (let loop ((chars (string->list fmt))
+ (state 'literal)
+ (params '())
+ (conditions '())
+ (end-group #f)
+ (min-count 0)
+ (max-count 0))
+ (if (null? chars)
+ (if end-group
+ (throw &syntax-error 'unterminated-conditional)
+ (values min-count max-count))
+ (case state
+ ((tilde)
+ (case (car chars)
+ ((#\~ #\% #\& #\t #\T #\_ #\newline #\( #\) #\! #\| #\/ #\q #\Q)
+ (loop (cdr chars) 'literal '()
+ conditions end-group
+ min-count max-count))
+ ((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\, #\: #\@ #\+ #\- #\#)
+ (loop (cdr chars)
+ 'tilde (cons (car chars) params)
+ conditions end-group
+ min-count max-count))
+ ((#\v #\V) (loop (cdr chars)
+ 'tilde (cons (car chars) params)
+ conditions end-group
+ (+ 1 min-count)
+ (+ 1 max-count)))
+ ((#\p #\P) (let* ((colon? (memq #\: params))
+ (min-count (if colon?
+ (max 1 min-count)
+ (+ 1 min-count))))
+ (loop (cdr chars) 'literal '()
+ conditions end-group
+ min-count
+ (if colon?
+ (max max-count min-count)
+ (+ 1 max-count)))))
+ ((#\[)
+ (loop chars 'literal '() '()
+ (let ((selector (previous-number params))
+ (at? (memq #\@ params)))
+ (lambda (chars conds)
+ ;; end of group
+ (let ((mins (map car conds))
+ (maxs (map cdr conds))
+ (sel? (and selector
+ (< selector (length conds)))))
+ (if (and (every number? mins)
+ (every number? maxs))
+ (loop chars 'literal '() conditions end-group
+ (+ min-count
+ (if sel?
+ (car (list-ref conds selector))
+ (+ (if at? 0 1)
+ (if (null? mins)
+ 0
+ (apply min mins)))))
+ (+ max-count
+ (if sel?
+ (cdr (list-ref conds selector))
+ (+ (if at? 0 1)
+ (if (null? maxs)
+ 0
+ (apply max maxs))))))
+ (values 'any 'any))))) ;; XXX: approximation
+ 0 0))
+ ((#\;)
+ (if end-group
+ (loop (cdr chars) 'literal '()
+ (cons (cons min-count max-count) conditions)
+ end-group
+ 0 0)
+ (throw &syntax-error 'unexpected-semicolon)))
+ ((#\])
+ (if end-group
+ (end-group (cdr chars)
+ (reverse (cons (cons min-count max-count)
+ conditions)))
+ (throw &syntax-error 'unexpected-conditional-termination)))
+ ((#\{) (if (memq #\@ params)
+ (values min-count 'any)
+ (loop (drop-group (cdr chars) #\})
+ 'literal '()
+ conditions end-group
+ (+ 1 min-count) (+ 1 max-count))))
+ ((#\*) (if (memq #\@ params)
+ (values 'any 'any) ;; it's unclear what to do here
+ (loop (cdr chars)
+ 'literal '()
+ conditions end-group
+ (+ (or (previous-number params) 1)
+ min-count)
+ (+ (or (previous-number params) 1)
+ max-count))))
+ ((#\? #\k #\K)
+ ;; We don't have enough info to determine the exact number
+ ;; of args, but we could determine a lower bound (TODO).
+ (values 'any 'any))
+ ((#\^)
+ (values min-count 'any))
+ ((#\h #\H)
+ (let ((argc (if (memq #\: params) 2 1)))
+ (loop (cdr chars) 'literal '()
+ conditions end-group
+ (+ argc min-count)
+ (+ argc max-count))))
+ ((#\')
+ (if (null? (cdr chars))
+ (throw &syntax-error 'unexpected-termination)
+ (loop (cddr chars) 'tilde (cons (cadr chars) params)
+ conditions end-group min-count max-count)))
+ (else (loop (cdr chars) 'literal '()
+ conditions end-group
+ (+ 1 min-count) (+ 1 max-count)))))
+ ((literal)
+ (case (car chars)
+ ((#\~) (loop (cdr chars) 'tilde '()
+ conditions end-group
+ min-count max-count))
+ (else (loop (cdr chars) 'literal '()
+ conditions end-group
+ min-count max-count))))
+ (else (error "computer bought the farm" state))))))
+
+(define (proc-ref? exp proc special-name env)
+ "Return #t when EXP designates procedure PROC in ENV. As a last
+resort, return #t when EXP refers to the global variable SPECIAL-NAME."
+
+ (define special?
+ (cut eq? <> special-name))
+
+ (match exp
+ (($ <toplevel-ref> _ (? special?))
+ ;; Allow top-levels like: (define _ (cut gettext <> "my-domain")).
+ #t)
+ (($ <toplevel-ref> _ name)
+ (let ((var (module-variable env name)))
+ (and var (variable-bound? var)
+ (eq? (variable-ref var) proc))))
+ (($ <module-ref> _ _ (? special?))
+ #t)
+ (($ <module-ref> _ module name public?)
+ (let* ((mod (if public?
+ (false-if-exception (resolve-interface module))
+ (resolve-module module #\ensure #f)))
+ (var (and mod (module-variable mod name))))
+ (and var (variable-bound? var) (eq? (variable-ref var) proc))))
+ (($ <lexical-ref> _ (? special?))
+ #t)
+ (_ #f)))
+
+(define gettext? (cut proc-ref? <> gettext '_ <>))
+(define ngettext? (cut proc-ref? <> ngettext 'N_ <>))
+
+(define (const-fmt x env)
+ ;; Return the literal format string for X, or #f.
+ (match x
+ (($ <const> _ (? string? exp))
+ exp)
+ (($ <application> _ (? (cut gettext? <> env))
+ (($ <const> _ (? string? fmt))))
+ ;; Gettexted literals, like `(_ "foo")'.
+ fmt)
+ (($ <application> _ (? (cut ngettext? <> env))
+ (($ <const> _ (? string? fmt)) ($ <const> _ (? string?)) _ \.\.1))
+ ;; Plural gettextized literals, like `(N_ "singular" "plural" n)'.
+
+ ;; TODO: Check whether the singular and plural strings have the
+ ;; same format escapes.
+ fmt)
+ (_ #f)))
+
+(define format-analysis
+ ;; Report arity mismatches in the given tree.
+ (make-tree-analysis
+ (lambda (x _ env locs)
+ ;; X is a leaf.
+ #t)
+
+ (lambda (x _ env locs)
+ ;; Down into X.
+ (define (check-format-args args loc)
+ (pmatch args
+ ((,port ,fmt . ,rest)
+ (guard (const-fmt fmt env))
+ (if (and (const? port)
+ (not (boolean? (const-exp port))))
+ (warning 'format loc 'wrong-port (const-exp port)))
+ (let ((fmt (const-fmt fmt env))
+ (count (length rest)))
+ (catch &syntax-error
+ (lambda ()
+ (let-values (((min max)
+ (format-string-argument-count fmt)))
+ (and min max
+ (or (and (or (eq? min 'any) (>= count min))
+ (or (eq? max 'any) (<= count max)))
+ (warning 'format loc 'wrong-format-arg-count
+ fmt min max count)))))
+ (lambda (_ key)
+ (warning 'format loc 'syntax-error key fmt)))))
+ ((,port ,fmt . ,rest)
+ (if (and (const? port)
+ (not (boolean? (const-exp port))))
+ (warning 'format loc 'wrong-port (const-exp port)))
+
+ (match fmt
+ (($ <const> loc* (? (negate string?) fmt))
+ (warning 'format (or loc* loc) 'wrong-format-string fmt))
+
+ ;; Warn on non-literal format strings, unless they refer to
+ ;; a lexical variable named "fmt".
+ (($ <lexical-ref> _ fmt)
+ #t)
+ ((? (negate const?))
+ (warning 'format loc 'non-literal-format-string))))
+ (else
+ (warning 'format loc 'wrong-num-args (length args)))))
+
+ (define (check-simple-format-args args loc)
+ ;; Check the arguments to the `simple-format' procedure, which is
+ ;; less capable than that of (ice-9 format).
+
+ (define allowed-chars
+ '(#\A #\S #\a #\s #\~ #\%))
+
+ (define (format-chars fmt)
+ (let loop ((chars (string->list fmt))
+ (result '()))
+ (match chars
+ (()
+ (reverse result))
+ ((#\~ opt rest ...)
+ (loop rest (cons opt result)))
+ ((_ rest ...)
+ (loop rest result)))))
+
+ (match args
+ ((port ($ <const> _ (? string? fmt)) _ ...)
+ (let ((opts (format-chars fmt)))
+ (or (every (cut memq <> allowed-chars) opts)
+ (begin
+ (warning 'format loc 'simple-format fmt
+ (find (negate (cut memq <> allowed-chars)) opts))
+ #f))))
+ ((port (= (cut const-fmt <> env) (? string? fmt)) args ...)
+ (check-simple-format-args `(,port ,(make-const loc fmt) ,args) loc))
+ (_ #t)))
+
+ (define (resolve-toplevel name)
+ (and (module? env)
+ (false-if-exception (module-ref env name))))
+
+ (match x
+ (($ <application> src ($ <toplevel-ref> _ name) args)
+ (let ((proc (resolve-toplevel name)))
+ (if (or (and (eq? proc (@ (guile) simple-format))
+ (check-simple-format-args args
+ (or src (find pair? locs))))
+ (eq? proc (@ (ice-9 format) format)))
+ (check-format-args args (or src (find pair? locs))))))
+ (($ <application> src ($ <module-ref> _ '(ice-9 format) 'format) args)
+ (check-format-args args (or src (find pair? locs))))
+ (($ <application> src ($ <module-ref> _ '(guile)
+ (or 'format 'simple-format))
+ args)
+ (and (check-simple-format-args args
+ (or src (find pair? locs)))
+ (check-format-args args (or src (find pair? locs)))))
+ (_ #t))
+ #t)
+
+ (lambda (x _ env locs)
+ ;; Up from X.
+ #t)
+
+ (lambda (_ env)
+ ;; Post-processing.
+ #t)
+
+ #t))
+;;; Tree-il canonicalizer
+
+;; Copyright (C) 2011, 2012, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language tree-il canonicalize)
+ #\use-module (language tree-il)
+ #\use-module (ice-9 match)
+ #\use-module (srfi srfi-1)
+ #\export (canonicalize!))
+
+(define (tree-il-any proc exp)
+ (tree-il-fold (lambda (exp res)
+ (or res (proc exp)))
+ (lambda (exp res)
+ (or res (proc exp)))
+ (lambda (exp res) res)
+ #f exp))
+
+(define (canonicalize! x)
+ (post-order!
+ (lambda (x)
+ (match x
+ (($ <sequence> src (tail))
+ tail)
+ (($ <sequence> src exps)
+ (and (any sequence? exps)
+ (make-sequence src
+ (append-map (lambda (x)
+ (if (sequence? x)
+ (sequence-exps x)
+ (list x)))
+ exps))))
+ (($ <let> src () () () body)
+ body)
+ (($ <letrec> src _ () () () body)
+ body)
+ (($ <fix> src () () () body)
+ body)
+ (($ <dynlet> src () () body)
+ body)
+ (($ <lambda> src meta #f)
+ ;; Give a body to case-lambda with no clauses.
+ (make-lambda
+ src meta
+ (make-lambda-case
+ #f '() #f #f #f '() '()
+ (make-application
+ #f
+ (make-primitive-ref #f 'throw)
+ (list (make-const #f 'wrong-number-of-args)
+ (make-const #f #f)
+ (make-const #f "Wrong number of arguments")
+ (make-const #f '())
+ (make-const #f #f)))
+ #f)))
+ (($ <prompt> src tag body handler)
+ (define (escape-only? handler)
+ (match handler
+ (($ <lambda-case> _ (_ . _) _ _ _ _ (cont . _) body #f)
+ (not (tree-il-any (lambda (x)
+ (and (lexical-ref? x)
+ (eq? (lexical-ref-gensym x) cont)))
+ body)))
+ (else #f)))
+ (define (thunk-application? x)
+ (match x
+ (($ <application> _
+ ($ <lambda> _ _ ($ <lambda-case> _ () #f #f #f))
+ ()) #t)
+ (_ #f)))
+ (define (make-thunk-application body)
+ (define thunk
+ (make-lambda #f '()
+ (make-lambda-case #f '() #f #f #f '() '() body #f)))
+ (make-application #f thunk '()))
+
+ ;; This code has a nasty job to do: to ensure that either the
+ ;; handler is escape-only, or the body is the application of a
+ ;; thunk. Sad but true.
+ (if (or (escape-only? handler)
+ (thunk-application? body))
+ #f
+ (make-prompt src tag (make-thunk-application body) handler)))
+ (_ #f)))
+ x))
+;;; TREE-IL -> GLIL compiler
+
+;; Copyright (C) 2001,2008,2009,2010,2011,2012,2013,2014 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language tree-il compile-glil)
+ #\use-module (system base syntax)
+ #\use-module (system base pmatch)
+ #\use-module (system base message)
+ #\use-module (ice-9 receive)
+ #\use-module (language glil)
+ #\use-module (system vm instruction)
+ #\use-module (language tree-il)
+ #\use-module (language tree-il optimize)
+ #\use-module (language tree-il canonicalize)
+ #\use-module (language tree-il analyze)
+ #\use-module ((srfi srfi-1) #\select (filter-map))
+ #\export (compile-glil))
+
+;; allocation:
+;; sym -> {lambda -> address}
+;; lambda -> (labels . free-locs)
+;; lambda-case -> (gensym . nlocs)
+;;
+;; address ::= (local? boxed? . index)
+;; labels ::= ((sym . lambda) ...)
+;; free-locs ::= ((sym0 . address0) (sym1 . address1) ...)
+;; free variable addresses are relative to parent proc.
+
+(define *comp-module* (make-fluid))
+
+(define %warning-passes
+ `((unused-variable . ,unused-variable-analysis)
+ (unused-toplevel . ,unused-toplevel-analysis)
+ (unbound-variable . ,unbound-variable-analysis)
+ (arity-mismatch . ,arity-analysis)
+ (format . ,format-analysis)))
+
+(define (compile-glil x e opts)
+ (define warnings
+ (or (and=> (memq #\warnings opts) cadr)
+ '()))
+
+ ;; Go through the warning passes.
+ (let ((analyses (filter-map (lambda (kind)
+ (assoc-ref %warning-passes kind))
+ warnings)))
+ (analyze-tree analyses x e))
+
+ (let* ((x (make-lambda (tree-il-src x) '()
+ (make-lambda-case #f '() #f #f #f '() '() x #f)))
+ (x (optimize! x e opts))
+ (x (canonicalize! x))
+ (allocation (analyze-lexicals x)))
+
+ (with-fluids ((*comp-module* e))
+ (values (flatten-lambda x #f allocation)
+ e
+ e))))
+
+
+
+(define *primcall-ops* (make-hash-table))
+(for-each
+ (lambda (x) (hash-set! *primcall-ops* (car x) (cdr x)))
+ '(((eq? . 2) . eq?)
+ ((eqv? . 2) . eqv?)
+ ((equal? . 2) . equal?)
+ ((= . 2) . ee?)
+ ((< . 2) . lt?)
+ ((> . 2) . gt?)
+ ((<= . 2) . le?)
+ ((>= . 2) . ge?)
+ ((+ . 2) . add)
+ ((- . 2) . sub)
+ ((1+ . 1) . add1)
+ ((1- . 1) . sub1)
+ ((* . 2) . mul)
+ ((/ . 2) . div)
+ ((quotient . 2) . quo)
+ ((remainder . 2) . rem)
+ ((modulo . 2) . mod)
+ ((ash . 2) . ash)
+ ((logand . 2) . logand)
+ ((logior . 2) . logior)
+ ((logxor . 2) . logxor)
+ ((not . 1) . not)
+ ((pair? . 1) . pair?)
+ ((cons . 2) . cons)
+ ((car . 1) . car)
+ ((cdr . 1) . cdr)
+ ((set-car! . 2) . set-car!)
+ ((set-cdr! . 2) . set-cdr!)
+ ((null? . 1) . null?)
+ ((list? . 1) . list?)
+ ((symbol? . 1) . symbol?)
+ ((vector? . 1) . vector?)
+ (list . list)
+ (vector . vector)
+ ((class-of . 1) . class-of)
+ ((vector-ref . 2) . vector-ref)
+ ((vector-set! . 3) . vector-set)
+ ((variable-ref . 1) . variable-ref)
+ ;; nb, *not* variable-set! -- the args are switched
+ ((variable-bound? . 1) . variable-bound?)
+ ((struct? . 1) . struct?)
+ ((struct-vtable . 1) . struct-vtable)
+ ((struct-ref . 2) . struct-ref)
+ ((struct-set! . 3) . struct-set)
+ (make-struct/no-tail . make-struct)
+
+ ;; hack for javascript
+ ((return . 1) . return)
+ ;; hack for lua
+ (return/values . return/values)
+
+ ((bytevector-u8-ref . 2) . bv-u8-ref)
+ ((bytevector-u8-set! . 3) . bv-u8-set)
+ ((bytevector-s8-ref . 2) . bv-s8-ref)
+ ((bytevector-s8-set! . 3) . bv-s8-set)
+
+ ((bytevector-u16-ref . 3) . bv-u16-ref)
+ ((bytevector-u16-set! . 4) . bv-u16-set)
+ ((bytevector-u16-native-ref . 2) . bv-u16-native-ref)
+ ((bytevector-u16-native-set! . 3) . bv-u16-native-set)
+ ((bytevector-s16-ref . 3) . bv-s16-ref)
+ ((bytevector-s16-set! . 4) . bv-s16-set)
+ ((bytevector-s16-native-ref . 2) . bv-s16-native-ref)
+ ((bytevector-s16-native-set! . 3) . bv-s16-native-set)
+
+ ((bytevector-u32-ref . 3) . bv-u32-ref)
+ ((bytevector-u32-set! . 4) . bv-u32-set)
+ ((bytevector-u32-native-ref . 2) . bv-u32-native-ref)
+ ((bytevector-u32-native-set! . 3) . bv-u32-native-set)
+ ((bytevector-s32-ref . 3) . bv-s32-ref)
+ ((bytevector-s32-set! . 4) . bv-s32-set)
+ ((bytevector-s32-native-ref . 2) . bv-s32-native-ref)
+ ((bytevector-s32-native-set! . 3) . bv-s32-native-set)
+
+ ((bytevector-u64-ref . 3) . bv-u64-ref)
+ ((bytevector-u64-set! . 4) . bv-u64-set)
+ ((bytevector-u64-native-ref . 2) . bv-u64-native-ref)
+ ((bytevector-u64-native-set! . 3) . bv-u64-native-set)
+ ((bytevector-s64-ref . 3) . bv-s64-ref)
+ ((bytevector-s64-set! . 4) . bv-s64-set)
+ ((bytevector-s64-native-ref . 2) . bv-s64-native-ref)
+ ((bytevector-s64-native-set! . 3) . bv-s64-native-set)
+
+ ((bytevector-ieee-single-ref . 3) . bv-f32-ref)
+ ((bytevector-ieee-single-set! . 4) . bv-f32-set)
+ ((bytevector-ieee-single-native-ref . 2) . bv-f32-native-ref)
+ ((bytevector-ieee-single-native-set! . 3) . bv-f32-native-set)
+ ((bytevector-ieee-double-ref . 3) . bv-f64-ref)
+ ((bytevector-ieee-double-set! . 4) . bv-f64-set)
+ ((bytevector-ieee-double-native-ref . 2) . bv-f64-native-ref)
+ ((bytevector-ieee-double-native-set! . 3) . bv-f64-native-set)))
+
+
+
+
+(define (make-label) (gensym ":L"))
+
+(define (vars->bind-list ids vars allocation proc)
+ (map (lambda (id v)
+ (pmatch (hashq-ref (hashq-ref allocation v) proc)
+ ((#t ,boxed? . ,n)
+ (list id boxed? n))
+ (,x (error "bad var list element" id v x))))
+ ids
+ vars))
+
+(define (emit-bindings src ids vars allocation proc emit-code)
+ (emit-code src (make-glil-bind
+ (vars->bind-list ids vars allocation proc))))
+
+(define (with-output-to-code proc)
+ (let ((out '()))
+ (define (emit-code src x)
+ (set! out (cons x out))
+ (if src
+ (set! out (cons (make-glil-source src) out))))
+ (proc emit-code)
+ (reverse out)))
+
+(define (flatten-lambda x self-label allocation)
+ (record-case x
+ ((<lambda> src meta body)
+ (make-glil-program
+ meta
+ (with-output-to-code
+ (lambda (emit-code)
+ ;; write source info for proc
+ (if src (emit-code #f (make-glil-source src)))
+ ;; compile the body, yo
+ (flatten-lambda-case body allocation x self-label
+ (car (hashq-ref allocation x))
+ emit-code)))))))
+
+(define (flatten-lambda-case lcase allocation self self-label fix-labels
+ emit-code)
+ (define (emit-label label)
+ (emit-code #f (make-glil-label label)))
+ (define (emit-branch src inst label)
+ (emit-code src (make-glil-branch inst label)))
+
+ ;; RA: "return address"; #f unless we're in a non-tail fix with labels
+ ;; MVRA: "multiple-values return address"; #f unless we're in a let-values
+ (let comp ((x lcase) (context 'tail) (RA #f) (MVRA #f))
+ (define (comp-tail tree) (comp tree context RA MVRA))
+ (define (comp-push tree) (comp tree 'push #f #f))
+ (define (comp-drop tree) (comp tree 'drop #f #f))
+ (define (comp-vals tree MVRA) (comp tree 'vals #f MVRA))
+ (define (comp-fix tree RA) (comp tree context RA MVRA))
+
+ ;; A couple of helpers. Note that if we are in tail context, we
+ ;; won't have an RA.
+ (define (maybe-emit-return)
+ (if RA
+ (emit-branch #f 'br RA)
+ (if (eq? context 'tail)
+ (emit-code #f (make-glil-call 'return 1)))))
+
+ ;; After lexical binding forms in non-tail context, call this
+ ;; function to clear stack slots, allowing their previous values to
+ ;; be collected.
+ (define (clear-stack-slots context syms)
+ (case context
+ ((push drop)
+ (for-each (lambda (v)
+ (and=>
+ ;; Can be #f if the var is labels-allocated.
+ (hashq-ref allocation v)
+ (lambda (h)
+ (pmatch (hashq-ref h self)
+ ((#t _ . ,n)
+ (emit-code #f (make-glil-void))
+ (emit-code #f (make-glil-lexical #t #f 'set n)))
+ (,loc (error "bad let var allocation" x loc))))))
+ syms))))
+
+ (record-case x
+ ((<void>)
+ (case context
+ ((push vals tail)
+ (emit-code #f (make-glil-void))))
+ (maybe-emit-return))
+
+ ((<const> src exp)
+ (case context
+ ((push vals tail)
+ (emit-code src (make-glil-const exp))))
+ (maybe-emit-return))
+
+ ;; FIXME: should represent sequence as exps tail
+ ((<sequence> exps)
+ (let lp ((exps exps))
+ (if (null? (cdr exps))
+ (comp-tail (car exps))
+ (begin
+ (comp-drop (car exps))
+ (lp (cdr exps))))))
+
+ ((<application> src proc args)
+ ;; FIXME: need a better pattern-matcher here
+ (cond
+ ((and (primitive-ref? proc)
+ (eq? (primitive-ref-name proc) '@apply)
+ (>= (length args) 1))
+ (let ((proc (car args))
+ (args (cdr args)))
+ (cond
+ ((and (primitive-ref? proc) (eq? (primitive-ref-name proc) 'values)
+ (not (eq? context 'push)) (not (eq? context 'vals)))
+ ;; tail: (lambda () (apply values '(1 2)))
+ ;; drop: (lambda () (apply values '(1 2)) 3)
+ ;; push: (lambda () (list (apply values '(10 12)) 1))
+ (case context
+ ((drop) (for-each comp-drop args) (maybe-emit-return))
+ ((tail)
+ (for-each comp-push args)
+ (emit-code src (make-glil-call 'return/values* (length args))))))
+
+ (else
+ (case context
+ ((tail)
+ (comp-push proc)
+ (for-each comp-push args)
+ (emit-code src (make-glil-call 'tail-apply (1+ (length args)))))
+ ((push)
+ (emit-code src (make-glil-call 'new-frame 0))
+ (comp-push proc)
+ (for-each comp-push args)
+ (emit-code src (make-glil-call 'apply (1+ (length args))))
+ (maybe-emit-return))
+ ((vals)
+ (comp-vals
+ (make-application src (make-primitive-ref #f 'apply)
+ (cons proc args))
+ MVRA)
+ (maybe-emit-return))
+ ((drop)
+ ;; Well, shit. The proc might return any number of
+ ;; values (including 0), since it's in a drop context,
+ ;; yet apply does not create a MV continuation. So we
+ ;; mv-call out to our trampoline instead.
+ (comp-drop
+ (make-application src (make-primitive-ref #f 'apply)
+ (cons proc args)))
+ (maybe-emit-return)))))))
+
+ ((and (primitive-ref? proc) (eq? (primitive-ref-name proc) 'values))
+ ;; tail: (lambda () (values '(1 2)))
+ ;; drop: (lambda () (values '(1 2)) 3)
+ ;; push: (lambda () (list (values '(10 12)) 1))
+ ;; vals: (let-values (((a b ...) (values 1 2 ...))) ...)
+ (case context
+ ((drop) (for-each comp-drop args) (maybe-emit-return))
+ ((push)
+ (case (length args)
+ ((0)
+ ;; FIXME: This is surely an error. We need to add a
+ ;; values-mismatch warning pass.
+ (emit-code src (make-glil-call 'new-frame 0))
+ (comp-push proc)
+ (emit-code src (make-glil-call 'call 0))
+ (maybe-emit-return))
+ (else
+ ;; Taking advantage of unspecified order of evaluation of
+ ;; arguments.
+ (for-each comp-drop (cdr args))
+ (comp-push (car args))
+ (maybe-emit-return))))
+ ((vals)
+ (for-each comp-push args)
+ (emit-code #f (make-glil-const (length args)))
+ (emit-branch src 'br MVRA))
+ ((tail)
+ (for-each comp-push args)
+ (emit-code src (let ((len (length args)))
+ (if (= len 1)
+ (make-glil-call 'return 1)
+ (make-glil-call 'return/values len)))))))
+
+ ((and (primitive-ref? proc)
+ (eq? (primitive-ref-name proc) '@call-with-values)
+ (= (length args) 2))
+ ;; CONSUMER
+ ;; PRODUCER
+ ;; (mv-call MV)
+ ;; ([tail]-call 1)
+ ;; goto POST
+ ;; MV: [tail-]call/nargs
+ ;; POST: (maybe-drop)
+ (case context
+ ((vals)
+ ;; Fall back.
+ (comp-vals
+ (make-application src (make-primitive-ref #f 'call-with-values)
+ args)
+ MVRA)
+ (maybe-emit-return))
+ (else
+ (let ((MV (make-label)) (POST (make-label))
+ (producer (car args)) (consumer (cadr args)))
+ (if (not (eq? context 'tail))
+ (emit-code src (make-glil-call 'new-frame 0)))
+ (comp-push consumer)
+ (emit-code src (make-glil-call 'new-frame 0))
+ (comp-push producer)
+ (emit-code src (make-glil-mv-call 0 MV))
+ (case context
+ ((tail) (emit-code src (make-glil-call 'tail-call 1)))
+ (else (emit-code src (make-glil-call 'call 1))
+ (emit-branch #f 'br POST)))
+ (emit-label MV)
+ (case context
+ ((tail) (emit-code src (make-glil-call 'tail-call/nargs 0)))
+ (else (emit-code src (make-glil-call 'call/nargs 0))
+ (emit-label POST)
+ (if (eq? context 'drop)
+ (emit-code #f (make-glil-call 'drop 1)))
+ (maybe-emit-return)))))))
+
+ ((and (primitive-ref? proc)
+ (eq? (primitive-ref-name proc) '@call-with-current-continuation)
+ (= (length args) 1))
+ (case context
+ ((tail)
+ (comp-push (car args))
+ (emit-code src (make-glil-call 'tail-call/cc 1)))
+ ((vals)
+ (comp-vals
+ (make-application
+ src (make-primitive-ref #f 'call-with-current-continuation)
+ args)
+ MVRA)
+ (maybe-emit-return))
+ ((push)
+ (comp-push (car args))
+ (emit-code src (make-glil-call 'call/cc 1))
+ (maybe-emit-return))
+ ((drop)
+ ;; Crap. Just like `apply' in drop context.
+ (comp-drop
+ (make-application
+ src (make-primitive-ref #f 'call-with-current-continuation)
+ args))
+ (maybe-emit-return))))
+
+ ;; A hack for variable-set, the opcode for which takes its args
+ ;; reversed, relative to the variable-set! function
+ ((and (primitive-ref? proc)
+ (eq? (primitive-ref-name proc) 'variable-set!)
+ (= (length args) 2))
+ (comp-push (cadr args))
+ (comp-push (car args))
+ (emit-code src (make-glil-call 'variable-set 2))
+ (case context
+ ((tail push vals) (emit-code #f (make-glil-void))))
+ (maybe-emit-return))
+
+ ((and (primitive-ref? proc)
+ (or (hash-ref *primcall-ops*
+ (cons (primitive-ref-name proc) (length args)))
+ (hash-ref *primcall-ops* (primitive-ref-name proc))))
+ => (lambda (op)
+ (for-each comp-push args)
+ (emit-code src (make-glil-call op (length args)))
+ (case (instruction-pushes op)
+ ((0)
+ (case context
+ ((tail push vals) (emit-code #f (make-glil-void))))
+ (maybe-emit-return))
+ ((1)
+ (case context
+ ((drop) (emit-code #f (make-glil-call 'drop 1))))
+ (maybe-emit-return))
+ ((-1)
+ ;; A control instruction, like return/values. Here we
+ ;; just have to hope that the author of the tree-il
+ ;; knew what they were doing.
+ *unspecified*)
+ (else
+ (error "bad primitive op: too many pushes"
+ op (instruction-pushes op))))))
+
+ ;; call to the same lambda-case in tail position
+ ((and (lexical-ref? proc)
+ self-label (eq? (lexical-ref-gensym proc) self-label)
+ (eq? context 'tail)
+ (not (lambda-case-kw lcase))
+ (not (lambda-case-rest lcase))
+ (= (length args)
+ (+ (length (lambda-case-req lcase))
+ (or (and=> (lambda-case-opt lcase) length) 0))))
+ (for-each comp-push args)
+ (for-each (lambda (sym)
+ (pmatch (hashq-ref (hashq-ref allocation sym) self)
+ ((#t #f . ,index) ; unboxed
+ (emit-code #f (make-glil-lexical #t #f 'set index)))
+ ((#t #t . ,index) ; boxed
+ ;; new box
+ (emit-code #f (make-glil-lexical #t #t 'box index)))
+ (,x (error "bad lambda-case arg allocation" x))))
+ (reverse (lambda-case-gensyms lcase)))
+ (emit-branch src 'br (car (hashq-ref allocation lcase))))
+
+ ;; lambda, the ultimate goto
+ ((and (lexical-ref? proc)
+ (assq (lexical-ref-gensym proc) fix-labels))
+ ;; like the self-tail-call case, though we can handle "drop"
+ ;; contexts too. first, evaluate new values, pushing them on
+ ;; the stack
+ (for-each comp-push args)
+ ;; find the specific case, rename args, and goto the case label
+ (let lp ((lcase (lambda-body
+ (assq-ref fix-labels (lexical-ref-gensym proc)))))
+ (cond
+ ((and (lambda-case? lcase)
+ (not (lambda-case-kw lcase))
+ (not (lambda-case-opt lcase))
+ (not (lambda-case-rest lcase))
+ (= (length args) (length (lambda-case-req lcase))))
+ ;; we have a case that matches the args; rename variables
+ ;; and goto the case label
+ (for-each (lambda (sym)
+ (pmatch (hashq-ref (hashq-ref allocation sym) self)
+ ((#t #f . ,index) ; unboxed
+ (emit-code #f (make-glil-lexical #t #f 'set index)))
+ ((#t #t . ,index) ; boxed
+ (emit-code #f (make-glil-lexical #t #t 'box index)))
+ (,x (error "bad lambda-case arg allocation" x))))
+ (reverse (lambda-case-gensyms lcase)))
+ (emit-branch src 'br (car (hashq-ref allocation lcase))))
+ ((lambda-case? lcase)
+ ;; no match, try next case
+ (lp (lambda-case-alternate lcase)))
+ (else
+ ;; no cases left. we can't really handle this currently.
+ ;; ideally we would push on a new frame, then do a "local
+ ;; call" -- which doesn't require consing up a program
+ ;; object. but for now error, as this sort of case should
+ ;; preclude label allocation.
+ (error "couldn't find matching case for label call" x)))))
+
+ (else
+ (if (not (eq? context 'tail))
+ (emit-code src (make-glil-call 'new-frame 0)))
+ (comp-push proc)
+ (for-each comp-push args)
+ (let ((len (length args)))
+ (case context
+ ((tail) (if (<= len #xff)
+ (emit-code src (make-glil-call 'tail-call len))
+ (begin
+ (comp-push (make-const #f len))
+ (emit-code src (make-glil-call 'tail-call/nargs 0)))))
+ ((push) (if (<= len #xff)
+ (emit-code src (make-glil-call 'call len))
+ (begin
+ (comp-push (make-const #f len))
+ (emit-code src (make-glil-call 'call/nargs 0))))
+ (maybe-emit-return))
+ ;; FIXME: mv-call doesn't have a /nargs variant, so it is
+ ;; limited to 255 args. Can work around it with a
+ ;; trampoline and tail-call/nargs, but it's not so nice.
+ ((vals) (emit-code src (make-glil-mv-call len MVRA))
+ (maybe-emit-return))
+ ((drop) (let ((MV (make-label)) (POST (make-label)))
+ (emit-code src (make-glil-mv-call len MV))
+ (emit-code #f (make-glil-call 'drop 1))
+ (emit-branch #f 'br (or RA POST))
+ (emit-label MV)
+ (emit-code #f (make-glil-mv-bind 0 #f))
+ (if RA
+ (emit-branch #f 'br RA)
+ (emit-label POST)))))))))
+
+ ((<conditional> src test consequent alternate)
+ ;; TEST
+ ;; (br-if-not L1)
+ ;; consequent
+ ;; (br L2)
+ ;; L1: alternate
+ ;; L2:
+ (let ((L1 (make-label)) (L2 (make-label)))
+ ;; need a pattern matcher
+ (record-case test
+ ((<application> proc args)
+ (record-case proc
+ ((<primitive-ref> name)
+ (let ((len (length args)))
+ (cond
+
+ ((and (eq? name 'eq?) (= len 2))
+ (comp-push (car args))
+ (comp-push (cadr args))
+ (emit-branch src 'br-if-not-eq L1))
+
+ ((and (eq? name 'null?) (= len 1))
+ (comp-push (car args))
+ (emit-branch src 'br-if-not-null L1))
+
+ ((and (eq? name 'not) (= len 1))
+ (let ((app (car args)))
+ (record-case app
+ ((<application> proc args)
+ (let ((len (length args)))
+ (record-case proc
+ ((<primitive-ref> name)
+ (cond
+
+ ((and (eq? name 'eq?) (= len 2))
+ (comp-push (car args))
+ (comp-push (cadr args))
+ (emit-branch src 'br-if-eq L1))
+
+ ((and (eq? name 'null?) (= len 1))
+ (comp-push (car args))
+ (emit-branch src 'br-if-null L1))
+
+ (else
+ (comp-push app)
+ (emit-branch src 'br-if L1))))
+ (else
+ (comp-push app)
+ (emit-branch src 'br-if L1)))))
+ (else
+ (comp-push app)
+ (emit-branch src 'br-if L1)))))
+
+ (else
+ (comp-push test)
+ (emit-branch src 'br-if-not L1)))))
+ (else
+ (comp-push test)
+ (emit-branch src 'br-if-not L1))))
+ (else
+ (comp-push test)
+ (emit-branch src 'br-if-not L1)))
+
+ (comp-tail consequent)
+ ;; if there is an RA, comp-tail will cause a jump to it -- just
+ ;; have to clean up here if there is no RA.
+ (if (and (not RA) (not (eq? context 'tail)))
+ (emit-branch #f 'br L2))
+ (emit-label L1)
+ (comp-tail alternate)
+ (if (and (not RA) (not (eq? context 'tail)))
+ (emit-label L2))))
+
+ ((<primitive-ref> src name)
+ (cond
+ ((eq? (module-variable (fluid-ref *comp-module*) name)
+ (module-variable the-root-module name))
+ (case context
+ ((tail push vals)
+ (emit-code src (make-glil-toplevel 'ref name))))
+ (maybe-emit-return))
+ ((module-variable the-root-module name)
+ (case context
+ ((tail push vals)
+ (emit-code src (make-glil-module 'ref '(guile) name #f))))
+ (maybe-emit-return))
+ (else
+ (case context
+ ((tail push vals)
+ (emit-code src (make-glil-module
+ 'ref (module-name (fluid-ref *comp-module*)) name #f))))
+ (maybe-emit-return))))
+
+ ((<lexical-ref> src gensym)
+ (case context
+ ((push vals tail)
+ (pmatch (hashq-ref (hashq-ref allocation gensym) self)
+ ((,local? ,boxed? . ,index)
+ (emit-code src (make-glil-lexical local? boxed? 'ref index)))
+ (,loc
+ (error "bad lexical allocation" x loc)))))
+ (maybe-emit-return))
+
+ ((<lexical-set> src gensym exp)
+ (comp-push exp)
+ (pmatch (hashq-ref (hashq-ref allocation gensym) self)
+ ((,local? ,boxed? . ,index)
+ (emit-code src (make-glil-lexical local? boxed? 'set index)))
+ (,loc
+ (error "bad lexical allocation" x loc)))
+ (case context
+ ((tail push vals)
+ (emit-code #f (make-glil-void))))
+ (maybe-emit-return))
+
+ ((<module-ref> src mod name public?)
+ (emit-code src (make-glil-module 'ref mod name public?))
+ (case context
+ ((drop) (emit-code #f (make-glil-call 'drop 1))))
+ (maybe-emit-return))
+
+ ((<module-set> src mod name public? exp)
+ (comp-push exp)
+ (emit-code src (make-glil-module 'set mod name public?))
+ (case context
+ ((tail push vals)
+ (emit-code #f (make-glil-void))))
+ (maybe-emit-return))
+
+ ((<toplevel-ref> src name)
+ (emit-code src (make-glil-toplevel 'ref name))
+ (case context
+ ((drop) (emit-code #f (make-glil-call 'drop 1))))
+ (maybe-emit-return))
+
+ ((<toplevel-set> src name exp)
+ (comp-push exp)
+ (emit-code src (make-glil-toplevel 'set name))
+ (case context
+ ((tail push vals)
+ (emit-code #f (make-glil-void))))
+ (maybe-emit-return))
+
+ ((<toplevel-define> src name exp)
+ (comp-push exp)
+ (emit-code src (make-glil-toplevel 'define name))
+ (case context
+ ((tail push vals)
+ (emit-code #f (make-glil-void))))
+ (maybe-emit-return))
+
+ ((<lambda>)
+ (let ((free-locs (cdr (hashq-ref allocation x))))
+ (case context
+ ((push vals tail)
+ (emit-code #f (flatten-lambda x #f allocation))
+ (if (not (null? free-locs))
+ (begin
+ (for-each
+ (lambda (loc)
+ (pmatch loc
+ ((,local? ,boxed? . ,n)
+ (emit-code #f (make-glil-lexical local? #f 'ref n)))
+ (else (error "bad lambda free var allocation" x loc))))
+ free-locs)
+ (emit-code #f (make-glil-call 'make-closure
+ (length free-locs))))))))
+ (maybe-emit-return))
+
+ ((<lambda-case> src req opt rest kw inits gensyms alternate body)
+ ;; o/~ feature on top of feature o/~
+ ;; req := (name ...)
+ ;; opt := (name ...) | #f
+ ;; rest := name | #f
+ ;; kw: (allow-other-keys? (keyword name var) ...) | #f
+ ;; gensyms: (sym ...)
+ ;; init: tree-il in context of gensyms
+ ;; gensyms map to named arguments in the following order:
+ ;; required, optional (positional), rest, keyword.
+ (let* ((nreq (length req))
+ (nopt (if opt (length opt) 0))
+ (rest-idx (and rest (+ nreq nopt)))
+ (opt-names (or opt '()))
+ (allow-other-keys? (if kw (car kw) #f))
+ (kw-indices (map (lambda (x)
+ (pmatch x
+ ((,key ,name ,var)
+ (cons key (list-index gensyms var)))
+ (else (error "bad kwarg" x))))
+ (if kw (cdr kw) '())))
+ (nargs (apply max (+ nreq nopt (if rest 1 0))
+ (map 1+ (map cdr kw-indices))))
+ (nlocs (cdr (hashq-ref allocation x)))
+ (alternate-label (and alternate (make-label))))
+ (or (= nargs
+ (length gensyms)
+ (+ nreq (length inits) (if rest 1 0)))
+ (error "lambda-case gensyms don't correspond to args"
+ req opt rest kw inits gensyms nreq nopt kw-indices nargs))
+ ;; the prelude, to check args & reset the stack pointer,
+ ;; allowing room for locals
+ (emit-code
+ src
+ (cond
+ (kw
+ (make-glil-kw-prelude nreq nopt rest-idx kw-indices
+ allow-other-keys? nlocs alternate-label))
+ ((or rest opt)
+ (make-glil-opt-prelude nreq nopt rest-idx nlocs alternate-label))
+ (#t
+ (make-glil-std-prelude nreq nlocs alternate-label))))
+ ;; box args if necessary
+ (for-each
+ (lambda (v)
+ (pmatch (hashq-ref (hashq-ref allocation v) self)
+ ((#t #t . ,n)
+ (emit-code #f (make-glil-lexical #t #f 'ref n))
+ (emit-code #f (make-glil-lexical #t #t 'box n)))))
+ gensyms)
+ ;; write bindings info
+ (if (not (null? gensyms))
+ (emit-bindings
+ #f
+ (let lp ((kw (if kw (cdr kw) '()))
+ (names (append (reverse opt-names) (reverse req)))
+ (gensyms (list-tail gensyms (+ nreq nopt
+ (if rest 1 0)))))
+ (pmatch kw
+ (()
+ ;; fixme: check that gensyms is empty
+ (reverse (if rest (cons rest names) names)))
+ (((,key ,name ,var) . ,kw)
+ (if (memq var gensyms)
+ (lp kw (cons name names) (delq var gensyms))
+ (lp kw names gensyms)))
+ (,kw (error "bad keywords, yo" kw))))
+ gensyms allocation self emit-code))
+ ;; init optional/kw args
+ (let lp ((inits inits) (n nreq) (gensyms (list-tail gensyms nreq)))
+ (cond
+ ((null? inits)) ; done
+ ((and rest-idx (= n rest-idx))
+ (lp inits (1+ n) (cdr gensyms)))
+ (#t
+ (pmatch (hashq-ref (hashq-ref allocation (car gensyms)) self)
+ ((#t ,boxed? . ,n*) (guard (= n* n))
+ (let ((L (make-label)))
+ (emit-code #f (make-glil-lexical #t boxed? 'bound? n))
+ (emit-code #f (make-glil-branch 'br-if L))
+ (comp-push (car inits))
+ (emit-code #f (make-glil-lexical #t boxed? 'set n))
+ (emit-label L)
+ (lp (cdr inits) (1+ n) (cdr gensyms))))
+ (#t (error "bad arg allocation" (car gensyms) inits))))))
+ ;; post-prelude case label for label calls
+ (emit-label (car (hashq-ref allocation x)))
+ (comp-tail body)
+ (if (not (null? gensyms))
+ (emit-code #f (make-glil-unbind)))
+ (if alternate-label
+ (begin
+ (emit-label alternate-label)
+ (flatten-lambda-case alternate allocation self self-label
+ fix-labels emit-code)))))
+
+ ((<let> src names gensyms vals body)
+ (for-each comp-push vals)
+ (emit-bindings src names gensyms allocation self emit-code)
+ (for-each (lambda (v)
+ (pmatch (hashq-ref (hashq-ref allocation v) self)
+ ((#t #f . ,n)
+ (emit-code src (make-glil-lexical #t #f 'set n)))
+ ((#t #t . ,n)
+ (emit-code src (make-glil-lexical #t #t 'box n)))
+ (,loc (error "bad let var allocation" x loc))))
+ (reverse gensyms))
+ (comp-tail body)
+ (clear-stack-slots context gensyms)
+ (emit-code #f (make-glil-unbind)))
+
+ ((<letrec> src in-order? names gensyms vals body)
+ ;; First prepare heap storage slots.
+ (for-each (lambda (v)
+ (pmatch (hashq-ref (hashq-ref allocation v) self)
+ ((#t #t . ,n)
+ (emit-code src (make-glil-lexical #t #t 'empty-box n)))
+ (,loc (error "bad letrec var allocation" x loc))))
+ gensyms)
+ ;; Even though the slots are empty, the bindings are valid.
+ (emit-bindings src names gensyms allocation self emit-code)
+ (cond
+ (in-order?
+ ;; For letrec*, bind values in order.
+ (for-each (lambda (name v val)
+ (pmatch (hashq-ref (hashq-ref allocation v) self)
+ ((#t #t . ,n)
+ (comp-push val)
+ (emit-code src (make-glil-lexical #t #t 'set n)))
+ (,loc (error "bad letrec var allocation" x loc))))
+ names gensyms vals))
+ (else
+ ;; But for letrec, eval all values, then bind.
+ (for-each comp-push vals)
+ (for-each (lambda (v)
+ (pmatch (hashq-ref (hashq-ref allocation v) self)
+ ((#t #t . ,n)
+ (emit-code src (make-glil-lexical #t #t 'set n)))
+ (,loc (error "bad letrec var allocation" x loc))))
+ (reverse gensyms))))
+ (comp-tail body)
+ (clear-stack-slots context gensyms)
+ (emit-code #f (make-glil-unbind)))
+
+ ((<fix> src names gensyms vals body)
+ ;; The ideal here is to just render the lambda bodies inline, and
+ ;; wire the code together with gotos. We can do that if
+ ;; analyze-lexicals has determined that a given var has "label"
+ ;; allocation -- which is the case if it is in `fix-labels'.
+ ;;
+ ;; But even for closures that we can't inline, we can do some
+ ;; tricks to avoid heap-allocation for the binding itself. Since
+ ;; we know the vals are lambdas, we can set them to their local
+ ;; var slots first, then capture their bindings, mutating them in
+ ;; place.
+ (let ((new-RA (if (or (eq? context 'tail) RA) #f (make-label))))
+ (for-each
+ (lambda (x v)
+ (cond
+ ((hashq-ref allocation x)
+ ;; allocating a closure
+ (emit-code #f (flatten-lambda x v allocation))
+ (let ((free-locs (cdr (hashq-ref allocation x))))
+ (if (not (null? free-locs))
+ ;; Need to make-closure first, so we have a fresh closure on
+ ;; the heap, but with a temporary free values.
+ (begin
+ (for-each (lambda (loc)
+ (emit-code #f (make-glil-const #f)))
+ free-locs)
+ (emit-code #f (make-glil-call 'make-closure
+ (length free-locs))))))
+ (pmatch (hashq-ref (hashq-ref allocation v) self)
+ ((#t #f . ,n)
+ (emit-code src (make-glil-lexical #t #f 'set n)))
+ (,loc (error "bad fix var allocation" x loc))))
+ (else
+ ;; labels allocation: emit label & body, but jump over it
+ (let ((POST (make-label)))
+ (emit-branch #f 'br POST)
+ (let lp ((lcase (lambda-body x)))
+ (if lcase
+ (record-case lcase
+ ((<lambda-case> src req gensyms body alternate)
+ (emit-label (car (hashq-ref allocation lcase)))
+ ;; FIXME: opt & kw args in the bindings
+ (emit-bindings #f req gensyms allocation self emit-code)
+ (if src
+ (emit-code #f (make-glil-source src)))
+ (comp-fix body (or RA new-RA))
+ (emit-code #f (make-glil-unbind))
+ (lp alternate)))
+ (emit-label POST)))))))
+ vals
+ gensyms)
+ ;; Emit bindings metadata for closures
+ (let ((binds (let lp ((out '()) (gensyms gensyms) (names names))
+ (cond ((null? gensyms) (reverse! out))
+ ((assq (car gensyms) fix-labels)
+ (lp out (cdr gensyms) (cdr names)))
+ (else
+ (lp (acons (car gensyms) (car names) out)
+ (cdr gensyms) (cdr names)))))))
+ (emit-bindings src (map cdr binds) (map car binds)
+ allocation self emit-code))
+ ;; Now go back and fix up the bindings for closures.
+ (for-each
+ (lambda (x v)
+ (let ((free-locs (if (hashq-ref allocation x)
+ (cdr (hashq-ref allocation x))
+ ;; can hit this latter case for labels allocation
+ '())))
+ (if (not (null? free-locs))
+ (begin
+ (for-each
+ (lambda (loc)
+ (pmatch loc
+ ((,local? ,boxed? . ,n)
+ (emit-code #f (make-glil-lexical local? #f 'ref n)))
+ (else (error "bad free var allocation" x loc))))
+ free-locs)
+ (pmatch (hashq-ref (hashq-ref allocation v) self)
+ ((#t #f . ,n)
+ (emit-code #f (make-glil-lexical #t #f 'fix n)))
+ (,loc (error "bad fix var allocation" x loc)))))))
+ vals
+ gensyms)
+ (comp-tail body)
+ (if new-RA
+ (emit-label new-RA))
+ (clear-stack-slots context gensyms)
+ (emit-code #f (make-glil-unbind))))
+
+ ((<let-values> src exp body)
+ (record-case body
+ ((<lambda-case> req opt kw rest gensyms body alternate)
+ (if (or opt kw alternate)
+ (error "unexpected lambda-case in let-values" x))
+ (let ((MV (make-label)))
+ (comp-vals exp MV)
+ (emit-code #f (make-glil-const 1))
+ (emit-label MV)
+ (emit-code src (make-glil-mv-bind
+ (vars->bind-list
+ (append req (if rest (list rest) '()))
+ gensyms allocation self)
+ (and rest #t)))
+ (for-each (lambda (v)
+ (pmatch (hashq-ref (hashq-ref allocation v) self)
+ ((#t #f . ,n)
+ (emit-code src (make-glil-lexical #t #f 'set n)))
+ ((#t #t . ,n)
+ (emit-code src (make-glil-lexical #t #t 'box n)))
+ (,loc (error "bad let-values var allocation" x loc))))
+ (reverse gensyms))
+ (comp-tail body)
+ (clear-stack-slots context gensyms)
+ (emit-code #f (make-glil-unbind))))))
+
+ ;; much trickier than i thought this would be, at first, due to the need
+ ;; to have body's return value(s) on the stack while the unwinder runs,
+ ;; then proceed with returning or dropping or what-have-you, interacting
+ ;; with RA and MVRA. What have you, I say.
+ ((<dynwind> src body winder unwinder)
+ (comp-push winder)
+ (comp-push unwinder)
+ (comp-drop (make-application src winder '()))
+ (emit-code #f (make-glil-call 'wind 2))
+
+ (case context
+ ((tail)
+ (let ((MV (make-label)))
+ (comp-vals body MV)
+ ;; one value: unwind...
+ (emit-code #f (make-glil-call 'unwind 0))
+ (comp-drop (make-application src unwinder '()))
+ ;; ...and return the val
+ (emit-code #f (make-glil-call 'return 1))
+
+ (emit-label MV)
+ ;; multiple values: unwind...
+ (emit-code #f (make-glil-call 'unwind 0))
+ (comp-drop (make-application src unwinder '()))
+ ;; and return the values.
+ (emit-code #f (make-glil-call 'return/nvalues 1))))
+
+ ((push)
+ ;; we only want one value. so ask for one value
+ (comp-push body)
+ ;; and unwind, leaving the val on the stack
+ (emit-code #f (make-glil-call 'unwind 0))
+ (comp-drop (make-application src unwinder '())))
+
+ ((vals)
+ (let ((MV (make-label)))
+ (comp-vals body MV)
+ ;; one value: push 1 and fall through to MV case
+ (emit-code #f (make-glil-const 1))
+
+ (emit-label MV)
+ ;; multiple values: unwind...
+ (emit-code #f (make-glil-call 'unwind 0))
+ (comp-drop (make-application src unwinder '()))
+ ;; and goto the MVRA.
+ (emit-branch #f 'br MVRA)))
+
+ ((drop)
+ ;; compile body, discarding values. then unwind...
+ (comp-drop body)
+ (emit-code #f (make-glil-call 'unwind 0))
+ (comp-drop (make-application src unwinder '()))
+ ;; and fall through, or goto RA if there is one.
+ (if RA
+ (emit-branch #f 'br RA)))))
+
+ ((<dynlet> src fluids vals body)
+ (for-each comp-push fluids)
+ (for-each comp-push vals)
+ (emit-code #f (make-glil-call 'wind-fluids (length fluids)))
+
+ (case context
+ ((tail)
+ (let ((MV (make-label)))
+ ;; NB: in tail case, it is possible to preserve asymptotic tail
+ ;; recursion, via merging unwind-fluids structures -- but we'd need
+ ;; to compile in the body twice (once in tail context, assuming the
+ ;; caller unwinds, and once with this trampoline thing, unwinding
+ ;; ourselves).
+ (comp-vals body MV)
+ ;; one value: unwind and return
+ (emit-code #f (make-glil-call 'unwind-fluids 0))
+ (emit-code #f (make-glil-call 'return 1))
+
+ (emit-label MV)
+ ;; multiple values: unwind and return values
+ (emit-code #f (make-glil-call 'unwind-fluids 0))
+ (emit-code #f (make-glil-call 'return/nvalues 1))))
+
+ ((push)
+ (comp-push body)
+ (emit-code #f (make-glil-call 'unwind-fluids 0)))
+
+ ((vals)
+ (let ((MV (make-label)))
+ (comp-vals body MV)
+ ;; one value: push 1 and fall through to MV case
+ (emit-code #f (make-glil-const 1))
+
+ (emit-label MV)
+ ;; multiple values: unwind and goto MVRA
+ (emit-code #f (make-glil-call 'unwind-fluids 0))
+ (emit-branch #f 'br MVRA)))
+
+ ((drop)
+ ;; compile body, discarding values. then unwind...
+ (comp-drop body)
+ (emit-code #f (make-glil-call 'unwind-fluids 0))
+ ;; and fall through, or goto RA if there is one.
+ (if RA
+ (emit-branch #f 'br RA)))))
+
+ ((<dynref> src fluid)
+ (case context
+ ((drop)
+ (comp-drop fluid))
+ ((push vals tail)
+ (comp-push fluid)
+ (emit-code #f (make-glil-call 'fluid-ref 1))))
+ (maybe-emit-return))
+
+ ((<dynset> src fluid exp)
+ (comp-push fluid)
+ (comp-push exp)
+ (emit-code #f (make-glil-call 'fluid-set 2))
+ (case context
+ ((push vals tail)
+ (emit-code #f (make-glil-void))))
+ (maybe-emit-return))
+
+ ;; What's the deal here? The deal is that we are compiling the start of a
+ ;; delimited continuation. We try to avoid heap allocation in the normal
+ ;; case; so the body is an expression, not a thunk, and we try to render
+ ;; the handler inline. Also we did some analysis, in analyze.scm, so that
+ ;; if the continuation isn't referenced, we don't reify it. This makes it
+ ;; possible to implement catch and throw with delimited continuations,
+ ;; without any overhead.
+ ((<prompt> src tag body handler)
+ (let ((H (make-label))
+ (POST (make-label))
+ (escape-only? (hashq-ref allocation x)))
+ ;; First, set up the prompt.
+ (comp-push tag)
+ (emit-code src (make-glil-prompt H escape-only?))
+
+ ;; Then we compile the body, with its normal return path, unwinding
+ ;; before proceeding.
+ (case context
+ ((tail)
+ (let ((MV (make-label)))
+ (comp-vals body MV)
+ ;; one value: unwind and return
+ (emit-code #f (make-glil-call 'unwind 0))
+ (emit-code #f (make-glil-call 'return 1))
+ ;; multiple values: unwind and return
+ (emit-label MV)
+ (emit-code #f (make-glil-call 'unwind 0))
+ (emit-code #f (make-glil-call 'return/nvalues 1))))
+
+ ((push)
+ ;; we only want one value. so ask for one value, unwind, and jump to
+ ;; post
+ (comp-push body)
+ (emit-code #f (make-glil-call 'unwind 0))
+ (emit-branch #f 'br (or RA POST)))
+
+ ((vals)
+ (let ((MV (make-label)))
+ (comp-vals body MV)
+ ;; one value: push 1 and fall through to MV case
+ (emit-code #f (make-glil-const 1))
+ ;; multiple values: unwind and goto MVRA
+ (emit-label MV)
+ (emit-code #f (make-glil-call 'unwind 0))
+ (emit-branch #f 'br MVRA)))
+
+ ((drop)
+ ;; compile body, discarding values, then unwind & fall through.
+ (comp-drop body)
+ (emit-code #f (make-glil-call 'unwind 0))
+ (emit-branch #f 'br (or RA POST))))
+
+ (emit-label H)
+ ;; Now the handler. The stack is now made up of the continuation, and
+ ;; then the args to the continuation (pushed separately), and then the
+ ;; number of args, including the continuation.
+ (record-case handler
+ ((<lambda-case> req opt kw rest gensyms body alternate)
+ (if (or opt kw alternate)
+ (error "unexpected lambda-case in prompt" x))
+ (emit-code src (make-glil-mv-bind
+ (vars->bind-list
+ (append req (if rest (list rest) '()))
+ gensyms allocation self)
+ (and rest #t)))
+ (for-each (lambda (v)
+ (pmatch (hashq-ref (hashq-ref allocation v) self)
+ ((#t #f . ,n)
+ (emit-code src (make-glil-lexical #t #f 'set n)))
+ ((#t #t . ,n)
+ (emit-code src (make-glil-lexical #t #t 'box n)))
+ (,loc
+ (error "bad prompt handler arg allocation" x loc))))
+ (reverse gensyms))
+ (comp-tail body)
+ (emit-code #f (make-glil-unbind))))
+
+ (if (and (not RA)
+ (or (eq? context 'push) (eq? context 'drop)))
+ (emit-label POST))))
+
+ ((<abort> src tag args tail)
+ (comp-push tag)
+ (for-each comp-push args)
+ (comp-push tail)
+ (emit-code src (make-glil-call 'abort (length args)))
+ ;; so, the abort can actually return. if it does, the values will be on
+ ;; the stack, then the MV marker, just as in an MV context.
+ (case context
+ ((tail)
+ ;; Return values.
+ (emit-code #f (make-glil-call 'return/nvalues 1)))
+ ((drop)
+ ;; Drop all values and goto RA, or otherwise fall through.
+ (emit-code #f (make-glil-mv-bind 0 #f))
+ (if RA (emit-branch #f 'br RA)))
+ ((push)
+ ;; Truncate to one value.
+ (emit-code #f (make-glil-mv-bind 1 #f)))
+ ((vals)
+ ;; Go to MVRA.
+ (emit-branch #f 'br MVRA)))))))
+;;; Common Subexpression Elimination (CSE) on Tree-IL
+
+;; Copyright (C) 2011, 2012, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (language tree-il cse)
+ #\use-module (language tree-il)
+ #\use-module (language tree-il primitives)
+ #\use-module (language tree-il effects)
+ #\use-module (ice-9 vlist)
+ #\use-module (ice-9 match)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-11)
+ #\use-module (srfi srfi-26)
+ #\export (cse))
+
+;;;
+;;; This pass eliminates common subexpressions in Tree-IL. It works
+;;; best locally -- within a function -- so it is meant to be run after
+;;; partial evaluation, which usually inlines functions and so opens up
+;;; a bigger space for CSE to work.
+;;;
+;;; The algorithm traverses the tree of expressions, returning two
+;;; values: the newly rebuilt tree, and a "database". The database is
+;;; the set of expressions that will have been evaluated as part of
+;;; evaluating an expression. For example, in:
+;;;
+;;; (1- (+ (if a b c) (* x y)))
+;;;
+;;; We can say that when it comes time to evaluate (1- <>), that the
+;;; subexpressions +, x, y, and (* x y) must have been evaluated in
+;;; values context. We know that a was evaluated in test context, but
+;;; we don't know if it was true or false.
+;;;
+;;; The expressions in the database /dominate/ any subsequent
+;;; expression: FOO dominates BAR if evaluation of BAR implies that any
+;;; effects associated with FOO have already occured.
+;;;
+;;; When adding expressions to the database, we record the context in
+;;; which they are evaluated. We treat expressions in test context
+;;; specially: the presence of such an expression indicates that the
+;;; expression is true. In this way we can elide duplicate predicates.
+;;;
+;;; Duplicate predicates are not common in code that users write, but
+;;; can occur quite frequently in macro-generated code.
+;;;
+;;; For example:
+;;;
+;;; (and (foo? x) (foo-bar x))
+;;; => (if (and (struct? x) (eq? (struct-vtable x) <foo>))
+;;; (if (and (struct? x) (eq? (struct-vtable x) <foo>))
+;;; (struct-ref x 1)
+;;; (throw 'not-a-foo))
+;;; #f))
+;;; => (if (and (struct? x) (eq? (struct-vtable x) <foo>))
+;;; (struct-ref x 1)
+;;; #f)
+;;;
+;;; A conditional bailout in effect context also has the effect of
+;;; adding predicates to the database:
+;;;
+;;; (begin (foo-bar x) (foo-baz x))
+;;; => (begin
+;;; (if (and (struct? x) (eq? (struct-vtable x) <foo>))
+;;; (struct-ref x 1)
+;;; (throw 'not-a-foo))
+;;; (if (and (struct? x) (eq? (struct-vtable x) <foo>))
+;;; (struct-ref x 2)
+;;; (throw 'not-a-foo)))
+;;; => (begin
+;;; (if (and (struct? x) (eq? (struct-vtable x) <foo>))
+;;; (struct-ref x 1)
+;;; (throw 'not-a-foo))
+;;; (struct-ref x 2))
+;;;
+;;; When removing code, we have to ensure that the semantics of the
+;;; source program and the residual program are the same. It's easy to
+;;; ensure that they have the same value, because those manipulations
+;;; are just algebraic, but the tricky thing is to ensure that the
+;;; expressions exhibit the same ordering of effects. For that, we use
+;;; the effects analysis of (language tree-il effects). We only
+;;; eliminate code if the duplicate code commutes with all of the
+;;; dominators on the path from the duplicate to the original.
+;;;
+;;; The implementation uses vhashes as the fundamental data structure.
+;;; This can be seen as a form of global value numbering. This
+;;; algorithm currently spends most of its time in vhash-assoc. I'm not
+;;; sure whether that is due to our bad hash function in Guile 2.0, an
+;;; inefficiency in vhashes, or what. Overall though the complexity
+;;; should be linear, or N log N -- whatever vhash-assoc's complexity
+;;; is. Walking the dominators is nonlinear, but that only happens when
+;;; we've actually found a common subexpression so that should be OK.
+;;;
+
+;; Logging helpers, as in peval.
+;;
+(define-syntax *logging* (identifier-syntax #f))
+;; (define %logging #f)
+;; (define-syntax *logging* (identifier-syntax %logging))
+(define-syntax log
+ (syntax-rules (quote)
+ ((log 'event arg ...)
+ (if (and *logging*
+ (or (eq? *logging* #t)
+ (memq 'event *logging*)))
+ (log* 'event arg ...)))))
+(define (log* event . args)
+ (let ((pp (module-ref (resolve-interface '(ice-9 pretty-print))
+ 'pretty-print)))
+ (pp `(log ,event . ,args))
+ (newline)
+ (values)))
+
+;; A pre-pass on the source program to determine the set of assigned
+;; lexicals.
+;;
+(define* (build-assigned-var-table exp #\optional (table vlist-null))
+ (tree-il-fold
+ (lambda (exp res)
+ res)
+ (lambda (exp res)
+ (match exp
+ (($ <lexical-set> src name gensym exp)
+ (vhash-consq gensym #t res))
+ (_ res)))
+ (lambda (exp res) res)
+ table exp))
+
+(define (boolean-valued-primitive? primitive)
+ (or (negate-primitive primitive)
+ (eq? primitive 'not)
+ (let ((chars (symbol->string primitive)))
+ (eqv? (string-ref chars (1- (string-length chars)))
+ #\?))))
+
+(define (boolean-valued-expression? x ctx)
+ (match x
+ (($ <application> _
+ ($ <primitive-ref> _ (? boolean-valued-primitive?))) #t)
+ (($ <const> _ (? boolean?)) #t)
+ (_ (eq? ctx 'test))))
+
+(define (singly-valued-expression? x ctx)
+ (match x
+ (($ <const>) #t)
+ (($ <lexical-ref>) #t)
+ (($ <void>) #t)
+ (($ <lexical-ref>) #t)
+ (($ <primitive-ref>) #t)
+ (($ <module-ref>) #t)
+ (($ <toplevel-ref>) #t)
+ (($ <application> _
+ ($ <primitive-ref> _ (? singly-valued-primitive?))) #t)
+ (($ <application> _ ($ <primitive-ref> _ 'values) (val)) #t)
+ (($ <lambda>) #t)
+ (_ (eq? ctx 'value))))
+
+(define* (cse exp)
+ "Eliminate common subexpressions in EXP."
+
+ (define assigned-lexical?
+ (let ((table (build-assigned-var-table exp)))
+ (lambda (sym)
+ (vhash-assq sym table))))
+
+ (define %compute-effects
+ (make-effects-analyzer assigned-lexical?))
+
+ (define (negate exp ctx)
+ (match exp
+ (($ <const> src x)
+ (make-const src (not x)))
+ (($ <void> src)
+ (make-const src #f))
+ (($ <conditional> src test consequent alternate)
+ (make-conditional src test (negate consequent ctx) (negate alternate ctx)))
+ (($ <application> _ ($ <primitive-ref> _ 'not)
+ ((and x (? (cut boolean-valued-expression? <> ctx)))))
+ x)
+ (($ <application> src
+ ($ <primitive-ref> _ (and pred (? negate-primitive)))
+ args)
+ (make-application src
+ (make-primitive-ref #f (negate-primitive pred))
+ args))
+ (_
+ (make-application #f (make-primitive-ref #f 'not) (list exp)))))
+
+
+ (define (hasher n)
+ (lambda (x size) (modulo n size)))
+
+ (define (add-to-db exp effects ctx db)
+ (let ((v (vector exp effects ctx))
+ (h (tree-il-hash exp)))
+ (vhash-cons v h db (hasher h))))
+
+ (define (control-flow-boundary db)
+ (let ((h (hashq 'lambda most-positive-fixnum)))
+ (vhash-cons 'lambda h db (hasher h))))
+
+ (define (find-dominating-expression exp effects ctx db)
+ (define (entry-matches? v1 v2)
+ (match (if (vector? v1) v1 v2)
+ (#(exp* effects* ctx*)
+ (and (tree-il=? exp exp*)
+ (or (not ctx) (eq? ctx* ctx))))
+ (_ #f)))
+
+ (let ((len (vlist-length db))
+ (h (tree-il-hash exp)))
+ (and (vhash-assoc #t db entry-matches? (hasher h))
+ (let lp ((n 0))
+ (and (< n len)
+ (match (vlist-ref db n)
+ (('lambda . h*)
+ ;; We assume that lambdas can escape and thus be
+ ;; called from anywhere. Thus code inside a lambda
+ ;; only has a dominating expression if it does not
+ ;; depend on any effects.
+ (and (not (depends-on-effects? effects &all-effects))
+ (lp (1+ n))))
+ ((#(exp* effects* ctx*) . h*)
+ (log 'walk (unparse-tree-il exp) effects
+ (unparse-tree-il exp*) effects* ctx*)
+ (or (and (= h h*)
+ (or (not ctx) (eq? ctx ctx*))
+ (tree-il=? exp exp*))
+ (and (effects-commute? effects effects*)
+ (lp (1+ n)))))))))))
+
+ ;; Return #t if EXP is dominated by an instance of itself. In that
+ ;; case, we can exclude *type-check* effects, because the first
+ ;; expression already caused them if needed.
+ (define (has-dominating-effect? exp effects db)
+ (or (constant? effects)
+ (and
+ (effect-free?
+ (exclude-effects effects
+ (logior &zero-values
+ &allocation
+ &type-check)))
+ (find-dominating-expression exp effects #f db))))
+
+ (define (find-dominating-test exp effects db)
+ (and
+ (effect-free?
+ (exclude-effects effects (logior &allocation
+ &type-check)))
+ (match exp
+ (($ <const> src val)
+ (if (boolean? val)
+ exp
+ (make-const src (not (not val)))))
+ ;; For (not FOO), try to prove FOO, then negate the result.
+ (($ <application> src ($ <primitive-ref> _ 'not) (exp*))
+ (match (find-dominating-test exp* effects db)
+ (($ <const> _ val)
+ (log 'inferring exp (not val))
+ (make-const src (not val)))
+ (_
+ #f)))
+ (_
+ (cond
+ ((find-dominating-expression exp effects 'test db)
+ ;; We have an EXP fact, so we infer #t.
+ (log 'inferring exp #t)
+ (make-const (tree-il-src exp) #t))
+ ((find-dominating-expression (negate exp 'test) effects 'test db)
+ ;; We have a (not EXP) fact, so we infer #f.
+ (log 'inferring exp #f)
+ (make-const (tree-il-src exp) #f))
+ (else
+ ;; Otherwise we don't know.
+ #f))))))
+
+ (define (add-to-env exp name sym db env)
+ (let* ((v (vector exp name sym (vlist-length db)))
+ (h (tree-il-hash exp)))
+ (vhash-cons v h env (hasher h))))
+
+ (define (augment-env env names syms exps db)
+ (if (null? names)
+ env
+ (let ((name (car names)) (sym (car syms)) (exp (car exps)))
+ (augment-env (if (or (assigned-lexical? sym)
+ (lexical-ref? exp))
+ env
+ (add-to-env exp name sym db env))
+ (cdr names) (cdr syms) (cdr exps) db))))
+
+ (define (find-dominating-lexical exp effects env db)
+ (define (entry-matches? v1 v2)
+ (match (if (vector? v1) v1 v2)
+ (#(exp* name sym db)
+ (tree-il=? exp exp*))
+ (_ #f)))
+
+ (define (unroll db base n)
+ (or (zero? n)
+ (match (vlist-ref db base)
+ (('lambda . h*)
+ ;; See note in find-dominating-expression.
+ (and (not (depends-on-effects? effects &all-effects))
+ (unroll db (1+ base) (1- n))))
+ ((#(exp* effects* ctx*) . h*)
+ (and (effects-commute? effects effects*)
+ (unroll db (1+ base) (1- n)))))))
+
+ (let ((h (tree-il-hash exp)))
+ (and (effect-free? (exclude-effects effects &type-check))
+ (vhash-assoc exp env entry-matches? (hasher h))
+ (let ((env-len (vlist-length env))
+ (db-len (vlist-length db)))
+ (let lp ((n 0) (m 0))
+ (and (< n env-len)
+ (match (vlist-ref env n)
+ ((#(exp* name sym db-len*) . h*)
+ (let ((niter (- (- db-len db-len*) m)))
+ (and (unroll db m niter)
+ (if (and (= h h*) (tree-il=? exp* exp))
+ (make-lexical-ref (tree-il-src exp) name sym)
+ (lp (1+ n) (- db-len db-len*)))))))))))))
+
+ (define (lookup-lexical sym env)
+ (let ((env-len (vlist-length env)))
+ (let lp ((n 0))
+ (and (< n env-len)
+ (match (vlist-ref env n)
+ ((#(exp _ sym* _) . _)
+ (if (eq? sym sym*)
+ exp
+ (lp (1+ n)))))))))
+
+ (define (intersection db+ db-)
+ (vhash-fold-right
+ (lambda (k h out)
+ (if (vhash-assoc k db- equal? (hasher h))
+ (vhash-cons k h out (hasher h))
+ out))
+ vlist-null
+ db+))
+
+ (define (concat db1 db2)
+ (vhash-fold-right (lambda (k h tail)
+ (vhash-cons k h tail (hasher h)))
+ db2 db1))
+
+ (let visit ((exp exp)
+ (db vlist-null) ; dominating expressions: #(exp effects ctx) -> hash
+ (env vlist-null) ; named expressions: #(exp name sym db) -> hash
+ (ctx 'values)) ; test, effect, value, or values
+
+ (define (parallel-visit exps db env ctx)
+ (let lp ((in exps) (out '()) (db* vlist-null))
+ (if (pair? in)
+ (call-with-values (lambda () (visit (car in) db env ctx))
+ (lambda (x db**)
+ (lp (cdr in) (cons x out) (concat db** db*))))
+ (values (reverse out) db*))))
+
+ (define (compute-effects exp)
+ (%compute-effects exp (lambda (sym) (lookup-lexical sym env))))
+
+ (define (bailout? exp)
+ (causes-effects? (compute-effects exp) &definite-bailout))
+
+ (define (return exp db*)
+ (let ((effects (compute-effects exp)))
+ (cond
+ ((and (eq? ctx 'effect)
+ (not (lambda-case? exp))
+ (or (effect-free?
+ (exclude-effects effects
+ (logior &zero-values
+ &allocation)))
+ (has-dominating-effect? exp effects db)))
+ (cond
+ ((void? exp)
+ (values exp db*))
+ (else
+ (log 'elide ctx (unparse-tree-il exp))
+ (values (make-void #f) db*))))
+ ((and (boolean-valued-expression? exp ctx)
+ (find-dominating-test exp effects db))
+ => (lambda (exp)
+ (log 'propagate-test ctx (unparse-tree-il exp))
+ (values exp db*)))
+ ((and (singly-valued-expression? exp ctx)
+ (find-dominating-lexical exp effects env db))
+ => (lambda (exp)
+ (log 'propagate-value ctx (unparse-tree-il exp))
+ (values exp db*)))
+ ((and (constant? effects) (memq ctx '(value values)))
+ ;; Adds nothing to the db.
+ (values exp db*))
+ (else
+ (log 'return ctx effects (unparse-tree-il exp) db*)
+ (values exp
+ (add-to-db exp effects ctx db*))))))
+
+ (log 'visit ctx (unparse-tree-il exp) db env)
+
+ (match exp
+ (($ <const>)
+ (return exp vlist-null))
+ (($ <void>)
+ (return exp vlist-null))
+ (($ <lexical-ref> _ _ gensym)
+ (return exp vlist-null))
+ (($ <lexical-set> src name gensym exp)
+ (let*-values (((exp db*) (visit exp db env 'value)))
+ (return (make-lexical-set src name gensym exp)
+ db*)))
+ (($ <let> src names gensyms vals body)
+ (let*-values (((vals db*) (parallel-visit vals db env 'value))
+ ((body db**) (visit body (concat db* db)
+ (augment-env env names gensyms vals db)
+ ctx)))
+ (return (make-let src names gensyms vals body)
+ (concat db** db*))))
+ (($ <letrec> src in-order? names gensyms vals body)
+ (let*-values (((vals db*) (parallel-visit vals db env 'value))
+ ((body db**) (visit body (concat db* db)
+ (augment-env env names gensyms vals db)
+ ctx)))
+ (return (make-letrec src in-order? names gensyms vals body)
+ (concat db** db*))))
+ (($ <fix> src names gensyms vals body)
+ (let*-values (((vals db*) (parallel-visit vals db env 'value))
+ ((body db**) (visit body (concat db* db) env ctx)))
+ (return (make-fix src names gensyms vals body)
+ (concat db** db*))))
+ (($ <let-values> src producer consumer)
+ (let*-values (((producer db*) (visit producer db env 'values))
+ ((consumer db**) (visit consumer (concat db* db) env ctx)))
+ (return (make-let-values src producer consumer)
+ (concat db** db*))))
+ (($ <dynwind> src winder body unwinder)
+ (let*-values (((pre db*) (visit winder db env 'value))
+ ((body db**) (visit body (concat db* db) env ctx))
+ ((post db***) (visit unwinder db env 'value)))
+ (return (make-dynwind src pre body post)
+ (concat db* (concat db** db***)))))
+ (($ <dynlet> src fluids vals body)
+ (let*-values (((fluids db*) (parallel-visit fluids db env 'value))
+ ((vals db**) (parallel-visit vals db env 'value))
+ ((body db***) (visit body (concat db** (concat db* db))
+ env ctx)))
+ (return (make-dynlet src fluids vals body)
+ (concat db*** (concat db** db*)))))
+ (($ <dynref> src fluid)
+ (let*-values (((fluid db*) (visit fluid db env 'value)))
+ (return (make-dynref src fluid)
+ db*)))
+ (($ <dynset> src fluid exp)
+ (let*-values (((fluid db*) (visit fluid db env 'value))
+ ((exp db**) (visit exp db env 'value)))
+ (return (make-dynset src fluid exp)
+ (concat db** db*))))
+ (($ <toplevel-ref>)
+ (return exp vlist-null))
+ (($ <module-ref>)
+ (return exp vlist-null))
+ (($ <module-set> src mod name public? exp)
+ (let*-values (((exp db*) (visit exp db env 'value)))
+ (return (make-module-set src mod name public? exp)
+ db*)))
+ (($ <toplevel-define> src name exp)
+ (let*-values (((exp db*) (visit exp db env 'value)))
+ (return (make-toplevel-define src name exp)
+ db*)))
+ (($ <toplevel-set> src name exp)
+ (let*-values (((exp db*) (visit exp db env 'value)))
+ (return (make-toplevel-set src name exp)
+ db*)))
+ (($ <primitive-ref>)
+ (return exp vlist-null))
+ (($ <conditional> src test consequent alternate)
+ (let*-values
+ (((test db+) (visit test db env 'test))
+ ((converse db-) (visit (negate test 'test) db env 'test))
+ ((consequent db++) (visit consequent (concat db+ db) env ctx))
+ ((alternate db--) (visit alternate (concat db- db) env ctx)))
+ (match (make-conditional src test consequent alternate)
+ (($ <conditional> _ ($ <const> _ exp))
+ (if exp
+ (return consequent (concat db++ db+))
+ (return alternate (concat db-- db-))))
+ ;; (if FOO A A) => (begin FOO A)
+ (($ <conditional> src _
+ ($ <const> _ a) ($ <const> _ (? (cut equal? a <>))))
+ (visit (make-sequence #f (list test (make-const #f a)))
+ db env ctx))
+ ;; (if FOO #t #f) => FOO for boolean-valued FOO.
+ (($ <conditional> src
+ (? (cut boolean-valued-expression? <> ctx))
+ ($ <const> _ #t) ($ <const> _ #f))
+ (return test db+))
+ ;; (if FOO #f #t) => (not FOO)
+ (($ <conditional> src _ ($ <const> _ #f) ($ <const> _ #t))
+ (visit (negate test ctx) db env ctx))
+
+ ;; Allow "and"-like conditions to accumulate in test context.
+ ((and c ($ <conditional> _ _ _ ($ <const> _ #f)))
+ (return c (if (eq? ctx 'test) (concat db++ db+) vlist-null)))
+ ((and c ($ <conditional> _ _ ($ <const> _ #f) _))
+ (return c (if (eq? ctx 'test) (concat db-- db-) vlist-null)))
+
+ ;; Conditional bailouts turn expressions into predicates.
+ ((and c ($ <conditional> _ _ _ (? bailout?)))
+ (return c (concat db++ db+)))
+ ((and c ($ <conditional> _ _ (? bailout?) _))
+ (return c (concat db-- db-)))
+
+ (c
+ (return c (intersection (concat db++ db+) (concat db-- db-)))))))
+ (($ <application> src proc args)
+ (let*-values (((proc db*) (visit proc db env 'value))
+ ((args db**) (parallel-visit args db env 'value)))
+ (return (make-application src proc args)
+ (concat db** db*))))
+ (($ <lambda> src meta body)
+ (let*-values (((body _) (if body
+ (visit body (control-flow-boundary db)
+ env 'values)
+ (values #f #f))))
+ (return (make-lambda src meta body)
+ vlist-null)))
+ (($ <lambda-case> src req opt rest kw inits gensyms body alt)
+ (let*-values (((inits _) (parallel-visit inits db env 'value))
+ ((body db*) (visit body db env ctx))
+ ((alt _) (if alt
+ (visit alt db env ctx)
+ (values #f #f))))
+ (return (make-lambda-case src req opt rest kw inits gensyms body alt)
+ (if alt vlist-null db*))))
+ (($ <sequence> src exps)
+ (let lp ((in exps) (out '()) (db* vlist-null))
+ (match in
+ ((last)
+ (let*-values (((last db**) (visit last (concat db* db) env ctx)))
+ (if (null? out)
+ (return last (concat db** db*))
+ (return (make-sequence src (reverse (cons last out)))
+ (concat db** db*)))))
+ ((head . rest)
+ (let*-values (((head db**) (visit head (concat db* db) env 'effect)))
+ (cond
+ ((sequence? head)
+ (lp (append (sequence-exps head) rest) out db*))
+ ((void? head)
+ (lp rest out db*))
+ (else
+ (lp rest (cons head out) (concat db** db*)))))))))
+ (($ <prompt> src tag body handler)
+ (let*-values (((tag db*) (visit tag db env 'value))
+ ((body _) (visit body (concat db* db) env 'values))
+ ((handler _) (visit handler (concat db* db) env ctx)))
+ (return (make-prompt src tag body handler)
+ db*)))
+ (($ <abort> src tag args tail)
+ (let*-values (((tag db*) (visit tag db env 'value))
+ ((args db**) (parallel-visit args db env 'value))
+ ((tail db***) (visit tail db env 'value)))
+ (return (make-abort src tag args tail)
+ (concat db* (concat db** db***))))))))
+;;; Tree-IL verifier
+
+;; Copyright (C) 2011, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (language tree-il debug)
+ #\use-module (language tree-il)
+ #\use-module (ice-9 match)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-26)
+ #\export (verify-tree-il))
+
+(define (verify-tree-il exp)
+ (define seen-gensyms (make-hash-table))
+ (define (add sym env)
+ (if (hashq-ref seen-gensyms sym)
+ (error "duplicate gensym" sym)
+ (begin
+ (hashq-set! seen-gensyms sym #t)
+ (cons sym env))))
+ (define (add-env new env)
+ (if (null? new)
+ env
+ (add-env (cdr new) (add (car new) env))))
+
+ (let visit ((exp exp)
+ (env '()))
+ (match exp
+ (($ <lambda-case> src req opt rest kw inits gensyms body alt)
+ (cond
+ ((not (and (list? req) (and-map symbol? req)))
+ (error "bad required args (should be list of symbols)" exp))
+ ((and opt (not (and (list? opt) (and-map symbol? opt))))
+ (error "bad optionals (should be #f or list of symbols)" exp))
+ ((and rest (not (symbol? rest)))
+ (error "bad required args (should be #f or symbol)" exp))
+ ((and kw (not (match kw
+ ((aok . kwlist)
+ (and (list? kwlist)
+ (and-map
+ (lambda (x)
+ (match x
+ (((? keyword?) (? symbol?) (? symbol? sym))
+ (memq sym gensyms))
+ (_ #f)))
+ kwlist)))
+ (_ #f))))
+ (error "bad keywords (should be #f or (aok (kw name sym) ...))" exp))
+ ((not (and (list? gensyms) (and-map symbol? gensyms)))
+ (error "bad gensyms (should be list of symbols)" exp))
+ ((not (and (list? gensyms) (and-map symbol? gensyms)))
+ (error "bad gensyms (should be list of symbols)" exp))
+ ((not (= (length gensyms)
+ (+ (length req)
+ (if opt (length opt) 0)
+ ;; FIXME: technically possible for kw gensyms to
+ ;; alias other gensyms
+ (if rest 1 0)
+ (if kw (1- (length kw)) 0))))
+ (error "unexpected gensyms length" exp))
+ (else
+ (let lp ((env (add-env (take gensyms (length req)) env))
+ (nopt (if opt (length opt) 0))
+ (inits inits)
+ (tail (drop gensyms (length req))))
+ (if (zero? nopt)
+ (let lp ((env (if rest (add (car tail) env) env))
+ (inits inits)
+ (tail (if rest (cdr tail) tail)))
+ (if (pair? inits)
+ (begin
+ (visit (car inits) env)
+ (lp (add (car tail) env) (cdr inits)
+ (cdr tail)))
+ (visit body env)))
+ (begin
+ (visit (car inits) env)
+ (lp (add (car tail) env)
+ (1- nopt)
+ (cdr inits)
+ (cdr tail)))))
+ (if alt (visit alt env)))))
+ (($ <lexical-ref> src name gensym)
+ (cond
+ ((not (symbol? name))
+ (error "name should be a symbol" name))
+ ((not (hashq-ref seen-gensyms gensym))
+ (error "unbound lexical" exp))
+ ((not (memq gensym env))
+ (error "displaced lexical" exp))))
+ (($ <lexical-set> src name gensym exp)
+ (cond
+ ((not (symbol? name))
+ (error "name should be a symbol" name))
+ ((not (hashq-ref seen-gensyms gensym))
+ (error "unbound lexical" exp))
+ ((not (memq gensym env))
+ (error "displaced lexical" exp))
+ (else
+ (visit exp env))))
+ (($ <lambda> src meta body)
+ (cond
+ ((and meta (not (and (list? meta) (and-map pair? meta))))
+ (error "meta should be alist" meta))
+ ((and body (not (lambda-case? body)))
+ (error "lambda body should be lambda-case" exp))
+ (else
+ (if body
+ (visit body env)))))
+ (($ <let> src names gensyms vals body)
+ (cond
+ ((not (and (list? names) (and-map symbol? names)))
+ (error "names should be list of syms" exp))
+ ((not (and (list? gensyms) (and-map symbol? gensyms)))
+ (error "gensyms should be list of syms" exp))
+ ((not (list? vals))
+ (error "vals should be list" exp))
+ ((not (= (length names) (length gensyms) (length vals)))
+ (error "names, syms, vals should be same length" exp))
+ (else
+ (for-each (cut visit <> env) vals)
+ (visit body (add-env gensyms env)))))
+ (($ <letrec> src in-order? names gensyms vals body)
+ (cond
+ ((not (and (list? names) (and-map symbol? names)))
+ (error "names should be list of syms" exp))
+ ((not (and (list? gensyms) (and-map symbol? gensyms)))
+ (error "gensyms should be list of syms" exp))
+ ((not (list? vals))
+ (error "vals should be list" exp))
+ ((not (= (length names) (length gensyms) (length vals)))
+ (error "names, syms, vals should be same length" exp))
+ (else
+ (let ((env (add-env gensyms env)))
+ (for-each (cut visit <> env) vals)
+ (visit body env)))))
+ (($ <fix> src names gensyms vals body)
+ (cond
+ ((not (and (list? names) (and-map symbol? names)))
+ (error "names should be list of syms" exp))
+ ((not (and (list? gensyms) (and-map symbol? gensyms)))
+ (error "gensyms should be list of syms" exp))
+ ((not (list? vals))
+ (error "vals should be list" exp))
+ ((not (= (length names) (length gensyms) (length vals)))
+ (error "names, syms, vals should be same length" exp))
+ (else
+ (let ((env (add-env gensyms env)))
+ (for-each (cut visit <> env) vals)
+ (visit body env)))))
+ (($ <let-values> src exp body)
+ (cond
+ ((not (lambda-case? body))
+ (error "let-values body should be lambda-case" exp))
+ (else
+ (visit exp env)
+ (visit body env))))
+ (($ <const> src val) #t)
+ (($ <void> src) #t)
+ (($ <toplevel-ref> src name)
+ (cond
+ ((not (symbol? name))
+ (error "name should be a symbol" name))))
+ (($ <module-ref> src mod name public?)
+ (cond
+ ((not (and (list? mod) (and-map symbol? mod)))
+ (error "module name should be list of symbols" exp))
+ ((not (symbol? name))
+ (error "name should be symbol" exp))))
+ (($ <primitive-ref> src name)
+ (cond
+ ((not (symbol? name))
+ (error "name should be symbol" exp))))
+ (($ <toplevel-set> src name exp)
+ (cond
+ ((not (symbol? name))
+ (error "name should be a symbol" name))
+ (else
+ (visit exp env))))
+ (($ <toplevel-define> src name exp)
+ (cond
+ ((not (symbol? name))
+ (error "name should be a symbol" name))
+ (else
+ (visit exp env))))
+ (($ <module-set> src mod name public? exp)
+ (cond
+ ((not (and (list? mod) (and-map symbol? mod)))
+ (error "module name should be list of symbols" exp))
+ ((not (symbol? name))
+ (error "name should be symbol" exp))
+ (else
+ (visit exp env))))
+ (($ <dynlet> src fluids vals body)
+ (cond
+ ((not (list? fluids))
+ (error "fluids should be list" exp))
+ ((not (list? vals))
+ (error "vals should be list" exp))
+ ((not (= (length fluids) (length vals)))
+ (error "mismatch in fluids/vals" exp))
+ (else
+ (for-each (cut visit <> env) fluids)
+ (for-each (cut visit <> env) vals)
+ (visit body env))))
+ (($ <dynwind> src winder body unwinder)
+ (visit winder env)
+ (visit body env)
+ (visit unwinder env))
+ (($ <dynref> src fluid)
+ (visit fluid env))
+ (($ <dynset> src fluid exp)
+ (visit fluid env)
+ (visit exp env))
+ (($ <conditional> src condition subsequent alternate)
+ (visit condition env)
+ (visit subsequent env)
+ (visit alternate env))
+ (($ <application> src proc args)
+ (cond
+ ((not (list? args))
+ (error "expected list of args" args))
+ (else
+ (visit proc env)
+ (for-each (cut visit <> env) args))))
+ (($ <sequence> src exps)
+ (cond
+ ((not (list? exps))
+ (error "expected list of exps" exp))
+ ((null? exps)
+ (error "expected more than one exp" exp))
+ (else
+ (for-each (cut visit <> env) exps))))
+ (($ <prompt> src tag body handler)
+ (visit tag env)
+ (visit body env)
+ (visit handler env))
+ (($ <abort> src tag args tail)
+ (visit tag env)
+ (for-each (cut visit <> env) args)
+ (visit tail env))
+ (_
+ (error "unexpected tree-il" exp)))
+ (let ((src (tree-il-src exp)))
+ (if (and src (not (and (list? src) (and-map pair? src)
+ (and-map symbol? (map car src)))))
+ (error "bad src"))
+ ;; Return it, why not.
+ exp)))
+;;; Effects analysis on Tree-IL
+
+;; Copyright (C) 2011, 2012, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (language tree-il effects)
+ #\use-module (language tree-il)
+ #\use-module (language tree-il primitives)
+ #\use-module (ice-9 match)
+ #\export (make-effects-analyzer
+ &mutable-lexical
+ &toplevel
+ &fluid
+ &definite-bailout
+ &possible-bailout
+ &zero-values
+ &allocation
+ &mutable-data
+ &type-check
+ &all-effects
+ effects-commute?
+ exclude-effects
+ effect-free?
+ constant?
+ depends-on-effects?
+ causes-effects?))
+
+;;;
+;;; Hey, it's some effects analysis! If you invoke
+;;; `make-effects-analyzer', you get a procedure that computes the set
+;;; of effects that an expression depends on and causes. This
+;;; information is useful when writing algorithms that move code around,
+;;; while preserving the semantics of an input program.
+;;;
+;;; The effects set is represented by a bitfield, as a fixnum. The set
+;;; of possible effects is modelled rather coarsely. For example, a
+;;; toplevel reference to FOO is modelled as depending on the &toplevel
+;;; effect, and causing a &type-check effect. If any intervening code
+;;; sets any toplevel variable, that will block motion of FOO.
+;;;
+;;; For each effect, two bits are reserved: one to indicate that an
+;;; expression depends on the effect, and the other to indicate that an
+;;; expression causes the effect.
+;;;
+
+(define-syntax define-effects
+ (lambda (x)
+ (syntax-case x ()
+ ((_ all name ...)
+ (with-syntax (((n ...) (iota (length #'(name ...)))))
+ #'(begin
+ (define-syntax name (identifier-syntax (ash 1 (* n 2))))
+ ...
+ (define-syntax all (identifier-syntax (logior name ...)))))))))
+
+;; Here we define the effects, indicating the meaning of the effect.
+;;
+;; Effects that are described in a "depends on" sense can also be used
+;; in the "causes" sense.
+;;
+;; Effects that are described as causing an effect are not usually used
+;; in a "depends-on" sense. Although the "depends-on" sense is used
+;; when checking for the existence of the "causes" effect, the effects
+;; analyzer will not associate the "depends-on" sense of these effects
+;; with any expression.
+;;
+(define-effects &all-effects
+ ;; Indicates that an expression depends on the value of a mutable
+ ;; lexical variable.
+ &mutable-lexical
+
+ ;; Indicates that an expression depends on the value of a toplevel
+ ;; variable.
+ &toplevel
+
+ ;; Indicates that an expression depends on the value of a fluid
+ ;; variable.
+ &fluid
+
+ ;; Indicates that an expression definitely causes a non-local,
+ ;; non-resumable exit -- a bailout. Only used in the "changes" sense.
+ &definite-bailout
+
+ ;; Indicates that an expression may cause a bailout.
+ &possible-bailout
+
+ ;; Indicates than an expression may return zero values -- a "causes"
+ ;; effect.
+ &zero-values
+
+ ;; Indicates that an expression may return a fresh object -- a
+ ;; "causes" effect.
+ &allocation
+
+ ;; Indicates that an expression depends on the value of a mutable data
+ ;; structure.
+ &mutable-data
+
+ ;; Indicates that an expression may cause a type check. A type check,
+ ;; for the purposes of this analysis, is the possibility of throwing
+ ;; an exception the first time an expression is evaluated. If the
+ ;; expression did not cause an exception to be thrown, users can
+ ;; assume that evaluating the expression again will not cause an
+ ;; exception to be thrown.
+ ;;
+ ;; For example, (+ x y) might throw if X or Y are not numbers. But if
+ ;; it doesn't throw, it should be safe to elide a dominated, common
+ ;; subexpression (+ x y).
+ &type-check)
+
+(define-syntax &no-effects (identifier-syntax 0))
+
+;; Definite bailout is an oddball effect. Since it indicates that an
+;; expression definitely causes bailout, it's not in the set of effects
+;; of a call to an unknown procedure. At the same time, it's also
+;; special in that a definite bailout in a subexpression doesn't always
+;; cause an outer expression to include &definite-bailout in its
+;; effects. For that reason we have to treat it specially.
+;;
+(define-syntax &all-effects-but-bailout
+ (identifier-syntax
+ (logand &all-effects (lognot &definite-bailout))))
+
+(define-inlinable (cause effect)
+ (ash effect 1))
+
+(define-inlinable (&depends-on a)
+ (logand a &all-effects))
+(define-inlinable (&causes a)
+ (logand a (cause &all-effects)))
+
+(define (exclude-effects effects exclude)
+ (logand effects (lognot (cause exclude))))
+(define (effect-free? effects)
+ (zero? (&causes effects)))
+(define (constant? effects)
+ (zero? effects))
+
+(define-inlinable (depends-on-effects? x effects)
+ (not (zero? (logand (&depends-on x) effects))))
+(define-inlinable (causes-effects? x effects)
+ (not (zero? (logand (&causes x) (cause effects)))))
+
+(define-inlinable (effects-commute? a b)
+ (and (not (causes-effects? a (&depends-on b)))
+ (not (causes-effects? b (&depends-on a)))))
+
+(define (make-effects-analyzer assigned-lexical?)
+ "Returns a procedure of type EXP -> EFFECTS that analyzes the effects
+of an expression."
+
+ (let ((cache (make-hash-table)))
+ (define* (compute-effects exp #\optional (lookup (lambda (x) #f)))
+ (define (compute-effects exp)
+ (or (hashq-ref cache exp)
+ (let ((effects (visit exp)))
+ (hashq-set! cache exp effects)
+ effects)))
+
+ (define (accumulate-effects exps)
+ (let lp ((exps exps) (out &no-effects))
+ (if (null? exps)
+ out
+ (lp (cdr exps) (logior out (compute-effects (car exps)))))))
+
+ (define (visit exp)
+ (match exp
+ (($ <const>)
+ &no-effects)
+ (($ <void>)
+ &no-effects)
+ (($ <lexical-ref> _ _ gensym)
+ (if (assigned-lexical? gensym)
+ &mutable-lexical
+ &no-effects))
+ (($ <lexical-set> _ name gensym exp)
+ (logior (cause &mutable-lexical)
+ (compute-effects exp)))
+ (($ <let> _ names gensyms vals body)
+ (logior (if (or-map assigned-lexical? gensyms)
+ (cause &allocation)
+ &no-effects)
+ (accumulate-effects vals)
+ (compute-effects body)))
+ (($ <letrec> _ in-order? names gensyms vals body)
+ (logior (if (or-map assigned-lexical? gensyms)
+ (cause &allocation)
+ &no-effects)
+ (accumulate-effects vals)
+ (compute-effects body)))
+ (($ <fix> _ names gensyms vals body)
+ (logior (if (or-map assigned-lexical? gensyms)
+ (cause &allocation)
+ &no-effects)
+ (accumulate-effects vals)
+ (compute-effects body)))
+ (($ <let-values> _ producer consumer)
+ (logior (compute-effects producer)
+ (compute-effects consumer)
+ (cause &type-check)))
+ (($ <dynwind> _ winder body unwinder)
+ (logior (compute-effects winder)
+ (compute-effects body)
+ (compute-effects unwinder)))
+ (($ <dynlet> _ fluids vals body)
+ (logior (accumulate-effects fluids)
+ (accumulate-effects vals)
+ (cause &type-check)
+ (cause &fluid)
+ (compute-effects body)))
+ (($ <dynref> _ fluid)
+ (logior (compute-effects fluid)
+ (cause &type-check)
+ &fluid))
+ (($ <dynset> _ fluid exp)
+ (logior (compute-effects fluid)
+ (compute-effects exp)
+ (cause &type-check)
+ (cause &fluid)))
+ (($ <toplevel-ref>)
+ (logior &toplevel
+ (cause &type-check)))
+ (($ <module-ref>)
+ (logior &toplevel
+ (cause &type-check)))
+ (($ <module-set> _ mod name public? exp)
+ (logior (cause &toplevel)
+ (cause &type-check)
+ (compute-effects exp)))
+ (($ <toplevel-define> _ name exp)
+ (logior (cause &toplevel)
+ (compute-effects exp)))
+ (($ <toplevel-set> _ name exp)
+ (logior (cause &toplevel)
+ (compute-effects exp)))
+ (($ <primitive-ref>)
+ &no-effects)
+ (($ <conditional> _ test consequent alternate)
+ (let ((tfx (compute-effects test))
+ (cfx (compute-effects consequent))
+ (afx (compute-effects alternate)))
+ (if (causes-effects? (logior tfx (logand afx cfx))
+ &definite-bailout)
+ (logior tfx cfx afx)
+ (exclude-effects (logior tfx cfx afx)
+ &definite-bailout))))
+
+ ;; Zero values.
+ (($ <application> _ ($ <primitive-ref> _ 'values) ())
+ (cause &zero-values))
+
+ ;; Effect-free primitives.
+ (($ <application> _
+ ($ <primitive-ref> _ (or 'values 'eq? 'eqv? 'equal?))
+ args)
+ (accumulate-effects args))
+
+ (($ <application> _
+ ($ <primitive-ref> _ (or 'not 'pair? 'null? 'list? 'symbol?
+ 'vector? 'struct? 'string? 'number?
+ 'char?))
+ (arg))
+ (compute-effects arg))
+
+ ;; Primitives that allocate memory.
+ (($ <application> _ ($ <primitive-ref> _ 'cons) (x y))
+ (logior (compute-effects x) (compute-effects y)
+ (cause &allocation)))
+
+ (($ <application> _ ($ <primitive-ref> _ (or 'list 'vector)) args)
+ (logior (accumulate-effects args) (cause &allocation)))
+
+ (($ <application> _ ($ <primitive-ref> _ 'make-prompt-tag) ())
+ (cause &allocation))
+
+ (($ <application> _ ($ <primitive-ref> _ 'make-prompt-tag) (arg))
+ (logior (compute-effects arg) (cause &allocation)))
+
+ ;; Primitives that are normally effect-free, but which might
+ ;; cause type checks, allocate memory, or access mutable
+ ;; memory. FIXME: expand, to be more precise.
+ (($ <application> _
+ ($ <primitive-ref> _ (and name
+ (? effect-free-primitive?)))
+ args)
+ (logior (accumulate-effects args)
+ (cause &type-check)
+ (if (constructor-primitive? name)
+ (cause &allocation)
+ (if (accessor-primitive? name)
+ &mutable-data
+ &no-effects))))
+
+ ;; Lambda applications might throw wrong-number-of-args.
+ (($ <application> _ ($ <lambda> _ _ body) args)
+ (logior (accumulate-effects args)
+ (match body
+ (($ <lambda-case> _ req #f #f #f () syms body #f)
+ (logior (compute-effects body)
+ (if (= (length req) (length args))
+ 0
+ (cause &type-check))))
+ (($ <lambda-case>)
+ (logior (compute-effects body)
+ (cause &type-check)))
+ (#f
+ ;; Calling a case-lambda with no clauses
+ ;; definitely causes bailout.
+ (logior (cause &definite-bailout)
+ (cause &possible-bailout))))))
+
+ ;; Bailout primitives.
+ (($ <application> src ($ <primitive-ref> _ (? bailout-primitive? name))
+ args)
+ (logior (accumulate-effects args)
+ (cause &definite-bailout)
+ (cause &possible-bailout)))
+
+ ;; A call to a lexically bound procedure, perhaps labels
+ ;; allocated.
+ (($ <application> _ (and proc ($ <lexical-ref> _ _ sym)) args)
+ (cond
+ ((lookup sym)
+ => (lambda (proc)
+ (compute-effects (make-application #f proc args))))
+ (else
+ (logior &all-effects-but-bailout
+ (cause &all-effects-but-bailout)))))
+
+ ;; A call to an unknown procedure can do anything.
+ (($ <application> _ proc args)
+ (logior &all-effects-but-bailout
+ (cause &all-effects-but-bailout)))
+
+ (($ <lambda> _ meta body)
+ &no-effects)
+ (($ <lambda-case> _ req opt rest kw inits gensyms body alt)
+ (logior (exclude-effects (accumulate-effects inits)
+ &definite-bailout)
+ (if (or-map assigned-lexical? gensyms)
+ (cause &allocation)
+ &no-effects)
+ (compute-effects body)
+ (if alt (compute-effects alt) &no-effects)))
+
+ (($ <sequence> _ exps)
+ (let lp ((exps exps) (effects &no-effects))
+ (match exps
+ ((tail)
+ (logior (compute-effects tail)
+ ;; Returning zero values to a for-effect continuation is
+ ;; not observable.
+ (exclude-effects effects (cause &zero-values))))
+ ((head . tail)
+ (lp tail (logior (compute-effects head) effects))))))
+
+ (($ <prompt> _ tag body handler)
+ (logior (compute-effects tag)
+ (compute-effects body)
+ (compute-effects handler)))
+
+ (($ <abort> _ tag args tail)
+ (logior &all-effects-but-bailout
+ (cause &all-effects-but-bailout)))))
+
+ (compute-effects exp))
+
+ compute-effects))
+;;; transformation of letrec into simpler forms
+
+;; Copyright (C) 2009, 2010, 2011, 2012, 2016 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (language tree-il fix-letrec)
+ #\use-module (system base syntax)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-11)
+ #\use-module (language tree-il)
+ #\use-module (language tree-il effects)
+ #\export (fix-letrec!))
+
+;; For a detailed discussion, see "Fixing Letrec: A Faithful Yet
+;; Efficient Implementation of Scheme's Recursive Binding Construct", by
+;; Oscar Waddell, Dipanwita Sarkar, and R. Kent Dybvig.
+
+(define fix-fold
+ (make-tree-il-folder unref ref set simple lambda complex))
+
+(define (simple-expression? x bound-vars simple-primcall?)
+ (record-case x
+ ((<void>) #t)
+ ((<const>) #t)
+ ((<lexical-ref> gensym)
+ (not (memq gensym bound-vars)))
+ ((<conditional> test consequent alternate)
+ (and (simple-expression? test bound-vars simple-primcall?)
+ (simple-expression? consequent bound-vars simple-primcall?)
+ (simple-expression? alternate bound-vars simple-primcall?)))
+ ((<sequence> exps)
+ (and-map (lambda (x) (simple-expression? x bound-vars simple-primcall?))
+ exps))
+ ((<application> proc args)
+ (and (primitive-ref? proc)
+ (simple-primcall? x)
+ (and-map (lambda (x)
+ (simple-expression? x bound-vars simple-primcall?))
+ args)))
+ (else #f)))
+
+(define (partition-vars x)
+ (let-values
+ (((unref ref set simple lambda* complex)
+ (fix-fold x
+ (lambda (x unref ref set simple lambda* complex)
+ (record-case x
+ ((<lexical-ref> gensym)
+ (values (delq gensym unref)
+ (lset-adjoin eq? ref gensym)
+ set
+ simple
+ lambda*
+ complex))
+ ((<lexical-set> gensym)
+ (values unref
+ ref
+ (lset-adjoin eq? set gensym)
+ simple
+ lambda*
+ complex))
+ ((<letrec> gensyms)
+ (values (append gensyms unref)
+ ref
+ set
+ simple
+ lambda*
+ complex))
+ ((<let> gensyms)
+ (values (append gensyms unref)
+ ref
+ set
+ simple
+ lambda*
+ complex))
+ (else
+ (values unref ref set simple lambda* complex))))
+ (lambda (x unref ref set simple lambda* complex)
+ (record-case x
+ ((<letrec> in-order? (orig-gensyms gensyms) vals)
+ (define compute-effects
+ (make-effects-analyzer (lambda (x) (memq x set))))
+ (define (effect-free-primcall? x)
+ (let ((effects (compute-effects x)))
+ (effect-free?
+ (exclude-effects effects (logior &allocation
+ &type-check)))))
+ (define (effect+exception-free-primcall? x)
+ (let ((effects (compute-effects x)))
+ (effect-free?
+ (exclude-effects effects &allocation))))
+ (let lp ((gensyms orig-gensyms) (vals vals)
+ (s '()) (l '()) (c '()))
+ (cond
+ ((null? gensyms)
+ ;; Unreferenced complex vars are still
+ ;; complex for letrec*. We need to update
+ ;; our algorithm to "Fixing letrec reloaded"
+ ;; to fix this.
+ (values (if in-order?
+ (lset-difference eq? unref c)
+ unref)
+ ref
+ set
+ (append s simple)
+ (append l lambda*)
+ (append c complex)))
+ ((memq (car gensyms) unref)
+ ;; See above note about unref and letrec*.
+ (if (and in-order?
+ (not (lambda? (car vals)))
+ (not (simple-expression?
+ (car vals) orig-gensyms
+ effect+exception-free-primcall?)))
+ (lp (cdr gensyms) (cdr vals)
+ s l (cons (car gensyms) c))
+ (lp (cdr gensyms) (cdr vals)
+ s l c)))
+ ((memq (car gensyms) set)
+ (lp (cdr gensyms) (cdr vals)
+ s l (cons (car gensyms) c)))
+ ((lambda? (car vals))
+ (lp (cdr gensyms) (cdr vals)
+ s (cons (car gensyms) l) c))
+ ((simple-expression?
+ (car vals) orig-gensyms
+ (if in-order?
+ effect+exception-free-primcall?
+ effect-free-primcall?))
+ ;; For letrec*, we can't consider e.g. `car' to be
+ ;; "simple", as it could raise an exception. Hence
+ ;; effect+exception-free-primitive? above.
+ (lp (cdr gensyms) (cdr vals)
+ (cons (car gensyms) s) l c))
+ (else
+ (lp (cdr gensyms) (cdr vals)
+ s l (cons (car gensyms) c))))))
+ ((<let> (orig-gensyms gensyms) vals)
+ ;; The point is to compile let-bound lambdas as
+ ;; efficiently as we do letrec-bound lambdas, so
+ ;; we use the same algorithm for analyzing the
+ ;; gensyms. There is no problem recursing into the
+ ;; bindings after the let, because all variables
+ ;; have been renamed.
+ (let lp ((gensyms orig-gensyms) (vals vals)
+ (s '()) (l '()) (c '()))
+ (cond
+ ((null? gensyms)
+ (values unref
+ ref
+ set
+ (append s simple)
+ (append l lambda*)
+ (append c complex)))
+ ((memq (car gensyms) unref)
+ (lp (cdr gensyms) (cdr vals)
+ s l c))
+ ((memq (car gensyms) set)
+ (lp (cdr gensyms) (cdr vals)
+ s l (cons (car gensyms) c)))
+ ((and (lambda? (car vals))
+ (not (memq (car gensyms) set)))
+ (lp (cdr gensyms) (cdr vals)
+ s (cons (car gensyms) l) c))
+ ;; There is no difference between simple and
+ ;; complex, for the purposes of let. Just lump
+ ;; them all into complex.
+ (else
+ (lp (cdr gensyms) (cdr vals)
+ s l (cons (car gensyms) c))))))
+ (else
+ (values unref ref set simple lambda* complex))))
+ '()
+ '()
+ '()
+ '()
+ '()
+ '())))
+ (values unref simple lambda* complex)))
+
+(define (make-sequence* src exps)
+ (let lp ((in exps) (out '()))
+ (if (null? (cdr in))
+ (if (null? out)
+ (car in)
+ (make-sequence src (reverse (cons (car in) out))))
+ (let ((head (car in)))
+ (record-case head
+ ((<lambda>) (lp (cdr in) out))
+ ((<const>) (lp (cdr in) out))
+ ((<lexical-ref>) (lp (cdr in) out))
+ ((<void>) (lp (cdr in) out))
+ (else (lp (cdr in) (cons head out))))))))
+
+(define (fix-letrec! x)
+ (let-values (((unref simple lambda* complex) (partition-vars x)))
+ (post-order!
+ (lambda (x)
+ (record-case x
+
+ ;; Sets to unreferenced variables may be replaced by their
+ ;; expression, called for effect.
+ ((<lexical-set> gensym exp)
+ (if (memq gensym unref)
+ (make-sequence* #f (list exp (make-void #f)))
+ x))
+
+ ((<letrec> src in-order? names gensyms vals body)
+ (let ((binds (map list gensyms names vals)))
+ ;; The bindings returned by this function need to appear in the same
+ ;; order that they appear in the letrec.
+ (define (lookup set)
+ (let lp ((binds binds))
+ (cond
+ ((null? binds) '())
+ ((memq (caar binds) set)
+ (cons (car binds) (lp (cdr binds))))
+ (else (lp (cdr binds))))))
+ (let ((u (lookup unref))
+ (s (lookup simple))
+ (l (lookup lambda*))
+ (c (lookup complex)))
+ ;; Bind "simple" bindings, and locations for complex
+ ;; bindings.
+ (make-let
+ src
+ (append (map cadr s) (map cadr c))
+ (append (map car s) (map car c))
+ (append (map caddr s) (map (lambda (x) (make-void #f)) c))
+ ;; Bind lambdas using the fixpoint operator.
+ (make-fix
+ src (map cadr l) (map car l) (map caddr l)
+ (make-sequence*
+ src
+ (append
+ ;; The right-hand-sides of the unreferenced
+ ;; bindings, for effect.
+ (map caddr u)
+ (cond
+ ((null? c)
+ ;; No complex bindings, just emit the body.
+ (list body))
+ (in-order?
+ ;; For letrec*, assign complex bindings in order, then the
+ ;; body.
+ (append
+ (map (lambda (c)
+ (make-lexical-set #f (cadr c) (car c)
+ (caddr c)))
+ c)
+ (list body)))
+ (else
+ ;; Otherwise for plain letrec, evaluate the "complex"
+ ;; bindings, in a `let' to indicate that order doesn't
+ ;; matter, and bind to their variables.
+ (list
+ (let ((tmps (map (lambda (x)
+ (module-gensym "fixlr"))
+ c)))
+ (make-let
+ #f (map cadr c) tmps (map caddr c)
+ (make-sequence
+ #f
+ (map (lambda (x tmp)
+ (make-lexical-set
+ #f (cadr x) (car x)
+ (make-lexical-ref #f (cadr x) tmp)))
+ c tmps))))
+ body))))))))))
+
+ ((<let> src names gensyms vals body)
+ (let ((binds (map list gensyms names vals)))
+ (define (lookup set)
+ (map (lambda (v) (assq v binds))
+ (lset-intersection eq? gensyms set)))
+ (let ((u (lookup unref))
+ (l (lookup lambda*))
+ (c (lookup complex)))
+ (make-sequence*
+ src
+ (append
+ ;; unreferenced bindings, called for effect.
+ (map caddr u)
+ (list
+ ;; unassigned lambdas use fix.
+ (make-fix src (map cadr l) (map car l) (map caddr l)
+ ;; and the "complex" bindings.
+ (make-let src (map cadr c) (map car c) (map caddr c)
+ body))))))))
+
+ (else x)))
+ x)))
+
+;;; Local Variables:
+;;; eval: (put 'record-case 'scheme-indent-function 1)
+;;; End:
+;;; a simple inliner
+
+;; Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (language tree-il inline)
+ #\export (inline!))
+
+(define (inline! x)
+ (issue-deprecation-warning
+ "`inline!' is deprecated. Use (language tree-il peval) instead.")
+ x)
+;;; Tree-il optimizer
+
+;; Copyright (C) 2009, 2011, 2012 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language tree-il optimize)
+ #\use-module (language tree-il)
+ #\use-module (language tree-il primitives)
+ #\use-module (language tree-il peval)
+ #\use-module (language tree-il cse)
+ #\use-module (language tree-il fix-letrec)
+ #\use-module (language tree-il debug)
+ #\use-module (ice-9 match)
+ #\export (optimize!))
+
+(define (optimize! x env opts)
+ (let ((peval (match (memq #\partial-eval? opts)
+ ((#\partial-eval? #f _ ...)
+ ;; Disable partial evaluation.
+ (lambda (x e) x))
+ (_ peval)))
+ (cse (match (memq #\cse? opts)
+ ((#\cse? #f _ ...)
+ ;; Disable CSE.
+ (lambda (x) x))
+ (_ cse))))
+ (fix-letrec!
+ (verify-tree-il
+ (cse
+ (verify-tree-il
+ (peval (expand-primitives! (resolve-primitives! x env))
+ env)))))))
+;;; Tree-IL partial evaluator
+
+;; Copyright (C) 2011-2014 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (language tree-il peval)
+ #\use-module (language tree-il)
+ #\use-module (language tree-il primitives)
+ #\use-module (language tree-il effects)
+ #\use-module (ice-9 vlist)
+ #\use-module (ice-9 match)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-11)
+ #\use-module (srfi srfi-26)
+ #\use-module (ice-9 control)
+ #\export (peval))
+
+;;;
+;;; Partial evaluation is Guile's most important source-to-source
+;;; optimization pass. It performs copy propagation, dead code
+;;; elimination, inlining, and constant folding, all while preserving
+;;; the order of effects in the residual program.
+;;;
+;;; For more on partial evaluation, see William Cook’s excellent
+;;; tutorial on partial evaluation at DSL 2011, called “Build your own
+;;; partial evaluator in 90 minutes”[0].
+;;;
+;;; Our implementation of this algorithm was heavily influenced by
+;;; Waddell and Dybvig's paper, "Fast and Effective Procedure Inlining",
+;;; IU CS Dept. TR 484.
+;;;
+;;; [0] http://www.cs.utexas.edu/~wcook/tutorial/.
+;;;
+
+;; First, some helpers.
+;;
+(define-syntax *logging* (identifier-syntax #f))
+
+;; For efficiency we define *logging* to inline to #f, so that the call
+;; to log* gets optimized out. If you want to log, uncomment these
+;; lines:
+;;
+;; (define %logging #f)
+;; (define-syntax *logging* (identifier-syntax %logging))
+;;
+;; Then you can change %logging at runtime.
+
+(define-syntax log
+ (syntax-rules (quote)
+ ((log 'event arg ...)
+ (if (and *logging*
+ (or (eq? *logging* #t)
+ (memq 'event *logging*)))
+ (log* 'event arg ...)))))
+
+(define (log* event . args)
+ (let ((pp (module-ref (resolve-interface '(ice-9 pretty-print))
+ 'pretty-print)))
+ (pp `(log ,event . ,args))
+ (newline)
+ (values)))
+
+(define (tree-il-any proc exp)
+ (let/ec k
+ (tree-il-fold (lambda (exp res)
+ (let ((res (proc exp)))
+ (if res (k res) #f)))
+ (lambda (exp res)
+ (let ((res (proc exp)))
+ (if res (k res) #f)))
+ (lambda (exp res) #f)
+ #f exp)))
+
+(define (vlist-any proc vlist)
+ (let ((len (vlist-length vlist)))
+ (let lp ((i 0))
+ (and (< i len)
+ (or (proc (vlist-ref vlist i))
+ (lp (1+ i)))))))
+
+(define (singly-valued-expression? exp)
+ (match exp
+ (($ <const>) #t)
+ (($ <lexical-ref>) #t)
+ (($ <void>) #t)
+ (($ <lexical-ref>) #t)
+ (($ <primitive-ref>) #t)
+ (($ <module-ref>) #t)
+ (($ <toplevel-ref>) #t)
+ (($ <application> _
+ ($ <primitive-ref> _ (? singly-valued-primitive?))) #t)
+ (($ <application> _ ($ <primitive-ref> _ 'values) (val)) #t)
+ (($ <lambda>) #t)
+ (else #f)))
+
+(define (truncate-values x)
+ "Discard all but the first value of X."
+ (if (singly-valued-expression? x)
+ x
+ (make-application (tree-il-src x)
+ (make-primitive-ref #f 'values)
+ (list x))))
+
+;; Peval will do a one-pass analysis on the source program to determine
+;; the set of assigned lexicals, and to identify unreferenced and
+;; singly-referenced lexicals.
+;;
+(define-record-type <var>
+ (make-var name gensym refcount set?)
+ var?
+ (name var-name)
+ (gensym var-gensym)
+ (refcount var-refcount set-var-refcount!)
+ (set? var-set? set-var-set?!))
+
+(define* (build-var-table exp #\optional (table vlist-null))
+ (tree-il-fold
+ (lambda (exp res)
+ (match exp
+ (($ <lexical-ref> src name gensym)
+ (let ((var (cdr (vhash-assq gensym res))))
+ (set-var-refcount! var (1+ (var-refcount var)))
+ res))
+ (_ res)))
+ (lambda (exp res)
+ (match exp
+ (($ <lambda-case> src req opt rest kw init gensyms body alt)
+ (fold (lambda (name sym res)
+ (vhash-consq sym (make-var name sym 0 #f) res))
+ res
+ (append req (or opt '()) (if rest (list rest) '())
+ (match kw
+ ((aok? (kw name sym) ...) name)
+ (_ '())))
+ gensyms))
+ (($ <let> src names gensyms vals body)
+ (fold (lambda (name sym res)
+ (vhash-consq sym (make-var name sym 0 #f) res))
+ res names gensyms))
+ (($ <letrec> src in-order? names gensyms vals body)
+ (fold (lambda (name sym res)
+ (vhash-consq sym (make-var name sym 0 #f) res))
+ res names gensyms))
+ (($ <fix> src names gensyms vals body)
+ (fold (lambda (name sym res)
+ (vhash-consq sym (make-var name sym 0 #f) res))
+ res names gensyms))
+ (($ <lexical-set> src name gensym exp)
+ (set-var-set?! (cdr (vhash-assq gensym res)) #t)
+ res)
+ (_ res)))
+ (lambda (exp res) res)
+ table exp))
+
+;; Counters are data structures used to limit the effort that peval
+;; spends on particular inlining attempts. Each call site in the source
+;; program is allocated some amount of effort. If peval exceeds the
+;; effort counter while attempting to inline a call site, it aborts the
+;; inlining attempt and residualizes a call instead.
+;;
+;; As there is a fixed number of call sites, that makes `peval' O(N) in
+;; the number of call sites in the source program.
+;;
+;; Counters should limit the size of the residual program as well, but
+;; currently this is not implemented.
+;;
+;; At the top level, before seeing any peval call, there is no counter,
+;; because inlining will terminate as there is no recursion. When peval
+;; sees a call at the top level, it will make a new counter, allocating
+;; it some amount of effort and size.
+;;
+;; This top-level effort counter effectively "prints money". Within a
+;; toplevel counter, no more effort is printed ex nihilo; for a nested
+;; inlining attempt to proceed, effort must be transferred from the
+;; toplevel counter to the nested counter.
+;;
+;; Via `data' and `prev', counters form a linked list, terminating in a
+;; toplevel counter. In practice `data' will be the a pointer to the
+;; source expression of the procedure being inlined.
+;;
+;; In this way peval can detect a recursive inlining attempt, by walking
+;; back on the `prev' links looking for matching `data'. Recursive
+;; counters receive a more limited effort allocation, as we don't want
+;; to spend all of the effort for a toplevel inlining site on loops.
+;; Also, recursive counters don't need a prompt at each inlining site:
+;; either the call chain folds entirely, or it will be residualized at
+;; its original call.
+;;
+(define-record-type <counter>
+ (%make-counter effort size continuation recursive? data prev)
+ counter?
+ (effort effort-counter)
+ (size size-counter)
+ (continuation counter-continuation)
+ (recursive? counter-recursive? set-counter-recursive?!)
+ (data counter-data)
+ (prev counter-prev))
+
+(define (abort-counter c)
+ ((counter-continuation c)))
+
+(define (record-effort! c)
+ (let ((e (effort-counter c)))
+ (if (zero? (variable-ref e))
+ (abort-counter c)
+ (variable-set! e (1- (variable-ref e))))))
+
+(define (record-size! c)
+ (let ((s (size-counter c)))
+ (if (zero? (variable-ref s))
+ (abort-counter c)
+ (variable-set! s (1- (variable-ref s))))))
+
+(define (find-counter data counter)
+ (and counter
+ (if (eq? data (counter-data counter))
+ counter
+ (find-counter data (counter-prev counter)))))
+
+(define* (transfer! from to #\optional
+ (effort (variable-ref (effort-counter from)))
+ (size (variable-ref (size-counter from))))
+ (define (transfer-counter! from-v to-v amount)
+ (let* ((from-balance (variable-ref from-v))
+ (to-balance (variable-ref to-v))
+ (amount (min amount from-balance)))
+ (variable-set! from-v (- from-balance amount))
+ (variable-set! to-v (+ to-balance amount))))
+
+ (transfer-counter! (effort-counter from) (effort-counter to) effort)
+ (transfer-counter! (size-counter from) (size-counter to) size))
+
+(define (make-top-counter effort-limit size-limit continuation data)
+ (%make-counter (make-variable effort-limit)
+ (make-variable size-limit)
+ continuation
+ #t
+ data
+ #f))
+
+(define (make-nested-counter continuation data current)
+ (let ((c (%make-counter (make-variable 0)
+ (make-variable 0)
+ continuation
+ #f
+ data
+ current)))
+ (transfer! current c)
+ c))
+
+(define (make-recursive-counter effort-limit size-limit orig current)
+ (let ((c (%make-counter (make-variable 0)
+ (make-variable 0)
+ (counter-continuation orig)
+ #t
+ (counter-data orig)
+ current)))
+ (transfer! current c effort-limit size-limit)
+ c))
+
+;; Operand structures allow bindings to be processed lazily instead of
+;; eagerly. By doing so, hopefully we can get process them in a way
+;; appropriate to their use contexts. Operands also prevent values from
+;; being visited multiple times, wasting effort.
+;;
+;; TODO: Record value size in operand structure?
+;;
+(define-record-type <operand>
+ (%make-operand var sym visit source visit-count use-count
+ copyable? residual-value constant-value alias)
+ operand?
+ (var operand-var)
+ (sym operand-sym)
+ (visit %operand-visit)
+ (source operand-source)
+ (visit-count operand-visit-count set-operand-visit-count!)
+ (use-count operand-use-count set-operand-use-count!)
+ (copyable? operand-copyable? set-operand-copyable?!)
+ (residual-value operand-residual-value %set-operand-residual-value!)
+ (constant-value operand-constant-value set-operand-constant-value!)
+ (alias operand-alias set-operand-alias!))
+
+(define* (make-operand var sym #\optional source visit alias)
+ ;; Bind SYM to VAR, with value SOURCE. Unassigned bound operands are
+ ;; considered copyable until we prove otherwise. If we have a source
+ ;; expression, truncate it to one value. Copy propagation does not
+ ;; work on multiply-valued expressions.
+ (let ((source (and=> source truncate-values)))
+ (%make-operand var sym visit source 0 0
+ (and source (not (var-set? var))) #f #f
+ (and (not (var-set? var)) alias))))
+
+(define* (make-bound-operands vars syms sources visit #\optional aliases)
+ (if aliases
+ (map (lambda (name sym source alias)
+ (make-operand name sym source visit alias))
+ vars syms sources aliases)
+ (map (lambda (name sym source)
+ (make-operand name sym source visit #f))
+ vars syms sources)))
+
+(define (make-unbound-operands vars syms)
+ (map make-operand vars syms))
+
+(define (set-operand-residual-value! op val)
+ (%set-operand-residual-value!
+ op
+ (match val
+ (($ <application> src ($ <primitive-ref> _ 'values) (first))
+ ;; The continuation of a residualized binding does not need the
+ ;; introduced `values' node, so undo the effects of truncation.
+ first)
+ (else
+ val))))
+
+(define* (visit-operand op counter ctx #\optional effort-limit size-limit)
+ ;; Peval is O(N) in call sites of the source program. However,
+ ;; visiting an operand can introduce new call sites. If we visit an
+ ;; operand outside a counter -- i.e., outside an inlining attempt --
+ ;; this can lead to divergence. So, if we are visiting an operand to
+ ;; try to copy it, and there is no counter, make a new one.
+ ;;
+ ;; This will only happen at most as many times as there are lexical
+ ;; references in the source program.
+ (and (zero? (operand-visit-count op))
+ (dynamic-wind
+ (lambda ()
+ (set-operand-visit-count! op (1+ (operand-visit-count op))))
+ (lambda ()
+ (and (operand-source op)
+ (if (or counter (and (not effort-limit) (not size-limit)))
+ ((%operand-visit op) (operand-source op) counter ctx)
+ (let/ec k
+ (define (abort)
+ ;; If we abort when visiting the value in a
+ ;; fresh context, we won't succeed in any future
+ ;; attempt, so don't try to copy it again.
+ (set-operand-copyable?! op #f)
+ (k #f))
+ ((%operand-visit op)
+ (operand-source op)
+ (make-top-counter effort-limit size-limit abort op)
+ ctx)))))
+ (lambda ()
+ (set-operand-visit-count! op (1- (operand-visit-count op)))))))
+
+;; A helper for constant folding.
+;;
+(define (types-check? primitive-name args)
+ (case primitive-name
+ ((values) #t)
+ ((not pair? null? list? symbol? vector? struct?)
+ (= (length args) 1))
+ ((eq? eqv? equal?)
+ (= (length args) 2))
+ ;; FIXME: add more cases?
+ (else #f)))
+
+(define* (peval exp #\optional (cenv (current-module)) (env vlist-null)
+ #\key
+ (operator-size-limit 40)
+ (operand-size-limit 20)
+ (value-size-limit 10)
+ (effort-limit 500)
+ (recursive-effort-limit 100))
+ "Partially evaluate EXP in compilation environment CENV, with
+top-level bindings from ENV and return the resulting expression."
+
+ ;; This is a simple partial evaluator. It effectively performs
+ ;; constant folding, copy propagation, dead code elimination, and
+ ;; inlining.
+
+ ;; TODO:
+ ;;
+ ;; Propagate copies across toplevel bindings, if we can prove the
+ ;; bindings to be immutable.
+ ;;
+ ;; Specialize lambda expressions with invariant arguments.
+
+ (define local-toplevel-env
+ ;; The top-level environment of the module being compiled.
+ (match exp
+ (($ <toplevel-define> _ name)
+ (vhash-consq name #t env))
+ (($ <sequence> _ exps)
+ (fold (lambda (x r)
+ (match x
+ (($ <toplevel-define> _ name)
+ (vhash-consq name #t r))
+ (_ r)))
+ env
+ exps))
+ (_ env)))
+
+ (define (local-toplevel? name)
+ (vhash-assq name local-toplevel-env))
+
+ ;; gensym -> <var>
+ ;; renamed-term -> original-term
+ ;;
+ (define store (build-var-table exp))
+
+ (define (record-new-temporary! name sym refcount)
+ (set! store (vhash-consq sym (make-var name sym refcount #f) store)))
+
+ (define (lookup-var sym)
+ (let ((v (vhash-assq sym store)))
+ (if v (cdr v) (error "unbound var" sym (vlist->list store)))))
+
+ (define (fresh-gensyms vars)
+ (map (lambda (var)
+ (let ((new (gensym (string-append (symbol->string (var-name var))
+ " "))))
+ (set! store (vhash-consq new var store))
+ new))
+ vars))
+
+ (define (fresh-temporaries ls)
+ (map (lambda (elt)
+ (let ((new (gensym "tmp ")))
+ (record-new-temporary! 'tmp new 1)
+ new))
+ ls))
+
+ (define (assigned-lexical? sym)
+ (var-set? (lookup-var sym)))
+
+ (define (lexical-refcount sym)
+ (var-refcount (lookup-var sym)))
+
+ ;; ORIG has been alpha-renamed to NEW. Analyze NEW and record a link
+ ;; from it to ORIG.
+ ;;
+ (define (record-source-expression! orig new)
+ (set! store (vhash-consq new (source-expression orig) store))
+ new)
+
+ ;; Find the source expression corresponding to NEW. Used to detect
+ ;; recursive inlining attempts.
+ ;;
+ (define (source-expression new)
+ (let ((x (vhash-assq new store)))
+ (if x (cdr x) new)))
+
+ (define (record-operand-use op)
+ (set-operand-use-count! op (1+ (operand-use-count op))))
+
+ (define (unrecord-operand-uses op n)
+ (let ((count (- (operand-use-count op) n)))
+ (when (zero? count)
+ (set-operand-residual-value! op #f))
+ (set-operand-use-count! op count)))
+
+ (define* (residualize-lexical op #\optional ctx val)
+ (log 'residualize op)
+ (record-operand-use op)
+ (if (memq ctx '(value values))
+ (set-operand-residual-value! op val))
+ (make-lexical-ref #f (var-name (operand-var op)) (operand-sym op)))
+
+ (define (fold-constants src name args ctx)
+ (define (apply-primitive name args)
+ ;; todo: further optimize commutative primitives
+ (catch #t
+ (lambda ()
+ (call-with-values
+ (lambda ()
+ (case name
+ ((eq? eqv?)
+ ;; Constants will be deduplicated later, but eq?
+ ;; folding can happen now. Anticipate the
+ ;; deduplication by using equal? instead of eq?.
+ ;; Same for eqv?.
+ (apply equal? args))
+ (else
+ (apply (module-ref the-scm-module name) args))))
+ (lambda results
+ (values #t results))))
+ (lambda _
+ (values #f '()))))
+
+ (define (make-values src values)
+ (match values
+ ((single) single) ; 1 value
+ ((_ ...) ; 0, or 2 or more values
+ (make-application src (make-primitive-ref src 'values)
+ values))))
+ (define (residualize-call)
+ (make-application src (make-primitive-ref #f name) args))
+ (cond
+ ((every const? args)
+ (let-values (((success? values)
+ (apply-primitive name (map const-exp args))))
+ (log 'fold success? values name args)
+ (if success?
+ (case ctx
+ ((effect) (make-void src))
+ ((test)
+ ;; Values truncation: only take the first
+ ;; value.
+ (if (pair? values)
+ (make-const src (car values))
+ (make-values src '())))
+ (else
+ (make-values src (map (cut make-const src <>) values))))
+ (residualize-call))))
+ ((and (eq? ctx 'effect) (types-check? name args))
+ (make-void #f))
+ (else
+ (residualize-call))))
+
+ (define (inline-values src exp nmin nmax consumer)
+ (let loop ((exp exp))
+ (match exp
+ ;; Some expression types are always singly-valued.
+ ((or ($ <const>)
+ ($ <void>)
+ ($ <lambda>)
+ ($ <lexical-ref>)
+ ($ <toplevel-ref>)
+ ($ <module-ref>)
+ ($ <primitive-ref>)
+ ($ <dynref>)
+ ($ <lexical-set>) ; FIXME: these set! expressions
+ ($ <toplevel-set>) ; could return zero values in
+ ($ <toplevel-define>) ; the future
+ ($ <module-set>) ;
+ ($ <dynset>) ;
+ ($ <application> src
+ ($ <primitive-ref> _ (? singly-valued-primitive?))))
+ (and (<= nmin 1) (or (not nmax) (>= nmax 1))
+ (make-application src (make-lambda #f '() consumer) (list exp))))
+
+ ;; Statically-known number of values.
+ (($ <application> src ($ <primitive-ref> _ 'values) vals)
+ (and (<= nmin (length vals)) (or (not nmax) (>= nmax (length vals)))
+ (make-application src (make-lambda #f '() consumer) vals)))
+
+ ;; Not going to copy code into both branches.
+ (($ <conditional>) #f)
+
+ ;; Bail on other applications.
+ (($ <application>) #f)
+
+ ;; Bail on prompt and abort.
+ (($ <prompt>) #f)
+ (($ <abort>) #f)
+
+ ;; Propagate to tail positions.
+ (($ <let> src names gensyms vals body)
+ (let ((body (loop body)))
+ (and body
+ (make-let src names gensyms vals body))))
+ (($ <letrec> src in-order? names gensyms vals body)
+ (let ((body (loop body)))
+ (and body
+ (make-letrec src in-order? names gensyms vals body))))
+ (($ <fix> src names gensyms vals body)
+ (let ((body (loop body)))
+ (and body
+ (make-fix src names gensyms vals body))))
+ (($ <let-values> src exp
+ ($ <lambda-case> src2 req opt rest kw inits gensyms body #f))
+ (let ((body (loop body)))
+ (and body
+ (make-let-values src exp
+ (make-lambda-case src2 req opt rest kw
+ inits gensyms body #f)))))
+ (($ <dynwind> src winder body unwinder)
+ (let ((body (loop body)))
+ (and body
+ (make-dynwind src winder body unwinder))))
+ (($ <dynlet> src fluids vals body)
+ (let ((body (loop body)))
+ (and body
+ (make-dynlet src fluids vals body))))
+ (($ <sequence> src exps)
+ (match exps
+ ((head ... tail)
+ (let ((tail (loop tail)))
+ (and tail
+ (make-sequence src (append head (list tail)))))))))))
+
+ (define compute-effects
+ (make-effects-analyzer assigned-lexical?))
+
+ (define (constant-expression? x)
+ ;; Return true if X is constant, for the purposes of copying or
+ ;; elision---i.e., if it is known to have no effects, does not
+ ;; allocate storage for a mutable object, and does not access
+ ;; mutable data (like `car' or toplevel references).
+ (constant? (compute-effects x)))
+
+ (define (prune-bindings ops in-order? body counter ctx build-result)
+ ;; This helper handles both `let' and `letrec'/`fix'. In the latter
+ ;; cases we need to make sure that if referenced binding A needs
+ ;; as-yet-unreferenced binding B, that B is processed for value.
+ ;; Likewise if C, when processed for effect, needs otherwise
+ ;; unreferenced D, then D needs to be processed for value too.
+ ;;
+ (define (referenced? op)
+ ;; When we visit lambdas in operator context, we just copy them,
+ ;; as we will process their body later. However this does have
+ ;; the problem that any free var referenced by the lambda is not
+ ;; marked as needing residualization. Here we hack around this
+ ;; and treat all bindings as referenced if we are in operator
+ ;; context.
+ (or (eq? ctx 'operator)
+ (not (zero? (operand-use-count op)))))
+
+ ;; values := (op ...)
+ ;; effects := (op ...)
+ (define (residualize values effects)
+ ;; Note, values and effects are reversed.
+ (cond
+ (in-order?
+ (let ((values (filter operand-residual-value ops)))
+ (if (null? values)
+ body
+ (build-result (map (compose var-name operand-var) values)
+ (map operand-sym values)
+ (map operand-residual-value values)
+ body))))
+ (else
+ (let ((body
+ (if (null? effects)
+ body
+ (let ((effect-vals (map operand-residual-value effects)))
+ (make-sequence #f (reverse (cons body effect-vals)))))))
+ (if (null? values)
+ body
+ (let ((values (reverse values)))
+ (build-result (map (compose var-name operand-var) values)
+ (map operand-sym values)
+ (map operand-residual-value values)
+ body)))))))
+
+ ;; old := (bool ...)
+ ;; values := (op ...)
+ ;; effects := ((op . value) ...)
+ (let prune ((old (map referenced? ops)) (values '()) (effects '()))
+ (let lp ((ops* ops) (values values) (effects effects))
+ (cond
+ ((null? ops*)
+ (let ((new (map referenced? ops)))
+ (if (not (equal? new old))
+ (prune new values '())
+ (residualize values
+ (map (lambda (op val)
+ (set-operand-residual-value! op val)
+ op)
+ (map car effects) (map cdr effects))))))
+ (else
+ (let ((op (car ops*)))
+ (cond
+ ((memq op values)
+ (lp (cdr ops*) values effects))
+ ((operand-residual-value op)
+ (lp (cdr ops*) (cons op values) effects))
+ ((referenced? op)
+ (set-operand-residual-value! op (visit-operand op counter 'value))
+ (lp (cdr ops*) (cons op values) effects))
+ (else
+ (lp (cdr ops*)
+ values
+ (let ((effect (visit-operand op counter 'effect)))
+ (if (void? effect)
+ effects
+ (acons op effect effects))))))))))))
+
+ (define (small-expression? x limit)
+ (let/ec k
+ (tree-il-fold
+ (lambda (x res) ; leaf
+ (1+ res))
+ (lambda (x res) ; down
+ (1+ res))
+ (lambda (x res) ; up
+ (if (< res limit)
+ res
+ (k #f)))
+ 0 x)
+ #t))
+
+ (define (extend-env sym op env)
+ (vhash-consq (operand-sym op) op (vhash-consq sym op env)))
+
+ (let loop ((exp exp)
+ (env vlist-null) ; vhash of gensym -> <operand>
+ (counter #f) ; inlined call stack
+ (ctx 'values)) ; effect, value, values, test, operator, or call
+ (define (lookup var)
+ (cond
+ ((vhash-assq var env) => cdr)
+ (else (error "unbound var" var))))
+
+ ;; Find a value referenced a specific number of times. This is a hack
+ ;; that's used for propagating fresh data structures like rest lists and
+ ;; prompt tags. Usually we wouldn't copy consed data, but we can do so in
+ ;; some special cases like `apply' or prompts if we can account
+ ;; for all of its uses.
+ ;;
+ ;; You don't want to use this in general because it introduces a slight
+ ;; nonlinearity by running peval again (though with a small effort and size
+ ;; counter).
+ ;;
+ (define (find-definition x n-aliases)
+ (cond
+ ((lexical-ref? x)
+ (cond
+ ((lookup (lexical-ref-gensym x))
+ => (lambda (op)
+ (if (var-set? (operand-var op))
+ (values #f #f)
+ (let ((y (or (operand-residual-value op)
+ (visit-operand op counter 'value 10 10)
+ (operand-source op))))
+ (cond
+ ((and (lexical-ref? y)
+ (= (lexical-refcount (lexical-ref-gensym x)) 1))
+ ;; X is a simple alias for Y. Recurse, regardless of
+ ;; the number of aliases we were expecting.
+ (find-definition y n-aliases))
+ ((= (lexical-refcount (lexical-ref-gensym x)) n-aliases)
+ ;; We found a definition that is aliased the right
+ ;; number of times. We still recurse in case it is a
+ ;; lexical.
+ (values (find-definition y 1)
+ op))
+ (else
+ ;; We can't account for our aliases.
+ (values #f #f)))))))
+ (else
+ ;; A formal parameter. Can't say anything about that.
+ (values #f #f))))
+ ((= n-aliases 1)
+ ;; Not a lexical: success, but only if we are looking for an
+ ;; unaliased value.
+ (values x #f))
+ (else (values #f #f))))
+
+ (define (visit exp ctx)
+ (loop exp env counter ctx))
+
+ (define (for-value exp) (visit exp 'value))
+ (define (for-values exp) (visit exp 'values))
+ (define (for-test exp) (visit exp 'test))
+ (define (for-effect exp) (visit exp 'effect))
+ (define (for-call exp) (visit exp 'call))
+ (define (for-tail exp) (visit exp ctx))
+
+ (if counter
+ (record-effort! counter))
+
+ (log 'visit ctx (and=> counter effort-counter)
+ (unparse-tree-il exp))
+
+ (match exp
+ (($ <const>)
+ (case ctx
+ ((effect) (make-void #f))
+ (else exp)))
+ (($ <void>)
+ (case ctx
+ ((test) (make-const #f #t))
+ (else exp)))
+ (($ <lexical-ref> _ _ gensym)
+ (log 'begin-copy gensym)
+ (let lp ((op (lookup gensym)))
+ (cond
+ ((eq? ctx 'effect)
+ (log 'lexical-for-effect gensym)
+ (make-void #f))
+ ((operand-alias op)
+ ;; This is an unassigned operand that simply aliases some
+ ;; other operand. Recurse to avoid residualizing the leaf
+ ;; binding.
+ => lp)
+ ((eq? ctx 'call)
+ ;; Don't propagate copies if we are residualizing a call.
+ (log 'residualize-lexical-call gensym op)
+ (residualize-lexical op))
+ ((var-set? (operand-var op))
+ ;; Assigned lexicals don't copy-propagate.
+ (log 'assigned-var gensym op)
+ (residualize-lexical op))
+ ((not (operand-copyable? op))
+ ;; We already know that this operand is not copyable.
+ (log 'not-copyable gensym op)
+ (residualize-lexical op))
+ ((and=> (operand-constant-value op)
+ (lambda (x) (or (const? x) (void? x) (primitive-ref? x))))
+ ;; A cache hit.
+ (let ((val (operand-constant-value op)))
+ (log 'memoized-constant gensym val)
+ (for-tail val)))
+ ((visit-operand op counter (if (eq? ctx 'values) 'value ctx)
+ recursive-effort-limit operand-size-limit)
+ =>
+ ;; If we end up deciding to residualize this value instead of
+ ;; copying it, save that residualized value.
+ (lambda (val)
+ (cond
+ ((not (constant-expression? val))
+ (log 'not-constant gensym op)
+ ;; At this point, ctx is operator, test, or value. A
+ ;; value that is non-constant in one context will be
+ ;; non-constant in the others, so it's safe to record
+ ;; that here, and avoid future visits.
+ (set-operand-copyable?! op #f)
+ (residualize-lexical op ctx val))
+ ((or (const? val)
+ (void? val)
+ (primitive-ref? val))
+ ;; Always propagate simple values that cannot lead to
+ ;; code bloat.
+ (log 'copy-simple gensym val)
+ ;; It could be this constant is the result of folding.
+ ;; If that is the case, cache it. This helps loop
+ ;; unrolling get farther.
+ (if (or (eq? ctx 'value) (eq? ctx 'values))
+ (begin
+ (log 'memoize-constant gensym val)
+ (set-operand-constant-value! op val)))
+ val)
+ ((= 1 (var-refcount (operand-var op)))
+ ;; Always propagate values referenced only once.
+ (log 'copy-single gensym val)
+ val)
+ ;; FIXME: do demand-driven size accounting rather than
+ ;; these heuristics.
+ ((eq? ctx 'operator)
+ ;; A pure expression in the operator position. Inline
+ ;; if it's a lambda that's small enough.
+ (if (and (lambda? val)
+ (small-expression? val operator-size-limit))
+ (begin
+ (log 'copy-operator gensym val)
+ val)
+ (begin
+ (log 'too-big-for-operator gensym val)
+ (residualize-lexical op ctx val))))
+ (else
+ ;; A pure expression, processed for call or for value.
+ ;; Don't inline lambdas, because they will probably won't
+ ;; fold because we don't know the operator.
+ (if (and (small-expression? val value-size-limit)
+ (not (tree-il-any lambda? val)))
+ (begin
+ (log 'copy-value gensym val)
+ val)
+ (begin
+ (log 'too-big-or-has-lambda gensym val)
+ (residualize-lexical op ctx val)))))))
+ (else
+ ;; Visit failed. Either the operand isn't bound, as in
+ ;; lambda formal parameters, or the copy was aborted.
+ (log 'unbound-or-aborted gensym op)
+ (residualize-lexical op)))))
+ (($ <lexical-set> src name gensym exp)
+ (let ((op (lookup gensym)))
+ (if (zero? (var-refcount (operand-var op)))
+ (let ((exp (for-effect exp)))
+ (if (void? exp)
+ exp
+ (make-sequence src (list exp (make-void #f)))))
+ (begin
+ (record-operand-use op)
+ (make-lexical-set src name (operand-sym op) (for-value exp))))))
+ (($ <let> src
+ (names ... rest)
+ (gensyms ... rest-sym)
+ (vals ... ($ <application> _ ($ <primitive-ref> _ 'list) rest-args))
+ ($ <application> asrc
+ ($ <primitive-ref> _ (or 'apply '@apply))
+ (proc args ...
+ ($ <lexical-ref> _
+ (? (cut eq? <> rest))
+ (? (lambda (sym)
+ (and (eq? sym rest-sym)
+ (= (lexical-refcount sym) 1))))))))
+ (let* ((tmps (make-list (length rest-args) 'tmp))
+ (tmp-syms (fresh-temporaries tmps)))
+ (for-tail
+ (make-let src
+ (append names tmps)
+ (append gensyms tmp-syms)
+ (append vals rest-args)
+ (make-application
+ asrc
+ proc
+ (append args
+ (map (cut make-lexical-ref #f <> <>)
+ tmps tmp-syms)))))))
+ (($ <let> src names gensyms vals body)
+ (define (lookup-alias exp)
+ ;; It's very common for macros to introduce something like:
+ ;;
+ ;; ((lambda (x y) ...) x-exp y-exp)
+ ;;
+ ;; In that case you might end up trying to inline something like:
+ ;;
+ ;; (let ((x x-exp) (y y-exp)) ...)
+ ;;
+ ;; But if x-exp is itself a lexical-ref that aliases some much
+ ;; larger expression, perhaps it will fail to inline due to
+ ;; size. However we don't want to introduce a useless alias
+ ;; (in this case, x). So if the RHS of a let expression is a
+ ;; lexical-ref, we record that expression. If we end up having
+ ;; to residualize X, then instead we residualize X-EXP, as long
+ ;; as it isn't assigned.
+ ;;
+ (match exp
+ (($ <lexical-ref> _ _ sym)
+ (let ((op (lookup sym)))
+ (and (not (var-set? (operand-var op))) op)))
+ (_ #f)))
+
+ (let* ((vars (map lookup-var gensyms))
+ (new (fresh-gensyms vars))
+ (ops (make-bound-operands vars new vals
+ (lambda (exp counter ctx)
+ (loop exp env counter ctx))
+ (map lookup-alias vals)))
+ (env (fold extend-env env gensyms ops))
+ (body (loop body env counter ctx)))
+ (cond
+ ((const? body)
+ (for-tail (make-sequence src (append vals (list body)))))
+ ((and (lexical-ref? body)
+ (memq (lexical-ref-gensym body) new))
+ (let ((sym (lexical-ref-gensym body))
+ (pairs (map cons new vals)))
+ ;; (let ((x foo) (y bar) ...) x) => (begin bar ... foo)
+ (for-tail
+ (make-sequence
+ src
+ (append (map cdr (alist-delete sym pairs eq?))
+ (list (assq-ref pairs sym)))))))
+ (else
+ ;; Only include bindings for which lexical references
+ ;; have been residualized.
+ (prune-bindings ops #f body counter ctx
+ (lambda (names gensyms vals body)
+ (if (null? names) (error "what!" names))
+ (make-let src names gensyms vals body)))))))
+ (($ <letrec> src in-order? names gensyms vals body)
+ ;; Note the difference from the `let' case: here we use letrec*
+ ;; so that the `visit' procedure for the new operands closes over
+ ;; an environment that includes the operands. Also we don't try
+ ;; to elide aliases, because we can't sensibly reduce something
+ ;; like (letrec ((a b) (b a)) a).
+ (letrec* ((visit (lambda (exp counter ctx)
+ (loop exp env* counter ctx)))
+ (vars (map lookup-var gensyms))
+ (new (fresh-gensyms vars))
+ (ops (make-bound-operands vars new vals visit))
+ (env* (fold extend-env env gensyms ops))
+ (body* (visit body counter ctx)))
+ (if (and (const? body*) (every constant-expression? vals))
+ ;; We may have folded a loop completely, even though there
+ ;; might be cyclical references between the bound values.
+ ;; Handle this degenerate case specially.
+ body*
+ (prune-bindings ops in-order? body* counter ctx
+ (lambda (names gensyms vals body)
+ (make-letrec src in-order?
+ names gensyms vals body))))))
+ (($ <fix> src names gensyms vals body)
+ (letrec* ((visit (lambda (exp counter ctx)
+ (loop exp env* counter ctx)))
+ (vars (map lookup-var gensyms))
+ (new (fresh-gensyms vars))
+ (ops (make-bound-operands vars new vals visit))
+ (env* (fold extend-env env gensyms ops))
+ (body* (visit body counter ctx)))
+ (if (const? body*)
+ body*
+ (prune-bindings ops #f body* counter ctx
+ (lambda (names gensyms vals body)
+ (make-fix src names gensyms vals body))))))
+ (($ <let-values> lv-src producer consumer)
+ ;; Peval the producer, then try to inline the consumer into
+ ;; the producer. If that succeeds, peval again. Otherwise
+ ;; reconstruct the let-values, pevaling the consumer.
+ (let ((producer (for-values producer)))
+ (or (match consumer
+ (($ <lambda-case> src req opt rest #f inits gensyms body #f)
+ (let* ((nmin (length req))
+ (nmax (and (not rest) (+ nmin (if opt (length opt) 0)))))
+ (cond
+ ((inline-values lv-src producer nmin nmax consumer)
+ => for-tail)
+ (else #f))))
+ (_ #f))
+ (make-let-values lv-src producer (for-tail consumer)))))
+ (($ <dynwind> src winder body unwinder)
+ (let ((pre (for-value winder))
+ (body (for-tail body))
+ (post (for-value unwinder)))
+ (cond
+ ((not (constant-expression? pre))
+ (cond
+ ((not (constant-expression? post))
+ (let ((pre-sym (gensym "pre-")) (post-sym (gensym "post-")))
+ (record-new-temporary! 'pre pre-sym 1)
+ (record-new-temporary! 'post post-sym 1)
+ (make-let src '(pre post) (list pre-sym post-sym) (list pre post)
+ (make-dynwind src
+ (make-lexical-ref #f 'pre pre-sym)
+ body
+ (make-lexical-ref #f 'post post-sym)))))
+ (else
+ (let ((pre-sym (gensym "pre-")))
+ (record-new-temporary! 'pre pre-sym 1)
+ (make-let src '(pre) (list pre-sym) (list pre)
+ (make-dynwind src
+ (make-lexical-ref #f 'pre pre-sym)
+ body
+ post))))))
+ ((not (constant-expression? post))
+ (let ((post-sym (gensym "post-")))
+ (record-new-temporary! 'post post-sym 1)
+ (make-let src '(post) (list post-sym) (list post)
+ (make-dynwind src
+ pre
+ body
+ (make-lexical-ref #f 'post post-sym)))))
+ (else
+ (make-dynwind src pre body post)))))
+ (($ <dynlet> src fluids vals body)
+ (make-dynlet src (map for-value fluids) (map for-value vals)
+ (for-tail body)))
+ (($ <dynref> src fluid)
+ (make-dynref src (for-value fluid)))
+ (($ <dynset> src fluid exp)
+ (make-dynset src (for-value fluid) (for-value exp)))
+ (($ <toplevel-ref> src (? effect-free-primitive? name))
+ (if (local-toplevel? name)
+ exp
+ (let ((exp (resolve-primitives! exp cenv)))
+ (if (primitive-ref? exp)
+ (for-tail exp)
+ exp))))
+ (($ <toplevel-ref>)
+ ;; todo: open private local bindings.
+ exp)
+ (($ <module-ref> src module (? effect-free-primitive? name) #f)
+ (let ((module (false-if-exception
+ (resolve-module module #\ensure #f))))
+ (if (module? module)
+ (let ((var (module-variable module name)))
+ (if (eq? var (module-variable the-scm-module name))
+ (make-primitive-ref src name)
+ exp))
+ exp)))
+ (($ <module-ref>)
+ exp)
+ (($ <module-set> src mod name public? exp)
+ (make-module-set src mod name public? (for-value exp)))
+ (($ <toplevel-define> src name exp)
+ (make-toplevel-define src name (for-value exp)))
+ (($ <toplevel-set> src name exp)
+ (make-toplevel-set src name (for-value exp)))
+ (($ <primitive-ref>)
+ (case ctx
+ ((effect) (make-void #f))
+ ((test) (make-const #f #t))
+ (else exp)))
+ (($ <conditional> src condition subsequent alternate)
+ (define (call-with-failure-thunk exp proc)
+ (match exp
+ (($ <application> _ _ ()) (proc exp))
+ (($ <const>) (proc exp))
+ (($ <void>) (proc exp))
+ (($ <lexical-ref>) (proc exp))
+ (_
+ (let ((t (gensym "failure-")))
+ (record-new-temporary! 'failure t 2)
+ (make-let
+ src (list 'failure) (list t)
+ (list
+ (make-lambda
+ #f '()
+ (make-lambda-case #f '() #f #f #f '() '() exp #f)))
+ (proc (make-application #f (make-lexical-ref #f 'failure t)
+ '())))))))
+ (define (simplify-conditional c)
+ (match c
+ ;; Swap the arms of (if (not FOO) A B), to simplify.
+ (($ <conditional> src
+ ($ <application> _ ($ <primitive-ref> _ 'not) (pred))
+ subsequent alternate)
+ (simplify-conditional
+ (make-conditional src pred alternate subsequent)))
+ ;; Special cases for common tests in the predicates of chains
+ ;; of if expressions.
+ (($ <conditional> src
+ ($ <conditional> src* outer-test inner-test ($ <const> _ #f))
+ inner-subsequent
+ alternate)
+ (let lp ((alternate alternate))
+ (match alternate
+ ;; Lift a common repeated test out of a chain of if
+ ;; expressions.
+ (($ <conditional> _ (? (cut tree-il=? outer-test <>))
+ other-subsequent alternate)
+ (make-conditional
+ src outer-test
+ (simplify-conditional
+ (make-conditional src* inner-test inner-subsequent
+ other-subsequent))
+ alternate))
+ ;; Likewise, but punching through any surrounding
+ ;; failure continuations.
+ (($ <let> let-src (name) (sym) ((and thunk ($ <lambda>))) body)
+ (make-let
+ let-src (list name) (list sym) (list thunk)
+ (lp body)))
+ ;; Otherwise, rotate AND tests to expose a simple
+ ;; condition in the front. Although this may result in
+ ;; lexically binding failure thunks, the thunks will be
+ ;; compiled to labels allocation, so there's no actual
+ ;; code growth.
+ (_
+ (call-with-failure-thunk
+ alternate
+ (lambda (failure)
+ (make-conditional
+ src outer-test
+ (simplify-conditional
+ (make-conditional src* inner-test inner-subsequent failure))
+ failure)))))))
+ (_ c)))
+ (match (for-test condition)
+ (($ <const> _ val)
+ (if val
+ (for-tail subsequent)
+ (for-tail alternate)))
+ (c
+ (simplify-conditional
+ (make-conditional src c (for-tail subsequent)
+ (for-tail alternate))))))
+ (($ <application> src
+ ($ <primitive-ref> _ '@call-with-values)
+ (producer
+ ($ <lambda> _ _
+ (and consumer
+ ;; No optional or kwargs.
+ ($ <lambda-case>
+ _ req #f rest #f () gensyms body #f)))))
+ (for-tail (make-let-values src (make-application src producer '())
+ consumer)))
+ (($ <application> src ($ <primitive-ref> _ 'values) exps)
+ (cond
+ ((null? exps)
+ (if (eq? ctx 'effect)
+ (make-void #f)
+ exp))
+ (else
+ (let ((vals (map for-value exps)))
+ (if (and (case ctx
+ ((value test effect) #t)
+ (else (null? (cdr vals))))
+ (every singly-valued-expression? vals))
+ (for-tail (make-sequence src (append (cdr vals) (list (car vals)))))
+ (make-application src (make-primitive-ref #f 'values) vals))))))
+ (($ <application> src (and apply ($ <primitive-ref> _ (or 'apply '@apply)))
+ (proc args ... tail))
+ (let lp ((tail* (find-definition tail 1)) (speculative? #t))
+ (define (copyable? x)
+ ;; Inlining a result from find-definition effectively copies it,
+ ;; relying on the let-pruning to remove its original binding. We
+ ;; shouldn't copy non-constant expressions.
+ (or (not speculative?) (constant-expression? x)))
+ (match tail*
+ (($ <const> _ (args* ...))
+ (let ((args* (map (cut make-const #f <>) args*)))
+ (for-tail (make-application src proc (append args args*)))))
+ (($ <application> _ ($ <primitive-ref> _ 'cons)
+ ((and head (? copyable?)) (and tail (? copyable?))))
+ (for-tail (make-application src apply
+ (cons proc
+ (append args (list head tail))))))
+ (($ <application> _ ($ <primitive-ref> _ 'list)
+ (and args* ((? copyable?) ...)))
+ (for-tail (make-application src proc (append args args*))))
+ (tail*
+ (if speculative?
+ (lp (for-value tail) #f)
+ (let ((args (append (map for-value args) (list tail*))))
+ (make-application src apply
+ (cons (for-value proc) args))))))))
+ (($ <application> src orig-proc orig-args)
+ ;; todo: augment the global env with specialized functions
+ (let revisit-proc ((proc (visit orig-proc 'operator)))
+ (match proc
+ (($ <primitive-ref> _ (? constructor-primitive? name))
+ (cond
+ ((and (memq ctx '(effect test))
+ (match (cons name orig-args)
+ ((or ('cons _ _)
+ ('list . _)
+ ('vector . _)
+ ('make-prompt-tag)
+ ('make-prompt-tag ($ <const> _ (? string?))))
+ #t)
+ (_ #f)))
+ ;; Some expressions can be folded without visiting the
+ ;; arguments for value.
+ (let ((res (if (eq? ctx 'effect)
+ (make-void #f)
+ (make-const #f #t))))
+ (for-tail (make-sequence src (append orig-args (list res))))))
+ (else
+ (match (cons name (map for-value orig-args))
+ (('cons head tail)
+ (match tail
+ (($ <const> src (? (cut eq? <> '())))
+ (make-application src (make-primitive-ref #f 'list)
+ (list head)))
+ (($ <application> src ($ <primitive-ref> _ 'list) elts)
+ (make-application src (make-primitive-ref #f 'list)
+ (cons head elts)))
+ (_ (make-application src proc (list head tail)))))
+ ((_ . args)
+ (make-application src proc args))))))
+ (($ <primitive-ref> _ (? accessor-primitive? name))
+ (match (cons name (map for-value orig-args))
+ ;; FIXME: these for-tail recursions could take place outside
+ ;; an effort counter.
+ (('car ($ <application> src ($ <primitive-ref> _ 'cons) (head tail)))
+ (for-tail (make-sequence src (list tail head))))
+ (('cdr ($ <application> src ($ <primitive-ref> _ 'cons) (head tail)))
+ (for-tail (make-sequence src (list head tail))))
+ (('car ($ <application> src ($ <primitive-ref> _ 'list) (head . tail)))
+ (for-tail (make-sequence src (append tail (list head)))))
+ (('cdr ($ <application> src ($ <primitive-ref> _ 'list) (head . tail)))
+ (for-tail (make-sequence
+ src
+ (list head
+ (make-application
+ src (make-primitive-ref #f 'list) tail)))))
+
+ (('car ($ <const> src (head . tail)))
+ (for-tail (make-const src head)))
+ (('cdr ($ <const> src (head . tail)))
+ (for-tail (make-const src tail)))
+ (((or 'memq 'memv) k ($ <const> _ (elts ...)))
+ ;; FIXME: factor
+ (case ctx
+ ((effect)
+ (for-tail
+ (make-sequence src (list k (make-void #f)))))
+ ((test)
+ (cond
+ ((const? k)
+ ;; A shortcut. The `else' case would handle it, but
+ ;; this way is faster.
+ (let ((member (case name ((memq) memq) ((memv) memv))))
+ (make-const #f (and (member (const-exp k) elts) #t))))
+ ((null? elts)
+ (for-tail
+ (make-sequence src (list k (make-const #f #f)))))
+ (else
+ (let ((t (gensym "t-"))
+ (eq (if (eq? name 'memq) 'eq? 'eqv?)))
+ (record-new-temporary! 't t (length elts))
+ (for-tail
+ (make-let
+ src (list 't) (list t) (list k)
+ (let lp ((elts elts))
+ (define test
+ (make-application
+ #f (make-primitive-ref #f eq)
+ (list (make-lexical-ref #f 't t)
+ (make-const #f (car elts)))))
+ (if (null? (cdr elts))
+ test
+ (make-conditional src test
+ (make-const #f #t)
+ (lp (cdr elts)))))))))))
+ (else
+ (cond
+ ((const? k)
+ (let ((member (case name ((memq) memq) ((memv) memv))))
+ (make-const #f (member (const-exp k) elts))))
+ ((null? elts)
+ (for-tail (make-sequence src (list k (make-const #f #f)))))
+ (else
+ (make-application src proc (list k (make-const #f elts))))))))
+ ((_ . args)
+ (or (fold-constants src name args ctx)
+ (make-application src proc args)))))
+ (($ <primitive-ref> _ (? effect-free-primitive? name))
+ (let ((args (map for-value orig-args)))
+ (or (fold-constants src name args ctx)
+ (make-application src proc args))))
+ (($ <lambda> _ _
+ ($ <lambda-case> _ req opt rest #f inits gensyms body #f))
+ ;; Simple case: no keyword arguments.
+ ;; todo: handle the more complex cases
+ (let* ((nargs (length orig-args))
+ (nreq (length req))
+ (nopt (if opt (length opt) 0))
+ (key (source-expression proc)))
+ (define (inlined-application)
+ (cond
+ ((= nargs (+ nreq nopt))
+ (make-let src
+ (append req
+ (or opt '())
+ (if rest (list rest) '()))
+ gensyms
+ (append orig-args
+ (if rest
+ (list (make-const #f '()))
+ '()))
+ body))
+ ((> nargs (+ nreq nopt))
+ (make-let src
+ (append req
+ (or opt '())
+ (list rest))
+ gensyms
+ (append (take orig-args (+ nreq nopt))
+ (list (make-application
+ #f
+ (make-primitive-ref #f 'list)
+ (drop orig-args (+ nreq nopt)))))
+ body))
+ (else
+ ;; Here we handle the case where nargs < nreq + nopt,
+ ;; so the rest argument (if any) will be empty, and
+ ;; there will be optional arguments that rely on their
+ ;; default initializers.
+ ;;
+ ;; The default initializers of optional arguments
+ ;; may refer to earlier arguments, so in the general
+ ;; case we must expand into a series of nested let
+ ;; expressions.
+ ;;
+ ;; In the generated code, the outermost let
+ ;; expression will bind all arguments provided by
+ ;; the application's argument list, as well as the
+ ;; empty rest argument, if any. Each remaining
+ ;; optional argument that relies on its default
+ ;; initializer will be bound within an inner let.
+ ;;
+ ;; rest-gensyms, rest-vars and rest-inits will have
+ ;; either 0 or 1 elements. They are oddly named, but
+ ;; allow simpler code below.
+ (let*-values
+ (((non-rest-gensyms rest-gensyms)
+ (split-at gensyms (+ nreq nopt)))
+ ((provided-gensyms default-gensyms)
+ (split-at non-rest-gensyms nargs))
+ ((provided-vars default-vars)
+ (split-at (append req opt) nargs))
+ ((rest-vars)
+ (if rest (list rest) '()))
+ ((rest-inits)
+ (if rest
+ (list (make-const #f '()))
+ '()))
+ ((default-inits)
+ (drop inits (- nargs nreq))))
+ (make-let src
+ (append provided-vars rest-vars)
+ (append provided-gensyms rest-gensyms)
+ (append orig-args rest-inits)
+ (fold-right (lambda (var gensym init body)
+ (make-let src
+ (list var)
+ (list gensym)
+ (list init)
+ body))
+ body
+ default-vars
+ default-gensyms
+ default-inits))))))
+
+ (cond
+ ((or (< nargs nreq) (and (not rest) (> nargs (+ nreq nopt))))
+ ;; An error, or effecting arguments.
+ (make-application src (for-call orig-proc)
+ (map for-value orig-args)))
+ ((or (and=> (find-counter key counter) counter-recursive?)
+ (lambda? orig-proc))
+ ;; A recursive call, or a lambda in the operator
+ ;; position of the source expression. Process again in
+ ;; tail context.
+ ;;
+ ;; In the recursive case, mark intervening counters as
+ ;; recursive, so we can handle a toplevel counter that
+ ;; recurses mutually with some other procedure.
+ ;; Otherwise, the next time we see the other procedure,
+ ;; the effort limit would be clamped to 100.
+ ;;
+ (let ((found (find-counter key counter)))
+ (if (and found (counter-recursive? found))
+ (let lp ((counter counter))
+ (if (not (eq? counter found))
+ (begin
+ (set-counter-recursive?! counter #t)
+ (lp (counter-prev counter)))))))
+
+ (log 'inline-recurse key)
+ (loop (inlined-application) env counter ctx))
+ (else
+ ;; An integration at the top-level, the first
+ ;; recursion of a recursive procedure, or a nested
+ ;; integration of a procedure that hasn't been seen
+ ;; yet.
+ (log 'inline-begin exp)
+ (let/ec k
+ (define (abort)
+ (log 'inline-abort exp)
+ (k (make-application src (for-call orig-proc)
+ (map for-value orig-args))))
+ (define new-counter
+ (cond
+ ;; These first two cases will transfer effort
+ ;; from the current counter into the new
+ ;; counter.
+ ((find-counter key counter)
+ => (lambda (prev)
+ (make-recursive-counter recursive-effort-limit
+ operand-size-limit
+ prev counter)))
+ (counter
+ (make-nested-counter abort key counter))
+ ;; This case opens a new account, effectively
+ ;; printing money. It should only do so once
+ ;; for each call site in the source program.
+ (else
+ (make-top-counter effort-limit operand-size-limit
+ abort key))))
+ (define result
+ (loop (inlined-application) env new-counter ctx))
+
+ (if counter
+ ;; The nested inlining attempt succeeded.
+ ;; Deposit the unspent effort and size back
+ ;; into the current counter.
+ (transfer! new-counter counter))
+
+ (log 'inline-end result exp)
+ result)))))
+ (($ <let> _ _ _ vals _)
+ ;; Attempt to inline `let' in the operator position.
+ ;;
+ ;; We have to re-visit the proc in value mode, since the
+ ;; `let' bindings might have been introduced or renamed,
+ ;; whereas the lambda (if any) in operator position has not
+ ;; been renamed.
+ (if (or (and-map constant-expression? vals)
+ (and-map constant-expression? orig-args))
+ ;; The arguments and the let-bound values commute.
+ (match (for-value orig-proc)
+ (($ <let> lsrc names syms vals body)
+ (log 'inline-let orig-proc)
+ (for-tail
+ (make-let lsrc names syms vals
+ (make-application src body orig-args))))
+ ;; It's possible for a `let' to go away after the
+ ;; visit due to the fact that visiting a procedure in
+ ;; value context will prune unused bindings, whereas
+ ;; visiting in operator mode can't because it doesn't
+ ;; traverse through lambdas. In that case re-visit
+ ;; the procedure.
+ (proc (revisit-proc proc)))
+ (make-application src (for-call orig-proc)
+ (map for-value orig-args))))
+ (_
+ (make-application src (for-call orig-proc)
+ (map for-value orig-args))))))
+ (($ <lambda> src meta body)
+ (case ctx
+ ((effect) (make-void #f))
+ ((test) (make-const #f #t))
+ ((operator) exp)
+ (else (record-source-expression!
+ exp
+ (make-lambda src meta (and body (for-values body)))))))
+ (($ <lambda-case> src req opt rest kw inits gensyms body alt)
+ (define (lift-applied-lambda body gensyms)
+ (and (not opt) rest (not kw)
+ (match body
+ (($ <application> _
+ ($ <primitive-ref> _ '@apply)
+ (($ <lambda> _ _ (and lcase ($ <lambda-case>)))
+ ($ <lexical-ref> _ _ sym)
+ ...))
+ (and (equal? sym gensyms)
+ (not (lambda-case-alternate lcase))
+ lcase))
+ (_ #f))))
+ (let* ((vars (map lookup-var gensyms))
+ (new (fresh-gensyms vars))
+ (env (fold extend-env env gensyms
+ (make-unbound-operands vars new)))
+ (new-sym (lambda (old)
+ (operand-sym (cdr (vhash-assq old env)))))
+ (body (loop body env counter ctx)))
+ (or
+ ;; (lambda args (apply (lambda ...) args)) => (lambda ...)
+ (lift-applied-lambda body new)
+ (make-lambda-case src req opt rest
+ (match kw
+ ((aok? (kw name old) ...)
+ (cons aok? (map list kw name (map new-sym old))))
+ (_ #f))
+ (map (cut loop <> env counter 'value) inits)
+ new
+ body
+ (and alt (for-tail alt))))))
+ (($ <sequence> src exps)
+ (let lp ((exps exps) (effects '()))
+ (match exps
+ ((last)
+ (if (null? effects)
+ (for-tail last)
+ (make-sequence
+ src
+ (reverse (cons (for-tail last) effects)))))
+ ((head . rest)
+ (let ((head (for-effect head)))
+ (cond
+ ((sequence? head)
+ (lp (append (sequence-exps head) rest) effects))
+ ((void? head)
+ (lp rest effects))
+ (else
+ (lp rest (cons head effects)))))))))
+ (($ <prompt> src tag body handler)
+ (define (make-prompt-tag? x)
+ (match x
+ (($ <application> _ ($ <primitive-ref> _ 'make-prompt-tag)
+ (or () ((? constant-expression?))))
+ #t)
+ (_ #f)))
+
+ (let ((tag (for-value tag))
+ (body (for-values body)))
+ (cond
+ ((find-definition tag 1)
+ (lambda (val op)
+ (make-prompt-tag? val))
+ => (lambda (val op)
+ ;; There is no way that an <abort> could know the tag
+ ;; for this <prompt>, so we can elide the <prompt>
+ ;; entirely.
+ (unrecord-operand-uses op 1)
+ body))
+ ((find-definition tag 2)
+ (lambda (val op)
+ (and (make-prompt-tag? val)
+ (abort? body)
+ (tree-il=? (abort-tag body) tag)))
+ => (lambda (val op)
+ ;; (let ((t (make-prompt-tag)))
+ ;; (call-with-prompt t
+ ;; (lambda () (abort-to-prompt t val ...))
+ ;; (lambda (k arg ...) e ...)))
+ ;; => (let-values (((k arg ...) (values values val ...)))
+ ;; e ...)
+ (unrecord-operand-uses op 2)
+ (for-tail
+ (make-let-values
+ src
+ (make-application #f (make-primitive-ref #f 'apply)
+ `(,(make-primitive-ref #f 'values)
+ ,(make-primitive-ref #f 'values)
+ ,@(abort-args body)
+ ,(abort-tail body)))
+ (for-tail handler)))))
+ (else
+ (make-prompt src tag body (for-tail handler))))))
+ (($ <abort> src tag args tail)
+ (make-abort src (for-value tag) (map for-value args)
+ (for-value tail))))))
+;;; open-coding primitive procedures
+
+;; Copyright (C) 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language tree-il primitives)
+ #\use-module (system base pmatch)
+ #\use-module (ice-9 match)
+ #\use-module (rnrs bytevectors)
+ #\use-module (system base syntax)
+ #\use-module (language tree-il)
+ #\use-module (srfi srfi-4)
+ #\use-module (srfi srfi-16)
+ #\export (resolve-primitives! add-interesting-primitive!
+ expand-primitives!
+ effect-free-primitive? effect+exception-free-primitive?
+ constructor-primitive? accessor-primitive?
+ singly-valued-primitive? bailout-primitive?
+ negate-primitive))
+
+;; When adding to this, be sure to update *multiply-valued-primitives*
+;; if appropriate.
+(define *interesting-primitive-names*
+ '(apply @apply
+ call-with-values @call-with-values
+ call-with-current-continuation @call-with-current-continuation
+ call/cc
+ dynamic-wind
+ @dynamic-wind
+ values
+ eq? eqv? equal?
+ memq memv
+ = < > <= >= zero? positive? negative?
+ + * - / 1- 1+ quotient remainder modulo
+ ash logand logior logxor lognot
+ not
+ pair? null? list? symbol? vector? string? struct? number? char?
+
+ complex? real? rational? inf? nan? integer? exact? inexact? even? odd?
+
+ char<? char<=? char>=? char>?
+
+ integer->char char->integer number->string string->number
+
+ acons cons cons*
+
+ list vector
+
+ car cdr
+ set-car! set-cdr!
+
+ caar cadr cdar cddr
+
+ caaar caadr cadar caddr cdaar cdadr cddar cdddr
+
+ caaaar caaadr caadar caaddr cadaar cadadr caddar cadddr
+ cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar cddddr
+
+ vector-ref vector-set!
+ variable-ref variable-set!
+ variable-bound?
+
+ fluid-ref fluid-set!
+
+ @prompt call-with-prompt @abort abort-to-prompt
+ make-prompt-tag
+
+ throw error scm-error
+
+ string-length string-ref string-set!
+
+ struct-vtable make-struct struct-ref struct-set!
+
+ bytevector-u8-ref bytevector-u8-set!
+ bytevector-s8-ref bytevector-s8-set!
+ u8vector-ref u8vector-set! s8vector-ref s8vector-set!
+
+ bytevector-u16-ref bytevector-u16-set!
+ bytevector-u16-native-ref bytevector-u16-native-set!
+ bytevector-s16-ref bytevector-s16-set!
+ bytevector-s16-native-ref bytevector-s16-native-set!
+ u16vector-ref u16vector-set! s16vector-ref s16vector-set!
+
+ bytevector-u32-ref bytevector-u32-set!
+ bytevector-u32-native-ref bytevector-u32-native-set!
+ bytevector-s32-ref bytevector-s32-set!
+ bytevector-s32-native-ref bytevector-s32-native-set!
+ u32vector-ref u32vector-set! s32vector-ref s32vector-set!
+
+ bytevector-u64-ref bytevector-u64-set!
+ bytevector-u64-native-ref bytevector-u64-native-set!
+ bytevector-s64-ref bytevector-s64-set!
+ bytevector-s64-native-ref bytevector-s64-native-set!
+ u64vector-ref u64vector-set! s64vector-ref s64vector-set!
+
+ bytevector-ieee-single-ref bytevector-ieee-single-set!
+ bytevector-ieee-single-native-ref bytevector-ieee-single-native-set!
+ bytevector-ieee-double-ref bytevector-ieee-double-set!
+ bytevector-ieee-double-native-ref bytevector-ieee-double-native-set!
+ f32vector-ref f32vector-set! f64vector-ref f64vector-set!))
+
+(define (add-interesting-primitive! name)
+ (hashq-set! *interesting-primitive-vars*
+ (or (module-variable (current-module) name)
+ (error "unbound interesting primitive" name))
+ name))
+
+(define *interesting-primitive-vars* (make-hash-table))
+
+(for-each add-interesting-primitive! *interesting-primitive-names*)
+
+(define *primitive-constructors*
+ ;; Primitives that return a fresh object.
+ '(acons cons cons* list vector make-struct make-struct/no-tail
+ make-prompt-tag))
+
+(define *primitive-accessors*
+ ;; Primitives that are pure, but whose result depends on the mutable
+ ;; memory pointed to by their operands.
+ '(vector-ref
+ car cdr
+ memq memv
+ struct-ref
+ string-ref
+ bytevector-u8-ref bytevector-s8-ref
+ bytevector-u16-ref bytevector-u16-native-ref
+ bytevector-s16-ref bytevector-s16-native-ref
+ bytevector-u32-ref bytevector-u32-native-ref
+ bytevector-s32-ref bytevector-s32-native-ref
+ bytevector-u64-ref bytevector-u64-native-ref
+ bytevector-s64-ref bytevector-s64-native-ref
+ bytevector-ieee-single-ref bytevector-ieee-single-native-ref
+ bytevector-ieee-double-ref bytevector-ieee-double-native-ref))
+
+(define *effect-free-primitives*
+ `(values
+ eq? eqv? equal?
+ = < > <= >= zero? positive? negative?
+ ash logand logior logxor lognot
+ + * - / 1- 1+ quotient remainder modulo
+ not
+ pair? null? list? symbol? vector? struct? string? number? char?
+ complex? real? rational? inf? nan? integer? exact? inexact? even? odd?
+ char<? char<=? char>=? char>?
+ integer->char char->integer number->string string->number
+ struct-vtable
+ string-length
+ ;; These all should get expanded out by expand-primitives!.
+ caar cadr cdar cddr
+ caaar caadr cadar caddr cdaar cdadr cddar cdddr
+ caaaar caaadr caadar caaddr cadaar cadadr caddar cadddr
+ cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar cddddr
+ ,@*primitive-constructors*
+ ,@*primitive-accessors*))
+
+;; Like *effect-free-primitives* above, but further restricted in that they
+;; cannot raise exceptions.
+(define *effect+exception-free-primitives*
+ '(values
+ eq? eqv? equal?
+ not
+ pair? null? list? symbol? vector? struct? string? number? char?
+ acons cons cons* list vector))
+
+;; Primitives that don't always return one value.
+(define *multiply-valued-primitives*
+ '(apply @apply
+ call-with-values @call-with-values
+ call-with-current-continuation @call-with-current-continuation
+ call/cc
+ dynamic-wind
+ @dynamic-wind
+ values
+ @prompt call-with-prompt @abort abort-to-prompt))
+
+;; Procedures that cause a nonlocal, non-resumable abort.
+(define *bailout-primitives*
+ '(throw error scm-error))
+
+;; Negatable predicates.
+(define *negatable-primitives*
+ '((even? . odd?)
+ (exact? . inexact?)
+ ;; (< <= > >=) are not negatable because of NaNs.
+ (char<? . char>=?)
+ (char>? . char<=?)))
+
+(define *effect-free-primitive-table* (make-hash-table))
+(define *effect+exceptions-free-primitive-table* (make-hash-table))
+(define *multiply-valued-primitive-table* (make-hash-table))
+(define *bailout-primitive-table* (make-hash-table))
+(define *negatable-primitive-table* (make-hash-table))
+
+(for-each (lambda (x)
+ (hashq-set! *effect-free-primitive-table* x #t))
+ *effect-free-primitives*)
+(for-each (lambda (x)
+ (hashq-set! *effect+exceptions-free-primitive-table* x #t))
+ *effect+exception-free-primitives*)
+(for-each (lambda (x)
+ (hashq-set! *multiply-valued-primitive-table* x #t))
+ *multiply-valued-primitives*)
+(for-each (lambda (x)
+ (hashq-set! *bailout-primitive-table* x #t))
+ *bailout-primitives*)
+(for-each (lambda (x)
+ (hashq-set! *negatable-primitive-table* (car x) (cdr x))
+ (hashq-set! *negatable-primitive-table* (cdr x) (car x)))
+ *negatable-primitives*)
+
+(define (constructor-primitive? prim)
+ (memq prim *primitive-constructors*))
+(define (accessor-primitive? prim)
+ (memq prim *primitive-accessors*))
+(define (effect-free-primitive? prim)
+ (hashq-ref *effect-free-primitive-table* prim))
+(define (effect+exception-free-primitive? prim)
+ (hashq-ref *effect+exceptions-free-primitive-table* prim))
+(define (singly-valued-primitive? prim)
+ (not (hashq-ref *multiply-valued-primitive-table* prim)))
+(define (bailout-primitive? prim)
+ (hashq-ref *bailout-primitive-table* prim))
+(define (negate-primitive prim)
+ (hashq-ref *negatable-primitive-table* prim))
+
+(define (resolve-primitives! x mod)
+ (post-order!
+ (lambda (x)
+ (record-case x
+ ((<toplevel-ref> src name)
+ (and=> (hashq-ref *interesting-primitive-vars*
+ (module-variable mod name))
+ (lambda (name) (make-primitive-ref src name))))
+ ((<module-ref> src mod name public?)
+ (and=> (and=> (resolve-module mod)
+ (if public?
+ module-public-interface
+ identity))
+ (lambda (m)
+ (and=> (hashq-ref *interesting-primitive-vars*
+ (module-variable m name))
+ (lambda (name)
+ (make-primitive-ref src name))))))
+ (else #f)))
+ x))
+
+
+
+(define *primitive-expand-table* (make-hash-table))
+
+(define (expand-primitives! x)
+ (pre-order!
+ (lambda (x)
+ (record-case x
+ ((<application> src proc args)
+ (and (primitive-ref? proc)
+ (let ((expand (hashq-ref *primitive-expand-table*
+ (primitive-ref-name proc))))
+ (and expand (apply expand src args)))))
+ (else #f)))
+ x))
+
+;;; I actually did spend about 10 minutes trying to redo this with
+;;; syntax-rules. Patches appreciated.
+;;;
+(define-macro (define-primitive-expander sym . clauses)
+ (define (inline-args args)
+ (let lp ((in args) (out '()))
+ (cond ((null? in) `(list ,@(reverse out)))
+ ((symbol? in) `(cons* ,@(reverse out) ,in))
+ ((pair? (car in))
+ (lp (cdr in)
+ (cons (if (eq? (caar in) 'quote)
+ `(make-const src ,@(cdar in))
+ `(make-application src (make-primitive-ref src ',(caar in))
+ ,(inline-args (cdar in))))
+ out)))
+ ((symbol? (car in))
+ ;; assume it's locally bound
+ (lp (cdr in) (cons (car in) out)))
+ ((self-evaluating? (car in))
+ (lp (cdr in) (cons `(make-const src ,(car in)) out)))
+ (else
+ (error "what what" (car in))))))
+ (define (consequent exp)
+ (cond
+ ((pair? exp)
+ (pmatch exp
+ ((if ,test ,then ,else)
+ `(if ,test
+ ,(consequent then)
+ ,(consequent else)))
+ (else
+ `(make-application src (make-primitive-ref src ',(car exp))
+ ,(inline-args (cdr exp))))))
+ ((symbol? exp)
+ ;; assume locally bound
+ exp)
+ ((number? exp)
+ `(make-const src ,exp))
+ ((not exp)
+ ;; failed match
+ #f)
+ (else (error "bad consequent yall" exp))))
+ `(hashq-set! *primitive-expand-table*
+ ',sym
+ (match-lambda*
+ ,@(let lp ((in clauses) (out '()))
+ (if (null? in)
+ (reverse (cons '(_ #f) out))
+ (lp (cddr in)
+ (cons `((src . ,(car in))
+ ,(consequent (cadr in)))
+ out)))))))
+
+(define-primitive-expander zero? (x)
+ (= x 0))
+
+(define-primitive-expander positive? (x)
+ (> x 0))
+
+(define-primitive-expander negative? (x)
+ (< x 0))
+
+;; FIXME: All the code that uses `const?' is redundant with `peval'.
+
+(define-primitive-expander +
+ () 0
+ (x) (values x)
+ (x y) (if (and (const? y) (eqv? (const-exp y) 1))
+ (1+ x)
+ (if (and (const? y) (eqv? (const-exp y) -1))
+ (1- x)
+ (if (and (const? x) (eqv? (const-exp x) 1))
+ (1+ y)
+ (if (and (const? x) (eqv? (const-exp x) -1))
+ (1- y)
+ (+ x y)))))
+ (x y z ... last) (+ (+ x y . z) last))
+
+(define-primitive-expander *
+ () 1
+ (x) (values x)
+ (x y z ... last) (* (* x y . z) last))
+
+(define-primitive-expander -
+ (x) (- 0 x)
+ (x y) (if (and (const? y) (eqv? (const-exp y) 1))
+ (1- x)
+ (- x y))
+ (x y z ... last) (- (- x y . z) last))
+
+(define-primitive-expander /
+ (x) (/ 1 x)
+ (x y z ... last) (/ (/ x y . z) last))
+
+(define-primitive-expander logior
+ () 0
+ (x) (logior x 0)
+ (x y) (logior x y)
+ (x y z ... last) (logior (logior x y . z) last))
+
+(define-primitive-expander logand
+ () -1
+ (x) (logand x -1)
+ (x y) (logand x y)
+ (x y z ... last) (logand (logand x y . z) last))
+
+(define-primitive-expander caar (x) (car (car x)))
+(define-primitive-expander cadr (x) (car (cdr x)))
+(define-primitive-expander cdar (x) (cdr (car x)))
+(define-primitive-expander cddr (x) (cdr (cdr x)))
+(define-primitive-expander caaar (x) (car (car (car x))))
+(define-primitive-expander caadr (x) (car (car (cdr x))))
+(define-primitive-expander cadar (x) (car (cdr (car x))))
+(define-primitive-expander caddr (x) (car (cdr (cdr x))))
+(define-primitive-expander cdaar (x) (cdr (car (car x))))
+(define-primitive-expander cdadr (x) (cdr (car (cdr x))))
+(define-primitive-expander cddar (x) (cdr (cdr (car x))))
+(define-primitive-expander cdddr (x) (cdr (cdr (cdr x))))
+(define-primitive-expander caaaar (x) (car (car (car (car x)))))
+(define-primitive-expander caaadr (x) (car (car (car (cdr x)))))
+(define-primitive-expander caadar (x) (car (car (cdr (car x)))))
+(define-primitive-expander caaddr (x) (car (car (cdr (cdr x)))))
+(define-primitive-expander cadaar (x) (car (cdr (car (car x)))))
+(define-primitive-expander cadadr (x) (car (cdr (car (cdr x)))))
+(define-primitive-expander caddar (x) (car (cdr (cdr (car x)))))
+(define-primitive-expander cadddr (x) (car (cdr (cdr (cdr x)))))
+(define-primitive-expander cdaaar (x) (cdr (car (car (car x)))))
+(define-primitive-expander cdaadr (x) (cdr (car (car (cdr x)))))
+(define-primitive-expander cdadar (x) (cdr (car (cdr (car x)))))
+(define-primitive-expander cdaddr (x) (cdr (car (cdr (cdr x)))))
+(define-primitive-expander cddaar (x) (cdr (cdr (car (car x)))))
+(define-primitive-expander cddadr (x) (cdr (cdr (car (cdr x)))))
+(define-primitive-expander cdddar (x) (cdr (cdr (cdr (car x)))))
+(define-primitive-expander cddddr (x) (cdr (cdr (cdr (cdr x)))))
+
+(define-primitive-expander cons*
+ (x) (values x)
+ (x y) (cons x y)
+ (x y . rest) (cons x (cons* y . rest)))
+
+(define-primitive-expander acons (x y z)
+ (cons (cons x y) z))
+
+(define-primitive-expander apply (f a0 . args)
+ (@apply f a0 . args))
+
+(define-primitive-expander call-with-values (producer consumer)
+ (@call-with-values producer consumer))
+
+(define-primitive-expander call-with-current-continuation (proc)
+ (@call-with-current-continuation proc))
+
+(define-primitive-expander call/cc (proc)
+ (@call-with-current-continuation proc))
+
+(define-primitive-expander make-struct (vtable tail-size . args)
+ (if (and (const? tail-size)
+ (let ((n (const-exp tail-size)))
+ (and (number? n) (exact? n) (zero? n))))
+ (make-struct/no-tail vtable . args)
+ #f))
+
+(define-primitive-expander u8vector-ref (vec i)
+ (bytevector-u8-ref vec i))
+(define-primitive-expander u8vector-set! (vec i x)
+ (bytevector-u8-set! vec i x))
+(define-primitive-expander s8vector-ref (vec i)
+ (bytevector-s8-ref vec i))
+(define-primitive-expander s8vector-set! (vec i x)
+ (bytevector-s8-set! vec i x))
+
+(define-primitive-expander u16vector-ref (vec i)
+ (bytevector-u16-native-ref vec (* i 2)))
+(define-primitive-expander u16vector-set! (vec i x)
+ (bytevector-u16-native-set! vec (* i 2) x))
+(define-primitive-expander s16vector-ref (vec i)
+ (bytevector-s16-native-ref vec (* i 2)))
+(define-primitive-expander s16vector-set! (vec i x)
+ (bytevector-s16-native-set! vec (* i 2) x))
+
+(define-primitive-expander u32vector-ref (vec i)
+ (bytevector-u32-native-ref vec (* i 4)))
+(define-primitive-expander u32vector-set! (vec i x)
+ (bytevector-u32-native-set! vec (* i 4) x))
+(define-primitive-expander s32vector-ref (vec i)
+ (bytevector-s32-native-ref vec (* i 4)))
+(define-primitive-expander s32vector-set! (vec i x)
+ (bytevector-s32-native-set! vec (* i 4) x))
+
+(define-primitive-expander u64vector-ref (vec i)
+ (bytevector-u64-native-ref vec (* i 8)))
+(define-primitive-expander u64vector-set! (vec i x)
+ (bytevector-u64-native-set! vec (* i 8) x))
+(define-primitive-expander s64vector-ref (vec i)
+ (bytevector-s64-native-ref vec (* i 8)))
+(define-primitive-expander s64vector-set! (vec i x)
+ (bytevector-s64-native-set! vec (* i 8) x))
+
+(define-primitive-expander f32vector-ref (vec i)
+ (bytevector-ieee-single-native-ref vec (* i 4)))
+(define-primitive-expander f32vector-set! (vec i x)
+ (bytevector-ieee-single-native-set! vec (* i 4) x))
+(define-primitive-expander f32vector-ref (vec i)
+ (bytevector-ieee-single-native-ref vec (* i 4)))
+(define-primitive-expander f32vector-set! (vec i x)
+ (bytevector-ieee-single-native-set! vec (* i 4) x))
+
+(define-primitive-expander f64vector-ref (vec i)
+ (bytevector-ieee-double-native-ref vec (* i 8)))
+(define-primitive-expander f64vector-set! (vec i x)
+ (bytevector-ieee-double-native-set! vec (* i 8) x))
+(define-primitive-expander f64vector-ref (vec i)
+ (bytevector-ieee-double-native-ref vec (* i 8)))
+(define-primitive-expander f64vector-set! (vec i x)
+ (bytevector-ieee-double-native-set! vec (* i 8) x))
+
+(define (chained-comparison-expander prim-name)
+ (case-lambda
+ ((src) (make-const src #t))
+ ((src a) #f)
+ ((src a b) #f)
+ ((src a b . rest)
+ (let* ((prim (make-primitive-ref src prim-name))
+ (b-sym (gensym "b"))
+ (b* (make-lexical-ref src 'b b-sym)))
+ (make-let src
+ '(b)
+ (list b-sym)
+ (list b)
+ (make-conditional src
+ (make-application src prim (list a b*))
+ (make-application src prim (cons b* rest))
+ (make-const src #f)))))))
+
+(for-each (lambda (prim-name)
+ (hashq-set! *primitive-expand-table* prim-name
+ (chained-comparison-expander prim-name)))
+ '(< > <= >= =))
+
+;; Appropriate for use with either 'eqv?' or 'equal?'.
+(define maybe-simplify-to-eq
+ (case-lambda
+ ((src a b)
+ ;; Simplify cases where either A or B is constant.
+ (define (maybe-simplify a b)
+ (and (const? a)
+ (let ((v (const-exp a)))
+ (and (or (memq v '(#f #t () #nil))
+ (symbol? v)
+ (and (integer? v)
+ (exact? v)
+ (<= most-negative-fixnum v most-positive-fixnum)))
+ (make-application src (make-primitive-ref #f 'eq?)
+ (list a b))))))
+ (or (maybe-simplify a b) (maybe-simplify b a)))
+ (else #f)))
+
+(hashq-set! *primitive-expand-table* 'eqv? maybe-simplify-to-eq)
+(hashq-set! *primitive-expand-table* 'equal? maybe-simplify-to-eq)
+
+(hashq-set! *primitive-expand-table*
+ 'dynamic-wind
+ (case-lambda
+ ((src pre thunk post)
+ (let ((PRE (gensym "pre-"))
+ (THUNK (gensym "thunk-"))
+ (POST (gensym "post-")))
+ (make-let
+ src
+ '(pre thunk post)
+ (list PRE THUNK POST)
+ (list pre thunk post)
+ (make-dynwind
+ src
+ (make-lexical-ref #f 'pre PRE)
+ (make-application #f (make-lexical-ref #f 'thunk THUNK) '())
+ (make-lexical-ref #f 'post POST)))))
+ (else #f)))
+
+(hashq-set! *primitive-expand-table*
+ '@dynamic-wind
+ (case-lambda
+ ((src pre expr post)
+ (let ((PRE (gensym "pre-"))
+ (POST (gensym "post-")))
+ (make-let
+ src
+ '(pre post)
+ (list PRE POST)
+ (list pre post)
+ (make-dynwind
+ src
+ (make-lexical-ref #f 'pre PRE)
+ expr
+ (make-lexical-ref #f 'post POST)))))))
+
+(hashq-set! *primitive-expand-table*
+ 'fluid-ref
+ (case-lambda
+ ((src fluid) (make-dynref src fluid))
+ (else #f)))
+
+(hashq-set! *primitive-expand-table*
+ 'fluid-set!
+ (case-lambda
+ ((src fluid exp) (make-dynset src fluid exp))
+ (else #f)))
+
+(hashq-set! *primitive-expand-table*
+ '@prompt
+ (case-lambda
+ ((src tag exp handler)
+ (let ((args-sym (gensym)))
+ (make-prompt
+ src tag exp
+ ;; If handler itself is a lambda, the inliner can do some
+ ;; trickery here.
+ (make-lambda-case
+ (tree-il-src handler) '() #f 'args #f '() (list args-sym)
+ (make-application #f (make-primitive-ref #f 'apply)
+ (list handler
+ (make-lexical-ref #f 'args args-sym)))
+ #f))))
+ (else #f)))
+
+(hashq-set! *primitive-expand-table*
+ 'call-with-prompt
+ (case-lambda
+ ((src tag thunk handler)
+ (let ((handler-sym (gensym))
+ (args-sym (gensym)))
+ (make-let
+ src '(handler) (list handler-sym) (list handler)
+ (make-prompt
+ src tag (make-application #f thunk '())
+ ;; If handler itself is a lambda, the inliner can do some
+ ;; trickery here.
+ (make-lambda-case
+ (tree-il-src handler) '() #f 'args #f '() (list args-sym)
+ (make-application
+ #f (make-primitive-ref #f 'apply)
+ (list (make-lexical-ref #f 'handler handler-sym)
+ (make-lexical-ref #f 'args args-sym)))
+ #f)))))
+ (else #f)))
+
+(hashq-set! *primitive-expand-table*
+ '@abort
+ (case-lambda
+ ((src tag tail-args)
+ (make-abort src tag '() tail-args))
+ (else #f)))
+(hashq-set! *primitive-expand-table*
+ 'abort-to-prompt
+ (case-lambda
+ ((src tag . args)
+ (make-abort src tag args (make-const #f '())))
+ (else #f)))
+;;; Tree Intermediate Language
+
+;; Copyright (C) 2009, 2010, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language tree-il spec)
+ #\use-module (system base language)
+ #\use-module (system base pmatch)
+ #\use-module (language glil)
+ #\use-module (language tree-il)
+ #\use-module (language tree-il compile-glil)
+ #\export (tree-il))
+
+(define (write-tree-il exp . port)
+ (apply write (unparse-tree-il exp) port))
+
+(define (join exps env)
+ (pmatch exps
+ (() (make-void #f))
+ ((,x) x)
+ (else (make-sequence #f exps))))
+
+(define-language tree-il
+ #\title "Tree Intermediate Language"
+ #\reader (lambda (port env) (read port))
+ #\printer write-tree-il
+ #\parser parse-tree-il
+ #\joiner join
+ #\compilers `((glil . ,compile-glil))
+ #\for-humans? #f
+ )
+;;; Guile Lowlevel Intermediate Language
+
+;; Copyright (C) 2001, 2010, 2013 Free Software Foundation, Inc.
+
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (language value spec)
+ #\use-module (system base language)
+ #\export (value))
+
+(define-language value
+ #\title "Values"
+ #\reader #f
+ #\printer write
+ #\for-humans? #f
+ )
+;;; installed-scm-file
+
+;;;; Copyright (C) 1998,1999,2000,2001,2002, 2003, 2006, 2009, 2010, 2011, 2014, 2015 Free Software Foundation, Inc.
+;;;; Copyright (C) 1993-1998 Erick Gallesio - I3S-CNRS/ESSI <eg@unice.fr>
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+;;;;
+;;;; This file was based upon stklos.stk from the STk distribution
+;;;; version 4.0.1 by Erick Gallesio <eg@unice.fr>.
+;;;;
+
+(define-module (oop goops)
+ #\use-module (srfi srfi-1)
+ #\use-module (ice-9 match)
+ #\use-module (oop goops util)
+ #\export-syntax (define-class class standard-define-class
+ define-generic define-accessor define-method
+ define-extended-generic define-extended-generics
+ method)
+ #\export (is-a? class-of
+ ensure-metaclass ensure-metaclass-with-supers
+ make-class
+ make-generic ensure-generic
+ make-extended-generic
+ make-accessor ensure-accessor
+ add-method!
+ class-slot-ref class-slot-set! slot-unbound slot-missing
+ slot-definition-name slot-definition-options
+ slot-definition-allocation
+ slot-definition-getter slot-definition-setter
+ slot-definition-accessor
+ slot-definition-init-value slot-definition-init-form
+ slot-definition-init-thunk slot-definition-init-keyword
+ slot-init-function class-slot-definition
+ method-source
+ compute-cpl compute-std-cpl compute-get-n-set compute-slots
+ compute-getter-method compute-setter-method
+ allocate-instance initialize make-instance make
+ no-next-method no-applicable-method no-method
+ change-class update-instance-for-different-class
+ shallow-clone deep-clone
+ class-redefinition
+ apply-generic apply-method apply-methods
+ compute-applicable-methods %compute-applicable-methods
+ method-more-specific? sort-applicable-methods
+ class-subclasses class-methods
+ goops-error
+ min-fixnum max-fixnum
+ ;;; *fixme* Should go into goops.c
+ instance? slot-ref-using-class
+ slot-set-using-class! slot-bound-using-class?
+ slot-exists-using-class? slot-ref slot-set! slot-bound?
+ class-name class-direct-supers class-direct-subclasses
+ class-direct-methods class-direct-slots class-precedence-list
+ class-slots
+ generic-function-name
+ generic-function-methods method-generic-function
+ method-specializers method-formals
+ primitive-generic-generic enable-primitive-generic!
+ method-procedure accessor-method-slot-definition
+ slot-exists? make find-method get-keyword))
+
+(define *goops-module* (current-module))
+
+;; First initialize the builtin part of GOOPS
+(eval-when (expand load eval)
+ (%init-goops-builtins))
+
+(eval-when (expand load eval)
+ (use-modules ((language tree-il primitives) \:select (add-interesting-primitive!)))
+ (add-interesting-primitive! 'class-of))
+
+;; Then load the rest of GOOPS
+(use-modules (oop goops dispatch))
+
+;;;
+;;; Compiling next methods into method bodies
+;;;
+
+;;; So, for the reader: there basic idea is that, given that the
+;;; semantics of `next-method' depend on the concrete types being
+;;; dispatched, why not compile a specific procedure to handle each type
+;;; combination that we see at runtime.
+;;;
+;;; In theory we can do much better than a bytecode compilation, because
+;;; we know the *exact* types of the arguments. It's ideal for native
+;;; compilation. A task for the future.
+;;;
+;;; I think this whole generic application mess would benefit from a
+;;; strict MOP.
+
+(define (compute-cmethod methods types)
+ (match methods
+ ((method . methods)
+ (let ((make-procedure (slot-ref method 'make-procedure)))
+ (if make-procedure
+ (make-procedure
+ (if (null? methods)
+ (lambda args
+ (no-next-method (method-generic-function method) args))
+ (compute-cmethod methods types)))
+ (method-procedure method))))))
+
+
+(eval-when (expand load eval)
+ (define min-fixnum (- (expt 2 29)))
+ (define max-fixnum (- (expt 2 29) 1)))
+
+;;
+;; goops-error
+;;
+(define (goops-error format-string . args)
+ (scm-error 'goops-error #f format-string args '()))
+
+;;
+;; is-a?
+;;
+(define (is-a? obj class)
+ (and (memq class (class-precedence-list (class-of obj))) #t))
+
+
+;;;
+;;; {Meta classes}
+;;;
+
+(define ensure-metaclass-with-supers
+ (let ((table-of-metas '()))
+ (lambda (meta-supers)
+ (let ((entry (assoc meta-supers table-of-metas)))
+ (if entry
+ ;; Found a previously created metaclass
+ (cdr entry)
+ ;; Create a new meta-class which inherit from "meta-supers"
+ (let ((new (make <class> #\dsupers meta-supers
+ #\slots '()
+ #\name (gensym "metaclass"))))
+ (set! table-of-metas (cons (cons meta-supers new) table-of-metas))
+ new))))))
+
+(define (ensure-metaclass supers)
+ (if (null? supers)
+ <class>
+ (let* ((all-metas (map (lambda (x) (class-of x)) supers))
+ (all-cpls (append-map (lambda (m)
+ (cdr (class-precedence-list m)))
+ all-metas))
+ (needed-metas '()))
+ ;; Find the most specific metaclasses. The new metaclass will be
+ ;; a subclass of these.
+ (for-each
+ (lambda (meta)
+ (if (and (not (member meta all-cpls))
+ (not (member meta needed-metas)))
+ (set! needed-metas (append needed-metas (list meta)))))
+ all-metas)
+ ;; Now return a subclass of the metaclasses we found.
+ (if (null? (cdr needed-metas))
+ (car needed-metas) ; If there's only one, just use it.
+ (ensure-metaclass-with-supers needed-metas)))))
+
+;;;
+;;; {Classes}
+;;;
+
+;;; (define-class NAME (SUPER ...) SLOT-DEFINITION ... OPTION ...)
+;;;
+;;; SLOT-DEFINITION ::= SLOT-NAME | (SLOT-NAME OPTION ...)
+;;; OPTION ::= KEYWORD VALUE
+;;;
+
+(define (kw-do-map mapper f kwargs)
+ (define (keywords l)
+ (cond
+ ((null? l) '())
+ ((or (null? (cdr l)) (not (keyword? (car l))))
+ (goops-error "malformed keyword arguments: ~a" kwargs))
+ (else (cons (car l) (keywords (cddr l))))))
+ (define (args l)
+ (if (null? l) '() (cons (cadr l) (args (cddr l)))))
+ ;; let* to check keywords first
+ (let* ((k (keywords kwargs))
+ (a (args kwargs)))
+ (mapper f k a)))
+
+(define (make-class supers slots . options)
+ (let* ((name (get-keyword #\name options (make-unbound)))
+ (supers (if (not (or-map (lambda (class)
+ (memq <object>
+ (class-precedence-list class)))
+ supers))
+ (append supers (list <object>))
+ supers))
+ (metaclass (or (get-keyword #\metaclass options #f)
+ (ensure-metaclass supers))))
+
+ ;; Verify that all direct slots are different and that we don't inherit
+ ;; several time from the same class
+ (let ((tmp1 (find-duplicate supers))
+ (tmp2 (find-duplicate (map slot-definition-name slots))))
+ (if tmp1
+ (goops-error "make-class: super class ~S is duplicate in class ~S"
+ tmp1 name))
+ (if tmp2
+ (goops-error "make-class: slot ~S is duplicate in class ~S"
+ tmp2 name)))
+
+ ;; Everything seems correct, build the class
+ (apply make metaclass
+ #\dsupers supers
+ #\slots slots
+ #\name name
+ options)))
+
+;;; (class (SUPER ...) SLOT-DEFINITION ... OPTION ...)
+;;;
+;;; SLOT-DEFINITION ::= SLOT-NAME | (SLOT-NAME OPTION ...)
+;;; OPTION ::= KEYWORD VALUE
+;;;
+(define-macro (class supers . slots)
+ (define (make-slot-definition-forms slots)
+ (map
+ (lambda (def)
+ (cond
+ ((pair? def)
+ `(list ',(car def)
+ ,@(kw-do-map append-map
+ (lambda (kw arg)
+ (case kw
+ ((#\init-form)
+ `(#\init-form ',arg
+ #\init-thunk (lambda () ,arg)))
+ (else (list kw arg))))
+ (cdr def))))
+ (else
+ `(list ',def))))
+ slots))
+ (if (not (list? supers))
+ (goops-error "malformed superclass list: ~S" supers))
+ (let ((slots (take-while (lambda (x) (not (keyword? x))) slots))
+ (options (or (find-tail keyword? slots) '())))
+ `(make-class
+ ;; evaluate super class variables
+ (list ,@supers)
+ ;; evaluate slot definitions, except the slot name!
+ (list ,@(make-slot-definition-forms slots))
+ ;; evaluate class options
+ ,@options)))
+
+(define-syntax define-class-pre-definition
+ (lambda (x)
+ (syntax-case x ()
+ ((_ (k arg rest ...) out ...)
+ (keyword? (syntax->datum #'k))
+ (case (syntax->datum #'k)
+ ((#\getter #\setter)
+ #'(define-class-pre-definition (rest ...)
+ out ...
+ (if (or (not (defined? 'arg))
+ (not (is-a? arg <generic>)))
+ (toplevel-define!
+ 'arg
+ (ensure-generic (if (defined? 'arg) arg #f) 'arg)))))
+ ((#\accessor)
+ #'(define-class-pre-definition (rest ...)
+ out ...
+ (if (or (not (defined? 'arg))
+ (not (is-a? arg <accessor>)))
+ (toplevel-define!
+ 'arg
+ (ensure-accessor (if (defined? 'arg) arg #f) 'arg)))))
+ (else
+ #'(define-class-pre-definition (rest ...) out ...))))
+ ((_ () out ...)
+ #'(begin out ...)))))
+
+;; Some slot options require extra definitions to be made. In
+;; particular, we want to make sure that the generic function objects
+;; which represent accessors exist before `make-class' tries to add
+;; methods to them.
+(define-syntax define-class-pre-definitions
+ (lambda (x)
+ (syntax-case x ()
+ ((_ () out ...)
+ #'(begin out ...))
+ ((_ (slot rest ...) out ...)
+ (keyword? (syntax->datum #'slot))
+ #'(begin out ...))
+ ((_ (slot rest ...) out ...)
+ (identifier? #'slot)
+ #'(define-class-pre-definitions (rest ...)
+ out ...))
+ ((_ ((slotname slotopt ...) rest ...) out ...)
+ #'(define-class-pre-definitions (rest ...)
+ out ... (define-class-pre-definition (slotopt ...)))))))
+
+(define-syntax-rule (define-class name supers slot ...)
+ (begin
+ (define-class-pre-definitions (slot ...))
+ (if (and (defined? 'name)
+ (is-a? name <class>)
+ (memq <object> (class-precedence-list name)))
+ (class-redefinition name
+ (class supers slot ... #\name 'name))
+ (toplevel-define! 'name (class supers slot ... #\name 'name)))))
+
+(define-syntax-rule (standard-define-class arg ...)
+ (define-class arg ...))
+
+;;;
+;;; {Generic functions and accessors}
+;;;
+
+;; Apparently the desired semantics are that we extend previous
+;; procedural definitions, but that if `name' was already a generic, we
+;; overwrite its definition.
+(define-macro (define-generic name)
+ (if (not (symbol? name))
+ (goops-error "bad generic function name: ~S" name))
+ `(define ,name
+ (if (and (defined? ',name) (is-a? ,name <generic>))
+ (make <generic> #\name ',name)
+ (ensure-generic (if (defined? ',name) ,name #f) ',name))))
+
+(define-macro (define-extended-generic name val)
+ (if (not (symbol? name))
+ (goops-error "bad generic function name: ~S" name))
+ `(define ,name (make-extended-generic ,val ',name)))
+
+(define-macro (define-extended-generics names . args)
+ (let ((prefixes (get-keyword #\prefix args #f)))
+ (if prefixes
+ `(begin
+ ,@(map (lambda (name)
+ `(define-extended-generic ,name
+ (list ,@(map (lambda (prefix)
+ (symbol-append prefix name))
+ prefixes))))
+ names))
+ (goops-error "no prefixes supplied"))))
+
+(define* (make-generic #\optional name)
+ (make <generic> #\name name))
+
+(define* (make-extended-generic gfs #\optional name)
+ (let* ((gfs (if (list? gfs) gfs (list gfs)))
+ (gws? (any (lambda (gf) (is-a? gf <generic-with-setter>)) gfs)))
+ (let ((ans (if gws?
+ (let* ((sname (and name (make-setter-name name)))
+ (setters
+ (append-map (lambda (gf)
+ (if (is-a? gf <generic-with-setter>)
+ (list (ensure-generic (setter gf)
+ sname))
+ '()))
+ gfs))
+ (es (make <extended-generic-with-setter>
+ #\name name
+ #\extends gfs
+ #\setter (make <extended-generic>
+ #\name sname
+ #\extends setters))))
+ (extended-by! setters (setter es))
+ es)
+ (make <extended-generic>
+ #\name name
+ #\extends gfs))))
+ (extended-by! gfs ans)
+ ans)))
+
+(define (extended-by! gfs eg)
+ (for-each (lambda (gf)
+ (slot-set! gf 'extended-by
+ (cons eg (slot-ref gf 'extended-by))))
+ gfs)
+ (invalidate-method-cache! eg))
+
+(define (not-extended-by! gfs eg)
+ (for-each (lambda (gf)
+ (slot-set! gf 'extended-by
+ (delq! eg (slot-ref gf 'extended-by))))
+ gfs)
+ (invalidate-method-cache! eg))
+
+(define* (ensure-generic old-definition #\optional name)
+ (cond ((is-a? old-definition <generic>) old-definition)
+ ((procedure-with-setter? old-definition)
+ (make <generic-with-setter>
+ #\name name
+ #\default (procedure old-definition)
+ #\setter (setter old-definition)))
+ ((procedure? old-definition)
+ (if (generic-capability? old-definition) old-definition
+ (make <generic> #\name name #\default old-definition)))
+ (else (make <generic> #\name name))))
+
+;; same semantics as <generic>
+(define-syntax-rule (define-accessor name)
+ (define name
+ (cond ((not (defined? 'name)) (ensure-accessor #f 'name))
+ ((is-a? name <accessor>) (make <accessor> #\name 'name))
+ (else (ensure-accessor name 'name)))))
+
+(define (make-setter-name name)
+ (string->symbol (string-append "setter:" (symbol->string name))))
+
+(define* (make-accessor #\optional name)
+ (make <accessor>
+ #\name name
+ #\setter (make <generic>
+ #\name (and name (make-setter-name name)))))
+
+(define* (ensure-accessor proc #\optional name)
+ (cond ((and (is-a? proc <accessor>)
+ (is-a? (setter proc) <generic>))
+ proc)
+ ((is-a? proc <generic-with-setter>)
+ (upgrade-accessor proc (setter proc)))
+ ((is-a? proc <generic>)
+ (upgrade-accessor proc (make-generic name)))
+ ((procedure-with-setter? proc)
+ (make <accessor>
+ #\name name
+ #\default (procedure proc)
+ #\setter (ensure-generic (setter proc) name)))
+ ((procedure? proc)
+ (ensure-accessor (if (generic-capability? proc)
+ (make <generic> #\name name #\default proc)
+ (ensure-generic proc name))
+ name))
+ (else
+ (make-accessor name))))
+
+(define (upgrade-accessor generic setter)
+ (let ((methods (slot-ref generic 'methods))
+ (gws (make (if (is-a? generic <extended-generic>)
+ <extended-generic-with-setter>
+ <accessor>)
+ #\name (generic-function-name generic)
+ #\extended-by (slot-ref generic 'extended-by)
+ #\setter setter)))
+ (if (is-a? generic <extended-generic>)
+ (let ((gfs (slot-ref generic 'extends)))
+ (not-extended-by! gfs generic)
+ (slot-set! gws 'extends gfs)
+ (extended-by! gfs gws)))
+ ;; Steal old methods
+ (for-each (lambda (method)
+ (slot-set! method 'generic-function gws))
+ methods)
+ (slot-set! gws 'methods methods)
+ (invalidate-method-cache! gws)
+ gws))
+
+;;;
+;;; {Methods}
+;;;
+
+(define (toplevel-define! name val)
+ (module-define! (current-module) name val))
+
+(define-syntax define-method
+ (syntax-rules (setter)
+ ((_ ((setter name) . args) body ...)
+ (begin
+ (if (or (not (defined? 'name))
+ (not (is-a? name <accessor>)))
+ (toplevel-define! 'name
+ (ensure-accessor
+ (if (defined? 'name) name #f) 'name)))
+ (add-method! (setter name) (method args body ...))))
+ ((_ (name . args) body ...)
+ (begin
+ ;; FIXME: this code is how it always was, but it's quite cracky:
+ ;; it will only define the generic function if it was undefined
+ ;; before (ok), or *was defined to #f*. The latter is crack. But
+ ;; there are bootstrap issues about fixing this -- change it to
+ ;; (is-a? name <generic>) and see.
+ (if (or (not (defined? 'name))
+ (not name))
+ (toplevel-define! 'name (make <generic> #\name 'name)))
+ (add-method! name (method args body ...))))))
+
+(define-syntax method
+ (lambda (x)
+ (define (parse-args args)
+ (let lp ((ls args) (formals '()) (specializers '()))
+ (syntax-case ls ()
+ (((f s) . rest)
+ (and (identifier? #'f) (identifier? #'s))
+ (lp #'rest
+ (cons #'f formals)
+ (cons #'s specializers)))
+ ((f . rest)
+ (identifier? #'f)
+ (lp #'rest
+ (cons #'f formals)
+ (cons #'<top> specializers)))
+ (()
+ (list (reverse formals)
+ (reverse (cons #''() specializers))))
+ (tail
+ (identifier? #'tail)
+ (list (append (reverse formals) #'tail)
+ (reverse (cons #'<top> specializers)))))))
+
+ (define (find-free-id exp referent)
+ (syntax-case exp ()
+ ((x . y)
+ (or (find-free-id #'x referent)
+ (find-free-id #'y referent)))
+ (x
+ (identifier? #'x)
+ (let ((id (datum->syntax #'x referent)))
+ (and (free-identifier=? #'x id) id)))
+ (_ #f)))
+
+ (define (compute-procedure formals body)
+ (syntax-case body ()
+ ((body0 ...)
+ (with-syntax ((formals formals))
+ #'(lambda formals body0 ...)))))
+
+ (define (->proper args)
+ (let lp ((ls args) (out '()))
+ (syntax-case ls ()
+ ((x . xs) (lp #'xs (cons #'x out)))
+ (() (reverse out))
+ (tail (reverse (cons #'tail out))))))
+
+ (define (compute-make-procedure formals body next-method)
+ (syntax-case body ()
+ ((body ...)
+ (with-syntax ((next-method next-method))
+ (syntax-case formals ()
+ ((formal ...)
+ #'(lambda (real-next-method)
+ (lambda (formal ...)
+ (let ((next-method (lambda args
+ (if (null? args)
+ (real-next-method formal ...)
+ (apply real-next-method args)))))
+ body ...))))
+ (formals
+ (with-syntax (((formal ...) (->proper #'formals)))
+ #'(lambda (real-next-method)
+ (lambda formals
+ (let ((next-method (lambda args
+ (if (null? args)
+ (apply real-next-method formal ...)
+ (apply real-next-method args)))))
+ body ...))))))))))
+
+ (define (compute-procedures formals body)
+ ;; So, our use of this is broken, because it operates on the
+ ;; pre-expansion source code. It's equivalent to just searching
+ ;; for referent in the datums. Ah well.
+ (let ((id (find-free-id body 'next-method)))
+ (if id
+ ;; return a make-procedure
+ (values #'#f
+ (compute-make-procedure formals body id))
+ (values (compute-procedure formals body)
+ #'#f))))
+
+ (syntax-case x ()
+ ((_ args) #'(method args (if #f #f)))
+ ((_ args body0 body1 ...)
+ (with-syntax (((formals (specializer ...)) (parse-args #'args)))
+ (call-with-values
+ (lambda ()
+ (compute-procedures #'formals #'(body0 body1 ...)))
+ (lambda (procedure make-procedure)
+ (with-syntax ((procedure procedure)
+ (make-procedure make-procedure))
+ #'(make <method>
+ #\specializers (cons* specializer ...)
+ #\formals 'formals
+ #\body '(body0 body1 ...)
+ #\make-procedure make-procedure
+ #\procedure procedure)))))))))
+
+;;;
+;;; {add-method!}
+;;;
+
+(define (add-method-in-classes! m)
+ ;; Add method in all the classes which appears in its specializers list
+ (for-each* (lambda (x)
+ (let ((dm (class-direct-methods x)))
+ (if (not (memq m dm))
+ (slot-set! x 'direct-methods (cons m dm)))))
+ (method-specializers m)))
+
+(define (remove-method-in-classes! m)
+ ;; Remove method in all the classes which appears in its specializers list
+ (for-each* (lambda (x)
+ (slot-set! x
+ 'direct-methods
+ (delv! m (class-direct-methods x))))
+ (method-specializers m)))
+
+(define (compute-new-list-of-methods gf new)
+ (let ((new-spec (method-specializers new))
+ (methods (slot-ref gf 'methods)))
+ (let loop ((l methods))
+ (if (null? l)
+ (cons new methods)
+ (if (equal? (method-specializers (car l)) new-spec)
+ (begin
+ ;; This spec. list already exists. Remove old method from dependents
+ (remove-method-in-classes! (car l))
+ (set-car! l new)
+ methods)
+ (loop (cdr l)))))))
+
+(define (method-n-specializers m)
+ (length* (slot-ref m 'specializers)))
+
+(define (calculate-n-specialized gf)
+ (fold (lambda (m n) (max n (method-n-specializers m)))
+ 0
+ (generic-function-methods gf)))
+
+(define (invalidate-method-cache! gf)
+ (%invalidate-method-cache! gf)
+ (slot-set! gf 'n-specialized (calculate-n-specialized gf))
+ (for-each (lambda (gf) (invalidate-method-cache! gf))
+ (slot-ref gf 'extended-by)))
+
+(define internal-add-method!
+ (method ((gf <generic>) (m <method>))
+ (slot-set! m 'generic-function gf)
+ (slot-set! gf 'methods (compute-new-list-of-methods gf m))
+ (invalidate-method-cache! gf)
+ (add-method-in-classes! m)
+ *unspecified*))
+
+(define-generic add-method!)
+
+((method-procedure internal-add-method!) add-method! internal-add-method!)
+
+(define-method (add-method! (proc <procedure>) (m <method>))
+ (if (generic-capability? proc)
+ (begin
+ (enable-primitive-generic! proc)
+ (add-method! proc m))
+ (next-method)))
+
+(define-method (add-method! (pg <primitive-generic>) (m <method>))
+ (add-method! (primitive-generic-generic pg) m))
+
+(define-method (add-method! obj (m <method>))
+ (goops-error "~S is not a valid generic function" obj))
+
+;;;
+;;; {Access to meta objects}
+;;;
+
+;;;
+;;; Methods
+;;;
+(define-method (method-source (m <method>))
+ (let* ((spec (map* class-name (slot-ref m 'specializers)))
+ (src (procedure-source (slot-ref m 'procedure))))
+ (and src
+ (let ((args (cadr src))
+ (body (cddr src)))
+ (cons 'method
+ (cons (map* list args spec)
+ body))))))
+
+(define-method (method-formals (m <method>))
+ (slot-ref m 'formals))
+
+;;;
+;;; Slots
+;;;
+(define slot-definition-name car)
+
+(define slot-definition-options cdr)
+
+(define (slot-definition-allocation s)
+ (get-keyword #\allocation (cdr s) #\instance))
+
+(define (slot-definition-getter s)
+ (get-keyword #\getter (cdr s) #f))
+
+(define (slot-definition-setter s)
+ (get-keyword #\setter (cdr s) #f))
+
+(define (slot-definition-accessor s)
+ (get-keyword #\accessor (cdr s) #f))
+
+(define (slot-definition-init-value s)
+ ;; can be #f, so we can't use #f as non-value
+ (get-keyword #\init-value (cdr s) (make-unbound)))
+
+(define (slot-definition-init-form s)
+ (get-keyword #\init-form (cdr s) (make-unbound)))
+
+(define (slot-definition-init-thunk s)
+ (get-keyword #\init-thunk (cdr s) #f))
+
+(define (slot-definition-init-keyword s)
+ (get-keyword #\init-keyword (cdr s) #f))
+
+(define (class-slot-definition class slot-name)
+ (assq slot-name (class-slots class)))
+
+(define (slot-init-function class slot-name)
+ (cadr (assq slot-name (slot-ref class 'getters-n-setters))))
+
+(define (accessor-method-slot-definition obj)
+ "Return the slot definition of the accessor @var{obj}."
+ (slot-ref obj 'slot-definition))
+
+
+;;;
+;;; {Standard methods used by the C runtime}
+;;;
+
+;;; Methods to compare objects
+;;;
+
+;; Have to do this in a strange order because equal? is used in the
+;; add-method! implementation; we need to make sure that when the
+;; primitive is extended, that the generic has a method. =
+(define g-equal? (make-generic 'equal?))
+;; When this generic gets called, we will have already checked eq? and
+;; eqv? -- the purpose of this generic is to extend equality. So by
+;; default, there is no extension, thus the #f return.
+(add-method! g-equal? (method (x y) #f))
+(set-primitive-generic! equal? g-equal?)
+
+;;;
+;;; methods to display/write an object
+;;;
+
+; Code for writing objects must test that the slots they use are
+; bound. Otherwise a slot-unbound method will be called and will
+; conduct to an infinite loop.
+
+;; Write
+(define (display-address o file)
+ (display (number->string (object-address o) 16) file))
+
+(define-method (write o file)
+ (display "#<instance " file)
+ (display-address o file)
+ (display #\> file))
+
+(define write-object (primitive-generic-generic write))
+
+(define-method (write (o <object>) file)
+ (let ((class (class-of o)))
+ (if (slot-bound? class 'name)
+ (begin
+ (display "#<" file)
+ (display (class-name class) file)
+ (display #\space file)
+ (display-address o file)
+ (display #\> file))
+ (next-method))))
+
+(define-method (write (class <class>) file)
+ (let ((meta (class-of class)))
+ (if (and (slot-bound? class 'name)
+ (slot-bound? meta 'name))
+ (begin
+ (display "#<" file)
+ (display (class-name meta) file)
+ (display #\space file)
+ (display (class-name class) file)
+ (display #\space file)
+ (display-address class file)
+ (display #\> file))
+ (next-method))))
+
+(define-method (write (gf <generic>) file)
+ (let ((meta (class-of gf)))
+ (if (and (slot-bound? meta 'name)
+ (slot-bound? gf 'methods))
+ (begin
+ (display "#<" file)
+ (display (class-name meta) file)
+ (let ((name (generic-function-name gf)))
+ (if name
+ (begin
+ (display #\space file)
+ (display name file))))
+ (display " (" file)
+ (display (length (generic-function-methods gf)) file)
+ (display ")>" file))
+ (next-method))))
+
+(define-method (write (o <method>) file)
+ (let ((meta (class-of o)))
+ (if (and (slot-bound? meta 'name)
+ (slot-bound? o 'specializers))
+ (begin
+ (display "#<" file)
+ (display (class-name meta) file)
+ (display #\space file)
+ (display (map* (lambda (spec)
+ (if (slot-bound? spec 'name)
+ (slot-ref spec 'name)
+ spec))
+ (method-specializers o))
+ file)
+ (display #\space file)
+ (display-address o file)
+ (display #\> file))
+ (next-method))))
+
+;; Display (do the same thing as write by default)
+(define-method (display o file)
+ (write-object o file))
+
+;;;
+;;; Handling of duplicate bindings in the module system
+;;;
+
+(define-method (merge-generics (module <module>)
+ (name <symbol>)
+ (int1 <module>)
+ (val1 <top>)
+ (int2 <module>)
+ (val2 <top>)
+ (var <top>)
+ (val <top>))
+ #f)
+
+(define-method (merge-generics (module <module>)
+ (name <symbol>)
+ (int1 <module>)
+ (val1 <generic>)
+ (int2 <module>)
+ (val2 <generic>)
+ (var <top>)
+ (val <boolean>))
+ (and (not (eq? val1 val2))
+ (make-variable (make-extended-generic (list val2 val1) name))))
+
+(define-method (merge-generics (module <module>)
+ (name <symbol>)
+ (int1 <module>)
+ (val1 <generic>)
+ (int2 <module>)
+ (val2 <generic>)
+ (var <top>)
+ (gf <extended-generic>))
+ (and (not (memq val2 (slot-ref gf 'extends)))
+ (begin
+ (slot-set! gf
+ 'extends
+ (cons val2 (delq! val2 (slot-ref gf 'extends))))
+ (slot-set! val2
+ 'extended-by
+ (cons gf (delq! gf (slot-ref val2 'extended-by))))
+ (invalidate-method-cache! gf)
+ var)))
+
+(module-define! duplicate-handlers 'merge-generics merge-generics)
+
+(define-method (merge-accessors (module <module>)
+ (name <symbol>)
+ (int1 <module>)
+ (val1 <top>)
+ (int2 <module>)
+ (val2 <top>)
+ (var <top>)
+ (val <top>))
+ #f)
+
+(define-method (merge-accessors (module <module>)
+ (name <symbol>)
+ (int1 <module>)
+ (val1 <accessor>)
+ (int2 <module>)
+ (val2 <accessor>)
+ (var <top>)
+ (val <top>))
+ (merge-generics module name int1 val1 int2 val2 var val))
+
+(module-define! duplicate-handlers 'merge-accessors merge-accessors)
+
+;;;
+;;; slot access
+;;;
+
+(define (class-slot-g-n-s class slot-name)
+ (let* ((this-slot (assq slot-name (slot-ref class 'slots)))
+ (g-n-s (cddr (or (assq slot-name (slot-ref class 'getters-n-setters))
+ (slot-missing class slot-name)))))
+ (if (not (memq (slot-definition-allocation this-slot)
+ '(#\class #\each-subclass)))
+ (slot-missing class slot-name))
+ g-n-s))
+
+(define (class-slot-ref class slot)
+ (let ((x ((car (class-slot-g-n-s class slot)) #f)))
+ (if (unbound? x)
+ (slot-unbound class slot)
+ x)))
+
+(define (class-slot-set! class slot value)
+ ((cadr (class-slot-g-n-s class slot)) #f value))
+
+(define-method (slot-unbound (c <class>) (o <object>) s)
+ (goops-error "Slot `~S' is unbound in object ~S" s o))
+
+(define-method (slot-unbound (c <class>) s)
+ (goops-error "Slot `~S' is unbound in class ~S" s c))
+
+(define-method (slot-unbound (o <object>))
+ (goops-error "Unbound slot in object ~S" o))
+
+(define-method (slot-missing (c <class>) (o <object>) s)
+ (goops-error "No slot with name `~S' in object ~S" s o))
+
+(define-method (slot-missing (c <class>) s)
+ (goops-error "No class slot with name `~S' in class ~S" s c))
+
+
+(define-method (slot-missing (c <class>) (o <object>) s value)
+ (slot-missing c o s))
+
+;;; Methods for the possible error we can encounter when calling a gf
+
+(define-method (no-next-method (gf <generic>) args)
+ (goops-error "No next method when calling ~S\nwith arguments ~S" gf args))
+
+(define-method (no-applicable-method (gf <generic>) args)
+ (goops-error "No applicable method for ~S in call ~S"
+ gf (cons (generic-function-name gf) args)))
+
+(define-method (no-method (gf <generic>) args)
+ (goops-error "No method defined for ~S" gf))
+
+;;;
+;;; {Cloning functions (from rdeline@CS.CMU.EDU)}
+;;;
+
+(define-method (shallow-clone (self <object>))
+ (let ((clone (%allocate-instance (class-of self) '()))
+ (slots (map slot-definition-name
+ (class-slots (class-of self)))))
+ (for-each (lambda (slot)
+ (if (slot-bound? self slot)
+ (slot-set! clone slot (slot-ref self slot))))
+ slots)
+ clone))
+
+(define-method (deep-clone (self <object>))
+ (let ((clone (%allocate-instance (class-of self) '()))
+ (slots (map slot-definition-name
+ (class-slots (class-of self)))))
+ (for-each (lambda (slot)
+ (if (slot-bound? self slot)
+ (slot-set! clone slot
+ (let ((value (slot-ref self slot)))
+ (if (instance? value)
+ (deep-clone value)
+ value)))))
+ slots)
+ clone))
+
+;;;
+;;; {Class redefinition utilities}
+;;;
+
+;;; (class-redefinition OLD NEW)
+;;;
+
+;;; Has correct the following conditions:
+
+;;; Methods
+;;;
+;;; 1. New accessor specializers refer to new header
+;;;
+;;; Classes
+;;;
+;;; 1. New class cpl refers to the new class header
+;;; 2. Old class header exists on old super classes direct-subclass lists
+;;; 3. New class header exists on new super classes direct-subclass lists
+
+(define-method (class-redefinition (old <class>) (new <class>))
+ ;; Work on direct methods:
+ ;; 1. Remove accessor methods from the old class
+ ;; 2. Patch the occurences of new in the specializers by old
+ ;; 3. Displace the methods from old to new
+ (remove-class-accessors! old) ;; -1-
+ (let ((methods (class-direct-methods new)))
+ (for-each (lambda (m)
+ (update-direct-method! m new old)) ;; -2-
+ methods)
+ (slot-set! new
+ 'direct-methods
+ (append methods (class-direct-methods old))))
+
+ ;; Substitute old for new in new cpl
+ (set-car! (slot-ref new 'cpl) old)
+
+ ;; Remove the old class from the direct-subclasses list of its super classes
+ (for-each (lambda (c) (slot-set! c 'direct-subclasses
+ (delv! old (class-direct-subclasses c))))
+ (class-direct-supers old))
+
+ ;; Replace the new class with the old in the direct-subclasses of the supers
+ (for-each (lambda (c)
+ (slot-set! c 'direct-subclasses
+ (cons old (delv! new (class-direct-subclasses c)))))
+ (class-direct-supers new))
+
+ ;; Swap object headers
+ (%modify-class old new)
+
+ ;; Now old is NEW!
+
+ ;; Redefine all the subclasses of old to take into account modification
+ (for-each
+ (lambda (c)
+ (update-direct-subclass! c new old))
+ (class-direct-subclasses new))
+
+ ;; Invalidate class so that subsequent instances slot accesses invoke
+ ;; change-object-class
+ (slot-set! new 'redefined old)
+ (%invalidate-class new) ;must come after slot-set!
+
+ old)
+
+;;;
+;;; remove-class-accessors!
+;;;
+
+(define-method (remove-class-accessors! (c <class>))
+ (for-each (lambda (m)
+ (if (is-a? m <accessor-method>)
+ (let ((gf (slot-ref m 'generic-function)))
+ ;; remove the method from its GF
+ (slot-set! gf 'methods
+ (delq1! m (slot-ref gf 'methods)))
+ (invalidate-method-cache! gf)
+ ;; remove the method from its specializers
+ (remove-method-in-classes! m))))
+ (class-direct-methods c)))
+
+;;;
+;;; update-direct-method!
+;;;
+
+(define-method (update-direct-method! (m <method>)
+ (old <class>)
+ (new <class>))
+ (let loop ((l (method-specializers m)))
+ ;; Note: the <top> in dotted list is never used.
+ ;; So we can work as if we had only proper lists.
+ (if (pair? l)
+ (begin
+ (if (eqv? (car l) old)
+ (set-car! l new))
+ (loop (cdr l))))))
+
+;;;
+;;; update-direct-subclass!
+;;;
+
+(define-method (update-direct-subclass! (c <class>)
+ (old <class>)
+ (new <class>))
+ (class-redefinition c
+ (make-class (class-direct-supers c)
+ (class-direct-slots c)
+ #\name (class-name c)
+ #\metaclass (class-of c))))
+
+;;;
+;;; {Utilities for INITIALIZE methods}
+;;;
+
+;;; compute-slot-accessors
+;;;
+(define (compute-slot-accessors class slots)
+ (for-each
+ (lambda (s g-n-s)
+ (let ((getter-function (slot-definition-getter s))
+ (setter-function (slot-definition-setter s))
+ (accessor (slot-definition-accessor s)))
+ (if getter-function
+ (add-method! getter-function
+ (compute-getter-method class g-n-s)))
+ (if setter-function
+ (add-method! setter-function
+ (compute-setter-method class g-n-s)))
+ (if accessor
+ (begin
+ (add-method! accessor
+ (compute-getter-method class g-n-s))
+ (add-method! (setter accessor)
+ (compute-setter-method class g-n-s))))))
+ slots (slot-ref class 'getters-n-setters)))
+
+(define-method (compute-getter-method (class <class>) g-n-s)
+ (let ((init-thunk (cadr g-n-s))
+ (g-n-s (cddr g-n-s)))
+ (make <accessor-method>
+ #\specializers (list class)
+ #\procedure (cond ((pair? g-n-s)
+ (make-generic-bound-check-getter (car g-n-s)))
+ (init-thunk
+ (standard-get g-n-s))
+ (else
+ (bound-check-get g-n-s)))
+ #\slot-definition g-n-s)))
+
+(define-method (compute-setter-method (class <class>) g-n-s)
+ (let ((init-thunk (cadr g-n-s))
+ (g-n-s (cddr g-n-s)))
+ (make <accessor-method>
+ #\specializers (list class <top>)
+ #\procedure (if (pair? g-n-s)
+ (cadr g-n-s)
+ (standard-set g-n-s))
+ #\slot-definition g-n-s)))
+
+(define (make-generic-bound-check-getter proc)
+ (lambda (o) (assert-bound (proc o) o)))
+
+;; the idea is to compile the index into the procedure, for fastest
+;; lookup.
+
+(eval-when (expand load eval)
+ (define num-standard-pre-cache 20))
+
+(define-macro (define-standard-accessor-method form . body)
+ (let ((name (caar form))
+ (n-var (cadar form))
+ (args (cdr form)))
+ (define (make-one x)
+ (define (body-trans form)
+ (cond ((not (pair? form)) form)
+ ((eq? (car form) 'struct-ref)
+ `(,(car form) ,(cadr form) ,x))
+ ((eq? (car form) 'struct-set!)
+ `(,(car form) ,(cadr form) ,x ,(cadddr form)))
+ (else
+ (map body-trans form))))
+ `(lambda ,args ,@(map body-trans body)))
+ `(define ,name
+ (let ((cache (vector ,@(map make-one (iota num-standard-pre-cache)))))
+ (lambda (n)
+ (if (< n ,num-standard-pre-cache)
+ (vector-ref cache n)
+ ((lambda (,n-var) (lambda ,args ,@body)) n)))))))
+
+(define-standard-accessor-method ((bound-check-get n) o)
+ (let ((x (struct-ref o n)))
+ (if (unbound? x)
+ (slot-unbound o)
+ x)))
+
+(define-standard-accessor-method ((standard-get n) o)
+ (struct-ref o n))
+
+(define-standard-accessor-method ((standard-set n) o v)
+ (struct-set! o n v))
+
+;;; compute-getters-n-setters
+;;;
+(define (compute-getters-n-setters class slots)
+
+ (define (compute-slot-init-function name s)
+ (or (let ((thunk (slot-definition-init-thunk s)))
+ (and thunk
+ (if (thunk? thunk)
+ thunk
+ (goops-error "Bad init-thunk for slot `~S' in ~S: ~S"
+ name class thunk))))
+ (let ((init (slot-definition-init-value s)))
+ (and (not (unbound? init))
+ (lambda () init)))))
+
+ (define (verify-accessors slot l)
+ (cond ((integer? l))
+ ((not (and (list? l) (= (length l) 2)))
+ (goops-error "Bad getter and setter for slot `~S' in ~S: ~S"
+ slot class l))
+ (else
+ (let ((get (car l))
+ (set (cadr l)))
+ (if (not (procedure? get))
+ (goops-error "Bad getter closure for slot `~S' in ~S: ~S"
+ slot class get))
+ (if (not (procedure? set))
+ (goops-error "Bad setter closure for slot `~S' in ~S: ~S"
+ slot class set))))))
+
+ (map (lambda (s)
+ ;; The strange treatment of nfields is due to backward compatibility.
+ (let* ((index (slot-ref class 'nfields))
+ (g-n-s (compute-get-n-set class s))
+ (size (- (slot-ref class 'nfields) index))
+ (name (slot-definition-name s)))
+ ;; NOTE: The following is interdependent with C macros
+ ;; defined above goops.c:scm_sys_prep_layout_x.
+ ;;
+ ;; For simple instance slots, we have the simplest form
+ ;; '(name init-function . index)
+ ;; For other slots we have
+ ;; '(name init-function getter setter . alloc)
+ ;; where alloc is:
+ ;; '(index size) for instance allocated slots
+ ;; '() for other slots
+ (verify-accessors name g-n-s)
+ (case (slot-definition-allocation s)
+ ((#\each-subclass #\class)
+ (unless (and (zero? size) (pair? g-n-s))
+ (error "Class-allocated slots should not reserve fields"))
+ ;; Don't initialize the slot; that's handled when the slot
+ ;; is allocated, in compute-get-n-set.
+ (cons name (cons #f g-n-s)))
+ (else
+ (cons name
+ (cons (compute-slot-init-function name s)
+ (if (or (integer? g-n-s)
+ (zero? size))
+ g-n-s
+ (append g-n-s (list index size)))))))))
+ slots))
+
+;;; compute-cpl
+;;;
+;;; Correct behaviour:
+;;;
+;;; (define-class food ())
+;;; (define-class fruit (food))
+;;; (define-class spice (food))
+;;; (define-class apple (fruit))
+;;; (define-class cinnamon (spice))
+;;; (define-class pie (apple cinnamon))
+;;; => cpl (pie) = pie apple fruit cinnamon spice food object top
+;;;
+;;; (define-class d ())
+;;; (define-class e ())
+;;; (define-class f ())
+;;; (define-class b (d e))
+;;; (define-class c (e f))
+;;; (define-class a (b c))
+;;; => cpl (a) = a b d c e f object top
+;;;
+
+(define-method (compute-cpl (class <class>))
+ (compute-std-cpl class class-direct-supers))
+
+;; Support
+
+(define (only-non-null lst)
+ (filter (lambda (l) (not (null? l))) lst))
+
+(define (compute-std-cpl c get-direct-supers)
+ (let ((c-direct-supers (get-direct-supers c)))
+ (merge-lists (list c)
+ (only-non-null (append (map class-precedence-list
+ c-direct-supers)
+ (list c-direct-supers))))))
+
+(define (merge-lists reversed-partial-result inputs)
+ (cond
+ ((every null? inputs)
+ (reverse! reversed-partial-result))
+ (else
+ (let* ((candidate (lambda (c)
+ (and (not (any (lambda (l)
+ (memq c (cdr l)))
+ inputs))
+ c)))
+ (candidate-car (lambda (l)
+ (and (not (null? l))
+ (candidate (car l)))))
+ (next (any candidate-car inputs)))
+ (if (not next)
+ (goops-error "merge-lists: Inconsistent precedence graph"))
+ (let ((remove-next (lambda (l)
+ (if (eq? (car l) next)
+ (cdr l)
+ l))))
+ (merge-lists (cons next reversed-partial-result)
+ (only-non-null (map remove-next inputs))))))))
+
+;; Modified from TinyClos:
+;;
+;; A simple topological sort.
+;;
+;; It's in this file so that both TinyClos and Objects can use it.
+;;
+;; This is a fairly modified version of code I originally got from Anurag
+;; Mendhekar <anurag@moose.cs.indiana.edu>.
+;;
+
+(define (compute-clos-cpl c get-direct-supers)
+ (top-sort ((build-transitive-closure get-direct-supers) c)
+ ((build-constraints get-direct-supers) c)
+ (std-tie-breaker get-direct-supers)))
+
+
+(define (top-sort elements constraints tie-breaker)
+ (let loop ((elements elements)
+ (constraints constraints)
+ (result '()))
+ (if (null? elements)
+ result
+ (let ((can-go-in-now
+ (filter
+ (lambda (x)
+ (every (lambda (constraint)
+ (or (not (eq? (cadr constraint) x))
+ (memq (car constraint) result)))
+ constraints))
+ elements)))
+ (if (null? can-go-in-now)
+ (goops-error "top-sort: Invalid constraints")
+ (let ((choice (if (null? (cdr can-go-in-now))
+ (car can-go-in-now)
+ (tie-breaker result
+ can-go-in-now))))
+ (loop
+ (filter (lambda (x) (not (eq? x choice)))
+ elements)
+ constraints
+ (append result (list choice)))))))))
+
+(define (std-tie-breaker get-supers)
+ (lambda (partial-cpl min-elts)
+ (let loop ((pcpl (reverse partial-cpl)))
+ (let ((current-elt (car pcpl)))
+ (let ((ds-of-ce (get-supers current-elt)))
+ (let ((common (filter (lambda (x)
+ (memq x ds-of-ce))
+ min-elts)))
+ (if (null? common)
+ (if (null? (cdr pcpl))
+ (goops-error "std-tie-breaker: Nothing valid")
+ (loop (cdr pcpl)))
+ (car common))))))))
+
+
+(define (build-transitive-closure get-follow-ons)
+ (lambda (x)
+ (let track ((result '())
+ (pending (list x)))
+ (if (null? pending)
+ result
+ (let ((next (car pending)))
+ (if (memq next result)
+ (track result (cdr pending))
+ (track (cons next result)
+ (append (get-follow-ons next)
+ (cdr pending)))))))))
+
+(define (build-constraints get-follow-ons)
+ (lambda (x)
+ (let loop ((elements ((build-transitive-closure get-follow-ons) x))
+ (this-one '())
+ (result '()))
+ (if (or (null? this-one) (null? (cdr this-one)))
+ (if (null? elements)
+ result
+ (loop (cdr elements)
+ (cons (car elements)
+ (get-follow-ons (car elements)))
+ result))
+ (loop elements
+ (cdr this-one)
+ (cons (list (car this-one) (cadr this-one))
+ result))))))
+
+;;; compute-get-n-set
+;;;
+(define-method (compute-get-n-set (class <class>) s)
+ (define (class-slot-init-value)
+ (let ((thunk (slot-definition-init-thunk s)))
+ (if thunk
+ (thunk)
+ (slot-definition-init-value s))))
+
+ (case (slot-definition-allocation s)
+ ((#\instance) ;; Instance slot
+ ;; get-n-set is just its offset
+ (let ((already-allocated (slot-ref class 'nfields)))
+ (slot-set! class 'nfields (+ already-allocated 1))
+ already-allocated))
+
+ ((#\class) ;; Class slot
+ ;; Class-slots accessors are implemented as 2 closures around
+ ;; a Scheme variable. As instance slots, class slots must be
+ ;; unbound at init time.
+ (let ((name (slot-definition-name s)))
+ (if (memq name (map slot-definition-name (class-direct-slots class)))
+ ;; This slot is direct; create a new shared variable
+ (make-closure-variable class (class-slot-init-value))
+ ;; Slot is inherited. Find its definition in superclass
+ (let loop ((l (cdr (class-precedence-list class))))
+ (let ((r (assoc name (slot-ref (car l) 'getters-n-setters))))
+ (if r
+ (cddr r)
+ (loop (cdr l))))))))
+
+ ((#\each-subclass) ;; slot shared by instances of direct subclass.
+ ;; (Thomas Buerger, April 1998)
+ (make-closure-variable class (class-slot-init-value)))
+
+ ((#\virtual) ;; No allocation
+ ;; slot-ref and slot-set! function must be given by the user
+ (let ((get (get-keyword #\slot-ref (slot-definition-options s) #f))
+ (set (get-keyword #\slot-set! (slot-definition-options s) #f)))
+ (if (not (and get set))
+ (goops-error "You must supply a #:slot-ref and a #:slot-set! in ~S"
+ s))
+ (list get set)))
+ (else (next-method))))
+
+(define (make-closure-variable class value)
+ (list (lambda (o) value)
+ (lambda (o v) (set! value v))))
+
+(define-method (compute-get-n-set (o <object>) s)
+ (goops-error "Allocation \"~S\" is unknown" (slot-definition-allocation s)))
+
+(define-method (compute-slots (class <class>))
+ (%compute-slots class))
+
+;;;
+;;; {Initialize}
+;;;
+
+(define-method (initialize (object <object>) initargs)
+ (%initialize-object object initargs))
+
+(define-method (initialize (class <class>) initargs)
+ (next-method)
+ (let ((dslots (get-keyword #\slots initargs '()))
+ (supers (get-keyword #\dsupers initargs '())))
+ (slot-set! class 'name (get-keyword #\name initargs '???))
+ (slot-set! class 'direct-supers supers)
+ (slot-set! class 'direct-slots dslots)
+ (slot-set! class 'direct-subclasses '())
+ (slot-set! class 'direct-methods '())
+ (slot-set! class 'cpl (compute-cpl class))
+ (slot-set! class 'redefined #f)
+ (let ((slots (compute-slots class)))
+ (slot-set! class 'slots slots)
+ (slot-set! class 'nfields 0)
+ (slot-set! class 'getters-n-setters (compute-getters-n-setters class
+ slots))
+ ;; Build getters - setters - accessors
+ (compute-slot-accessors class slots))
+
+ ;; Update the "direct-subclasses" of each inherited classes
+ (for-each (lambda (x)
+ (slot-set! x
+ 'direct-subclasses
+ (cons class (slot-ref x 'direct-subclasses))))
+ supers)
+
+ ;; Support for the underlying structs:
+
+ ;; Set the layout slot
+ (%prep-layout! class)
+ ;; Inherit class flags (invisible on scheme level) from supers
+ (%inherit-magic! class supers)))
+
+(define (initialize-object-procedure object initargs)
+ (let ((proc (get-keyword #\procedure initargs #f)))
+ (cond ((not proc))
+ ((pair? proc)
+ (apply slot-set! object 'procedure proc))
+ (else
+ (slot-set! object 'procedure proc)))))
+
+(define-method (initialize (applicable-struct <applicable-struct>) initargs)
+ (next-method)
+ (initialize-object-procedure applicable-struct initargs))
+
+(define-method (initialize (generic <generic>) initargs)
+ (let ((previous-definition (get-keyword #\default initargs #f))
+ (name (get-keyword #\name initargs #f)))
+ (next-method)
+ (slot-set! generic 'methods (if (is-a? previous-definition <procedure>)
+ (list (method args
+ (apply previous-definition args)))
+ '()))
+ (if name
+ (set-procedure-property! generic 'name name))
+ ))
+
+(define-method (initialize (gws <generic-with-setter>) initargs)
+ (next-method)
+ (%set-object-setter! gws (get-keyword #\setter initargs #f)))
+
+(define-method (initialize (eg <extended-generic>) initargs)
+ (next-method)
+ (slot-set! eg 'extends (get-keyword #\extends initargs '())))
+
+(define dummy-procedure (lambda args *unspecified*))
+
+(define-method (initialize (method <method>) initargs)
+ (next-method)
+ (slot-set! method 'generic-function (get-keyword #\generic-function initargs #f))
+ (slot-set! method 'specializers (get-keyword #\specializers initargs '()))
+ (slot-set! method 'procedure
+ (get-keyword #\procedure initargs #f))
+ (slot-set! method 'formals (get-keyword #\formals initargs '()))
+ (slot-set! method 'body (get-keyword #\body initargs '()))
+ (slot-set! method 'make-procedure (get-keyword #\make-procedure initargs #f)))
+
+
+;;;
+;;; {Change-class}
+;;;
+
+(define (change-object-class old-instance old-class new-class)
+ (let ((new-instance (allocate-instance new-class '())))
+ ;; Initialize the slots of the new instance
+ (for-each (lambda (slot)
+ (if (and (slot-exists-using-class? old-class old-instance slot)
+ (eq? (slot-definition-allocation
+ (class-slot-definition old-class slot))
+ #\instance)
+ (slot-bound-using-class? old-class old-instance slot))
+ ;; Slot was present and allocated in old instance; copy it
+ (slot-set-using-class!
+ new-class
+ new-instance
+ slot
+ (slot-ref-using-class old-class old-instance slot))
+ ;; slot was absent; initialize it with its default value
+ (let ((init (slot-init-function new-class slot)))
+ (if init
+ (slot-set-using-class!
+ new-class
+ new-instance
+ slot
+ (apply init '()))))))
+ (map slot-definition-name (class-slots new-class)))
+ ;; Exchange old and new instance in place to keep pointers valid
+ (%modify-instance old-instance new-instance)
+ ;; Allow class specific updates of instances (which now are swapped)
+ (update-instance-for-different-class new-instance old-instance)
+ old-instance))
+
+
+(define-method (update-instance-for-different-class (old-instance <object>)
+ (new-instance
+ <object>))
+ ;;not really important what we do, we just need a default method
+ new-instance)
+
+(define-method (change-class (old-instance <object>) (new-class <class>))
+ (change-object-class old-instance (class-of old-instance) new-class))
+
+;;;
+;;; {make}
+;;;
+;;; A new definition which overwrites the previous one which was built-in
+;;;
+
+(define-method (allocate-instance (class <class>) initargs)
+ (%allocate-instance class initargs))
+
+(define-method (make-instance (class <class>) . initargs)
+ (let ((instance (allocate-instance class initargs)))
+ (initialize instance initargs)
+ instance))
+
+(define make make-instance)
+
+;;;
+;;; {apply-generic}
+;;;
+;;; Protocol for calling standard generic functions. This protocol is
+;;; not used for real <generic> functions (in this case we use a
+;;; completely C hard-coded protocol). Apply-generic is used by
+;;; goops for calls to subclasses of <generic> and <generic-with-setter>.
+;;; The code below is similar to the first MOP described in AMOP. In
+;;; particular, it doesn't used the currified approach to gf
+;;; call. There are 2 reasons for that:
+;;; - the protocol below is exposed to mimic completely the one written in C
+;;; - the currified protocol would be imho inefficient in C.
+;;;
+
+(define-method (apply-generic (gf <generic>) args)
+ (if (null? (slot-ref gf 'methods))
+ (no-method gf args))
+ (let ((methods (compute-applicable-methods gf args)))
+ (if methods
+ (apply-methods gf (sort-applicable-methods gf methods args) args)
+ (no-applicable-method gf args))))
+
+;; compute-applicable-methods is bound to %compute-applicable-methods.
+;; *fixme* use let
+(define %%compute-applicable-methods
+ (make <generic> #\name 'compute-applicable-methods))
+
+(define-method (%%compute-applicable-methods (gf <generic>) args)
+ (%compute-applicable-methods gf args))
+
+(set! compute-applicable-methods %%compute-applicable-methods)
+
+(define-method (sort-applicable-methods (gf <generic>) methods args)
+ (let ((targs (map class-of args)))
+ (sort methods (lambda (m1 m2) (method-more-specific? m1 m2 targs)))))
+
+(define-method (method-more-specific? (m1 <method>) (m2 <method>) targs)
+ (%method-more-specific? m1 m2 targs))
+
+(define-method (apply-method (gf <generic>) methods build-next args)
+ (apply (method-procedure (car methods))
+ (build-next (cdr methods) args)
+ args))
+
+(define-method (apply-methods (gf <generic>) (l <list>) args)
+ (letrec ((next (lambda (procs args)
+ (lambda new-args
+ (let ((a (if (null? new-args) args new-args)))
+ (if (null? procs)
+ (no-next-method gf a)
+ (apply-method gf procs next a)))))))
+ (apply-method gf l next args)))
+
+;; We don't want the following procedure to turn up in backtraces:
+(for-each (lambda (proc)
+ (set-procedure-property! proc 'system-procedure #t))
+ (list slot-unbound
+ slot-missing
+ no-next-method
+ no-applicable-method
+ no-method
+ ))
+
+;;;
+;;; {<composite-metaclass> and <active-metaclass>}
+;;;
+
+;(autoload "active-slot" <active-metaclass>)
+;(autoload "composite-slot" <composite-metaclass>)
+;(export <composite-metaclass> <active-metaclass>)
+
+;;;
+;;; {Tools}
+;;;
+
+;; list2set
+;;
+;; duplicate the standard list->set function but using eq instead of
+;; eqv which really sucks a lot, uselessly here
+;;
+(define (list2set l)
+ (let loop ((l l)
+ (res '()))
+ (cond
+ ((null? l) res)
+ ((memq (car l) res) (loop (cdr l) res))
+ (else (loop (cdr l) (cons (car l) res))))))
+
+(define (class-subclasses c)
+ (letrec ((allsubs (lambda (c)
+ (cons c (mapappend allsubs
+ (class-direct-subclasses c))))))
+ (list2set (cdr (allsubs c)))))
+
+(define (class-methods c)
+ (list2set (mapappend class-direct-methods
+ (cons c (class-subclasses c)))))
+
+;;;
+;;; {Final initialization}
+;;;
+
+;; Tell C code that the main bulk of Goops has been loaded
+(%goops-loaded)
+;;;; Copyright (C) 1999, 2000, 2005, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (oop goops accessors)
+ \:use-module (oop goops)
+ \:re-export (standard-define-class)
+ \:export (define-class-with-accessors
+ define-class-with-accessors-keywords))
+
+(define-macro (define-class-with-accessors name supers . slots)
+ (let ((eat? #f))
+ `(standard-define-class
+ ,name ,supers
+ ,@(map-in-order
+ (lambda (slot)
+ (cond (eat?
+ (set! eat? #f)
+ slot)
+ ((keyword? slot)
+ (set! eat? #t)
+ slot)
+ ((pair? slot)
+ (if (get-keyword #\accessor (cdr slot) #f)
+ slot
+ (let ((name (car slot)))
+ `(,name #\accessor ,name ,@(cdr slot)))))
+ (else
+ `(,slot #\accessor ,slot))))
+ slots))))
+
+(define-macro (define-class-with-accessors-keywords name supers . slots)
+ (let ((eat? #f))
+ `(standard-define-class
+ ,name ,supers
+ ,@(map-in-order
+ (lambda (slot)
+ (cond (eat?
+ (set! eat? #f)
+ slot)
+ ((keyword? slot)
+ (set! eat? #t)
+ slot)
+ ((pair? slot)
+ (let ((slot
+ (if (get-keyword #\accessor (cdr slot) #f)
+ slot
+ (let ((name (car slot)))
+ `(,name #\accessor ,name ,@(cdr slot))))))
+ (if (get-keyword #\init-keyword (cdr slot) #f)
+ slot
+ (let* ((name (car slot))
+ (keyword (symbol->keyword name)))
+ `(,name #\init-keyword ,keyword ,@(cdr slot))))))
+ (else
+ `(,slot #\accessor ,slot
+ #\init-keyword ,(symbol->keyword slot)))))
+ slots))))
+;;; installed-scm-file
+
+;;;; Copyright (C) 1999, 2001, 2006, 2009 Free Software Foundation, Inc.
+;;;; Copyright (C) 1993-1998 Erick Gallesio - I3S-CNRS/ESSI <eg@unice.fr>
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+;;;;
+;;;; This file was based upon active-slot.stklos from the STk distribution
+;;;; version 4.0.1 by Erick Gallesio <eg@unice.fr>.
+;;;;
+
+(define-module (oop goops active-slot)
+ \:use-module (oop goops internal)
+ \:export (<active-class>))
+
+(define-class <active-class> (<class>))
+
+(define-method (compute-get-n-set (class <active-class>) slot)
+ (if (eq? (slot-definition-allocation slot) #\active)
+ (let* ((index (slot-ref class 'nfields))
+ (s (cdr slot))
+ (before-ref (get-keyword #\before-slot-ref s #f))
+ (after-ref (get-keyword #\after-slot-ref s #f))
+ (before-set! (get-keyword #\before-slot-set! s #f))
+ (after-set! (get-keyword #\after-slot-set! s #f))
+ (unbound (make-unbound)))
+ (slot-set! class 'nfields (+ index 1))
+ (list (lambda (o)
+ (if before-ref
+ (if (before-ref o)
+ (let ((res (%fast-slot-ref o index)))
+ (and after-ref (not (eqv? res unbound)) (after-ref o))
+ res)
+ (make-unbound))
+ (let ((res (%fast-slot-ref o index)))
+ (and after-ref (not (eqv? res unbound)) (after-ref o))
+ res)))
+
+ (lambda (o v)
+ (if before-set!
+ (if (before-set! o v)
+ (begin
+ (%fast-slot-set! o index v)
+ (and after-set! (after-set! o v))))
+ (begin
+ (%fast-slot-set! o index v)
+ (and after-set! (after-set! o v)))))))
+ (next-method)))
+;;;; Copyright (C) 1999, 2001, 2006, 2009, 2015 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (oop goops compile)
+ #\use-module (oop goops internal)
+ #\re-export (compute-cmethod))
+;;; installed-scm-file
+
+;;;; Copyright (C) 1999, 2000, 2001, 2006 Free Software Foundation, Inc.
+;;;; Copyright (C) 1993-1998 Erick Gallesio - I3S-CNRS/ESSI <eg@unice.fr>
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+;;;;
+;;;; This file was based upon composite-slot.stklos from the STk distribution
+;;;; version 4.0.1 by Erick Gallesio <eg@unice.fr>.
+;;;;
+
+(define-module (oop goops composite-slot)
+ \:use-module (oop goops)
+ \:export (<composite-class>))
+
+;;;
+;;; (define-class CLASS SUPERS
+;;; ...
+;;; (OBJECT ...)
+;;; ...
+;;; (SLOT #\allocation #\propagated
+;;; #\propagate-to '(PROPAGATION ...))
+;;; ...
+;;; #\metaclass <composite-class>)
+;;;
+;;; PROPAGATION ::= OBJECT | (OBJECT TARGETSLOT)
+;;;
+;;; The slot SLOT will be propagated to the slot TARGETSLOT in the object
+;;; stored in slot OBJECT. If TARGETSLOT is omitted, assume that the target
+;;; slot is named SLOT.
+;;;
+
+(define-class <composite-class> (<class>))
+
+(define-method (compute-get-n-set (class <composite-class>) slot)
+ (if (eq? (slot-definition-allocation slot) #\propagated)
+ (compute-propagated-get-n-set slot)
+ (next-method)))
+
+(define (compute-propagated-get-n-set s)
+ (let ((prop (get-keyword #\propagate-to (cdr s) #f))
+ (s-name (slot-definition-name s)))
+
+ (if (not prop)
+ (goops-error "Propagation not specified for slot ~S" s-name))
+ (if (not (pair? prop))
+ (goops-error "Bad propagation list for slot ~S" s-name))
+
+ (let ((objects (map (lambda (p) (if (pair? p) (car p) p)) prop))
+ (slots (map (lambda (p) (if (pair? p) (cadr p) s-name)) prop)))
+ (let ((first-object (car objects))
+ (first-slot (car slots)))
+ (list
+ ;; The getter
+ (lambda (o)
+ (slot-ref (slot-ref o first-object) first-slot))
+
+ ;; The setter
+ (if (null? (cdr objects))
+ (lambda (o v)
+ (slot-set! (slot-ref o first-object) first-slot v))
+ (lambda (o v)
+ (for-each (lambda (object slot)
+ (slot-set! (slot-ref o object) slot v))
+ objects
+ slots))))))))
+;;; installed-scm-file
+
+;;;; Copyright (C) 1998, 1999, 2001, 2006, 2008, 2009 Free Software Foundation, Inc.
+;;;; Copyright (C) 1993-1998 Erick Gallesio - I3S-CNRS/ESSI <eg@unice.fr>
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+;;;;
+;;;; This file was based upon describe.stklos from the STk distribution
+;;;; version 4.0.1 by Erick Gallesio <eg@unice.fr>.
+;;;;
+
+(define-module (oop goops describe)
+ \:use-module (oop goops)
+ \:use-module (ice-9 session)
+ \:use-module (ice-9 format)
+ \:export (describe)) ; Export the describe generic function
+
+;;;
+;;; describe for simple objects
+;;;
+(define-method (describe (x <top>))
+ (format #t "~s is " x)
+ (cond
+ ((integer? x) (format #t "an integer"))
+ ((real? x) (format #t "a real"))
+ ((complex? x) (format #t "a complex number"))
+ ((null? x) (format #t "an empty list"))
+ ((boolean? x) (format #t "a boolean value (~s)" (if x 'true 'false)))
+ ((char? x) (format #t "a character, ascii value is ~s"
+ (char->integer x)))
+ ((symbol? x) (format #t "a symbol"))
+ ((list? x) (format #t "a list"))
+ ((pair? x) (if (pair? (cdr x))
+ (format #t "an improper list")
+ (format #t "a pair")))
+ ((string? x) (if (eqv? x "")
+ (format #t "an empty string")
+ (format #t "a string of length ~s" (string-length x))))
+ ((vector? x) (if (eqv? x '#())
+ (format #t "an empty vector")
+ (format #t "a vector of length ~s" (vector-length x))))
+ ((eof-object? x) (format #t "the end-of-file object"))
+ (else (format #t "an unknown object (~s)" x)))
+ (format #t ".~%")
+ *unspecified*)
+
+(define-method (describe (x <procedure>))
+ (let ((name (procedure-name x)))
+ (if name
+ (format #t "`~s'" name)
+ (display x))
+ (display " is ")
+ (display (if name #\a "an anonymous"))
+ (display " procedure")
+ (display " with ")
+ (arity x)))
+
+;;;
+;;; describe for GOOPS instances
+;;;
+(define (safe-class-name class)
+ (if (slot-bound? class 'name)
+ (class-name class)
+ class))
+
+(define-method (describe (x <object>))
+ (format #t "~S is an instance of class ~A~%"
+ x (safe-class-name (class-of x)))
+
+ ;; print all the instance slots
+ (format #t "Slots are: ~%")
+ (for-each (lambda (slot)
+ (let ((name (slot-definition-name slot)))
+ (format #t " ~S = ~A~%"
+ name
+ (if (slot-bound? x name)
+ (format #f "~S" (slot-ref x name))
+ "#<unbound>"))))
+ (class-slots (class-of x)))
+ *unspecified*)
+
+;;;
+;;; Describe for classes
+;;;
+(define-method (describe (x <class>))
+ (format #t "~S is a class. It's an instance of ~A~%"
+ (safe-class-name x) (safe-class-name (class-of x)))
+
+ ;; Super classes
+ (format #t "Superclasses are:~%")
+ (for-each (lambda (class) (format #t " ~A~%" (safe-class-name class)))
+ (class-direct-supers x))
+
+ ;; Direct slots
+ (let ((slots (class-direct-slots x)))
+ (if (null? slots)
+ (format #t "(No direct slot)~%")
+ (begin
+ (format #t "Directs slots are:~%")
+ (for-each (lambda (s)
+ (format #t " ~A~%" (slot-definition-name s)))
+ slots))))
+
+
+ ;; Direct subclasses
+ (let ((classes (class-direct-subclasses x)))
+ (if (null? classes)
+ (format #t "(No direct subclass)~%")
+ (begin
+ (format #t "Directs subclasses are:~%")
+ (for-each (lambda (s)
+ (format #t " ~A~%" (safe-class-name s)))
+ classes))))
+
+ ;; CPL
+ (format #t "Class Precedence List is:~%")
+ (for-each (lambda (s) (format #t " ~A~%" (safe-class-name s)))
+ (class-precedence-list x))
+
+ ;; Direct Methods
+ (let ((methods (class-direct-methods x)))
+ (if (null? methods)
+ (format #t "(No direct method)~%")
+ (begin
+ (format #t "Class direct methods are:~%")
+ (for-each describe methods))))
+
+; (format #t "~%Field Initializers ~% ")
+; (write (slot-ref x 'initializers)) (newline)
+
+; (format #t "~%Getters and Setters~% ")
+; (write (slot-ref x 'getters-n-setters)) (newline)
+)
+
+;;;
+;;; Describe for generic functions
+;;;
+(define-method (describe (x <generic>))
+ (let ((name (generic-function-name x))
+ (methods (generic-function-methods x)))
+ ;; Title
+ (format #t "~S is a generic function. It's an instance of ~A.~%"
+ name (safe-class-name (class-of x)))
+ ;; Methods
+ (if (null? methods)
+ (format #t "(No method defined for ~S)~%" name)
+ (begin
+ (format #t "Methods defined for ~S~%" name)
+ (for-each (lambda (x) (describe x #t)) methods)))))
+
+;;;
+;;; Describe for methods
+;;;
+(define-method (describe (x <method>) . omit-generic)
+ (letrec ((print-args (lambda (args)
+ ;; take care of dotted arg lists
+ (cond ((null? args) (newline))
+ ((pair? args)
+ (display #\space)
+ (display (safe-class-name (car args)))
+ (print-args (cdr args)))
+ (else
+ (display #\space)
+ (display (safe-class-name args))
+ (newline))))))
+
+ ;; Title
+ (format #t " Method ~A~%" x)
+
+ ;; Associated generic
+ (if (null? omit-generic)
+ (let ((gf (method-generic-function x)))
+ (if gf
+ (format #t "\t Generic: ~A~%" (generic-function-name gf))
+ (format #t "\t(No generic)~%"))))
+
+ ;; GF specializers
+ (format #t "\tSpecializers:")
+ (print-args (method-specializers x))))
+
+(provide 'describe)
+;;;; Copyright (C) 1999, 2000, 2001, 2003, 2006, 2009, 2012, 2015 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+;; There are circularities here; you can't import (oop goops compile)
+;; before (oop goops). So when compiling, make sure that things are
+;; kosher.
+(eval-when (expand) (resolve-module '(oop goops)))
+
+(define-module (oop goops dispatch)
+ #\use-module (oop goops)
+ #\use-module (oop goops util)
+ #\use-module (system base target)
+ #\export (memoize-method!)
+ #\no-backtrace)
+
+
+(define *dispatch-module* (current-module))
+
+;;;
+;;; Generic functions have an applicable-methods cache associated with
+;;; them. Every distinct set of types that is dispatched through a
+;;; generic adds an entry to the cache. This cache gets compiled out to
+;;; a dispatch procedure. In steady-state, this dispatch procedure is
+;;; never recompiled; but during warm-up there is some churn, both to
+;;; the cache and to the dispatch procedure.
+;;;
+;;; So what is the deal if warm-up happens in a multithreaded context?
+;;; There is indeed a window between missing the cache for a certain set
+;;; of arguments, and then updating the cache with the newly computed
+;;; applicable methods. One of the updaters is liable to lose their new
+;;; entry.
+;;;
+;;; This is actually OK though, because a subsequent cache miss for the
+;;; race loser will just cause memoization to try again. The cache will
+;;; eventually be consistent. We're not mutating the old part of the
+;;; cache, just consing on the new entry.
+;;;
+;;; It doesn't even matter if the dispatch procedure and the cache are
+;;; inconsistent -- most likely the type-set that lost the dispatch
+;;; procedure race will simply re-trigger a memoization, but since the
+;;; winner isn't in the effective-methods cache, it will likely also
+;;; re-trigger a memoization, and the cache will finally be consistent.
+;;; As you can see there is a possibility for ping-pong effects, but
+;;; it's unlikely given the shortness of the window between slot-set!
+;;; invocations. We could add a mutex, but it is strictly unnecessary,
+;;; and would add runtime cost and complexity.
+;;;
+
+(define (emit-linear-dispatch gf-sym nargs methods free rest?)
+ (define (gen-syms n stem)
+ (let lp ((n (1- n)) (syms '()))
+ (if (< n 0)
+ syms
+ (lp (1- n) (cons (gensym stem) syms)))))
+ (let* ((args (gen-syms nargs "a"))
+ (types (gen-syms nargs "t")))
+ (let lp ((methods methods)
+ (free free)
+ (exp `(cache-miss ,gf-sym
+ ,(if rest?
+ `(cons* ,@args rest)
+ `(list ,@args)))))
+ (cond
+ ((null? methods)
+ (values `(,(if rest? `(,@args . rest) args)
+ (let ,(map (lambda (t a)
+ `(,t (class-of ,a)))
+ types args)
+ ,exp))
+ free))
+ (else
+ ;; jeez
+ (let preddy ((free free)
+ (types types)
+ (specs (vector-ref (car methods) 1))
+ (checks '()))
+ (if (null? types)
+ (let ((m-sym (gensym "p")))
+ (lp (cdr methods)
+ (acons (vector-ref (car methods) 3)
+ m-sym
+ free)
+ `(if (and . ,checks)
+ ,(if rest?
+ `(apply ,m-sym ,@args rest)
+ `(,m-sym . ,args))
+ ,exp)))
+ (let ((var (assq-ref free (car specs))))
+ (if var
+ (preddy free
+ (cdr types)
+ (cdr specs)
+ (cons `(eq? ,(car types) ,var)
+ checks))
+ (let ((var (gensym "c")))
+ (preddy (acons (car specs) var free)
+ (cdr types)
+ (cdr specs)
+ (cons `(eq? ,(car types) ,var)
+ checks))))))))))))
+
+(define (compute-dispatch-procedure gf cache)
+ (define (scan)
+ (let lp ((ls cache) (nreq -1) (nrest -1))
+ (cond
+ ((null? ls)
+ (collate (make-vector (1+ nreq) '())
+ (make-vector (1+ nrest) '())))
+ ((vector-ref (car ls) 2) ; rest
+ (lp (cdr ls) nreq (max nrest (vector-ref (car ls) 0))))
+ (else ; req
+ (lp (cdr ls) (max nreq (vector-ref (car ls) 0)) nrest)))))
+ (define (collate req rest)
+ (let lp ((ls cache))
+ (cond
+ ((null? ls)
+ (emit req rest))
+ ((vector-ref (car ls) 2) ; rest
+ (let ((n (vector-ref (car ls) 0)))
+ (vector-set! rest n (cons (car ls) (vector-ref rest n)))
+ (lp (cdr ls))))
+ (else ; req
+ (let ((n (vector-ref (car ls) 0)))
+ (vector-set! req n (cons (car ls) (vector-ref req n)))
+ (lp (cdr ls)))))))
+ (define (emit req rest)
+ (let ((gf-sym (gensym "g")))
+ (define (emit-rest n clauses free)
+ (if (< n (vector-length rest))
+ (let ((methods (vector-ref rest n)))
+ (cond
+ ((null? methods)
+ (emit-rest (1+ n) clauses free))
+ ;; FIXME: hash dispatch
+ (else
+ (call-with-values
+ (lambda ()
+ (emit-linear-dispatch gf-sym n methods free #t))
+ (lambda (clause free)
+ (emit-rest (1+ n) (cons clause clauses) free))))))
+ (emit-req (1- (vector-length req)) clauses free)))
+ (define (emit-req n clauses free)
+ (if (< n 0)
+ (comp `(lambda ,(map cdr free)
+ (case-lambda ,@clauses))
+ (map car free))
+ (let ((methods (vector-ref req n)))
+ (cond
+ ((null? methods)
+ (emit-req (1- n) clauses free))
+ ;; FIXME: hash dispatch
+ (else
+ (call-with-values
+ (lambda ()
+ (emit-linear-dispatch gf-sym n methods free #f))
+ (lambda (clause free)
+ (emit-req (1- n) (cons clause clauses) free))))))))
+
+ (emit-rest 0
+ (if (or (zero? (vector-length rest))
+ (null? (vector-ref rest 0)))
+ (list `(args (cache-miss ,gf-sym args)))
+ '())
+ (acons gf gf-sym '()))))
+ (define (comp exp vals)
+ ;; When cross-compiling Guile itself, the native Guile must generate
+ ;; code for the host.
+ (with-target %host-type
+ (lambda ()
+ (let ((p ((@ (system base compile) compile) exp
+ #\env *dispatch-module*
+ #\from 'scheme
+ #\opts '(#\partial-eval? #f #\cse? #f))))
+ (apply p vals)))))
+
+ ;; kick it.
+ (scan))
+
+;; o/~ ten, nine, eight
+;; sometimes that's just how it goes
+;; three, two, one
+;;
+;; get out before it blows o/~
+;;
+(define timer-init 30)
+(define (delayed-compile gf)
+ (let ((timer timer-init))
+ (lambda args
+ (set! timer (1- timer))
+ (cond
+ ((zero? timer)
+ (let ((dispatch (compute-dispatch-procedure
+ gf (slot-ref gf 'effective-methods))))
+ (slot-set! gf 'procedure dispatch)
+ (apply dispatch args)))
+ (else
+ ;; interestingly, this catches recursive compilation attempts as
+ ;; well; in that case, timer is negative
+ (cache-dispatch gf args))))))
+
+(define (cache-dispatch gf args)
+ (define (map-until n f ls)
+ (if (or (zero? n) (null? ls))
+ '()
+ (cons (f (car ls)) (map-until (1- n) f (cdr ls)))))
+ (define (equal? x y) ; can't use the stock equal? because it's a generic...
+ (cond ((pair? x) (and (pair? y)
+ (eq? (car x) (car y))
+ (equal? (cdr x) (cdr y))))
+ ((null? x) (null? y))
+ (else #f)))
+ (if (slot-ref gf 'n-specialized)
+ (let ((types (map-until (slot-ref gf 'n-specialized) class-of args)))
+ (let lp ((cache (slot-ref gf 'effective-methods)))
+ (cond ((null? cache)
+ (cache-miss gf args))
+ ((equal? (vector-ref (car cache) 1) types)
+ (apply (vector-ref (car cache) 3) args))
+ (else (lp (cdr cache))))))
+ (cache-miss gf args)))
+
+(define (cache-miss gf args)
+ (apply (memoize-method! gf args) args))
+
+(define (memoize-effective-method! gf args applicable)
+ (define (first-n ls n)
+ (if (or (zero? n) (null? ls))
+ '()
+ (cons (car ls) (first-n (cdr ls) (- n 1)))))
+ (define (parse n ls)
+ (cond ((null? ls)
+ (memoize n #f (map class-of args)))
+ ((= n (slot-ref gf 'n-specialized))
+ (memoize n #t (map class-of (first-n args n))))
+ (else
+ (parse (1+ n) (cdr ls)))))
+ (define (memoize len rest? types)
+ (let* ((cmethod ((@@ (oop goops) compute-cmethod) applicable types))
+ (cache (cons (vector len types rest? cmethod)
+ (slot-ref gf 'effective-methods))))
+ (slot-set! gf 'effective-methods cache)
+ (slot-set! gf 'procedure (delayed-compile gf))
+ cmethod))
+ (parse 0 args))
+
+
+;;;
+;;; Memoization
+;;;
+
+(define (memoize-method! gf args)
+ (let ((applicable ((if (eq? gf compute-applicable-methods)
+ %compute-applicable-methods
+ compute-applicable-methods)
+ gf args)))
+ (cond (applicable
+ (memoize-effective-method! gf args applicable))
+ (else
+ (no-applicable-method gf args)))))
+
+(set-procedure-property! memoize-method! 'system-procedure #t)
+;;; installed-scm-file
+
+;;;; Copyright (C) 1999, 2001, 2006 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (oop goops internal)
+ \:use-module (oop goops))
+
+;; Export all the bindings that are internal to `(oop goops)'.
+(let ((public-i (module-public-interface (current-module))))
+ (module-for-each (lambda (name var)
+ (if (eq? name '%module-public-interface)
+ #t
+ (module-add! public-i name var)))
+ (resolve-module '(oop goops))))
+;;; installed-scm-file
+
+;;;; Copyright (C) 2000,2001,2002, 2006, 2009, 2010, 2013 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (oop goops save)
+ \:use-module (oop goops internal)
+ \:use-module (oop goops util)
+ \:re-export (make-unbound)
+ \:export (save-objects load-objects restore
+ enumerate! enumerate-component!
+ write-readably write-component write-component-procedure
+ literal? readable make-readable))
+
+;;;
+;;; save-objects ALIST PORT [EXCLUDED] [USES]
+;;;
+;;; ALIST ::= ((NAME . OBJECT) ...)
+;;;
+;;; Save OBJECT ... to PORT so that when the data is read and evaluated
+;;; OBJECT ... are re-created under names NAME ... .
+;;; Exclude any references to objects in the list EXCLUDED.
+;;; Add a (use-modules . USES) line to the top of the saved text.
+;;;
+;;; In some instances, when `save-object' doesn't know how to produce
+;;; readable syntax for an object, you can explicitly register read
+;;; syntax for an object using the special form `readable'.
+;;;
+;;; Example:
+;;;
+;;; The function `foo' produces an object of obscure structure.
+;;; Only `foo' can construct such objects. Because of this, an
+;;; object such as
+;;;
+;;; (define x (vector 1 (foo)))
+;;;
+;;; cannot be saved by `save-objects'. But if you instead write
+;;;
+;;; (define x (vector 1 (readable (foo))))
+;;;
+;;; `save-objects' will happily produce the necessary read syntax.
+;;;
+;;; To add new read syntax, hang methods on `enumerate!' and
+;;; `write-readably'.
+;;;
+;;; enumerate! OBJECT ENV
+;;; Should call `enumerate-component!' (which takes same args) on
+;;; each component object. Should return #t if the composite object
+;;; can be written as a literal. (`enumerate-component!' returns #t
+;;; if the component is a literal.
+;;;
+;;; write-readably OBJECT PORT ENV
+;;; Should write a readable representation of OBJECT to PORT.
+;;; Should use `write-component' to print each component object.
+;;; Use `literal?' to decide if a component is a literal.
+;;;
+;;; Utilities:
+;;;
+;;; enumerate-component! OBJECT ENV
+;;;
+;;; write-component OBJECT PATCHER PORT ENV
+;;; PATCHER is an expression which, when evaluated, stores OBJECT
+;;; into its current location.
+;;;
+;;; Example:
+;;;
+;;; (write-component (car ls) `(set-car! ,ls ,(car ls)) file env)
+;;;
+;;; write-component is a macro.
+;;;
+;;; literal? COMPONENT ENV
+;;;
+
+(define-method (immediate? (o <top>)) #f)
+
+(define-method (immediate? (o <null>)) #t)
+(define-method (immediate? (o <number>)) #t)
+(define-method (immediate? (o <boolean>)) #t)
+(define-method (immediate? (o <symbol>)) #t)
+(define-method (immediate? (o <char>)) #t)
+(define-method (immediate? (o <keyword>)) #t)
+
+;;; enumerate! OBJECT ENVIRONMENT
+;;;
+;;; Return #t if object is a literal.
+;;;
+(define-method (enumerate! (o <top>) env) #t)
+
+(define-method (write-readably (o <top>) file env)
+ ;;(goops-error "No read-syntax defined for object `~S'" o)
+ (write o file) ;doesn't catch bugs, but is much more flexible
+ )
+
+;;;
+;;; Readables
+;;;
+
+(define readables (make-weak-key-hash-table 61))
+
+(define-macro (readable exp)
+ `(make-readable ,exp ',(copy-tree exp)))
+
+(define (make-readable obj expr)
+ (hashq-set! readables obj expr)
+ obj)
+
+(define (readable-expression obj)
+ `(readable ,(hashq-ref readables obj)))
+
+;; FIXME: if obj is nil or false, this can return a false value. OTOH
+;; usually this is only for non-immediates.
+(define (readable? obj)
+ (hashq-ref readables obj))
+
+;;;
+;;; Writer helpers
+;;;
+
+(define (write-component-procedure o file env)
+ "Return #f if circular reference"
+ (cond ((immediate? o) (write o file) #t)
+ ((readable? o) (write (readable-expression o) file) #t)
+ ((excluded? o env) (display #f file) #t)
+ (else
+ (let ((info (object-info o env)))
+ (cond ((not (binding? info)) (write-readably o file env) #t)
+ ((not (eq? (visiting info) #\defined)) #f) ;forward reference
+ (else (display (binding info) file) #t))))))
+
+;;; write-component OBJECT PATCHER FILE ENV
+;;;
+(define-macro (write-component object patcher file env)
+ `(or (write-component-procedure ,object ,file ,env)
+ (begin
+ (display #f ,file)
+ (add-patcher! ,patcher ,env))))
+
+;;;
+;;; Strings
+;;;
+
+(define-method (enumerate! (o <string>) env) #f)
+
+;;;
+;;; Vectors
+;;;
+
+(define-method (enumerate! (o <vector>) env)
+ (or (not (vector? o))
+ (let ((literal? #t))
+ (array-for-each (lambda (o)
+ (if (not (enumerate-component! o env))
+ (set! literal? #f)))
+ o)
+ literal?)))
+
+(define-method (write-readably (o <vector>) file env)
+ (if (not (vector? o))
+ (write o file)
+ (let ((n (vector-length o)))
+ (if (zero? n)
+ (display "#()" file)
+ (let ((not-literal? (not (literal? o env))))
+ (display (if not-literal?
+ "(vector "
+ "#(")
+ file)
+ (if (and not-literal?
+ (literal? (vector-ref o 0) env))
+ (display #\' file))
+ (write-component (vector-ref o 0)
+ `(vector-set! ,o 0 ,(vector-ref o 0))
+ file
+ env)
+ (do ((i 1 (+ 1 i)))
+ ((= i n))
+ (display #\space file)
+ (if (and not-literal?
+ (literal? (vector-ref o i) env))
+ (display #\' file))
+ (write-component (vector-ref o i)
+ `(vector-set! ,o ,i ,(vector-ref o i))
+ file
+ env))
+ (display #\) file))))))
+
+
+;;;
+;;; Arrays
+;;;
+
+(define-method (enumerate! (o <array>) env)
+ (enumerate-component! (shared-array-root o) env))
+
+(define (make-mapper array)
+ (let* ((n (array-rank array))
+ (indices (reverse (if (<= n 11)
+ (list-tail '(t s r q p n m l k j i) (- 11 n))
+ (let loop ((n n)
+ (ls '()))
+ (if (zero? n)
+ ls
+ (loop (- n 1)
+ (cons (gensym "i") ls))))))))
+ `(lambda ,indices
+ (+ ,(shared-array-offset array)
+ ,@(map (lambda (ind dim inc)
+ `(* ,inc ,(if (pair? dim) `(- ,ind ,(car dim)) ind)))
+ indices
+ (array-dimensions array)
+ (shared-array-increments array))))))
+
+(define (write-array prefix o not-literal? file env)
+ (letrec ((inner (lambda (n indices)
+ (if (not (zero? n))
+ (let ((el (apply array-ref o
+ (reverse (cons 0 indices)))))
+ (if (and not-literal?
+ (literal? el env))
+ (display #\' file))
+ (write-component
+ el
+ `(array-set! ,o ,el ,@indices)
+ file
+ env)))
+ (do ((i 1 (+ 1 i)))
+ ((= i n))
+ (display #\space file)
+ (let ((el (apply array-ref o
+ (reverse (cons i indices)))))
+ (if (and not-literal?
+ (literal? el env))
+ (display #\' file))
+ (write-component
+ el
+ `(array-set! ,o ,el ,@indices)
+ file
+ env))))))
+ (display prefix file)
+ (let loop ((dims (array-dimensions o))
+ (indices '()))
+ (cond ((null? (cdr dims))
+ (inner (car dims) indices))
+ (else
+ (let ((n (car dims)))
+ (do ((i 0 (+ 1 i)))
+ ((= i n))
+ (if (> i 0)
+ (display #\space file))
+ (display prefix file)
+ (loop (cdr dims) (cons i indices))
+ (display #\) file))))))
+ (display #\) file)))
+
+(define-method (write-readably (o <array>) file env)
+ (let ((root (shared-array-root o)))
+ (cond ((literal? o env)
+ (if (not (vector? root))
+ (write o file)
+ (begin
+ (display #\# file)
+ (display (array-rank o) file)
+ (write-array #\( o #f file env))))
+ ((binding? root env)
+ (display "(make-shared-array " file)
+ (if (literal? root env)
+ (display #\' file))
+ (write-component root
+ (goops-error "write-readably(<array>): internal error")
+ file
+ env)
+ (display #\space file)
+ (display (make-mapper o) file)
+ (for-each (lambda (dim)
+ (display #\space file)
+ (display dim file))
+ (array-dimensions o))
+ (display #\) file))
+ (else
+ (display "(list->uniform-array " file)
+ (display (array-rank o) file)
+ (display " '() " file)
+ (write-array "(list " o #f file env)))))
+
+;;;
+;;; Pairs
+;;;
+
+;;; These methods have more complex structure than is required for
+;;; most objects, since they take over some of the logic of
+;;; `write-component'.
+;;;
+
+(define-method (enumerate! (o <pair>) env)
+ (let ((literal? (enumerate-component! (car o) env)))
+ (and (enumerate-component! (cdr o) env)
+ literal?)))
+
+(define-method (write-readably (o <pair>) file env)
+ (let ((proper? (let loop ((ls o))
+ (or (null? ls)
+ (and (pair? ls)
+ (not (binding? (cdr ls) env))
+ (loop (cdr ls))))))
+ (1? (or (not (pair? (cdr o)))
+ (binding? (cdr o) env)))
+ (not-literal? (not (literal? o env)))
+ (infos '())
+ (refs (ref-stack env)))
+ (display (cond ((not not-literal?) #\()
+ (proper? "(list ")
+ (1? "(cons ")
+ (else "(cons* "))
+ file)
+ (if (and not-literal?
+ (literal? (car o) env))
+ (display #\' file))
+ (write-component (car o) `(set-car! ,o ,(car o)) file env)
+ (do ((ls (cdr o) (cdr ls))
+ (prev o ls))
+ ((or (not (pair? ls))
+ (binding? ls env))
+ (if (not (null? ls))
+ (begin
+ (if (not not-literal?)
+ (display " ." file))
+ (display #\space file)
+ (if (and not-literal?
+ (literal? ls env))
+ (display #\' file))
+ (write-component ls `(set-cdr! ,prev ,ls) file env)))
+ (display #\) file))
+ (display #\space file)
+ (set! infos (cons (object-info ls env) infos))
+ (push-ref! ls env) ;*fixme* optimize
+ (set! (visiting? (car infos)) #t)
+ (if (and not-literal?
+ (literal? (car ls) env))
+ (display #\' file))
+ (write-component (car ls) `(set-car! ,ls ,(car ls)) file env)
+ )
+ (for-each (lambda (info)
+ (set! (visiting? info) #f))
+ infos)
+ (set! (ref-stack env) refs)
+ ))
+
+;;;
+;;; Objects
+;;;
+
+;;; Doesn't yet handle unbound slots
+
+;; Don't export this function! This is all very temporary.
+;;
+(define (get-set-for-each proc class)
+ (for-each (lambda (slotdef g-n-s)
+ (let ((g-n-s (cddr g-n-s)))
+ (cond ((integer? g-n-s)
+ (proc (standard-get g-n-s) (standard-set g-n-s)))
+ ((not (memq (slot-definition-allocation slotdef)
+ '(#\class #\each-subclass)))
+ (proc (car g-n-s) (cadr g-n-s))))))
+ (class-slots class)
+ (slot-ref class 'getters-n-setters)))
+
+(define (access-for-each proc class)
+ (for-each (lambda (slotdef g-n-s)
+ (let ((g-n-s (cddr g-n-s))
+ (a (slot-definition-accessor slotdef)))
+ (cond ((integer? g-n-s)
+ (proc (slot-definition-name slotdef)
+ (and a (generic-function-name a))
+ (standard-get g-n-s)
+ (standard-set g-n-s)))
+ ((not (memq (slot-definition-allocation slotdef)
+ '(#\class #\each-subclass)))
+ (proc (slot-definition-name slotdef)
+ (and a (generic-function-name a))
+ (car g-n-s)
+ (cadr g-n-s))))))
+ (class-slots class)
+ (slot-ref class 'getters-n-setters)))
+
+(define-macro (restore class slots . exps)
+ "(restore CLASS (SLOT-NAME1 ...) EXP1 ...)"
+ `(let ((o ((@@ (oop goops) %allocate-instance) ,class '())))
+ (for-each (lambda (name val)
+ (slot-set! o name val))
+ ',slots
+ (list ,@exps))
+ o))
+
+(define-method (enumerate! (o <object>) env)
+ (get-set-for-each (lambda (get set)
+ (let ((val (get o)))
+ (if (not (unbound? val))
+ (enumerate-component! val env))))
+ (class-of o))
+ #f)
+
+(define-method (write-readably (o <object>) file env)
+ (let ((class (class-of o)))
+ (display "(restore " file)
+ (display (class-name class) file)
+ (display " (" file)
+ (let ((slotdefs
+ (filter (lambda (slotdef)
+ (not (or (memq (slot-definition-allocation slotdef)
+ '(#\class #\each-subclass))
+ (and (slot-bound? o (slot-definition-name slotdef))
+ (excluded?
+ (slot-ref o (slot-definition-name slotdef))
+ env)))))
+ (class-slots class))))
+ (if (not (null? slotdefs))
+ (begin
+ (display (slot-definition-name (car slotdefs)) file)
+ (for-each (lambda (slotdef)
+ (display #\space file)
+ (display (slot-definition-name slotdef) file))
+ (cdr slotdefs)))))
+ (display #\) file)
+ (access-for-each (lambda (name aname get set)
+ (display #\space file)
+ (let ((val (get o)))
+ (cond ((unbound? val)
+ (display '(make-unbound) file))
+ ((excluded? val env))
+ (else
+ (if (literal? val env)
+ (display #\' file))
+ (write-component val
+ (if aname
+ `(set! (,aname ,o) ,val)
+ `(slot-set! ,o ',name ,val))
+ file env)))))
+ class)
+ (display #\) file)))
+
+;;;
+;;; Classes
+;;;
+
+;;; Currently, we don't support reading in class objects
+;;;
+
+(define-method (enumerate! (o <class>) env) #f)
+
+(define-method (write-readably (o <class>) file env)
+ (display (class-name o) file))
+
+;;;
+;;; Generics
+;;;
+
+;;; Currently, we don't support reading in generic functions
+;;;
+
+(define-method (enumerate! (o <generic>) env) #f)
+
+(define-method (write-readably (o <generic>) file env)
+ (display (generic-function-name o) file))
+
+;;;
+;;; Method
+;;;
+
+;;; Currently, we don't support reading in methods
+;;;
+
+(define-method (enumerate! (o <method>) env) #f)
+
+(define-method (write-readably (o <method>) file env)
+ (goops-error "No read-syntax for <method> defined"))
+
+;;;
+;;; Environments
+;;;
+
+(define-class <environment> ()
+ (object-info #\accessor object-info
+ #\init-form (make-hash-table 61))
+ (excluded #\accessor excluded
+ #\init-form (make-hash-table 61))
+ (pass-2? #\accessor pass-2?
+ #\init-value #f)
+ (ref-stack #\accessor ref-stack
+ #\init-value '())
+ (objects #\accessor objects
+ #\init-value '())
+ (pre-defines #\accessor pre-defines
+ #\init-value '())
+ (locals #\accessor locals
+ #\init-value '())
+ (stand-ins #\accessor stand-ins
+ #\init-value '())
+ (post-defines #\accessor post-defines
+ #\init-value '())
+ (patchers #\accessor patchers
+ #\init-value '())
+ (multiple-bound #\accessor multiple-bound
+ #\init-value '())
+ )
+
+(define-method (initialize (env <environment>) initargs)
+ (next-method)
+ (cond ((get-keyword #\excluded initargs #f)
+ => (lambda (excludees)
+ (for-each (lambda (e)
+ (hashq-create-handle! (excluded env) e #f))
+ excludees)))))
+
+(define-method (object-info o env)
+ (hashq-ref (object-info env) o))
+
+(define-method ((setter object-info) o env x)
+ (hashq-set! (object-info env) o x))
+
+(define (excluded? o env)
+ (hashq-get-handle (excluded env) o))
+
+(define (add-patcher! patcher env)
+ (set! (patchers env) (cons patcher (patchers env))))
+
+(define (push-ref! o env)
+ (set! (ref-stack env) (cons o (ref-stack env))))
+
+(define (pop-ref! env)
+ (set! (ref-stack env) (cdr (ref-stack env))))
+
+(define (container env)
+ (car (ref-stack env)))
+
+(define-class <object-info> ()
+ (visiting #\accessor visiting
+ #\init-value #f)
+ (binding #\accessor binding
+ #\init-value #f)
+ (literal? #\accessor literal?
+ #\init-value #f)
+ )
+
+(define visiting? visiting)
+
+(define-method (binding (info <boolean>))
+ #f)
+
+(define-method (binding o env)
+ (binding (object-info o env)))
+
+(define binding? binding)
+
+(define-method (literal? (info <boolean>))
+ #t)
+
+;;; Note that this method is intended to be used only during the
+;;; writing pass
+;;;
+(define-method (literal? o env)
+ (or (immediate? o)
+ (excluded? o env)
+ (let ((info (object-info o env)))
+ ;; write-component sets all bindings first to #\defining,
+ ;; then to #\defined
+ (and (or (not (binding? info))
+ ;; we might be using `literal?' in a write-readably method
+ ;; to query about the object being defined
+ (and (eq? (visiting info) #\defining)
+ (null? (cdr (ref-stack env)))))
+ (literal? info)))))
+
+;;;
+;;; Enumeration
+;;;
+
+;;; Enumeration has two passes.
+;;;
+;;; Pass 1: Detect common substructure, circular references and order
+;;;
+;;; Pass 2: Detect literals
+
+(define (enumerate-component! o env)
+ (cond ((immediate? o) #t)
+ ((readable? o) #f)
+ ((excluded? o env) #t)
+ ((pass-2? env)
+ (let ((info (object-info o env)))
+ (if (binding? info)
+ ;; if circular reference, we print as a literal
+ ;; (note that during pass-2, circular references are
+ ;; forward references, i.e. *not* yet marked with #\pass-2
+ (not (eq? (visiting? info) #\pass-2))
+ (and (enumerate! o env)
+ (begin
+ (set! (literal? info) #t)
+ #t)))))
+ ((object-info o env)
+ => (lambda (info)
+ (set! (binding info) #t)
+ (if (visiting? info)
+ ;; circular reference--mark container
+ (set! (binding (object-info (container env) env)) #t))))
+ (else
+ (let ((info (make <object-info>)))
+ (set! (object-info o env) info)
+ (push-ref! o env)
+ (set! (visiting? info) #t)
+ (enumerate! o env)
+ (set! (visiting? info) #f)
+ (pop-ref! env)
+ (set! (objects env) (cons o (objects env)))))))
+
+
+;;;
+;;; Main engine
+;;;
+
+(define binding-name car)
+(define binding-object cdr)
+
+(define (pass-1! alist env)
+ ;; Determine object order and necessary bindings
+ (for-each (lambda (binding)
+ (enumerate-component! (binding-object binding) env))
+ alist))
+
+(define (make-local i)
+ (string->symbol (string-append "%o" (number->string i))))
+
+(define (name-bindings! alist env)
+ ;; Name top-level bindings
+ (for-each (lambda (b)
+ (let ((o (binding-object b)))
+ (if (not (or (immediate? o)
+ (readable? o)
+ (excluded? o env)))
+ (let ((info (object-info o env)))
+ (if (symbol? (binding info))
+ ;; already bound to a variable
+ (set! (multiple-bound env)
+ (acons (binding info)
+ (binding-name b)
+ (multiple-bound env)))
+ (set! (binding info)
+ (binding-name b)))))))
+ alist)
+ ;; Name rest of bindings and create stand-in and definition lists
+ (let post-loop ((ls (objects env))
+ (post-defs '()))
+ (cond ((or (null? ls)
+ (eq? (binding (car ls) env) #t))
+ (set! (post-defines env) post-defs)
+ (set! (objects env) ls))
+ ((not (binding (car ls) env))
+ (post-loop (cdr ls) post-defs))
+ (else
+ (post-loop (cdr ls) (cons (car ls) post-defs)))))
+ (let pre-loop ((ls (reverse (objects env)))
+ (i 0)
+ (pre-defs '())
+ (locs '())
+ (sins '()))
+ (if (null? ls)
+ (begin
+ (set! (pre-defines env) (reverse pre-defs))
+ (set! (locals env) (reverse locs))
+ (set! (stand-ins env) (reverse sins)))
+ (let ((info (object-info (car ls) env)))
+ (cond ((not (binding? info))
+ (pre-loop (cdr ls) i pre-defs locs sins))
+ ((boolean? (binding info))
+ ;; local
+ (set! (binding info) (make-local i))
+ (pre-loop (cdr ls)
+ (+ 1 i)
+ pre-defs
+ (cons (car ls) locs)
+ sins))
+ ((null? locs)
+ (pre-loop (cdr ls)
+ i
+ (cons (car ls) pre-defs)
+ locs
+ sins))
+ (else
+ (let ((real-name (binding info)))
+ (set! (binding info) (make-local i))
+ (pre-loop (cdr ls)
+ (+ 1 i)
+ pre-defs
+ (cons (car ls) locs)
+ (acons (binding info) real-name sins)))))))))
+
+(define (pass-2! env)
+ (set! (pass-2? env) #t)
+ (for-each (lambda (o)
+ (let ((info (object-info o env)))
+ (set! (literal? info) (enumerate! o env))
+ (set! (visiting info) #\pass-2)))
+ (append (pre-defines env)
+ (locals env)
+ (post-defines env))))
+
+(define (write-define! name val literal? file)
+ (display "(define " file)
+ (display name file)
+ (display #\space file)
+ (if literal? (display #\' file))
+ (write val file)
+ (display ")\n" file))
+
+(define (write-empty-defines! file env)
+ (for-each (lambda (stand-in)
+ (write-define! (cdr stand-in) #f #f file))
+ (stand-ins env))
+ (for-each (lambda (o)
+ (write-define! (binding o env) #f #f file))
+ (post-defines env)))
+
+(define (write-definition! prefix o file env)
+ (display prefix file)
+ (let ((info (object-info o env)))
+ (display (binding info) file)
+ (display #\space file)
+ (if (literal? info)
+ (display #\' file))
+ (push-ref! o env)
+ (set! (visiting info) #\defining)
+ (write-readably o file env)
+ (set! (visiting info) #\defined)
+ (pop-ref! env)
+ (display #\) file)))
+
+(define (write-let*-head! file env)
+ (display "(let* (" file)
+ (write-definition! "(" (car (locals env)) file env)
+ (for-each (lambda (o)
+ (write-definition! "\n (" o file env))
+ (cdr (locals env)))
+ (display ")\n" file))
+
+(define (write-rebindings! prefix bindings file env)
+ (for-each (lambda (patch)
+ (display prefix file)
+ (display (cdr patch) file)
+ (display #\space file)
+ (display (car patch) file)
+ (display ")\n" file))
+ bindings))
+
+(define (write-definitions! selector prefix file env)
+ (for-each (lambda (o)
+ (write-definition! prefix o file env)
+ (newline file))
+ (selector env)))
+
+(define (write-patches! prefix file env)
+ (for-each (lambda (patch)
+ (display prefix file)
+ (display (let name-objects ((patcher patch))
+ (cond ((binding patcher env)
+ => (lambda (name)
+ (cond ((assq name (stand-ins env))
+ => cdr)
+ (else name))))
+ ((pair? patcher)
+ (cons (name-objects (car patcher))
+ (name-objects (cdr patcher))))
+ (else patcher)))
+ file)
+ (newline file))
+ (reverse (patchers env))))
+
+(define (write-immediates! alist file)
+ (for-each (lambda (b)
+ (if (immediate? (binding-object b))
+ (write-define! (binding-name b)
+ (binding-object b)
+ #t
+ file)))
+ alist))
+
+(define (write-readables! alist file env)
+ (let ((written '()))
+ (for-each (lambda (b)
+ (cond ((not (readable? (binding-object b))))
+ ((assq (binding-object b) written)
+ => (lambda (p)
+ (set! (multiple-bound env)
+ (acons (cdr p)
+ (binding-name b)
+ (multiple-bound env)))))
+ (else
+ (write-define! (binding-name b)
+ (readable-expression (binding-object b))
+ #f
+ file)
+ (set! written (acons (binding-object b)
+ (binding-name b)
+ written)))))
+ alist)))
+
+(define-method (save-objects (alist <pair>) (file <string>) . rest)
+ (let ((port (open-output-file file)))
+ (apply save-objects alist port rest)
+ (close-port port)
+ *unspecified*))
+
+(define-method (save-objects (alist <pair>) (file <output-port>) . rest)
+ (let ((excluded (if (>= (length rest) 1) (car rest) '()))
+ (uses (if (>= (length rest) 2) (cadr rest) '())))
+ (let ((env (make <environment> #\excluded excluded)))
+ (pass-1! alist env)
+ (name-bindings! alist env)
+ (pass-2! env)
+ (if (not (null? uses))
+ (begin
+ (write `(use-modules ,@uses) file)
+ (newline file)))
+ (write-immediates! alist file)
+ (if (null? (locals env))
+ (begin
+ (write-definitions! post-defines "(define " file env)
+ (write-patches! "" file env))
+ (begin
+ (write-definitions! pre-defines "(define " file env)
+ (write-empty-defines! file env)
+ (write-let*-head! file env)
+ (write-rebindings! " (set! " (stand-ins env) file env)
+ (write-definitions! post-defines " (set! " file env)
+ (write-patches! " " file env)
+ (display " )\n" file)))
+ (write-readables! alist file env)
+ (write-rebindings! "(define " (reverse (multiple-bound env)) file env))))
+
+(define-method (load-objects (file <string>))
+ (let* ((port (open-input-file file))
+ (objects (load-objects port)))
+ (close-port port)
+ objects))
+
+(define iface (module-public-interface (current-module)))
+
+(define-method (load-objects (file <input-port>))
+ (let ((m (make-module)))
+ (module-use! m the-scm-module)
+ (module-use! m iface)
+ (save-module-excursion
+ (lambda ()
+ (set-current-module m)
+ (let loop ((sexp (read file)))
+ (if (not (eof-object? sexp))
+ (begin
+ (eval sexp m)
+ (loop (read file)))))))
+ (module-map (lambda (name var)
+ (cons name (variable-ref var)))
+ m)))
+;;; installed-scm-file
+
+;;;; Copyright (C) 2005, 2006, 2010, 2011 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (oop goops simple)
+ \:use-module (oop goops accessors)
+ \:export (define-class)
+ \:no-backtrace)
+
+(define-syntax-rule (define-class arg ...)
+ (define-class-with-accessors-keywords arg ...))
+
+(module-use! (module-public-interface (current-module))
+ (resolve-interface '(oop goops)))
+;;;; Copyright (C) 1999,2002, 2006, 2010, 2011 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (oop goops stklos)
+ \:use-module (oop goops internal)
+ \:no-backtrace
+ )
+
+;;;
+;;; This is the stklos compatibility module.
+;;;
+;;; WARNING: This module is under construction. While we expect to be able
+;;; to run most stklos code without problems in the future, this is not the
+;;; case now. The current compatibility is only superficial.
+;;;
+;;; Any comments/complaints/patches are welcome. Tell us about
+;;; your incompatibility problems (bug-guile@gnu.org).
+;;;
+
+;; Export all bindings that are exported from (oop goops)...
+(module-for-each (lambda (sym var)
+ (module-add! (module-public-interface (current-module))
+ sym var))
+ (resolve-interface '(oop goops)))
+
+;; ...but replace the following bindings:
+(export define-class define-method)
+
+;; Also export the following
+(export write-object)
+
+;;; Enable keyword support (*fixme*---currently this has global effect)
+(read-set! keywords 'prefix)
+
+(define-syntax-rule (define-class name supers (slot ...) rest ...)
+ (standard-define-class name supers slot ... rest ...))
+
+(define (toplevel-define! name val)
+ (module-define! (current-module) name val))
+
+(define-syntax define-method
+ (syntax-rules (setter)
+ ((_ (setter name) rest ...)
+ (begin
+ (if (or (not (defined? 'name))
+ (not (is-a? name <generic-with-setter>)))
+ (toplevel-define! 'name
+ (ensure-accessor
+ (if (defined? 'name) name #f) 'name)))
+ (add-method! (setter name) (method rest ...))))
+ ((_ name rest ...)
+ (begin
+ (if (or (not (defined? 'name))
+ (not (or (is-a? name <generic>)
+ (is-a? name <primitive-generic>))))
+ (toplevel-define! 'name
+ (ensure-generic
+ (if (defined? 'name) name #f) 'name)))
+ (add-method! name (method rest ...))))))
+;;;; Copyright (C) 1999, 2000, 2001, 2003, 2006, 2008 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (oop goops util)
+ \:export (mapappend find-duplicate
+ map* for-each* length* improper->proper)
+ \:use-module (srfi srfi-1)
+ \:re-export (any every)
+ \:no-backtrace
+ )
+
+
+;;;
+;;; {Utilities}
+;;;
+
+(define mapappend append-map)
+
+(define (find-duplicate l) ; find a duplicate in a list; #f otherwise
+ (cond
+ ((null? l) #f)
+ ((memv (car l) (cdr l)) (car l))
+ (else (find-duplicate (cdr l)))))
+
+(begin-deprecated
+ (define (top-level-env)
+ (let ((mod (current-module)))
+ (if mod
+ (module-eval-closure mod)
+ '())))
+
+ (define (top-level-env? env)
+ (or (null? env)
+ (procedure? (car env))))
+
+ (export top-level-env? top-level-env))
+
+(define (map* fn . l) ; A map which accepts dotted lists (arg lists
+ (cond ; must be "isomorph"
+ ((null? (car l)) '())
+ ((pair? (car l)) (cons (apply fn (map car l))
+ (apply map* fn (map cdr l))))
+ (else (apply fn l))))
+
+(define (for-each* fn . l) ; A for-each which accepts dotted lists (arg lists
+ (cond ; must be "isomorph"
+ ((null? (car l)) '())
+ ((pair? (car l)) (apply fn (map car l)) (apply for-each* fn (map cdr l)))
+ (else (apply fn l))))
+
+(define (length* ls)
+ (do ((n 0 (+ 1 n))
+ (ls ls (cdr ls)))
+ ((not (pair? ls)) n)))
+
+(define (improper->proper ls)
+ (if (pair? ls)
+ (cons (car ls) (improper->proper (cdr ls)))
+ (list ls)))
+;;; rnrs.scm --- The R6RS composite library
+
+;; Copyright (C) 2010, 2011 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs (6))
+ (export ;; (rnrs arithmetic bitwise)
+
+ bitwise-not bitwise-and bitwise-ior bitwise-xor bitwise-if
+ bitwise-bit-count bitwise-length bitwise-first-bit-set
+ bitwise-bit-set? bitwise-copy-bit bitwise-bit-field
+ bitwise-copy-bit-field bitwise-arithmetic-shift
+ bitwise-arithmetic-shift-left bitwise-arithmetic-shift-right
+ bitwise-rotate-bit-field bitwise-reverse-bit-field
+
+ ;; (rnrs arithmetic fixnums)
+
+ fixnum? fixnum-width least-fixnum greatest-fixnum fx=? fx>? fx<? fx>=?
+ fx<=? fxzero? fxpositive? fxnegative? fxodd? fxeven? fxmax fxmin fx+
+ fx* fx- fxdiv-and-mod fxdiv fxmod fxdiv0-and-mod0 fxdiv0 fxmod0
+ fx+/carry fx-/carry fx*/carry fxnot fxand fxior fxxor fxif fxbit-count
+ fxlength fxfirst-bit-set fxbit-set? fxcopy-bit fxbit-field
+ fxcopy-bit-field fxarithmetic-shift fxarithmetic-shift-left
+ fxarithmetic-shift-right fxrotate-bit-field fxreverse-bit-field
+
+ ;; (rnrs arithmetic flonums)
+
+ flonum? real->flonum fl=? fl<? fl<=? fl>? fl>=? flinteger? flzero?
+ flpositive? flnegative? flodd? fleven? flfinite? flinfinite? flnan?
+ flmax flmin fl+ fl* fl- fl/ flabs fldiv-and-mod fldiv flmod
+ fldiv0-and-mod0 fldiv0 flmod0 flnumerator fldenominator flfloor
+ flceiling fltruncate flround flexp fllog flsin flcos fltan flacos
+ flasin flatan flsqrt flexpt &no-infinities
+ make-no-infinities-violation no-infinities-violation? &no-nans
+ make-no-nans-violation no-nans-violation? fixnum->flonum
+
+ ;; (rnrs base)
+
+ boolean? symbol? char? vector? null? pair? number? string? procedure?
+ define define-syntax syntax-rules lambda let let* let-values
+ let*-values letrec letrec* begin quote lambda if set! cond case or
+ and not eqv? equal? eq? + - * / max min abs numerator denominator gcd
+ lcm floor ceiling truncate round rationalize real-part imag-part
+ make-rectangular angle div mod div-and-mod div0 mod0 div0-and-mod0
+ expt exact-integer-sqrt sqrt exp log sin cos tan asin acos atan
+ make-polar magnitude angle complex? real? rational? integer? exact?
+ inexact? real-valued? rational-valued? integer-valued? zero?
+ positive? negative? odd? even? nan? finite? infinite? exact inexact =
+ < > <= >= number->string string->number boolean=? cons car cdr caar
+ cadr cdar cddr caaar caadr cadar cdaar caddr cdadr cddar cdddr caaaar
+ caaadr caadar cadaar cdaaar cddaar cdadar cdaadr cadadr caaddr caddar
+ cadddr cdaddr cddadr cdddar cddddr list? list length append reverse
+ list-tail list-ref map for-each symbol->string string->symbol symbol=?
+ char->integer integer->char char=? char<? char>? char<=? char>=?
+ make-string string string-length string-ref string=? string<? string>?
+ string<=? string>=? substring string-append string->list list->string
+ string-for-each string-copy vector? make-vector vector vector-length
+ vector-ref vector-set! vector->list list->vector vector-fill!
+ vector-map vector-for-each error assertion-violation assert
+ call-with-current-continuation call/cc call-with-values dynamic-wind
+ values apply quasiquote unquote unquote-splicing let-syntax
+ letrec-syntax syntax-rules identifier-syntax
+
+ ;; (rnrs bytevectors)
+
+ endianness native-endianness bytevector? make-bytevector
+ bytevector-length bytevector=? bytevector-fill! bytevector-copy!
+ bytevector-copy uniform-array->bytevector bytevector-u8-ref
+ bytevector-s8-ref bytevector-u8-set! bytevector-s8-set!
+ bytevector->u8-list u8-list->bytevector bytevector-uint-ref
+ bytevector-uint-set! bytevector-sint-ref bytevector-sint-set!
+ bytevector->sint-list bytevector->uint-list uint-list->bytevector
+ sint-list->bytevector bytevector-u16-ref bytevector-s16-ref
+ bytevector-u16-set! bytevector-s16-set! bytevector-u16-native-ref
+ bytevector-s16-native-ref bytevector-u16-native-set!
+ bytevector-s16-native-set! bytevector-u32-ref bytevector-s32-ref
+ bytevector-u32-set! bytevector-s32-set! bytevector-u32-native-ref
+ bytevector-s32-native-ref bytevector-u32-native-set!
+ bytevector-s32-native-set! bytevector-u64-ref bytevector-s64-ref
+ bytevector-u64-set! bytevector-s64-set! bytevector-u64-native-ref
+ bytevector-s64-native-ref bytevector-u64-native-set!
+ bytevector-s64-native-set! bytevector-ieee-single-ref
+ bytevector-ieee-single-set! bytevector-ieee-single-native-ref
+ bytevector-ieee-single-native-set! bytevector-ieee-double-ref
+ bytevector-ieee-double-set! bytevector-ieee-double-native-ref
+ bytevector-ieee-double-native-set! string->utf8 string->utf16
+ string->utf32 utf8->string utf16->string utf32->string
+
+ ;; (rnrs conditions)
+
+ &condition condition simple-conditions condition? condition-predicate
+ condition-accessor define-condition-type &message
+ make-message-condition message-condition? condition-message &warning
+ make-warning warning? &serious make-serious-condition
+ serious-condition? &error make-error error? &violation make-violation
+ violation? &assertion make-assertion-violation assertion-violation?
+ &irritants make-irritants-condition irritants-condition?
+ condition-irritants &who make-who-condition who-condition?
+ condition-who &non-continuable make-non-continuable-violation
+ non-continuable-violation? &implementation-restriction
+ make-implementation-restriction-violation
+ implementation-restriction-violation? &lexical make-lexical-violation
+ lexical-violation? &syntax make-syntax-violation syntax-violation?
+ syntax-violation-form syntax-violation-subform &undefined
+ make-undefined-violation undefined-violation?
+
+ ;; (rnrs control)
+
+ when unless do case-lambda
+
+ ;; (rnrs enums)
+
+ make-enumeration enum-set-universe enum-set-indexer
+ enum-set-constructor enum-set->list enum-set-member? enum-set-subset?
+ enum-set=? enum-set-union enum-set-intersection enum-set-difference
+ enum-set-complement enum-set-projection define-enumeration
+
+ ;; (rnrs exceptions)
+
+ guard with-exception-handler raise raise-continuable
+
+ ;; (rnrs files)
+
+ file-exists? delete-file &i/o make-i/o-error i/o-error? &i/o-read
+ make-i/o-read-error i/o-read-error? &i/o-write make-i/o-write-error
+ i/o-write-error? &i/o-invalid-position
+ make-i/o-invalid-position-error i/o-invalid-position-error?
+ i/o-error-position &i/o-filename make-i/o-filename-error
+ i/o-filename-error? i/o-error-filename &i/o-file-protection
+ make-i/o-file-protection-error i/o-file-protection-error?
+ &i/o-file-is-read-only make-i/o-file-is-read-only-error
+ i/o-file-is-read-only-error? &i/o-file-already-exists
+ make-i/o-file-already-exists-error i/o-file-already-exists-error?
+ &i/o-file-does-not-exist make-i/o-file-does-not-exist-error
+ i/o-file-does-not-exist-error? &i/o-port make-i/o-port-error
+ i/o-port-error? i/o-error-port
+
+ ;; (rnrs hashtables)
+
+ make-eq-hashtable make-eqv-hashtable make-hashtable hashtable?
+ hashtable-size hashtable-ref hashtable-set! hashtable-delete!
+ hashtable-contains? hashtable-update! hashtable-copy hashtable-clear!
+ hashtable-keys hashtable-entries hashtable-equivalence-function
+ hashtable-hash-function hashtable-mutable? equal-hash string-hash
+ string-ci-hash symbol-hash
+
+ ;; (rnrs io ports)
+
+ file-options buffer-mode buffer-mode?
+ eol-style native-eol-style error-handling-mode
+ make-transcoder transcoder-codec transcoder-eol-style
+ transcoder-error-handling-mode native-transcoder
+ latin-1-codec utf-8-codec utf-16-codec
+
+ eof-object? port? input-port? output-port? eof-object port-eof?
+ port-transcoder
+ binary-port? textual-port? transcoded-port
+ port-position set-port-position!
+ port-has-port-position? port-has-set-port-position!?
+ close-port call-with-port
+ open-bytevector-input-port make-custom-binary-input-port get-u8
+ lookahead-u8 get-bytevector-n get-bytevector-n! get-bytevector-some
+ get-bytevector-all open-bytevector-output-port
+ make-custom-binary-output-port put-u8 put-bytevector
+ open-string-input-port open-string-output-port
+ call-with-bytevector-output-port
+ call-with-string-output-port
+ latin-1-codec utf-8-codec utf-16-codec
+ open-file-input-port open-file-output-port open-file-input/output-port
+ make-custom-textual-output-port
+ call-with-string-output-port
+ flush-output-port put-string
+ get-char get-datum get-line get-string-all get-string-n get-string-n!
+ lookahead-char
+ put-char put-datum put-string
+ standard-input-port standard-output-port standard-error-port
+
+ ;; (rnrs io simple)
+
+ call-with-input-file call-with-output-file current-input-port
+ current-output-port current-error-port with-input-from-file
+ with-output-to-file open-input-file open-output-file close-input-port
+ close-output-port read-char peek-char read write-char newline display
+ write
+
+ ;; (rnrs lists)
+
+ find for-all exists filter partition fold-left fold-right remp remove
+ remv remq memp member memv memq assp assoc assv assq cons*
+
+ ;; (rnrs programs)
+
+ command-line exit
+
+ ;; (rnrs records inspection)
+
+ record? record-rtd record-type-name record-type-parent
+ record-type-uid record-type-generative? record-type-sealed?
+ record-type-opaque? record-type-field-names record-field-mutable?
+
+ ;; (rnrs records procedural)
+
+ make-record-type-descriptor record-type-descriptor?
+ make-record-constructor-descriptor record-constructor record-predicate
+ record-accessor record-mutator
+
+ ;; (rnrs records syntactic)
+
+ define-record-type record-type-descriptor
+ record-constructor-descriptor
+
+ ;; (rnrs sorting)
+
+ list-sort vector-sort vector-sort!
+
+ ;; (rnrs syntax-case)
+
+ make-variable-transformer syntax
+ ;; Until the deprecated support for a unified modules and
+ ;; bindings namespace is removed, we need to manually resolve
+ ;; a conflict between two bindings: that of the (rnrs
+ ;; syntax-case) module, and the imported `syntax-case'
+ ;; binding. We do so here and below by renaming the macro
+ ;; import.
+ (rename (syntax-case-hack syntax-case))
+ identifier? bound-identifier=? free-identifier=?
+ syntax->datum datum->syntax generate-temporaries with-syntax
+ quasisyntax unsyntax unsyntax-splicing syntax-violation
+
+ ;; (rnrs unicode)
+
+ char-upcase char-downcase char-titlecase char-foldcase
+ char-ci=? char-ci<? char-ci>? char-ci<=? char-ci>=?
+ char-alphabetic? char-numeric? char-whitespace? char-upper-case?
+ char-lower-case? char-title-case? char-general-category
+ string-upcase string-downcase string-titlecase string-foldcase
+ string-ci=? string-ci<? string-ci>? string-ci<=? string-ci>=?
+ string-normalize-nfd string-normalize-nfkd string-normalize-nfc
+ string-normalize-nfkc)
+
+ (import (rnrs arithmetic bitwise (6))
+ (rnrs arithmetic fixnums (6))
+ (rnrs arithmetic flonums (6))
+ (rnrs base (6))
+
+ (rnrs bytevectors (6))
+
+ (rnrs conditions (6))
+ (rnrs control (6))
+ (rnrs enums (6))
+ (rnrs exceptions (6))
+
+ (rnrs files (6))
+
+ (rnrs hashtables (6))
+
+ (rnrs io ports (6))
+
+ (rnrs io simple (6))
+ (rnrs lists (6))
+ (rnrs programs (6))
+ (rnrs records inspection (6))
+ (rnrs records procedural (6))
+ (rnrs records syntactic (6))
+ (rnrs sorting (6))
+ ;; See note above on exporting syntax-case.
+ (rename (rnrs syntax-case (6))
+ (syntax-case syntax-case-hack))
+ (rnrs unicode (6))))
+;;; bitwise.scm --- The R6RS bitwise arithmetic operations library
+
+;; Copyright (C) 2010, 2013 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs arithmetic bitwise (6))
+ (export bitwise-not
+
+ bitwise-and
+ bitwise-ior
+ bitwise-xor
+
+ bitwise-if
+ bitwise-bit-count
+ bitwise-length
+
+ bitwise-first-bit-set
+ bitwise-bit-set?
+ bitwise-copy-bit
+ bitwise-bit-field
+ bitwise-copy-bit-field
+
+ bitwise-arithmetic-shift
+ bitwise-arithmetic-shift-left
+ bitwise-arithmetic-shift-right
+ bitwise-rotate-bit-field
+ bitwise-reverse-bit-field)
+ (import (rnrs base (6))
+ (rnrs control (6))
+ (rename (only (srfi srfi-60) bitwise-if
+ integer-length
+ first-set-bit
+ copy-bit
+ bit-field
+ copy-bit-field
+ rotate-bit-field
+ reverse-bit-field)
+ (integer-length bitwise-length)
+ (first-set-bit bitwise-first-bit-set)
+ (bit-field bitwise-bit-field)
+ (reverse-bit-field bitwise-reverse-bit-field))
+ (rename (only (guile) lognot
+ logand
+ logior
+ logxor
+ logcount
+ logbit?
+ modulo
+ ash)
+ (lognot bitwise-not)
+ (logand bitwise-and)
+ (logior bitwise-ior)
+ (logxor bitwise-xor)
+ (ash bitwise-arithmetic-shift)))
+
+ (define (bitwise-bit-count ei)
+ (if (negative? ei)
+ (bitwise-not (logcount ei))
+ (logcount ei)))
+
+ (define (bitwise-bit-set? ei1 ei2) (logbit? ei2 ei1))
+
+ (define (bitwise-copy-bit ei1 ei2 ei3)
+ ;; The specification states that ei3 should be either 0 or 1.
+ ;; However, other values have been tolerated by both Guile 2.0.x and
+ ;; the sample implementation given the R6RS library document, so for
+ ;; backward compatibility we continue to permit it.
+ (copy-bit ei2 ei1 (logbit? 0 ei3)))
+
+ (define (bitwise-copy-bit-field ei1 ei2 ei3 ei4)
+ (copy-bit-field ei1 ei4 ei2 ei3))
+
+ (define (bitwise-rotate-bit-field ei1 ei2 ei3 ei4)
+ (rotate-bit-field ei1 ei4 ei2 ei3))
+
+ (define bitwise-arithmetic-shift-left bitwise-arithmetic-shift)
+ (define (bitwise-arithmetic-shift-right ei1 ei2)
+ (bitwise-arithmetic-shift ei1 (- ei2))))
+;;; fixnums.scm --- The R6RS fixnums arithmetic library
+
+;; Copyright (C) 2010, 2011, 2013 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs arithmetic fixnums (6))
+ (export fixnum?
+
+ fixnum-width
+ least-fixnum
+ greatest-fixnum
+
+ fx=?
+ fx>?
+ fx<?
+ fx>=?
+ fx<=?
+
+ fxzero?
+ fxpositive?
+ fxnegative?
+ fxodd?
+ fxeven?
+
+ fxmax
+ fxmin
+
+ fx+
+ fx*
+ fx-
+
+ fxdiv-and-mod
+ fxdiv
+ fxmod
+ fxdiv0-and-mod0
+ fxdiv0
+ fxmod0
+
+ fx+/carry
+ fx-/carry
+ fx*/carry
+
+ fxnot
+ fxand
+ fxior
+ fxxor
+ fxif
+
+ fxbit-count
+ fxlength
+ fxfirst-bit-set
+ fxbit-set?
+ fxcopy-bit
+ fxbit-field
+ fxcopy-bit-field
+
+ fxarithmetic-shift
+ fxarithmetic-shift-left
+ fxarithmetic-shift-right
+
+ fxrotate-bit-field
+ fxreverse-bit-field)
+ (import (only (guile) ash
+ cons*
+ define-inlinable
+ inexact->exact
+ logand
+ logbit?
+ logcount
+ logior
+ lognot
+ logxor
+ most-positive-fixnum
+ most-negative-fixnum
+ object-address)
+ (ice-9 optargs)
+ (rnrs base (6))
+ (rnrs control (6))
+ (rnrs arithmetic bitwise (6))
+ (rnrs conditions (6))
+ (rnrs exceptions (6))
+ (rnrs lists (6)))
+
+ (define fixnum-width
+ (let ((w (do ((i 0 (+ 1 i))
+ (n 1 (* 2 n)))
+ ((> n most-positive-fixnum)
+ (+ 1 i)))))
+ (lambda () w)))
+
+ (define (greatest-fixnum) most-positive-fixnum)
+ (define (least-fixnum) most-negative-fixnum)
+
+ (define (fixnum? obj)
+ (not (= 0 (logand 2 (object-address obj)))))
+
+ (define-inlinable (inline-fixnum? obj)
+ (not (= 0 (logand 2 (object-address obj)))))
+
+ (define-syntax assert-fixnum
+ (syntax-rules ()
+ ((_ arg ...)
+ (or (and (inline-fixnum? arg) ...)
+ (raise (make-assertion-violation))))))
+
+ (define (assert-fixnums args)
+ (or (for-all inline-fixnum? args) (raise (make-assertion-violation))))
+
+ (define-syntax define-fxop*
+ (syntax-rules ()
+ ((_ name op)
+ (define name
+ (case-lambda
+ ((x y)
+ (assert-fixnum x y)
+ (op x y))
+ (args
+ (assert-fixnums args)
+ (apply op args)))))))
+
+ ;; All these predicates don't check their arguments for fixnum-ness,
+ ;; as this doesn't seem to be strictly required by R6RS.
+
+ (define fx=? =)
+ (define fx>? >)
+ (define fx<? <)
+ (define fx>=? >=)
+ (define fx<=? <=)
+
+ (define fxzero? zero?)
+ (define fxpositive? positive?)
+ (define fxnegative? negative?)
+ (define fxodd? odd?)
+ (define fxeven? even?)
+
+ (define-fxop* fxmax max)
+ (define-fxop* fxmin min)
+
+ (define (fx+ fx1 fx2)
+ (assert-fixnum fx1 fx2)
+ (let ((r (+ fx1 fx2)))
+ (or (inline-fixnum? r)
+ (raise (make-implementation-restriction-violation)))
+ r))
+
+ (define (fx* fx1 fx2)
+ (assert-fixnum fx1 fx2)
+ (let ((r (* fx1 fx2)))
+ (or (inline-fixnum? r)
+ (raise (make-implementation-restriction-violation)))
+ r))
+
+ (define* (fx- fx1 #\optional fx2)
+ (assert-fixnum fx1)
+ (if fx2
+ (begin
+ (assert-fixnum fx2)
+ (let ((r (- fx1 fx2)))
+ (or (inline-fixnum? r) (raise (make-assertion-violation)))
+ r))
+ (let ((r (- fx1)))
+ (or (inline-fixnum? r) (raise (make-assertion-violation)))
+ r)))
+
+ (define (fxdiv fx1 fx2)
+ (assert-fixnum fx1 fx2)
+ (div fx1 fx2))
+
+ (define (fxmod fx1 fx2)
+ (assert-fixnum fx1 fx2)
+ (mod fx1 fx2))
+
+ (define (fxdiv-and-mod fx1 fx2)
+ (assert-fixnum fx1 fx2)
+ (div-and-mod fx1 fx2))
+
+ (define (fxdiv0 fx1 fx2)
+ (assert-fixnum fx1 fx2)
+ (div0 fx1 fx2))
+
+ (define (fxmod0 fx1 fx2)
+ (assert-fixnum fx1 fx2)
+ (mod0 fx1 fx2))
+
+ (define (fxdiv0-and-mod0 fx1 fx2)
+ (assert-fixnum fx1 fx2)
+ (div0-and-mod0 fx1 fx2))
+
+ (define (fx+/carry fx1 fx2 fx3)
+ (assert-fixnum fx1 fx2 fx3)
+ (let* ((s (+ fx1 fx2 fx3))
+ (s0 (mod0 s (expt 2 (fixnum-width))))
+ (s1 (div0 s (expt 2 (fixnum-width)))))
+ (values s0 s1)))
+
+ (define (fx-/carry fx1 fx2 fx3)
+ (assert-fixnum fx1 fx2 fx3)
+ (let* ((d (- fx1 fx2 fx3))
+ (d0 (mod0 d (expt 2 (fixnum-width))))
+ (d1 (div0 d (expt 2 (fixnum-width)))))
+ (values d0 d1)))
+
+ (define (fx*/carry fx1 fx2 fx3)
+ (assert-fixnum fx1 fx2 fx3)
+ (let* ((s (+ (* fx1 fx2) fx3))
+ (s0 (mod0 s (expt 2 (fixnum-width))))
+ (s1 (div0 s (expt 2 (fixnum-width)))))
+ (values s0 s1)))
+
+ (define (fxnot fx) (assert-fixnum fx) (lognot fx))
+ (define-fxop* fxand logand)
+ (define-fxop* fxior logior)
+ (define-fxop* fxxor logxor)
+
+ (define (fxif fx1 fx2 fx3)
+ (assert-fixnum fx1 fx2 fx3)
+ (bitwise-if fx1 fx2 fx3))
+
+ (define (fxbit-count fx)
+ (assert-fixnum fx)
+ (if (negative? fx)
+ (bitwise-not (logcount fx))
+ (logcount fx)))
+
+ (define (fxlength fx) (assert-fixnum fx) (bitwise-length fx))
+ (define (fxfirst-bit-set fx) (assert-fixnum fx) (bitwise-first-bit-set fx))
+ (define (fxbit-set? fx1 fx2) (assert-fixnum fx1 fx2) (logbit? fx2 fx1))
+
+ (define (fxcopy-bit fx1 fx2 fx3)
+ (assert-fixnum fx1 fx2 fx3)
+ (bitwise-copy-bit fx1 fx2 fx3))
+
+ (define (fxbit-field fx1 fx2 fx3)
+ (assert-fixnum fx1 fx2 fx3)
+ (bitwise-bit-field fx1 fx2 fx3))
+
+ (define (fxcopy-bit-field fx1 fx2 fx3 fx4)
+ (assert-fixnum fx1 fx2 fx3 fx4)
+ (bitwise-copy-bit-field fx1 fx2 fx3 fx4))
+
+ (define (fxarithmetic-shift fx1 fx2) (assert-fixnum fx1 fx2) (ash fx1 fx2))
+ (define fxarithmetic-shift-left fxarithmetic-shift)
+
+ (define (fxarithmetic-shift-right fx1 fx2)
+ (assert-fixnum fx1 fx2) (ash fx1 (- fx2)))
+
+ (define (fxrotate-bit-field fx1 fx2 fx3 fx4)
+ (assert-fixnum fx1 fx2 fx3 fx4)
+ (bitwise-rotate-bit-field fx1 fx2 fx3 fx4))
+
+ (define (fxreverse-bit-field fx1 fx2 fx3)
+ (assert-fixnum fx1 fx2 fx3)
+ (bitwise-reverse-bit-field fx1 fx2 fx3))
+
+)
+;;; flonums.scm --- The R6RS flonums arithmetic library
+
+;; Copyright (C) 2010, 2011, 2013 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs arithmetic flonums (6))
+ (export flonum?
+ real->flonum
+
+ fl=? fl<? fl<=? fl>? fl>=?
+
+ flinteger? flzero? flpositive? flnegative? flodd? fleven? flfinite?
+ flinfinite? flnan?
+
+ flmax flmin
+
+ fl+ fl* fl- fl/
+
+ flabs
+
+ fldiv-and-mod
+ fldiv
+ flmod
+ fldiv0-and-mod0
+ fldiv0
+ flmod0
+
+ flnumerator
+ fldenominator
+
+ flfloor flceiling fltruncate flround
+
+ flexp fllog flsin flcos fltan flacos flasin flatan
+
+ flsqrt flexpt
+
+ &no-infinities
+ make-no-infinities-violation
+ no-infinities-violation?
+
+ &no-nans
+ make-no-nans-violation
+ no-nans-violation?
+
+ fixnum->flonum)
+ (import (ice-9 optargs)
+ (only (guile) inf?)
+ (rnrs arithmetic fixnums (6))
+ (rnrs base (6))
+ (rnrs control (6))
+ (rnrs conditions (6))
+ (rnrs exceptions (6))
+ (rnrs lists (6))
+ (rnrs r5rs (6)))
+
+ (define (flonum? obj) (and (real? obj) (inexact? obj)))
+ (define (assert-flonum . args)
+ (or (for-all flonum? args) (raise (make-assertion-violation))))
+ (define (assert-iflonum . args)
+ (or (for-all (lambda (i) (and (flonum? i) (integer? i))) args)
+ (raise (make-assertion-violation))))
+
+ (define (ensure-flonum z)
+ (cond ((real? z) z)
+ ((zero? (imag-part z)) (real-part z))
+ (else +nan.0)))
+
+ (define (real->flonum x)
+ (or (real? x) (raise (make-assertion-violation)))
+ (exact->inexact x))
+
+ (define (fl=? . args) (apply assert-flonum args) (apply = args))
+ (define (fl<? . args) (apply assert-flonum args) (apply < args))
+ (define (fl<=? . args) (apply assert-flonum args) (apply <= args))
+ (define (fl>? . args) (apply assert-flonum args) (apply > args))
+ (define (fl>=? . args) (apply assert-flonum args) (apply >= args))
+
+ (define (flinteger? fl) (assert-flonum fl) (integer? fl))
+ (define (flzero? fl) (assert-flonum fl) (zero? fl))
+ (define (flpositive? fl) (assert-flonum fl) (positive? fl))
+ (define (flnegative? fl) (assert-flonum fl) (negative? fl))
+ (define (flodd? ifl) (assert-iflonum ifl) (odd? ifl))
+ (define (fleven? ifl) (assert-iflonum ifl) (even? ifl))
+ (define (flfinite? fl) (assert-flonum fl) (not (or (inf? fl) (nan? fl))))
+ (define (flinfinite? fl) (assert-flonum fl) (inf? fl))
+ (define (flnan? fl) (assert-flonum fl) (nan? fl))
+
+ (define (flmax fl1 . args)
+ (let ((flargs (cons fl1 args)))
+ (apply assert-flonum flargs)
+ (apply max flargs)))
+
+ (define (flmin fl1 . args)
+ (let ((flargs (cons fl1 args)))
+ (apply assert-flonum flargs)
+ (apply min flargs)))
+
+ (define (fl+ . args)
+ (apply assert-flonum args)
+ (if (null? args) 0.0 (apply + args)))
+
+ (define (fl* . args)
+ (apply assert-flonum args)
+ (if (null? args) 1.0 (apply * args)))
+
+ (define (fl- fl1 . args)
+ (let ((flargs (cons fl1 args)))
+ (apply assert-flonum flargs)
+ (apply - flargs)))
+
+ (define (fl/ fl1 . args)
+ (let ((flargs (cons fl1 args)))
+ (apply assert-flonum flargs)
+ (apply / flargs)))
+
+ (define (flabs fl) (assert-flonum fl) (abs fl))
+
+ (define (fldiv-and-mod fl1 fl2)
+ (assert-iflonum fl1 fl2)
+ (div-and-mod fl1 fl2))
+
+ (define (fldiv fl1 fl2)
+ (assert-iflonum fl1 fl2)
+ (div fl1 fl2))
+
+ (define (flmod fl1 fl2)
+ (assert-iflonum fl1 fl2)
+ (mod fl1 fl2))
+
+ (define (fldiv0-and-mod0 fl1 fl2)
+ (assert-iflonum fl1 fl2)
+ (div0-and-mod0 fl1 fl2))
+
+ (define (fldiv0 fl1 fl2)
+ (assert-iflonum fl1 fl2)
+ (div0 fl1 fl2))
+
+ (define (flmod0 fl1 fl2)
+ (assert-iflonum fl1 fl2)
+ (mod0 fl1 fl2))
+
+ (define (flnumerator fl) (assert-flonum fl) (numerator fl))
+ (define (fldenominator fl) (assert-flonum fl) (denominator fl))
+
+ (define (flfloor fl) (assert-flonum fl) (floor fl))
+ (define (flceiling fl) (assert-flonum fl) (ceiling fl))
+ (define (fltruncate fl) (assert-flonum fl) (truncate fl))
+ (define (flround fl) (assert-flonum fl) (round fl))
+
+ (define (flexp fl) (assert-flonum fl) (exp fl))
+ (define fllog
+ (case-lambda
+ ((fl)
+ (assert-flonum fl)
+ ;; add 0.0 to fl, to change -0.0 to 0.0,
+ ;; so that (fllog -0.0) will be -inf.0, not -inf.0+pi*i.
+ (ensure-flonum (log (+ fl 0.0))))
+ ((fl fl2)
+ (assert-flonum fl fl2)
+ (ensure-flonum (/ (log (+ fl 0.0))
+ (log (+ fl2 0.0)))))))
+
+ (define (flsin fl) (assert-flonum fl) (sin fl))
+ (define (flcos fl) (assert-flonum fl) (cos fl))
+ (define (fltan fl) (assert-flonum fl) (tan fl))
+ (define (flasin fl) (assert-flonum fl) (ensure-flonum (asin fl)))
+ (define (flacos fl) (assert-flonum fl) (ensure-flonum (acos fl)))
+ (define flatan
+ (case-lambda
+ ((fl) (assert-flonum fl) (atan fl))
+ ((fl fl2) (assert-flonum fl fl2) (atan fl fl2))))
+
+ (define (flsqrt fl) (assert-flonum fl) (ensure-flonum (sqrt fl)))
+ (define (flexpt fl1 fl2) (assert-flonum fl1 fl2) (ensure-flonum (expt fl1 fl2)))
+
+ (define-condition-type &no-infinities
+ &implementation-restriction
+ make-no-infinities-violation
+ no-infinities-violation?)
+
+ (define-condition-type &no-nans
+ &implementation-restriction
+ make-no-nans-violation
+ no-nans-violation?)
+
+ (define (fixnum->flonum fx)
+ (or (fixnum? fx) (raise (make-assertion-violation)))
+ (exact->inexact fx))
+)
+;;; base.scm --- The R6RS base library
+
+;; Copyright (C) 2010, 2011 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs base (6))
+ (export boolean? symbol? char? vector? null? pair? number? string? procedure?
+
+ define define-syntax syntax-rules lambda let let* let-values
+ let*-values letrec letrec* begin
+
+ quote lambda if set! cond case
+
+ or and not
+
+ eqv? equal? eq?
+
+ + - * / max min abs numerator denominator gcd lcm floor ceiling
+ truncate round rationalize real-part imag-part make-rectangular angle
+ div mod div-and-mod div0 mod0 div0-and-mod0
+
+ expt exact-integer-sqrt sqrt exp log sin cos tan asin acos atan
+ make-polar magnitude angle
+
+ complex? real? rational? integer? exact? inexact? real-valued?
+ rational-valued? integer-valued? zero? positive? negative? odd? even?
+ nan? finite? infinite?
+
+ exact inexact = < > <= >=
+
+ number->string string->number
+
+ boolean=?
+
+ cons car cdr caar cadr cdar cddr caaar caadr cadar cdaar caddr cdadr
+ cddar cdddr caaaar caaadr caadar cadaar cdaaar cddaar cdadar cdaadr
+ cadadr caaddr caddar cadddr cdaddr cddadr cdddar cddddr
+
+ list? list length append reverse list-tail list-ref map for-each
+
+ symbol->string string->symbol symbol=?
+
+ char->integer integer->char char=? char<? char>? char<=? char>=?
+
+ make-string string string-length string-ref string=? string<? string>?
+ string<=? string>=? substring string-append string->list list->string
+ string-for-each string-copy
+
+ vector? make-vector vector vector-length vector-ref vector-set!
+ vector->list list->vector vector-fill! vector-map vector-for-each
+
+ error assertion-violation assert
+
+ call-with-current-continuation call/cc call-with-values dynamic-wind
+ values apply
+
+ quasiquote unquote unquote-splicing
+
+ let-syntax letrec-syntax
+
+ syntax-rules identifier-syntax)
+ (import (rename (except (guile) error raise map string-for-each)
+ (log log-internal)
+ (euclidean-quotient div)
+ (euclidean-remainder mod)
+ (euclidean/ div-and-mod)
+ (centered-quotient div0)
+ (centered-remainder mod0)
+ (centered/ div0-and-mod0)
+ (inf? infinite?)
+ (exact->inexact inexact)
+ (inexact->exact exact))
+ (srfi srfi-11))
+
+ (define string-for-each
+ (case-lambda
+ ((proc string)
+ (let ((end (string-length string)))
+ (let loop ((i 0))
+ (unless (= i end)
+ (proc (string-ref string i))
+ (loop (+ i 1))))))
+ ((proc string1 string2)
+ (let ((end1 (string-length string1))
+ (end2 (string-length string2)))
+ (unless (= end1 end2)
+ (assertion-violation 'string-for-each
+ "string arguments must all have the same length"
+ string1 string2))
+ (let loop ((i 0))
+ (unless (= i end1)
+ (proc (string-ref string1 i)
+ (string-ref string2 i))
+ (loop (+ i 1))))))
+ ((proc string . strings)
+ (let ((end (string-length string))
+ (ends (map string-length strings)))
+ (for-each (lambda (x)
+ (unless (= end x)
+ (apply assertion-violation
+ 'string-for-each
+ "string arguments must all have the same length"
+ string strings)))
+ ends)
+ (let loop ((i 0))
+ (unless (= i end)
+ (apply proc
+ (string-ref string i)
+ (map (lambda (s) (string-ref s i)) strings))
+ (loop (+ i 1))))))))
+
+ (define map
+ (case-lambda
+ ((f l)
+ (let map1 ((hare l) (tortoise l) (move? #f) (out '()))
+ (if (pair? hare)
+ (if move?
+ (if (eq? tortoise hare)
+ (scm-error 'wrong-type-arg "map" "Circular list: ~S"
+ (list l) #f)
+ (map1 (cdr hare) (cdr tortoise) #f
+ (cons (f (car hare)) out)))
+ (map1 (cdr hare) tortoise #t
+ (cons (f (car hare)) out)))
+ (if (null? hare)
+ (reverse out)
+ (scm-error 'wrong-type-arg "map" "Not a list: ~S"
+ (list l) #f)))))
+
+ ((f l1 l2)
+ (let map2 ((h1 l1) (h2 l2) (t1 l1) (t2 l2) (move? #f) (out '()))
+ (cond
+ ((pair? h1)
+ (cond
+ ((not (pair? h2))
+ (scm-error 'wrong-type-arg "map"
+ (if (list? h2)
+ "List of wrong length: ~S"
+ "Not a list: ~S")
+ (list l2) #f))
+ ((not move?)
+ (map2 (cdr h1) (cdr h2) t1 t2 #t
+ (cons (f (car h1) (car h2)) out)))
+ ((eq? t1 h1)
+ (scm-error 'wrong-type-arg "map" "Circular list: ~S"
+ (list l1) #f))
+ ((eq? t2 h2)
+ (scm-error 'wrong-type-arg "map" "Circular list: ~S"
+ (list l2) #f))
+ (else
+ (map2 (cdr h1) (cdr h2) (cdr t1) (cdr t2) #f
+ (cons (f (car h1) (car h2)) out)))))
+
+ ((and (null? h1) (null? h2))
+ (reverse out))
+
+ ((null? h1)
+ (scm-error 'wrong-type-arg "map"
+ (if (list? h2)
+ "List of wrong length: ~S"
+ "Not a list: ~S")
+ (list l2) #f))
+ (else
+ (scm-error 'wrong-type-arg "map"
+ "Not a list: ~S"
+ (list l1) #f)))))
+
+ ((f l1 . rest)
+ (let ((len (length l1)))
+ (let mapn ((rest rest))
+ (or (null? rest)
+ (if (= (length (car rest)) len)
+ (mapn (cdr rest))
+ (scm-error 'wrong-type-arg "map" "List of wrong length: ~S"
+ (list (car rest)) #f)))))
+ (let mapn ((l1 l1) (rest rest) (out '()))
+ (if (null? l1)
+ (reverse out)
+ (mapn (cdr l1) (map cdr rest)
+ (cons (apply f (car l1) (map car rest)) out)))))))
+
+ (define log
+ (case-lambda
+ ((n)
+ (log-internal n))
+ ((n base)
+ (/ (log n)
+ (log base)))))
+
+ (define (boolean=? . bools)
+ (define (boolean=?-internal lst last)
+ (or (null? lst)
+ (let ((bool (car lst)))
+ (and (eqv? bool last) (boolean=?-internal (cdr lst) bool)))))
+ (or (null? bools)
+ (let ((bool (car bools)))
+ (and (boolean? bool) (boolean=?-internal (cdr bools) bool)))))
+
+ (define (symbol=? . syms)
+ (define (symbol=?-internal lst last)
+ (or (null? lst)
+ (let ((sym (car lst)))
+ (and (eq? sym last) (symbol=?-internal (cdr lst) sym)))))
+ (or (null? syms)
+ (let ((sym (car syms)))
+ (and (symbol? sym) (symbol=?-internal (cdr syms) sym)))))
+
+ (define (real-valued? x)
+ (and (complex? x)
+ (zero? (imag-part x))))
+
+ (define (rational-valued? x)
+ (and (real-valued? x)
+ (rational? (real-part x))))
+
+ (define (integer-valued? x)
+ (and (rational-valued? x)
+ (= x (floor (real-part x)))))
+
+ (define (vector-for-each proc . vecs)
+ (apply for-each (cons proc (map vector->list vecs))))
+ (define (vector-map proc . vecs)
+ (list->vector (apply map (cons proc (map vector->list vecs)))))
+
+ (define-syntax define-proxy
+ (syntax-rules (@)
+ ;; Define BINDING to point to (@ MODULE ORIGINAL). This hack is to
+ ;; make sure MODULE is loaded lazily, at run-time, when BINDING is
+ ;; encountered, rather than being loaded while compiling and
+ ;; loading (rnrs base).
+ ;; This avoids circular dependencies among modules and makes
+ ;; (rnrs base) more lightweight.
+ ((_ binding (@ module original))
+ (define-syntax binding
+ (identifier-syntax
+ (module-ref (resolve-interface 'module) 'original))))))
+
+ (define-proxy raise
+ (@ (rnrs exceptions) raise))
+
+ (define-proxy condition
+ (@ (rnrs conditions) condition))
+ (define-proxy make-error
+ (@ (rnrs conditions) make-error))
+ (define-proxy make-assertion-violation
+ (@ (rnrs conditions) make-assertion-violation))
+ (define-proxy make-who-condition
+ (@ (rnrs conditions) make-who-condition))
+ (define-proxy make-message-condition
+ (@ (rnrs conditions) make-message-condition))
+ (define-proxy make-irritants-condition
+ (@ (rnrs conditions) make-irritants-condition))
+
+ (define (error who message . irritants)
+ (raise (apply condition
+ (append (list (make-error))
+ (if who (list (make-who-condition who)) '())
+ (list (make-message-condition message)
+ (make-irritants-condition irritants))))))
+
+ (define (assertion-violation who message . irritants)
+ (raise (apply condition
+ (append (list (make-assertion-violation))
+ (if who (list (make-who-condition who)) '())
+ (list (make-message-condition message)
+ (make-irritants-condition irritants))))))
+
+ (define-syntax assert
+ (syntax-rules ()
+ ((_ expression)
+ (or expression
+ (raise (condition
+ (make-assertion-violation)
+ (make-message-condition
+ (format #f "assertion failed: ~s" 'expression))))))))
+
+)
+;;;; bytevectors.scm --- R6RS bytevector API -*- coding: utf-8 -*-
+
+;;;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Ludovic Courtès <ludo@gnu.org>
+
+;;; Commentary:
+;;;
+;;; A "bytevector" is a raw bit string. This module provides procedures to
+;;; manipulate bytevectors and interpret their contents in a number of ways:
+;;; bytevector contents can be accessed as signed or unsigned integer of
+;;; various sizes and endianness, as IEEE-754 floating point numbers, or as
+;;; strings. It is a useful tool to decode binary data.
+;;;
+;;; Code:
+
+(define-module (rnrs bytevectors)
+ #\version (6)
+ #\export-syntax (endianness)
+ #\export (native-endianness bytevector?
+ make-bytevector bytevector-length bytevector=? bytevector-fill!
+ bytevector-copy! bytevector-copy
+ uniform-array->bytevector
+ bytevector-u8-ref bytevector-s8-ref
+ bytevector-u8-set! bytevector-s8-set! bytevector->u8-list
+ u8-list->bytevector
+ bytevector-uint-ref bytevector-uint-set!
+ bytevector-sint-ref bytevector-sint-set!
+ bytevector->sint-list bytevector->uint-list
+ uint-list->bytevector sint-list->bytevector
+
+ bytevector-u16-ref bytevector-s16-ref
+ bytevector-u16-set! bytevector-s16-set!
+ bytevector-u16-native-ref bytevector-s16-native-ref
+ bytevector-u16-native-set! bytevector-s16-native-set!
+
+ bytevector-u32-ref bytevector-s32-ref
+ bytevector-u32-set! bytevector-s32-set!
+ bytevector-u32-native-ref bytevector-s32-native-ref
+ bytevector-u32-native-set! bytevector-s32-native-set!
+
+ bytevector-u64-ref bytevector-s64-ref
+ bytevector-u64-set! bytevector-s64-set!
+ bytevector-u64-native-ref bytevector-s64-native-ref
+ bytevector-u64-native-set! bytevector-s64-native-set!
+
+ bytevector-ieee-single-ref
+ bytevector-ieee-single-set!
+ bytevector-ieee-single-native-ref
+ bytevector-ieee-single-native-set!
+
+ bytevector-ieee-double-ref
+ bytevector-ieee-double-set!
+ bytevector-ieee-double-native-ref
+ bytevector-ieee-double-native-set!
+
+ string->utf8 string->utf16 string->utf32
+ utf8->string utf16->string utf32->string))
+
+
+(load-extension (string-append "libguile-" (effective-version))
+ "scm_init_bytevectors")
+
+(define-macro (endianness sym)
+ (if (memq sym '(big little))
+ `(quote ,sym)
+ (error "unsupported endianness" sym)))
+
+;;; bytevector.scm ends here
+;;; conditions.scm --- The R6RS conditions library
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs conditions (6))
+ (export &condition
+ condition
+ simple-conditions
+ condition?
+ condition-predicate
+ condition-accessor
+ define-condition-type
+
+ &message
+ make-message-condition
+ message-condition?
+ condition-message
+
+ &warning
+ make-warning
+ warning?
+
+ &serious
+ make-serious-condition
+ serious-condition?
+
+ &error
+ make-error
+ error?
+
+ &violation
+ make-violation
+ violation?
+
+ &assertion
+ make-assertion-violation
+ assertion-violation?
+
+ &irritants
+ make-irritants-condition
+ irritants-condition?
+ condition-irritants
+
+ &who
+ make-who-condition
+ who-condition?
+ condition-who
+
+ &non-continuable
+ make-non-continuable-violation
+ non-continuable-violation?
+
+ &implementation-restriction
+ make-implementation-restriction-violation
+ implementation-restriction-violation?
+
+ &lexical
+ make-lexical-violation
+ lexical-violation?
+
+ &syntax
+ make-syntax-violation
+ syntax-violation?
+ syntax-violation-form
+ syntax-violation-subform
+
+ &undefined
+ make-undefined-violation
+ undefined-violation?)
+ (import (only (guile) and=> @@)
+ (rnrs base (6))
+ (rnrs lists (6))
+ (rnrs records procedural (6)))
+
+ (define &compound-condition (make-record-type-descriptor
+ '&compound-condition #f #f #f #f
+ '#((immutable components))))
+ (define compound-condition? (record-predicate &compound-condition))
+
+ (define make-compound-condition
+ (record-constructor (make-record-constructor-descriptor
+ &compound-condition #f #f)))
+ (define simple-conditions
+ (let ((compound-ref (record-accessor &compound-condition 0)))
+ (lambda (condition)
+ (cond ((compound-condition? condition)
+ (compound-ref condition))
+ ((condition-internal? condition)
+ (list condition))
+ (else
+ (assertion-violation 'simple-conditions
+ "not a condition"
+ condition))))))
+
+ (define (condition? obj)
+ (or (compound-condition? obj) (condition-internal? obj)))
+
+ (define condition
+ (lambda conditions
+ (define (flatten cond)
+ (if (compound-condition? cond) (simple-conditions cond) (list cond)))
+ (or (for-all condition? conditions)
+ (assertion-violation 'condition "non-condition argument" conditions))
+ (if (or (null? conditions) (> (length conditions) 1))
+ (make-compound-condition (apply append (map flatten conditions)))
+ (car conditions))))
+
+ (define-syntax define-condition-type
+ (syntax-rules ()
+ ((_ condition-type supertype constructor predicate
+ (field accessor) ...)
+ (letrec-syntax
+ ((transform-fields
+ (syntax-rules ()
+ ((_ (f a) . rest)
+ (cons '(immutable f a) (transform-fields . rest)))
+ ((_) '())))
+
+ (generate-accessors
+ (syntax-rules ()
+ ((_ counter (f a) . rest)
+ (begin (define a
+ (condition-accessor
+ condition-type
+ (record-accessor condition-type counter)))
+ (generate-accessors (+ counter 1) . rest)))
+ ((_ counter) (begin)))))
+ (begin
+ (define condition-type
+ (make-record-type-descriptor
+ 'condition-type supertype #f #f #f
+ (list->vector (transform-fields (field accessor) ...))))
+ (define constructor
+ (record-constructor
+ (make-record-constructor-descriptor condition-type #f #f)))
+ (define predicate (condition-predicate condition-type))
+ (generate-accessors 0 (field accessor) ...))))))
+
+ (define &condition (@@ (rnrs records procedural) &condition))
+ (define &condition-constructor-descriptor
+ (make-record-constructor-descriptor &condition #f #f))
+ (define condition-internal? (record-predicate &condition))
+
+ (define (condition-predicate rtd)
+ (let ((rtd-predicate (record-predicate rtd)))
+ (lambda (obj)
+ (cond ((compound-condition? obj)
+ (exists rtd-predicate (simple-conditions obj)))
+ ((condition-internal? obj) (rtd-predicate obj))
+ (else #f)))))
+
+ (define (condition-accessor rtd proc)
+ (let ((rtd-predicate (record-predicate rtd)))
+ (lambda (obj)
+ (cond ((rtd-predicate obj) (proc obj))
+ ((compound-condition? obj)
+ (and=> (find rtd-predicate (simple-conditions obj)) proc))
+ (else #f)))))
+
+ (define-condition-type &message &condition
+ make-message-condition message-condition?
+ (message condition-message))
+
+ (define-condition-type &warning &condition make-warning warning?)
+
+ (define &serious (@@ (rnrs records procedural) &serious))
+ (define make-serious-condition
+ (@@ (rnrs records procedural) make-serious-condition))
+ (define serious-condition? (condition-predicate &serious))
+
+ (define-condition-type &error &serious make-error error?)
+
+ (define &violation (@@ (rnrs records procedural) &violation))
+ (define make-violation (@@ (rnrs records procedural) make-violation))
+ (define violation? (condition-predicate &violation))
+
+ (define &assertion (@@ (rnrs records procedural) &assertion))
+ (define make-assertion-violation
+ (@@ (rnrs records procedural) make-assertion-violation))
+ (define assertion-violation? (condition-predicate &assertion))
+
+ (define-condition-type &irritants &condition
+ make-irritants-condition irritants-condition?
+ (irritants condition-irritants))
+
+ (define-condition-type &who &condition
+ make-who-condition who-condition?
+ (who condition-who))
+
+ (define-condition-type &non-continuable &violation
+ make-non-continuable-violation
+ non-continuable-violation?)
+
+ (define-condition-type &implementation-restriction
+ &violation
+ make-implementation-restriction-violation
+ implementation-restriction-violation?)
+
+ (define-condition-type &lexical &violation
+ make-lexical-violation lexical-violation?)
+
+ (define-condition-type &syntax &violation
+ make-syntax-violation syntax-violation?
+ (form syntax-violation-form)
+ (subform syntax-violation-subform))
+
+ (define-condition-type &undefined &violation
+ make-undefined-violation undefined-violation?)
+
+)
+;;; control.scm --- The R6RS control structures library
+
+;; Copyright (C) 2010, 2012 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs control (6))
+ (export when unless do case-lambda)
+ (import (only (guile) when unless do case-lambda)))
+;;; enums.scm --- The R6RS enumerations library
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs enums (6))
+ (export make-enumeration enum-set-universe enum-set-indexer
+ enum-set-constructor enum-set->list enum-set-member? enum-set-subset?
+ enum-set=? enum-set-union enum-set-intersection enum-set-difference
+ enum-set-complement enum-set-projection define-enumeration)
+ (import (only (guile) and=>)
+ (rnrs base (6))
+ (rnrs conditions (6))
+ (rnrs exceptions (6))
+ (rnrs records procedural (6))
+ (rnrs syntax-case (6))
+ (srfi 1))
+
+ (define enum-set-rtd (make-record-type-descriptor
+ 'enum-set #f #f #f #f '#((mutable universe)
+ (immutable set))))
+
+ (define make-enum-set
+ (record-constructor
+ (make-record-constructor-descriptor enum-set-rtd #f #f)))
+
+ (define enum-set-universe-internal (record-accessor enum-set-rtd 0))
+ (define enum-set-universe-set! (record-mutator enum-set-rtd 0))
+
+ (define enum-set-set (record-accessor enum-set-rtd 1))
+
+ (define (make-enumeration symbol-list)
+ (let ((es (make-enum-set #f symbol-list)))
+ (enum-set-universe-set! es es)))
+
+ (define (enum-set-universe enum-set)
+ (or (enum-set-universe-internal enum-set)
+ enum-set))
+
+ (define (enum-set-indexer enum-set)
+ (let* ((symbols (enum-set->list (enum-set-universe enum-set)))
+ (cardinality (length symbols)))
+ (lambda (x)
+ (and=> (memq x symbols)
+ (lambda (probe) (- cardinality (length probe)))))))
+
+ (define (enum-set-constructor enum-set)
+ (lambda (symbol-list)
+ (make-enum-set (enum-set-universe enum-set)
+ (list-copy symbol-list))))
+
+ (define (enum-set->list enum-set)
+ (lset-intersection eq?
+ (enum-set-set (enum-set-universe enum-set))
+ (enum-set-set enum-set)))
+
+ (define (enum-set-member? symbol enum-set)
+ (and (memq symbol (enum-set-set enum-set)) #t))
+
+ (define (enum-set-subset? enum-set-1 enum-set-2)
+ (and (lset<= eq?
+ (enum-set-set (enum-set-universe enum-set-1))
+ (enum-set-set (enum-set-universe enum-set-2)))
+ (lset<= eq? (enum-set-set enum-set-1) (enum-set-set enum-set-2))))
+
+ (define (enum-set=? enum-set-1 enum-set-2)
+ (and (enum-set-subset? enum-set-1 enum-set-2)
+ (enum-set-subset? enum-set-2 enum-set-1)))
+
+ (define (enum-set-union enum-set-1 enum-set-2)
+ (if (equal? (enum-set-universe enum-set-1)
+ (enum-set-universe enum-set-2))
+ (make-enum-set (enum-set-universe enum-set-1)
+ (lset-union eq?
+ (enum-set-set enum-set-1)
+ (enum-set-set enum-set-2)))
+ (raise (make-assertion-violation))))
+
+ (define (enum-set-intersection enum-set-1 enum-set-2)
+ (if (equal? (enum-set-universe enum-set-1)
+ (enum-set-universe enum-set-2))
+ (make-enum-set (enum-set-universe enum-set-1)
+ (lset-intersection eq?
+ (enum-set-set enum-set-1)
+ (enum-set-set enum-set-2)))
+ (raise (make-assertion-violation))))
+
+ (define (enum-set-difference enum-set-1 enum-set-2)
+ (if (equal? (enum-set-universe enum-set-1)
+ (enum-set-universe enum-set-2))
+ (make-enum-set (enum-set-universe enum-set-1)
+ (lset-difference eq?
+ (enum-set-set enum-set-1)
+ (enum-set-set enum-set-2)))
+ (raise (make-assertion-violation))))
+
+ (define (enum-set-complement enum-set)
+ (let ((universe (enum-set-universe enum-set)))
+ (make-enum-set universe
+ (lset-difference
+ eq? (enum-set->list universe) (enum-set-set enum-set)))))
+
+ (define (enum-set-projection enum-set-1 enum-set-2)
+ (make-enum-set (enum-set-universe enum-set-2)
+ (lset-intersection eq?
+ (enum-set-set enum-set-1)
+ (enum-set->list
+ (enum-set-universe enum-set-2)))))
+
+ (define-syntax define-enumeration
+ (syntax-rules ()
+ ((_ type-name (symbol ...) constructor-syntax)
+ (begin
+ (define-syntax type-name
+ (lambda (s)
+ (syntax-case s ()
+ ((type-name sym)
+ (if (memq (syntax->datum #'sym) '(symbol ...))
+ #'(quote sym)
+ (syntax-violation (symbol->string 'type-name)
+ "not a member of the set"
+ #f))))))
+ (define-syntax constructor-syntax
+ (lambda (s)
+ (syntax-case s ()
+ ((_ sym (... ...))
+ (let* ((universe '(symbol ...))
+ (syms (syntax->datum #'(sym (... ...))))
+ (quoted-universe
+ (datum->syntax s (list 'quote universe)))
+ (quoted-syms (datum->syntax s (list 'quote syms))))
+ (or (every (lambda (x) (memq x universe)) syms)
+ (syntax-violation (symbol->string 'constructor-syntax)
+ "not a subset of the universe"
+ #f))
+ #`((enum-set-constructor (make-enumeration #,quoted-universe))
+ #,quoted-syms))))))))))
+)
+;;; eval.scm --- The R6RS `eval' library
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs eval (6))
+ (export eval environment)
+ (import (only (guile) eval
+ make-module
+ module-uses
+ beautify-user-module!
+ set-module-uses!)
+ (rnrs base (6))
+ (rnrs io simple (6))
+ (rnrs lists (6)))
+
+ (define (environment . import-specs)
+ (let ((module (make-module))
+ (needs-purify? (not (member '(guile) import-specs))))
+ (beautify-user-module! module)
+ (for-each (lambda (import-spec) (eval (list 'import import-spec) module))
+ import-specs)
+ (if needs-purify? (set-module-uses! module (cdr (module-uses module))))
+ module))
+)
+;;; exceptions.scm --- The R6RS exceptions library
+
+;; Copyright (C) 2010, 2011, 2013 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs exceptions (6))
+ (export guard with-exception-handler raise raise-continuable)
+ (import (rnrs base (6))
+ (rnrs control (6))
+ (rnrs conditions (6))
+ (rnrs records procedural (6))
+ (rnrs records inspection (6))
+ (only (guile)
+ format
+ newline
+ display
+ filter
+ acons
+ assv-ref
+ throw
+ set-exception-printer!
+ with-throw-handler
+ *unspecified*
+ @@))
+
+ ;; When a native guile exception is caught by an R6RS exception
+ ;; handler, we convert it to an R6RS compound condition that includes
+ ;; not only the standard condition objects expected by R6RS code, but
+ ;; also a special &guile condition that preserves the original KEY and
+ ;; ARGS passed to the native Guile catch handler.
+
+ (define-condition-type &guile &condition
+ make-guile-condition guile-condition?
+ (key guile-condition-key)
+ (args guile-condition-args))
+
+ (define (default-guile-condition-converter key args)
+ (condition (make-serious-condition)
+ (guile-common-conditions key args)))
+
+ (define (guile-common-conditions key args)
+ (apply (case-lambda
+ ((subr msg margs . _)
+ (condition (make-who-condition subr)
+ (make-message-condition msg)
+ (make-irritants-condition margs)))
+ (_ (make-irritants-condition args)))
+ args))
+
+ (define (convert-guile-condition key args)
+ (let ((converter (assv-ref guile-condition-converters key)))
+ (condition (or (and converter (converter key args))
+ (default-guile-condition-converter key args))
+ ;; Preserve the original KEY and ARGS in the R6RS
+ ;; condition object.
+ (make-guile-condition key args))))
+
+ ;; If an R6RS exception handler chooses not to handle a given
+ ;; condition, it will re-raise the condition to pass it on to the next
+ ;; handler. If the condition was converted from a native Guile
+ ;; exception, we must re-raise using the native Guile facilities and
+ ;; the original exception KEY and ARGS. We arrange for this in
+ ;; 'raise' so that native Guile exception handlers will continue to
+ ;; work when mixed with R6RS code.
+
+ (define (raise obj)
+ (if (guile-condition? obj)
+ (apply throw (guile-condition-key obj) (guile-condition-args obj))
+ ((@@ (rnrs records procedural) r6rs-raise) obj)))
+ (define raise-continuable
+ (@@ (rnrs records procedural) r6rs-raise-continuable))
+
+ (define raise-object-wrapper?
+ (@@ (rnrs records procedural) raise-object-wrapper?))
+ (define raise-object-wrapper-obj
+ (@@ (rnrs records procedural) raise-object-wrapper-obj))
+ (define raise-object-wrapper-continuation
+ (@@ (rnrs records procedural) raise-object-wrapper-continuation))
+
+ (define (with-exception-handler handler thunk)
+ (with-throw-handler #t
+ thunk
+ (lambda (key . args)
+ (cond ((not (eq? key 'r6rs:exception))
+ (let ((obj (convert-guile-condition key args)))
+ (handler obj)
+ (raise (make-non-continuable-violation))))
+ ((and (not (null? args))
+ (raise-object-wrapper? (car args)))
+ (let* ((cargs (car args))
+ (obj (raise-object-wrapper-obj cargs))
+ (continuation (raise-object-wrapper-continuation cargs))
+ (handler-return (handler obj)))
+ (if continuation
+ (continuation handler-return)
+ (raise (make-non-continuable-violation)))))))))
+
+ (define-syntax guard0
+ (syntax-rules ()
+ ((_ (variable cond-clause ...) . body)
+ (call/cc (lambda (continuation)
+ (with-exception-handler
+ (lambda (variable)
+ (continuation (cond cond-clause ...)))
+ (lambda () . body)))))))
+
+ (define-syntax guard
+ (syntax-rules (else)
+ ((_ (variable cond-clause ... . ((else else-clause ...))) . body)
+ (guard0 (variable cond-clause ... (else else-clause ...)) . body))
+ ((_ (variable cond-clause ...) . body)
+ (guard0 (variable cond-clause ... (else (raise variable))) . body))))
+
+ ;;; Exception printing
+
+ (define (exception-printer port key args punt)
+ (cond ((and (= 1 (length args))
+ (raise-object-wrapper? (car args)))
+ (let ((obj (raise-object-wrapper-obj (car args))))
+ (cond ((condition? obj)
+ (display "ERROR: R6RS exception:\n" port)
+ (format-condition port obj))
+ (else
+ (format port "ERROR: R6RS exception: `~s'" obj)))))
+ (else
+ (punt))))
+
+ (define (format-condition port condition)
+ (let ((components (simple-conditions condition)))
+ (if (null? components)
+ (format port "Empty condition object")
+ (let loop ((i 1) (components components))
+ (cond ((pair? components)
+ (format port " ~a. " i)
+ (format-simple-condition port (car components))
+ (when (pair? (cdr components))
+ (newline port))
+ (loop (+ i 1) (cdr components))))))))
+
+ (define (format-simple-condition port condition)
+ (define (print-rtd-fields rtd field-names)
+ (let ((n-fields (vector-length field-names)))
+ (do ((i 0 (+ i 1)))
+ ((>= i n-fields))
+ (format port " ~a: ~s"
+ (vector-ref field-names i)
+ ((record-accessor rtd i) condition))
+ (unless (= i (- n-fields 1))
+ (newline port)))))
+ (let ((condition-name (record-type-name (record-rtd condition))))
+ (let loop ((rtd (record-rtd condition))
+ (rtd.fields-list '())
+ (n-fields 0))
+ (cond (rtd
+ (let ((field-names (record-type-field-names rtd)))
+ (loop (record-type-parent rtd)
+ (cons (cons rtd field-names) rtd.fields-list)
+ (+ n-fields (vector-length field-names)))))
+ (else
+ (let ((rtd.fields-list
+ (filter (lambda (rtd.fields)
+ (not (zero? (vector-length (cdr rtd.fields)))))
+ (reverse rtd.fields-list))))
+ (case n-fields
+ ((0) (format port "~a" condition-name))
+ ((1) (format port "~a: ~s"
+ condition-name
+ ((record-accessor (caar rtd.fields-list) 0)
+ condition)))
+ (else
+ (format port "~a:\n" condition-name)
+ (let loop ((lst rtd.fields-list))
+ (when (pair? lst)
+ (let ((rtd.fields (car lst)))
+ (print-rtd-fields (car rtd.fields) (cdr rtd.fields))
+ (when (pair? (cdr lst))
+ (newline port))
+ (loop (cdr lst)))))))))))))
+
+ (set-exception-printer! 'r6rs:exception exception-printer)
+
+ ;; Guile condition converters
+ ;;
+ ;; Each converter is a procedure (converter KEY ARGS) that returns
+ ;; either an R6RS condition or #f. If #f is returned,
+ ;; 'default-guile-condition-converter' will be used.
+
+ (define (guile-syntax-violation-converter key args)
+ (apply (case-lambda
+ ((who what where form subform . extra)
+ (condition (make-syntax-violation form subform)
+ (make-who-condition who)
+ (make-message-condition what)))
+ (_ #f))
+ args))
+
+ (define (guile-lexical-violation-converter key args)
+ (condition (make-lexical-violation) (guile-common-conditions key args)))
+
+ (define (guile-assertion-violation-converter key args)
+ (condition (make-assertion-violation) (guile-common-conditions key args)))
+
+ (define (guile-undefined-violation-converter key args)
+ (condition (make-undefined-violation) (guile-common-conditions key args)))
+
+ (define (guile-implementation-restriction-converter key args)
+ (condition (make-implementation-restriction-violation)
+ (guile-common-conditions key args)))
+
+ (define (guile-error-converter key args)
+ (condition (make-error) (guile-common-conditions key args)))
+
+ (define (guile-system-error-converter key args)
+ (apply (case-lambda
+ ((subr msg msg-args errno . rest)
+ ;; XXX TODO we should return a more specific error
+ ;; (usually an I/O error) as expected by R6RS programs.
+ ;; Unfortunately this often requires the 'filename' (or
+ ;; other?) which is not currently provided by the native
+ ;; Guile exceptions.
+ (condition (make-error) (guile-common-conditions key args)))
+ (_ (guile-error-converter key args)))
+ args))
+
+ ;; TODO: Arrange to have the needed information included in native
+ ;; Guile I/O exceptions, and arrange here to convert them to the
+ ;; proper conditions. Remove the earlier exception conversion
+ ;; mechanism: search for 'with-throw-handler' in the 'rnrs'
+ ;; tree, e.g. 'with-i/o-filename-conditions' and
+ ;; 'with-i/o-port-error' in (rnrs io ports).
+
+ ;; XXX TODO: How should we handle the 'misc-error', 'vm-error', and
+ ;; 'signal' native Guile exceptions?
+
+ ;; XXX TODO: Should we handle the 'quit' exception specially?
+
+ ;; An alist mapping native Guile exception keys to converters.
+ (define guile-condition-converters
+ `((read-error . ,guile-lexical-violation-converter)
+ (syntax-error . ,guile-syntax-violation-converter)
+ (unbound-variable . ,guile-undefined-violation-converter)
+ (wrong-number-of-args . ,guile-assertion-violation-converter)
+ (wrong-type-arg . ,guile-assertion-violation-converter)
+ (keyword-argument-error . ,guile-assertion-violation-converter)
+ (out-of-range . ,guile-assertion-violation-converter)
+ (regular-expression-syntax . ,guile-assertion-violation-converter)
+ (program-error . ,guile-assertion-violation-converter)
+ (goops-error . ,guile-assertion-violation-converter)
+ (null-pointer-error . ,guile-assertion-violation-converter)
+ (system-error . ,guile-system-error-converter)
+ (host-not-found . ,guile-error-converter)
+ (getaddrinfo-error . ,guile-error-converter)
+ (no-data . ,guile-error-converter)
+ (no-recovery . ,guile-error-converter)
+ (try-again . ,guile-error-converter)
+ (stack-overflow . ,guile-implementation-restriction-converter)
+ (numerical-overflow . ,guile-implementation-restriction-converter)
+ (memory-allocation-error . ,guile-implementation-restriction-converter)))
+
+ (define (set-guile-condition-converter! key proc)
+ (set! guile-condition-converters
+ (acons key proc guile-condition-converters))))
+;;; files.scm --- The R6RS file system library
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs files (6))
+ (export file-exists?
+ delete-file
+
+ &i/o make-i/o-error i/o-error?
+ &i/o-read make-i/o-read-error i/o-read-error?
+ &i/o-write make-i/o-write-error i/o-write-error?
+
+ &i/o-invalid-position
+ make-i/o-invalid-position-error
+ i/o-invalid-position-error?
+ i/o-error-position
+
+ &i/o-filename
+ make-i/o-filename-error
+ i/o-filename-error?
+ i/o-error-filename
+
+ &i/o-file-protection
+ make-i/o-file-protection-error
+ i/o-file-protection-error?
+
+ &i/o-file-is-read-only
+ make-i/o-file-is-read-only-error
+ i/o-file-is-read-only-error?
+
+ &i/o-file-already-exists
+ make-i/o-file-already-exists-error
+ i/o-file-already-exists-error?
+
+ &i/o-file-does-not-exist
+ make-i/o-file-does-not-exist-error
+ i/o-file-does-not-exist-error?
+
+ &i/o-port
+ make-i/o-port-error
+ i/o-port-error?
+ i/o-error-port)
+
+ (import (rename (only (guile) file-exists? delete-file catch @@)
+ (delete-file delete-file-internal))
+ (rnrs base (6))
+ (rnrs conditions (6))
+ (rnrs exceptions (6)))
+
+ (define (delete-file filename)
+ (catch #t
+ (lambda () (delete-file-internal filename))
+ (lambda (key . args) (raise (make-i/o-filename-error filename)))))
+
+ ;; Condition types that are used by (rnrs files), (rnrs io ports), and
+ ;; (rnrs io simple). These are defined here so as to be easily shareable by
+ ;; these three libraries.
+
+ (define-condition-type &i/o &error make-i/o-error i/o-error?)
+ (define-condition-type &i/o-read &i/o make-i/o-read-error i/o-read-error?)
+ (define-condition-type &i/o-write &i/o make-i/o-write-error i/o-write-error?)
+ (define-condition-type &i/o-invalid-position
+ &i/o make-i/o-invalid-position-error i/o-invalid-position-error?
+ (position i/o-error-position))
+ (define-condition-type &i/o-filename
+ &i/o make-i/o-filename-error i/o-filename-error?
+ (filename i/o-error-filename))
+ (define-condition-type &i/o-file-protection
+ &i/o-filename make-i/o-file-protection-error i/o-file-protection-error?)
+ (define-condition-type &i/o-file-is-read-only
+ &i/o-file-protection make-i/o-file-is-read-only-error
+ i/o-file-is-read-only-error?)
+ (define-condition-type &i/o-file-already-exists
+ &i/o-filename make-i/o-file-already-exists-error
+ i/o-file-already-exists-error?)
+ (define-condition-type &i/o-file-does-not-exist
+ &i/o-filename make-i/o-file-does-not-exist-error
+ i/o-file-does-not-exist-error?)
+ (define-condition-type &i/o-port &i/o make-i/o-port-error i/o-port-error?
+ (port i/o-error-port))
+)
+;;; hashtables.scm --- The R6RS hashtables library
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs hashtables (6))
+ (export make-eq-hashtable
+ make-eqv-hashtable
+ make-hashtable
+
+ hashtable?
+ hashtable-size
+ hashtable-ref
+ hashtable-set!
+ hashtable-delete!
+ hashtable-contains?
+ hashtable-update!
+ hashtable-copy
+ hashtable-clear!
+ hashtable-keys
+ hashtable-entries
+
+ hashtable-equivalence-function
+ hashtable-hash-function
+ hashtable-mutable?
+
+ equal-hash
+ string-hash
+ string-ci-hash
+ symbol-hash)
+ (import (rename (only (guile) string-hash-ci
+ string-hash
+ hashq
+ hashv
+ modulo
+ *unspecified*
+ @@)
+ (string-hash-ci string-ci-hash))
+ (only (ice-9 optargs) define*)
+ (rename (only (srfi 69) make-hash-table
+ hash
+ hash-by-identity
+ hash-table-size
+ hash-table-ref/default
+ hash-table-set!
+ hash-table-delete!
+ hash-table-exists?
+ hash-table-update!/default
+ hash-table-copy
+ hash-table-equivalence-function
+ hash-table-hash-function
+ hash-table-keys
+ hash-table-fold)
+ (hash equal-hash)
+ (hash-by-identity symbol-hash))
+ (rnrs base (6))
+ (rnrs records procedural (6)))
+
+ (define r6rs:hashtable
+ (make-record-type-descriptor
+ 'r6rs:hashtable #f #f #t #t
+ '#((mutable wrapped-table)
+ (immutable orig-hash-function)
+ (immutable mutable))))
+
+ (define hashtable? (record-predicate r6rs:hashtable))
+ (define make-r6rs-hashtable
+ (record-constructor (make-record-constructor-descriptor
+ r6rs:hashtable #f #f)))
+ (define r6rs:hashtable-wrapped-table (record-accessor r6rs:hashtable 0))
+ (define r6rs:hashtable-set-wrapped-table! (record-mutator r6rs:hashtable 0))
+ (define r6rs:hashtable-orig-hash-function (record-accessor r6rs:hashtable 1))
+ (define r6rs:hashtable-mutable? (record-accessor r6rs:hashtable 2))
+
+ (define hashtable-mutable? r6rs:hashtable-mutable?)
+
+ (define hash-by-value ((@@ (srfi srfi-69) caller-with-default-size) hashv))
+ (define (wrap-hash-function proc)
+ (lambda (key capacity) (modulo (proc key) capacity)))
+
+ (define* (make-eq-hashtable #\optional k)
+ (make-r6rs-hashtable
+ (if k (make-hash-table eq? hashq k) (make-hash-table eq? symbol-hash))
+ symbol-hash
+ #t))
+
+ (define* (make-eqv-hashtable #\optional k)
+ (make-r6rs-hashtable
+ (if k (make-hash-table eqv? hashv k) (make-hash-table eqv? hash-by-value))
+ hash-by-value
+ #t))
+
+ (define* (make-hashtable hash-function equiv #\optional k)
+ (let ((wrapped-hash-function (wrap-hash-function hash-function)))
+ (make-r6rs-hashtable
+ (if k
+ (make-hash-table equiv wrapped-hash-function k)
+ (make-hash-table equiv wrapped-hash-function))
+ hash-function
+ #t)))
+
+ (define (hashtable-size hashtable)
+ (hash-table-size (r6rs:hashtable-wrapped-table hashtable)))
+
+ (define (hashtable-ref hashtable key default)
+ (hash-table-ref/default
+ (r6rs:hashtable-wrapped-table hashtable) key default))
+
+ (define (hashtable-set! hashtable key obj)
+ (if (r6rs:hashtable-mutable? hashtable)
+ (hash-table-set! (r6rs:hashtable-wrapped-table hashtable) key obj))
+ *unspecified*)
+
+ (define (hashtable-delete! hashtable key)
+ (if (r6rs:hashtable-mutable? hashtable)
+ (hash-table-delete! (r6rs:hashtable-wrapped-table hashtable) key))
+ *unspecified*)
+
+ (define (hashtable-contains? hashtable key)
+ (hash-table-exists? (r6rs:hashtable-wrapped-table hashtable) key))
+
+ (define (hashtable-update! hashtable key proc default)
+ (if (r6rs:hashtable-mutable? hashtable)
+ (hash-table-update!/default
+ (r6rs:hashtable-wrapped-table hashtable) key proc default))
+ *unspecified*)
+
+ (define* (hashtable-copy hashtable #\optional mutable)
+ (make-r6rs-hashtable
+ (hash-table-copy (r6rs:hashtable-wrapped-table hashtable))
+ (r6rs:hashtable-orig-hash-function hashtable)
+ (and mutable #t)))
+
+ (define* (hashtable-clear! hashtable #\optional k)
+ (if (r6rs:hashtable-mutable? hashtable)
+ (let* ((ht (r6rs:hashtable-wrapped-table hashtable))
+ (equiv (hash-table-equivalence-function ht))
+ (hash-function (r6rs:hashtable-orig-hash-function hashtable))
+ (wrapped-hash-function (wrap-hash-function hash-function)))
+ (r6rs:hashtable-set-wrapped-table!
+ hashtable
+ (if k
+ (make-hash-table equiv wrapped-hash-function k)
+ (make-hash-table equiv wrapped-hash-function)))))
+ *unspecified*)
+
+ (define (hashtable-keys hashtable)
+ (list->vector (hash-table-keys (r6rs:hashtable-wrapped-table hashtable))))
+
+ (define (hashtable-entries hashtable)
+ (let* ((ht (r6rs:hashtable-wrapped-table hashtable))
+ (size (hash-table-size ht))
+ (keys (make-vector size))
+ (vals (make-vector size)))
+ (hash-table-fold (r6rs:hashtable-wrapped-table hashtable)
+ (lambda (k v i)
+ (vector-set! keys i k)
+ (vector-set! vals i v)
+ (+ i 1))
+ 0)
+ (values keys vals)))
+
+ (define (hashtable-equivalence-function hashtable)
+ (hash-table-equivalence-function (r6rs:hashtable-wrapped-table hashtable)))
+
+ (define (hashtable-hash-function hashtable)
+ (r6rs:hashtable-orig-hash-function hashtable)))
+;;;; ports.scm --- R6RS port API -*- coding: utf-8 -*-
+
+;;;; Copyright (C) 2009, 2010, 2011, 2013 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Ludovic Courtès <ludo@gnu.org>
+
+;;; Commentary:
+;;;
+;;; The I/O port API of the R6RS is provided by this module. In many areas
+;;; it complements or refines Guile's own historical port API. For instance,
+;;; it allows for binary I/O with bytevectors.
+;;;
+;;; Code:
+
+(library (rnrs io ports (6))
+ (export eof-object eof-object?
+
+ ;; auxiliary types
+ file-options buffer-mode buffer-mode?
+ eol-style native-eol-style error-handling-mode
+ make-transcoder transcoder-codec transcoder-eol-style
+ transcoder-error-handling-mode native-transcoder
+ latin-1-codec utf-8-codec utf-16-codec
+
+ ;; input & output ports
+ port? input-port? output-port?
+ port-eof?
+ port-transcoder binary-port? textual-port? transcoded-port
+ port-position set-port-position!
+ port-has-port-position? port-has-set-port-position!?
+ call-with-port close-port
+
+ ;; input ports
+ open-bytevector-input-port
+ open-string-input-port
+ open-file-input-port
+ make-custom-binary-input-port
+
+ ;; binary input
+ get-u8 lookahead-u8
+ get-bytevector-n get-bytevector-n!
+ get-bytevector-some get-bytevector-all
+
+ ;; output ports
+ open-bytevector-output-port
+ open-string-output-port
+ open-file-output-port
+ make-custom-binary-output-port
+ call-with-bytevector-output-port
+ call-with-string-output-port
+ make-custom-textual-output-port
+ flush-output-port
+
+ ;; input/output ports
+ open-file-input/output-port
+
+ ;; binary output
+ put-u8 put-bytevector
+
+ ;; textual input
+ get-char get-datum get-line get-string-all get-string-n get-string-n!
+ lookahead-char
+
+ ;; textual output
+ put-char put-datum put-string
+
+ ;; standard ports
+ standard-input-port standard-output-port standard-error-port
+ current-input-port current-output-port current-error-port
+
+ ;; condition types
+ &i/o i/o-error? make-i/o-error
+ &i/o-read i/o-read-error? make-i/o-read-error
+ &i/o-write i/o-write-error? make-i/o-write-error
+ &i/o-invalid-position i/o-invalid-position-error?
+ make-i/o-invalid-position-error
+ &i/o-filename i/o-filename-error? make-i/o-filename-error
+ i/o-error-filename
+ &i/o-file-protection i/o-file-protection-error?
+ make-i/o-file-protection-error
+ &i/o-file-is-read-only i/o-file-is-read-only-error?
+ make-i/o-file-is-read-only-error
+ &i/o-file-already-exists i/o-file-already-exists-error?
+ make-i/o-file-already-exists-error
+ &i/o-file-does-not-exist i/o-file-does-not-exist-error?
+ make-i/o-file-does-not-exist-error
+ &i/o-port i/o-port-error? make-i/o-port-error
+ i/o-error-port
+ &i/o-decoding-error i/o-decoding-error?
+ make-i/o-decoding-error
+ &i/o-encoding-error i/o-encoding-error?
+ make-i/o-encoding-error i/o-encoding-error-char)
+ (import (ice-9 binary-ports)
+ (only (rnrs base) assertion-violation)
+ (rnrs enums)
+ (rnrs records syntactic)
+ (rnrs exceptions)
+ (rnrs conditions)
+ (rnrs files) ;for the condition types
+ (srfi srfi-8)
+ (ice-9 rdelim)
+ (except (guile) raise display)
+ (prefix (only (guile) display)
+ guile\:))
+
+
+
+;;;
+;;; Auxiliary types
+;;;
+
+(define-enumeration file-option
+ (no-create no-fail no-truncate)
+ file-options)
+
+(define-enumeration buffer-mode
+ (none line block)
+ buffer-modes)
+
+(define (buffer-mode? symbol)
+ (enum-set-member? symbol (enum-set-universe (buffer-modes))))
+
+(define-enumeration eol-style
+ (lf cr crlf nel crnel ls none)
+ eol-styles)
+
+(define (native-eol-style)
+ (eol-style none))
+
+(define-enumeration error-handling-mode
+ (ignore raise replace)
+ error-handling-modes)
+
+(define-record-type (transcoder %make-transcoder transcoder?)
+ (fields codec eol-style error-handling-mode))
+
+(define* (make-transcoder codec
+ #\optional
+ (eol-style (native-eol-style))
+ (handling-mode (error-handling-mode replace)))
+ (%make-transcoder codec eol-style handling-mode))
+
+(define (native-transcoder)
+ (make-transcoder (or (fluid-ref %default-port-encoding)
+ (latin-1-codec))))
+
+(define (latin-1-codec)
+ "ISO-8859-1")
+
+(define (utf-8-codec)
+ "UTF-8")
+
+(define (utf-16-codec)
+ "UTF-16")
+
+
+;;;
+;;; Internal helpers
+;;;
+
+(define (with-i/o-filename-conditions filename thunk)
+ (with-throw-handler 'system-error
+ thunk
+ (lambda args
+ (let ((errno (system-error-errno args)))
+ (let ((construct-condition
+ (cond ((= errno EACCES)
+ make-i/o-file-protection-error)
+ ((= errno EEXIST)
+ make-i/o-file-already-exists-error)
+ ((= errno ENOENT)
+ make-i/o-file-does-not-exist-error)
+ ((= errno EROFS)
+ make-i/o-file-is-read-only-error)
+ (else
+ make-i/o-filename-error))))
+ (raise (construct-condition filename)))))))
+
+(define (with-i/o-port-error port make-primary-condition thunk)
+ (with-throw-handler 'system-error
+ thunk
+ (lambda args
+ (let ((errno (system-error-errno args)))
+ (if (memv errno (list EIO EFBIG ENOSPC EPIPE))
+ (raise (condition (make-primary-condition)
+ (make-i/o-port-error port)))
+ (apply throw args))))))
+
+(define-syntax with-textual-output-conditions
+ (syntax-rules ()
+ ((_ port body0 body ...)
+ (with-i/o-port-error port make-i/o-write-error
+ (lambda () (with-i/o-encoding-error body0 body ...))))))
+
+(define-syntax with-textual-input-conditions
+ (syntax-rules ()
+ ((_ port body0 body ...)
+ (with-i/o-port-error port make-i/o-read-error
+ (lambda () (with-i/o-decoding-error body0 body ...))))))
+
+
+;;;
+;;; Input and output ports.
+;;;
+
+(define (port-transcoder port)
+ "Return the transcoder object associated with @var{port}, or @code{#f}
+if the port has no transcoder."
+ (cond ((port-encoding port)
+ => (lambda (encoding)
+ (make-transcoder
+ encoding
+ (native-eol-style)
+ (case (port-conversion-strategy port)
+ ((error) 'raise)
+ ((substitute) 'replace)
+ (else
+ (assertion-violation 'port-transcoder
+ "unsupported error handling mode"))))))
+ (else
+ #f)))
+
+(define (binary-port? port)
+ "Returns @code{#t} if @var{port} does not have an associated encoding,
+@code{#f} otherwise."
+ (not (port-encoding port)))
+
+(define (textual-port? port)
+ "Always returns @code{#t}, as all ports can be used for textual I/O in
+Guile."
+ #t)
+
+(define (port-eof? port)
+ (eof-object? (if (binary-port? port)
+ (lookahead-u8 port)
+ (lookahead-char port))))
+
+(define (transcoded-port port transcoder)
+ "Return a new textual port based on @var{port}, using
+@var{transcoder} to encode and decode data written to or
+read from its underlying binary port @var{port}."
+ ;; Hackily get at %make-transcoded-port.
+ (let ((result ((@@ (ice-9 binary-ports) %make-transcoded-port) port)))
+ (set-port-encoding! result (transcoder-codec transcoder))
+ (case (transcoder-error-handling-mode transcoder)
+ ((raise)
+ (set-port-conversion-strategy! result 'error))
+ ((replace)
+ (set-port-conversion-strategy! result 'substitute))
+ (else
+ (error "unsupported error handling mode"
+ (transcoder-error-handling-mode transcoder))))
+ result))
+
+(define (port-position port)
+ "Return the offset (an integer) indicating where the next octet will be
+read from/written to in @var{port}."
+
+ ;; FIXME: We should raise an `&assertion' error when not supported.
+ (seek port 0 SEEK_CUR))
+
+(define (set-port-position! port offset)
+ "Set the position where the next octet will be read from/written to
+@var{port}."
+
+ ;; FIXME: We should raise an `&assertion' error when not supported.
+ (seek port offset SEEK_SET))
+
+(define (port-has-port-position? port)
+ "Return @code{#t} is @var{port} supports @code{port-position}."
+ (and (false-if-exception (port-position port)) #t))
+
+(define (port-has-set-port-position!? port)
+ "Return @code{#t} is @var{port} supports @code{set-port-position!}."
+ (and (false-if-exception (set-port-position! port (port-position port)))
+ #t))
+
+(define (call-with-port port proc)
+ "Call @var{proc}, passing it @var{port} and closing @var{port} upon exit of
+@var{proc}. Return the return values of @var{proc}."
+ (call-with-values
+ (lambda () (proc port))
+ (lambda vals
+ (close-port port)
+ (apply values vals))))
+
+(define* (call-with-bytevector-output-port proc #\optional (transcoder #f))
+ (receive (port extract) (open-bytevector-output-port transcoder)
+ (call-with-port port proc)
+ (extract)))
+
+(define (open-string-input-port str)
+ "Open an input port that will read from @var{str}."
+ (with-fluids ((%default-port-encoding "UTF-8"))
+ (open-input-string str)))
+
+(define (r6rs-open filename mode buffer-mode transcoder)
+ (let ((port (with-i/o-filename-conditions filename
+ (lambda ()
+ (with-fluids ((%default-port-encoding #f))
+ (open filename mode))))))
+ (cond (transcoder
+ (set-port-encoding! port (transcoder-codec transcoder))))
+ port))
+
+(define (file-options->mode file-options base-mode)
+ (logior base-mode
+ (if (enum-set-member? 'no-create file-options)
+ 0
+ O_CREAT)
+ (if (enum-set-member? 'no-truncate file-options)
+ 0
+ O_TRUNC)
+ (if (enum-set-member? 'no-fail file-options)
+ 0
+ O_EXCL)))
+
+(define* (open-file-input-port filename
+ #\optional
+ (file-options (file-options))
+ (buffer-mode (buffer-mode block))
+ transcoder)
+ "Return an input port for reading from @var{filename}."
+ (r6rs-open filename O_RDONLY buffer-mode transcoder))
+
+(define* (open-file-input/output-port filename
+ #\optional
+ (file-options (file-options))
+ (buffer-mode (buffer-mode block))
+ transcoder)
+ "Return a port for reading from and writing to @var{filename}."
+ (r6rs-open filename
+ (file-options->mode file-options O_RDWR)
+ buffer-mode
+ transcoder))
+
+(define (open-string-output-port)
+ "Return two values: an output port that will collect characters written to it
+as a string, and a thunk to retrieve the characters associated with that port."
+ (let ((port (with-fluids ((%default-port-encoding "UTF-8"))
+ (open-output-string))))
+ (values port
+ (lambda ()
+ (let ((s (get-output-string port)))
+ (seek port 0 SEEK_SET)
+ (truncate-file port 0)
+ s)))))
+
+(define* (open-file-output-port filename
+ #\optional
+ (file-options (file-options))
+ (buffer-mode (buffer-mode block))
+ maybe-transcoder)
+ "Return an output port for writing to @var{filename}."
+ (r6rs-open filename
+ (file-options->mode file-options O_WRONLY)
+ buffer-mode
+ maybe-transcoder))
+
+(define (call-with-string-output-port proc)
+ "Call @var{proc}, passing it a string output port. When @var{proc} returns,
+return the characters accumulated in that port."
+ (let ((port (open-output-string)))
+ (proc port)
+ (get-output-string port)))
+
+(define (make-custom-textual-output-port id
+ write!
+ get-position
+ set-position!
+ close)
+ (make-soft-port (vector (lambda (c) (write! (string c) 0 1))
+ (lambda (s) (write! s 0 (string-length s)))
+ #f ;flush
+ #f ;read character
+ close)
+ "w"))
+
+(define (flush-output-port port)
+ (force-output port))
+
+
+;;;
+;;; Textual output.
+;;;
+
+(define-condition-type &i/o-encoding &i/o-port
+ make-i/o-encoding-error i/o-encoding-error?
+ (char i/o-encoding-error-char))
+
+(define-syntax with-i/o-encoding-error
+ (syntax-rules ()
+ "Convert Guile throws to `encoding-error' to `&i/o-encoding-error'."
+ ((_ body ...)
+ ;; XXX: This is heavyweight for small functions like `put-char'.
+ (with-throw-handler 'encoding-error
+ (lambda ()
+ (begin body ...))
+ (lambda (key subr message errno port chr)
+ (raise (make-i/o-encoding-error port chr)))))))
+
+(define (put-char port char)
+ (with-textual-output-conditions port (write-char char port)))
+
+(define (put-datum port datum)
+ (with-textual-output-conditions port (write datum port)))
+
+(define* (put-string port s #\optional start count)
+ (with-textual-output-conditions port
+ (cond ((not (string? s))
+ (assertion-violation 'put-string "expected string" s))
+ ((and start count)
+ (display (substring/shared s start (+ start count)) port))
+ (start
+ (display (substring/shared s start (string-length s)) port))
+ (else
+ (display s port)))))
+
+;; Defined here to be able to make use of `with-i/o-encoding-error', but
+;; not exported from here, but from `(rnrs io simple)'.
+(define* (display object #\optional (port (current-output-port)))
+ (with-textual-output-conditions port (guile:display object port)))
+
+
+;;;
+;;; Textual input.
+;;;
+
+(define-condition-type &i/o-decoding &i/o-port
+ make-i/o-decoding-error i/o-decoding-error?)
+
+(define-syntax with-i/o-decoding-error
+ (syntax-rules ()
+ "Convert Guile throws to `decoding-error' to `&i/o-decoding-error'."
+ ((_ body ...)
+ ;; XXX: This is heavyweight for small functions like `get-char' and
+ ;; `lookahead-char'.
+ (with-throw-handler 'decoding-error
+ (lambda ()
+ (begin body ...))
+ (lambda (key subr message errno port)
+ (raise (make-i/o-decoding-error port)))))))
+
+(define (get-char port)
+ (with-textual-input-conditions port (read-char port)))
+
+(define (get-datum port)
+ (with-textual-input-conditions port (read port)))
+
+(define (get-line port)
+ (with-textual-input-conditions port (read-line port 'trim)))
+
+(define (get-string-all port)
+ (with-textual-input-conditions port (read-string port)))
+
+(define (get-string-n port count)
+ "Read up to @var{count} characters from @var{port}.
+If no characters could be read before encountering the end of file,
+return the end-of-file object, otherwise return a string containing
+the characters read."
+ (let* ((s (make-string count))
+ (rv (get-string-n! port s 0 count)))
+ (cond ((eof-object? rv) rv)
+ ((= rv count) s)
+ (else (substring/shared s 0 rv)))))
+
+(define (lookahead-char port)
+ (with-textual-input-conditions port (peek-char port)))
+
+
+;;;
+;;; Standard ports.
+;;;
+
+(define (standard-input-port)
+ (with-fluids ((%default-port-encoding #f))
+ (dup->inport 0)))
+
+(define (standard-output-port)
+ (with-fluids ((%default-port-encoding #f))
+ (dup->outport 1)))
+
+(define (standard-error-port)
+ (with-fluids ((%default-port-encoding #f))
+ (dup->outport 2)))
+
+)
+
+;;; ports.scm ends here
+;;; simple.scm --- The R6RS simple I/O library
+
+;; Copyright (C) 2010, 2011, 2014 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs io simple (6))
+ (export eof-object
+ eof-object?
+
+ call-with-input-file
+ call-with-output-file
+
+ input-port?
+ output-port?
+
+ current-input-port
+ current-output-port
+ current-error-port
+
+ with-input-from-file
+ with-output-to-file
+
+ open-input-file
+ open-output-file
+
+ close-input-port
+ close-output-port
+
+ read-char
+ peek-char
+ read
+ write-char
+ newline
+ display
+ write
+
+ &i/o make-i/o-error i/o-error?
+ &i/o-read make-i/o-read-error i/o-read-error?
+ &i/o-write make-i/o-write-error i/o-write-error?
+
+ &i/o-invalid-position
+ make-i/o-invalid-position-error
+ i/o-invalid-position-error?
+ i/o-error-position
+
+ &i/o-filename
+ make-i/o-filename-error
+ i/o-filename-error?
+ i/o-error-filename
+
+ &i/o-file-protection
+ make-i/o-file-protection-error
+ i/o-file-protection-error?
+
+ &i/o-file-is-read-only
+ make-i/o-file-is-read-only-error
+ i/o-file-is-read-only-error?
+
+ &i/o-file-already-exists
+ make-i/o-file-already-exists-error
+ i/o-file-already-exists-error?
+
+ &i/o-file-does-not-exist
+ make-i/o-file-does-not-exist-error
+ i/o-file-does-not-exist-error?
+
+ &i/o-port
+ make-i/o-port-error
+ i/o-port-error?
+ i/o-error-port)
+
+ (import (only (rnrs io ports)
+ call-with-port
+ close-port
+ open-file-input-port
+ open-file-output-port
+ eof-object
+ eof-object?
+ file-options
+ buffer-mode
+ native-transcoder
+ get-char
+ lookahead-char
+ get-datum
+ put-char
+ put-datum
+
+ input-port?
+ output-port?)
+ (only (guile)
+ @@
+ current-input-port
+ current-output-port
+ current-error-port
+
+ define*
+
+ with-input-from-port
+ with-output-to-port)
+ (rnrs base (6))
+ (rnrs files (6)) ;for the condition types
+ )
+
+ (define display (@@ (rnrs io ports) display))
+
+ (define (call-with-input-file filename proc)
+ (call-with-port (open-file-input-port filename) proc))
+
+ (define (call-with-output-file filename proc)
+ (call-with-port (open-file-output-port filename) proc))
+
+ (define (with-input-from-file filename thunk)
+ (call-with-input-file filename
+ (lambda (port) (with-input-from-port port thunk))))
+
+ (define (with-output-to-file filename thunk)
+ (call-with-output-file filename
+ (lambda (port) (with-output-to-port port thunk))))
+
+ (define (open-input-file filename)
+ (open-file-input-port filename
+ (file-options)
+ (buffer-mode block)
+ (native-transcoder)))
+
+ (define (open-output-file filename)
+ (open-file-output-port filename
+ (file-options)
+ (buffer-mode block)
+ (native-transcoder)))
+
+ (define close-input-port close-port)
+ (define close-output-port close-port)
+
+ (define* (read-char #\optional (port (current-input-port)))
+ (get-char port))
+
+ (define* (peek-char #\optional (port (current-input-port)))
+ (lookahead-char port))
+
+ (define* (read #\optional (port (current-input-port)))
+ (get-datum port))
+
+ (define* (write-char char #\optional (port (current-output-port)))
+ (put-char port char))
+
+ (define* (newline #\optional (port (current-output-port)))
+ (put-char port #\newline))
+
+ (define* (write object #\optional (port (current-output-port)))
+ (put-datum port object))
+
+ )
+;;; lists.scm --- The R6RS list utilities library
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs lists (6))
+ (export find for-all exists filter partition fold-left fold-right remp remove
+ remv remq memp member memv memq assp assoc assv assq cons*)
+ (import (rnrs base (6))
+ (only (guile) filter member memv memq assoc assv assq cons*)
+ (rename (only (srfi srfi-1) any
+ every
+ remove
+ member
+ assoc
+ find
+ partition
+ fold-right
+ filter-map)
+ (any exists)
+ (every for-all)
+ (remove remp)
+
+ (member memp-internal)
+ (assoc assp-internal)))
+
+ (define (fold-left combine nil list . lists)
+ (define (fold nil lists)
+ (if (exists null? lists)
+ nil
+ (fold (apply combine nil (map car lists))
+ (map cdr lists))))
+ (fold nil (cons list lists)))
+
+ (define (remove obj list) (remp (lambda (elt) (equal? obj elt)) list))
+ (define (remv obj list) (remp (lambda (elt) (eqv? obj elt)) list))
+ (define (remq obj list) (remp (lambda (elt) (eq? obj elt)) list))
+
+ (define (memp pred list) (memp-internal #f list (lambda (x y) (pred y))))
+ (define (assp pred list) (assp-internal #f list (lambda (x y) (pred y))))
+)
+;;; mutable-pairs.scm --- The R6RS mutable pair library
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+
+(library (rnrs mutable-pairs (6))
+ (export set-car! set-cdr!)
+ (import (only (guile) set-car! set-cdr!)))
+;;; mutable-strings.scm --- The R6RS mutable string library
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+
+(library (rnrs mutable-strings (6))
+ (export string-set! string-fill!)
+ (import (only (guile) string-set! string-fill!)))
+;;; programs.scm --- The R6RS process management library
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs programs (6))
+ (export command-line exit)
+ (import (only (guile) command-line exit)))
+;;; r5rs.scm --- The R6RS / R5RS compatibility library
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs r5rs (6))
+ (export exact->inexact inexact->exact
+
+ quotient remainder modulo
+
+ delay force
+
+ null-environment scheme-report-environment)
+ (import (only (guile) exact->inexact inexact->exact
+
+ quotient remainder modulo
+
+ delay force)
+ (only (ice-9 r5rs) scheme-report-environment)
+ (only (ice-9 safe-r5rs) null-environment)))
+;;; inspection.scm --- Inspection support for R6RS records
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs records inspection (6))
+ (export record?
+ record-rtd
+ record-type-name
+ record-type-parent
+ record-type-uid
+ record-type-generative?
+ record-type-sealed?
+ record-type-opaque?
+ record-type-field-names
+ record-field-mutable?)
+ (import (rnrs arithmetic bitwise (6))
+ (rnrs base (6))
+ (rnrs records procedural (6))
+ (only (guile) struct-ref struct-vtable vtable-index-layout @@))
+
+ (define record-internal? (@@ (rnrs records procedural) record-internal?))
+
+ (define rtd-index-name (@@ (rnrs records procedural) rtd-index-name))
+ (define rtd-index-parent (@@ (rnrs records procedural) rtd-index-parent))
+ (define rtd-index-uid (@@ (rnrs records procedural) rtd-index-uid))
+ (define rtd-index-sealed? (@@ (rnrs records procedural) rtd-index-sealed?))
+ (define rtd-index-opaque? (@@ (rnrs records procedural) rtd-index-opaque?))
+ (define rtd-index-field-names
+ (@@ (rnrs records procedural) rtd-index-field-names))
+ (define rtd-index-field-bit-field
+ (@@ (rnrs records procedural) rtd-index-field-bit-field))
+
+ (define (record? obj)
+ (and (record-internal? obj)
+ (not (record-type-opaque? (struct-vtable obj)))))
+
+ (define (record-rtd record)
+ (or (and (record-internal? record)
+ (let ((rtd (struct-vtable record)))
+ (and (not (struct-ref rtd rtd-index-opaque?)) rtd)))
+ (assertion-violation 'record-rtd "not a record" record)))
+
+ (define (guarantee-rtd who rtd)
+ (if (record-type-descriptor? rtd)
+ rtd
+ (assertion-violation who "not a record type descriptor" rtd)))
+
+ (define (record-type-name rtd)
+ (struct-ref (guarantee-rtd 'record-type-name rtd) rtd-index-name))
+ (define (record-type-parent rtd)
+ (struct-ref (guarantee-rtd 'record-type-parent rtd) rtd-index-parent))
+ (define (record-type-uid rtd)
+ (struct-ref (guarantee-rtd 'record-type-uid rtd) rtd-index-uid))
+ (define (record-type-generative? rtd)
+ (not (record-type-uid (guarantee-rtd 'record-type-generative? rtd))))
+ (define (record-type-sealed? rtd)
+ (struct-ref (guarantee-rtd 'record-type-sealed? rtd) rtd-index-sealed?))
+ (define (record-type-opaque? rtd)
+ (struct-ref (guarantee-rtd 'record-type-opaque? rtd) rtd-index-opaque?))
+ (define (record-type-field-names rtd)
+ (struct-ref (guarantee-rtd 'record-type-field-names rtd) rtd-index-field-names))
+ (define (record-field-mutable? rtd k)
+ (bitwise-bit-set? (struct-ref (guarantee-rtd 'record-field-mutable? rtd)
+ rtd-index-field-bit-field)
+ k))
+)
+;;; procedural.scm --- Procedural interface to R6RS records
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs records procedural (6))
+ (export make-record-type-descriptor
+ record-type-descriptor?
+ make-record-constructor-descriptor
+
+ record-constructor
+ record-predicate
+ record-accessor
+ record-mutator)
+
+ (import (rnrs base (6))
+ (only (guile) cons*
+ logand
+ logior
+ ash
+
+ and=>
+ throw
+ display
+ make-struct
+ make-vtable
+ map
+ simple-format
+ string-append
+ symbol-append
+
+ struct?
+ struct-layout
+ struct-ref
+ struct-set!
+ struct-vtable
+ vtable-index-layout
+
+ make-hash-table
+ hashq-ref
+ hashq-set!
+
+ vector->list)
+ (ice-9 receive)
+ (only (srfi 1) fold split-at take))
+
+ (define (record-internal? obj)
+ (and (struct? obj) (record-type-descriptor? (struct-vtable obj))))
+
+ (define rtd-index-name 8)
+ (define rtd-index-uid 9)
+ (define rtd-index-parent 10)
+ (define rtd-index-sealed? 11)
+ (define rtd-index-opaque? 12)
+ (define rtd-index-predicate 13)
+ (define rtd-index-field-names 14)
+ (define rtd-index-field-bit-field 15)
+ (define rtd-index-field-binder 16)
+
+ (define rctd-index-rtd 0)
+ (define rctd-index-parent 1)
+ (define rctd-index-protocol 2)
+
+ (define vtable-base-layout (symbol->string (struct-layout (make-vtable ""))))
+
+ (define record-type-vtable
+ (make-vtable (string-append vtable-base-layout "prprprprprprprprprpr")
+ (lambda (obj port)
+ (simple-format port "#<r6rs:record-type:~A>"
+ (struct-ref obj rtd-index-name)))))
+
+ (define record-constructor-vtable
+ (make-vtable "prprpr"
+ (lambda (obj port)
+ (simple-format port "#<r6rs:record-constructor:~A>"
+ (struct-ref (struct-ref obj rctd-index-rtd)
+ rtd-index-name)))))
+
+ (define uid-table (make-hash-table))
+
+ (define (make-record-type-descriptor name parent uid sealed? opaque? fields)
+ (define fields-pair
+ (let loop ((field-list (vector->list fields))
+ (layout-sym 'pr)
+ (layout-bit-field 0)
+ (counter 0))
+ (if (null? field-list)
+ (cons layout-sym layout-bit-field)
+ (case (caar field-list)
+ ((immutable)
+ (loop (cdr field-list)
+ (symbol-append layout-sym 'pr)
+ layout-bit-field
+ (+ counter 1)))
+ ((mutable)
+ (loop (cdr field-list)
+ (symbol-append layout-sym 'pw)
+ (logior layout-bit-field (ash 1 counter))
+ (+ counter 1)))
+ (else (r6rs-raise (make-assertion-violation)))))))
+
+ (define fields-layout (car fields-pair))
+ (define fields-bit-field (cdr fields-pair))
+
+ (define field-names (list->vector (map cadr (vector->list fields))))
+ (define late-rtd #f)
+
+ (define (private-record-predicate obj)
+ (and (record-internal? obj)
+ (or (eq? (struct-vtable obj) late-rtd)
+ (and=> (struct-ref obj 0) private-record-predicate))))
+
+ (define (field-binder parent-struct . args)
+ (apply make-struct (cons* late-rtd 0 parent-struct args)))
+
+ (if (and parent (struct-ref parent rtd-index-sealed?))
+ (r6rs-raise (make-assertion-violation)))
+
+ (let ((matching-rtd (and uid (hashq-ref uid-table uid)))
+ (opaque? (or opaque? (and parent (struct-ref
+ parent rtd-index-opaque?)))))
+ (if matching-rtd
+ (if (equal? (list name
+ parent
+ sealed?
+ opaque?
+ field-names
+ fields-bit-field)
+ (list (struct-ref matching-rtd rtd-index-name)
+ (struct-ref matching-rtd rtd-index-parent)
+ (struct-ref matching-rtd rtd-index-sealed?)
+ (struct-ref matching-rtd rtd-index-opaque?)
+ (struct-ref matching-rtd rtd-index-field-names)
+ (struct-ref matching-rtd
+ rtd-index-field-bit-field)))
+ matching-rtd
+ (r6rs-raise (make-assertion-violation)))
+
+ (let ((rtd (make-struct record-type-vtable 0
+
+ fields-layout
+ (lambda (obj port)
+ (simple-format
+ port "#<r6rs:record:~A>" name))
+
+ name
+ uid
+ parent
+ sealed?
+ opaque?
+
+ private-record-predicate
+ field-names
+ fields-bit-field
+ field-binder)))
+ (set! late-rtd rtd)
+ (if uid (hashq-set! uid-table uid rtd))
+ rtd))))
+
+ (define (record-type-descriptor? obj)
+ (and (struct? obj) (eq? (struct-vtable obj) record-type-vtable)))
+
+ (define (make-record-constructor-descriptor rtd
+ parent-constructor-descriptor
+ protocol)
+ (define rtd-arity (vector-length (struct-ref rtd rtd-index-field-names)))
+ (define (default-inherited-protocol n)
+ (lambda args
+ (receive
+ (n-args p-args)
+ (split-at args (- (length args) rtd-arity))
+ (let ((p (apply n n-args)))
+ (apply p p-args)))))
+ (define (default-protocol p) p)
+
+ (let* ((prtd (struct-ref rtd rtd-index-parent))
+ (pcd (or parent-constructor-descriptor
+ (and=> prtd (lambda (d) (make-record-constructor-descriptor
+ prtd #f #f)))))
+ (prot (or protocol (if pcd
+ default-inherited-protocol
+ default-protocol))))
+ (make-struct record-constructor-vtable 0 rtd pcd prot)))
+
+ (define (record-constructor rctd)
+ (let* ((rtd (struct-ref rctd rctd-index-rtd))
+ (parent-rctd (struct-ref rctd rctd-index-parent))
+ (protocol (struct-ref rctd rctd-index-protocol)))
+ (protocol
+ (if parent-rctd
+ (let ((parent-record-constructor (record-constructor parent-rctd))
+ (parent-rtd (struct-ref parent-rctd rctd-index-rtd)))
+ (lambda args
+ (let ((struct (apply parent-record-constructor args)))
+ (lambda args
+ (apply (struct-ref rtd rtd-index-field-binder)
+ (cons struct args))))))
+ (lambda args (apply (struct-ref rtd rtd-index-field-binder)
+ (cons #f args)))))))
+
+ (define (record-predicate rtd) (struct-ref rtd rtd-index-predicate))
+
+ (define (record-accessor rtd k)
+ (define (record-accessor-inner obj)
+ (if (eq? (struct-vtable obj) rtd)
+ (struct-ref obj (+ k 1))
+ (and=> (struct-ref obj 0) record-accessor-inner)))
+ (lambda (obj)
+ (if (not (record-internal? obj))
+ (r6rs-raise (make-assertion-violation)))
+ (record-accessor-inner obj)))
+
+ (define (record-mutator rtd k)
+ (define (record-mutator-inner obj val)
+ (and obj (or (and (eq? (struct-vtable obj) rtd)
+ (struct-set! obj (+ k 1) val))
+ (record-mutator-inner (struct-ref obj 0) val))))
+ (let ((bit-field (struct-ref rtd rtd-index-field-bit-field)))
+ (if (zero? (logand bit-field (ash 1 k)))
+ (r6rs-raise (make-assertion-violation))))
+ (lambda (obj val) (record-mutator-inner obj val)))
+
+ ;; Condition types that are used in the current library. These are defined
+ ;; here and not in (rnrs conditions) to avoid a circular dependency.
+
+ (define &condition (make-record-type-descriptor '&condition #f #f #f #f '#()))
+ (define &condition-constructor-descriptor
+ (make-record-constructor-descriptor &condition #f #f))
+
+ (define &serious (make-record-type-descriptor
+ '&serious &condition #f #f #f '#()))
+ (define &serious-constructor-descriptor
+ (make-record-constructor-descriptor
+ &serious &condition-constructor-descriptor #f))
+
+ (define make-serious-condition
+ (record-constructor &serious-constructor-descriptor))
+
+ (define &violation (make-record-type-descriptor
+ '&violation &serious #f #f #f '#()))
+ (define &violation-constructor-descriptor
+ (make-record-constructor-descriptor
+ &violation &serious-constructor-descriptor #f))
+ (define make-violation (record-constructor &violation-constructor-descriptor))
+
+ (define &assertion (make-record-type-descriptor
+ '&assertion &violation #f #f #f '#()))
+ (define make-assertion-violation
+ (record-constructor
+ (make-record-constructor-descriptor
+ &assertion &violation-constructor-descriptor #f)))
+
+ ;; Exception wrapper type, along with a wrapping `throw' implementation.
+ ;; These are used in the current library, and so they are defined here and not
+ ;; in (rnrs exceptions) to avoid a circular dependency.
+
+ (define &raise-object-wrapper
+ (make-record-type-descriptor '&raise-object-wrapper #f #f #f #f
+ '#((immutable obj) (immutable continuation))))
+ (define make-raise-object-wrapper
+ (record-constructor (make-record-constructor-descriptor
+ &raise-object-wrapper #f #f)))
+ (define raise-object-wrapper? (record-predicate &raise-object-wrapper))
+ (define raise-object-wrapper-obj (record-accessor &raise-object-wrapper 0))
+ (define raise-object-wrapper-continuation
+ (record-accessor &raise-object-wrapper 1))
+
+ (define (r6rs-raise obj)
+ (throw 'r6rs:exception (make-raise-object-wrapper obj #f)))
+ (define (r6rs-raise-continuable obj)
+ (define (r6rs-raise-continuable-internal continuation)
+ (throw 'r6rs:exception (make-raise-object-wrapper obj continuation)))
+ (call/cc r6rs-raise-continuable-internal))
+)
+;;; syntactic.scm --- Syntactic support for R6RS records
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs records syntactic (6))
+ (export define-record-type
+ record-type-descriptor
+ record-constructor-descriptor)
+ (import (only (guile) and=> gensym)
+ (rnrs base (6))
+ (rnrs conditions (6))
+ (rnrs exceptions (6))
+ (rnrs hashtables (6))
+ (rnrs lists (6))
+ (rnrs records procedural (6))
+ (rnrs syntax-case (6))
+ (only (srfi 1) take))
+
+ (define record-type-registry (make-eq-hashtable))
+
+ (define (guess-constructor-name record-name)
+ (string->symbol (string-append "make-" (symbol->string record-name))))
+ (define (guess-predicate-name record-name)
+ (string->symbol (string-append (symbol->string record-name) "?")))
+ (define (register-record-type name rtd rcd)
+ (hashtable-set! record-type-registry name (cons rtd rcd)))
+ (define (lookup-record-type-descriptor name)
+ (and=> (hashtable-ref record-type-registry name #f) car))
+ (define (lookup-record-constructor-descriptor name)
+ (and=> (hashtable-ref record-type-registry name #f) cdr))
+
+ (define-syntax define-record-type
+ (lambda (stx)
+ (syntax-case stx ()
+ ((_ (record-name constructor-name predicate-name) record-clause ...)
+ #'(define-record-type0
+ (record-name constructor-name predicate-name)
+ record-clause ...))
+ ((_ record-name record-clause ...)
+ (let* ((record-name-sym (syntax->datum #'record-name))
+ (constructor-name
+ (datum->syntax
+ #'record-name (guess-constructor-name record-name-sym)))
+ (predicate-name
+ (datum->syntax
+ #'record-name (guess-predicate-name record-name-sym))))
+ #`(define-record-type0
+ (record-name #,constructor-name #,predicate-name)
+ record-clause ...))))))
+
+ (define (sequence n)
+ (define (seq-inner n) (if (= n 0) '(0) (cons n (seq-inner (- n 1)))))
+ (reverse (seq-inner n)))
+ (define (number-fields fields)
+ (define (number-fields-inner fields counter)
+ (if (null? fields)
+ '()
+ (cons (cons fields counter)
+ (number-fields-inner (cdr fields) (+ counter 1)))))
+ (number-fields-inner fields 0))
+
+ (define (process-fields record-name fields)
+ (define (wrap x) (datum->syntax record-name x))
+ (define (id->string x)
+ (symbol->string (syntax->datum x)))
+ (define record-name-str (id->string record-name))
+ (define (guess-accessor-name field-name)
+ (wrap
+ (string->symbol (string-append
+ record-name-str "-" (id->string field-name)))))
+ (define (guess-mutator-name field-name)
+ (wrap
+ (string->symbol
+ (string-append
+ record-name-str "-" (id->string field-name) "-set!"))))
+ (define (f x)
+ (syntax-case x (immutable mutable)
+ [(immutable name)
+ (list (wrap `(immutable ,(syntax->datum #'name)))
+ (guess-accessor-name #'name)
+ #f)]
+ [(immutable name accessor)
+ (list (wrap `(immutable ,(syntax->datum #'name))) #'accessor #f)]
+ [(mutable name)
+ (list (wrap `(mutable ,(syntax->datum #'name)))
+ (guess-accessor-name #'name)
+ (guess-mutator-name #'name))]
+ [(mutable name accessor mutator)
+ (list (wrap `(mutable ,(syntax->datum #'name))) #'accessor #'mutator)]
+ [name
+ (identifier? #'name)
+ (list (wrap `(immutable ,(syntax->datum #'name)))
+ (guess-accessor-name #'name)
+ #f)]
+ [else
+ (syntax-violation 'define-record-type "invalid field specifier" x)]))
+ (map f fields))
+
+ (define-syntax define-record-type0
+ (lambda (stx)
+ (define *unspecified* (cons #f #f))
+ (define (unspecified? obj)
+ (eq? *unspecified* obj))
+ (syntax-case stx ()
+ ((_ (record-name constructor-name predicate-name) record-clause ...)
+ (let loop ((_fields *unspecified*)
+ (_parent *unspecified*)
+ (_protocol *unspecified*)
+ (_sealed *unspecified*)
+ (_opaque *unspecified*)
+ (_nongenerative *unspecified*)
+ (_constructor *unspecified*)
+ (_parent-rtd *unspecified*)
+ (record-clauses #'(record-clause ...)))
+ (syntax-case record-clauses
+ (fields parent protocol sealed opaque nongenerative
+ constructor parent-rtd)
+ [()
+ (let* ((fields (if (unspecified? _fields) '() _fields))
+ (field-names (list->vector (map car fields)))
+ (field-accessors
+ (fold-left (lambda (lst x c)
+ (cons #`(define #,(cadr x)
+ (record-accessor record-name #,c))
+ lst))
+ '() fields (sequence (length fields))))
+ (field-mutators
+ (fold-left (lambda (lst x c)
+ (if (caddr x)
+ (cons #`(define #,(caddr x)
+ (record-mutator record-name
+ #,c))
+ lst)
+ lst))
+ '() fields (sequence (length fields))))
+ (parent-cd (cond ((not (unspecified? _parent))
+ #`(record-constructor-descriptor
+ #,_parent))
+ ((not (unspecified? _parent-rtd))
+ (cadr _parent-rtd))
+ (else #f)))
+ (parent-rtd (cond ((not (unspecified? _parent))
+ #`(record-type-descriptor #,_parent))
+ ((not (unspecified? _parent-rtd))
+ (car _parent-rtd))
+ (else #f)))
+ (protocol (if (unspecified? _protocol) #f _protocol))
+ (uid (if (unspecified? _nongenerative) #f _nongenerative))
+ (sealed? (if (unspecified? _sealed) #f _sealed))
+ (opaque? (if (unspecified? _opaque) #f _opaque)))
+ #`(begin
+ (define record-name
+ (make-record-type-descriptor
+ (quote record-name)
+ #,parent-rtd #,uid #,sealed? #,opaque?
+ #,field-names))
+ (define constructor-name
+ (record-constructor
+ (make-record-constructor-descriptor
+ record-name #,parent-cd #,protocol)))
+ (define dummy
+ (let ()
+ (register-record-type
+ (quote record-name)
+ record-name (make-record-constructor-descriptor
+ record-name #,parent-cd #,protocol))
+ 'dummy))
+ (define predicate-name (record-predicate record-name))
+ #,@field-accessors
+ #,@field-mutators))]
+ [((fields record-fields ...) . rest)
+ (if (unspecified? _fields)
+ (loop (process-fields #'record-name #'(record-fields ...))
+ _parent _protocol _sealed _opaque _nongenerative
+ _constructor _parent-rtd #'rest)
+ (raise (make-assertion-violation)))]
+ [((parent parent-name) . rest)
+ (if (not (unspecified? _parent-rtd))
+ (raise (make-assertion-violation))
+ (if (unspecified? _parent)
+ (loop _fields #'parent-name _protocol _sealed _opaque
+ _nongenerative _constructor _parent-rtd #'rest)
+ (raise (make-assertion-violation))))]
+ [((protocol expression) . rest)
+ (if (unspecified? _protocol)
+ (loop _fields _parent #'expression _sealed _opaque
+ _nongenerative _constructor _parent-rtd #'rest)
+ (raise (make-assertion-violation)))]
+ [((sealed sealed?) . rest)
+ (if (unspecified? _sealed)
+ (loop _fields _parent _protocol #'sealed? _opaque
+ _nongenerative _constructor _parent-rtd #'rest)
+ (raise (make-assertion-violation)))]
+ [((opaque opaque?) . rest)
+ (if (unspecified? _opaque)
+ (loop _fields _parent _protocol _sealed #'opaque?
+ _nongenerative _constructor _parent-rtd #'rest)
+ (raise (make-assertion-violation)))]
+ [((nongenerative) . rest)
+ (if (unspecified? _nongenerative)
+ (loop _fields _parent _protocol _sealed _opaque
+ #`(quote #,(datum->syntax #'record-name (gensym)))
+ _constructor _parent-rtd #'rest)
+ (raise (make-assertion-violation)))]
+ [((nongenerative uid) . rest)
+ (if (unspecified? _nongenerative)
+ (loop _fields _parent _protocol _sealed
+ _opaque #''uid _constructor
+ _parent-rtd #'rest)
+ (raise (make-assertion-violation)))]
+ [((parent-rtd rtd cd) . rest)
+ (if (not (unspecified? _parent))
+ (raise (make-assertion-violation))
+ (if (unspecified? _parent-rtd)
+ (loop _fields _parent _protocol _sealed _opaque
+ _nongenerative _constructor #'(rtd cd)
+ #'rest)
+ (raise (make-assertion-violation))))]))))))
+
+ (define-syntax record-type-descriptor
+ (lambda (stx)
+ (syntax-case stx ()
+ ((_ name) #`(lookup-record-type-descriptor
+ #,(datum->syntax
+ stx (list 'quote (syntax->datum #'name))))))))
+
+ (define-syntax record-constructor-descriptor
+ (lambda (stx)
+ (syntax-case stx ()
+ ((_ name) #`(lookup-record-constructor-descriptor
+ #,(datum->syntax
+ stx (list 'quote (syntax->datum #'name))))))))
+)
+;;; sorting.scm --- The R6RS sorting library
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs sorting (6))
+ (export list-sort vector-sort vector-sort!)
+ (import (rnrs base (6))
+ (only (guile) *unspecified* stable-sort sort!))
+
+ (define (list-sort proc list) (stable-sort list proc))
+ (define (vector-sort proc vector) (stable-sort vector proc))
+ (define (vector-sort! proc vector) (sort! vector proc) *unspecified*))
+;;; syntax-case.scm --- R6RS support for `syntax-case' macros
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs syntax-case (6))
+ (export make-variable-transformer
+ syntax-case
+ syntax
+
+ identifier?
+ bound-identifier=?
+ free-identifier=?
+
+ syntax->datum
+ datum->syntax
+ generate-temporaries
+ with-syntax
+
+ quasisyntax
+ unsyntax
+ unsyntax-splicing
+
+ syntax-violation)
+ (import (only (guile) make-variable-transformer
+ syntax-case
+ syntax
+
+ identifier?
+ bound-identifier=?
+ free-identifier=?
+
+ syntax->datum
+ datum->syntax
+ generate-temporaries
+ with-syntax
+
+ quasisyntax
+ unsyntax
+ unsyntax-splicing)
+ (ice-9 optargs)
+ (rnrs base (6))
+ (rnrs conditions (6))
+ (rnrs exceptions (6))
+ (rnrs records procedural (6)))
+
+ (define* (syntax-violation who message form #\optional subform)
+ (let* ((conditions (list (make-message-condition message)
+ (make-syntax-violation form subform)))
+ (conditions (if who
+ (cons (make-who-condition who) conditions)
+ conditions)))
+ (raise (apply condition conditions))))
+)
+;;; unicode.scm --- The R6RS Unicode library
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (rnrs unicode (6))
+ (export char-upcase
+ char-downcase
+ char-titlecase
+ char-foldcase
+
+ char-ci=?
+ char-ci<?
+ char-ci>?
+ char-ci<=?
+ char-ci>=?
+
+ char-alphabetic?
+ char-numeric?
+ char-whitespace?
+ char-upper-case?
+ char-lower-case?
+ char-title-case?
+
+ char-general-category
+
+ string-upcase
+ string-downcase
+ string-titlecase
+ string-foldcase
+
+ string-ci=?
+ string-ci<?
+ string-ci>?
+ string-ci<=?
+ string-ci>=?
+
+ string-normalize-nfd
+ string-normalize-nfkd
+ string-normalize-nfc
+ string-normalize-nfkc)
+ (import (only (guile) char-upcase
+ char-downcase
+ char-titlecase
+
+ char-ci=?
+ char-ci<?
+ char-ci>?
+ char-ci<=?
+ char-ci>=?
+
+ char-alphabetic?
+ char-numeric?
+ char-whitespace?
+ char-upper-case?
+ char-lower-case?
+
+ char-set-contains?
+ char-set:title-case
+
+ char-general-category
+
+ char-upcase
+ char-downcase
+ char-titlecase
+
+ string-upcase
+ string-downcase
+ string-titlecase
+
+ string-ci=?
+ string-ci<?
+ string-ci>?
+ string-ci<=?
+ string-ci>=?
+
+ string-normalize-nfd
+ string-normalize-nfkd
+ string-normalize-nfc
+ string-normalize-nfkc)
+ (rnrs base (6)))
+
+ (define (char-foldcase char)
+ (if (or (eqv? char #\460) (eqv? char #\461))
+ char (char-downcase (char-upcase char))))
+
+ (define (char-title-case? char) (char-set-contains? char-set:title-case char))
+
+ (define (string-foldcase str) (string-downcase (string-upcase str)))
+)
+;;; api-diff --- diff guile-api.alist files
+
+;; Copyright (C) 2002, 2006, 2011 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
+
+;;; Commentary:
+
+;; Usage: api-diff [-d GROUPS] ALIST-FILE-A ALIST-FILE-B
+;;
+;; Read in the alists from files ALIST-FILE-A and ALIST-FILE-B
+;; and display a (count) summary of the groups defined therein.
+;; Optional arg "--details" (or "-d") specifies a comma-separated
+;; list of groups, in which case api-diff displays instead the
+;; elements added and deleted for each of the specified groups.
+;;
+;; For scheme programming, this module exports the proc:
+;; (api-diff A-file B-file)
+;;
+;; Note that the convention is that the "older" alist/file is
+;; specified first.
+;;
+;; TODO: Develop scheme interface.
+
+;;; Code:
+
+(define-module (scripts api-diff)
+ \:use-module (ice-9 common-list)
+ \:use-module (ice-9 format)
+ \:use-module (ice-9 getopt-long)
+ \:autoload (srfi srfi-13) (string-tokenize)
+ \:export (api-diff))
+
+(define %include-in-guild-list #f)
+(define %summary "Show differences between two scan-api files.")
+
+(define (read-alist-file file)
+ (with-input-from-file file
+ (lambda () (read))))
+
+(define put set-object-property!)
+(define get object-property)
+
+(define (read-api-alist-file file)
+ (let* ((alist (read-alist-file file))
+ (meta (assq-ref alist 'meta))
+ (interface (assq-ref alist 'interface)))
+ (put interface 'meta meta)
+ (put interface 'groups (let ((ht (make-hash-table 31)))
+ (for-each (lambda (group)
+ (hashq-set! ht group '()))
+ (assq-ref meta 'groups))
+ ht))
+ interface))
+
+(define (hang-by-the-roots interface)
+ (let ((ht (get interface 'groups)))
+ (for-each (lambda (x)
+ (for-each (lambda (group)
+ (hashq-set! ht group
+ (cons (car x)
+ (hashq-ref ht group))))
+ (assq-ref x 'groups)))
+ interface))
+ interface)
+
+(define (diff? a b)
+ (let ((result (set-difference a b)))
+ (if (null? result)
+ #f ; CL weenies bite me
+ result)))
+
+(define (diff+note! a b note-removals note-additions note-same)
+ (let ((same? #t))
+ (cond ((diff? a b) => (lambda (x) (note-removals x) (set! same? #f))))
+ (cond ((diff? b a) => (lambda (x) (note-additions x) (set! same? #f))))
+ (and same? (note-same))))
+
+(define (group-diff i-old i-new . options)
+ (let* ((i-old (hang-by-the-roots i-old))
+ (g-old (hash-fold acons '() (get i-old 'groups)))
+ (g-old-names (map car g-old))
+ (i-new (hang-by-the-roots i-new))
+ (g-new (hash-fold acons '() (get i-new 'groups)))
+ (g-new-names (map car g-new)))
+ (cond ((null? options)
+ (diff+note! g-old-names g-new-names
+ (lambda (removals)
+ (format #t "groups-removed: ~A\n" removals))
+ (lambda (additions)
+ (format #t "groups-added: ~A\n" additions))
+ (lambda () #t))
+ (for-each (lambda (group)
+ (let* ((old (assq-ref g-old group))
+ (new (assq-ref g-new group))
+ (old-count (and old (length old)))
+ (new-count (and new (length new)))
+ (delta (and old new (- new-count old-count))))
+ (format #t " ~5@A ~5@A : "
+ (or old-count "-")
+ (or new-count "-"))
+ (cond ((and old new)
+ (let ((add-count 0) (sub-count 0))
+ (diff+note!
+ old new
+ (lambda (subs)
+ (set! sub-count (length subs)))
+ (lambda (adds)
+ (set! add-count (length adds)))
+ (lambda () #t))
+ (format #t "~5@D ~5@D : ~5@D"
+ add-count (- sub-count) delta)))
+ (else
+ (format #t "~5@A ~5@A : ~5@A" "-" "-" "-")))
+ (format #t " ~A\n" group)))
+ (sort (union g-old-names g-new-names)
+ (lambda (a b)
+ (string<? (symbol->string a)
+ (symbol->string b))))))
+ ((assq-ref options 'details)
+ => (lambda (groups)
+ (for-each (lambda (group)
+ (let* ((old (or (assq-ref g-old group) '()))
+ (new (or (assq-ref g-new group) '()))
+ (>>! (lambda (label ls)
+ (format #t "~A ~A:\n" group label)
+ (for-each (lambda (x)
+ (format #t " ~A\n" x))
+ ls))))
+ (diff+note! old new
+ (lambda (removals)
+ (>>! 'removals removals))
+ (lambda (additions)
+ (>>! 'additions additions))
+ (lambda ()
+ (format #t "~A: no changes\n"
+ group)))))
+ groups)))
+ (else
+ (error "api-diff: group-diff: bad options")))))
+
+(define (api-diff . args)
+ (let* ((p (getopt-long (cons 'api-diff args)
+ '((details (single-char #\d)
+ (value #t))
+ ;; Add options here.
+ )))
+ (rest (option-ref p '() '("/dev/null" "/dev/null")))
+ (i-old (read-api-alist-file (car rest)))
+ (i-new (read-api-alist-file (cadr rest)))
+ (options '()))
+ (cond ((option-ref p 'details #f)
+ => (lambda (groups)
+ (set! options (cons (cons 'details
+ (map string->symbol
+ (string-tokenize
+ groups
+ #\,)))
+ options)))))
+ (apply group-diff i-old i-new options)))
+
+(define main api-diff)
+
+;;; api-diff ends here
+;;; autofrisk --- Generate module checks for use with auto* tools
+
+;; Copyright (C) 2002, 2006, 2009, 2011 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
+
+;;; Commentary:
+
+;; Usage: autofrisk [file]
+;;
+;; This program looks for the file modules.af in the current directory
+;; and writes out modules.af.m4 containing autoconf definitions.
+;; If given, look for FILE instead of modules.af and output to FILE.m4.
+;;
+;; After running autofrisk, you should add to configure.ac the lines:
+;; AUTOFRISK_CHECKS
+;; AUTOFRISK_SUMMARY
+;; Then run "aclocal -I ." to update aclocal.m4, and finally autoconf.
+;;
+;; The modules.af file consists of a series of configuration forms (Scheme
+;; lists), which have one of the following formats:
+;; (files-glob PATTERN ...)
+;; (non-critical-external MODULE ...)
+;; (non-critical-internal MODULE ...)
+;; (programs (MODULE PROG ...) ...)
+;; (pww-varname VARNAME)
+;; PATTERN is a string that may contain "*" and "?" characters to be
+;; expanded into filenames. MODULE is a list of symbols naming a
+;; module, such as `(srfi srfi-1)'. VARNAME is a shell-safe name to use
+;; instead of "probably_wont_work", the default. This var is passed to
+;; `AC_SUBST'. PROG is a string.
+;;
+;; Only the `files-glob' form is required.
+;;
+;; TODO: Write better commentary.
+;; Make "please see README" configurable.
+
+;;; Code:
+
+(define-module (scripts autofrisk)
+ \:autoload (ice-9 popen) (open-input-pipe)
+ \:use-module (srfi srfi-1)
+ \:use-module (srfi srfi-8)
+ \:use-module (srfi srfi-13)
+ \:use-module (srfi srfi-14)
+ \:use-module (scripts read-scheme-source)
+ \:use-module (scripts frisk)
+ \:export (autofrisk))
+
+(define %include-in-guild-list #f)
+(define %summary "Generate snippets for use in configure.ac files.")
+
+(define *recognized-keys* '(files-glob
+ non-critical-external
+ non-critical-internal
+ programs
+ pww-varname))
+
+(define (canonical-configuration forms)
+ (let ((chk (lambda (condition . x)
+ (or condition (apply error "syntax error:" x)))))
+ (chk (list? forms) "input not a list")
+ (chk (every list? forms) "non-list element")
+ (chk (every (lambda (form) (< 1 (length form))) forms) "list too short")
+ (let ((un #f))
+ (chk (every (lambda (form)
+ (let ((key (car form)))
+ (and (symbol? key)
+ (or (eq? 'quote key)
+ (memq key *recognized-keys*)
+ (begin
+ (set! un key)
+ #f)))))
+ forms)
+ "unrecognized key:" un))
+ (let ((bunched (map (lambda (key)
+ (fold (lambda (form so-far)
+ (or (and (eq? (car form) key)
+ (cdr form)
+ (append so-far (cdr form)))
+ so-far))
+ (list key)
+ forms))
+ *recognized-keys*)))
+ (lambda (key)
+ (assq-ref bunched key)))))
+
+(define (>>strong modules)
+ (for-each (lambda (module)
+ (format #t "GUILE_MODULE_REQUIRED~A\n" module))
+ modules))
+
+(define (safe-name module)
+ (let ((var (object->string module)))
+ (string-map! (lambda (c)
+ (if (char-set-contains? char-set:letter+digit c)
+ c
+ #\_))
+ var)
+ var))
+
+(define *pww* "probably_wont_work")
+
+(define (>>weak weak-edges)
+ (for-each (lambda (edge)
+ (let* ((up (edge-up edge))
+ (down (edge-down edge))
+ (var (format #f "have_guile_module~A" (safe-name up))))
+ (format #t "GUILE_MODULE_AVAILABLE(~A, ~A)\n" var up)
+ (format #t "test \"$~A\" = no &&\n ~A=\"~A $~A\"~A"
+ var *pww* down *pww* "\n\n")))
+ weak-edges))
+
+(define (>>program module progs)
+ (let ((vars (map (lambda (prog)
+ (format #f "guile_module~Asupport_~A"
+ (safe-name module)
+ prog))
+ progs)))
+ (for-each (lambda (var prog)
+ (format #t "AC_PATH_PROG(~A, ~A)\n" var prog))
+ vars progs)
+ (format #t "test \\\n")
+ (for-each (lambda (var)
+ (format #t " \"$~A\" = \"\" -o \\\n" var))
+ vars)
+ (format #t "~A &&\n~A=\"~A $~A\"\n\n"
+ (list-ref (list "war = peace"
+ "freedom = slavery"
+ "ignorance = strength")
+ (random 3))
+ *pww* module *pww*)))
+
+(define (>>programs programs)
+ (for-each (lambda (form)
+ (>>program (car form) (cdr form)))
+ programs))
+
+(define (unglob pattern)
+ (let ((p (open-input-pipe (format #f "echo '(' ~A ')'" pattern))))
+ (map symbol->string (read p))))
+
+(define (>>checks forms)
+ (let* ((cfg (canonical-configuration forms))
+ (files (apply append (map unglob (cfg 'files-glob))))
+ (ncx (cfg 'non-critical-external))
+ (nci (cfg 'non-critical-internal))
+ (report ((make-frisker) files))
+ (external (report 'external)))
+ (let ((pww-varname (cfg 'pww-varname)))
+ (or (null? pww-varname) (set! *pww* (car pww-varname))))
+ (receive (weak strong)
+ (partition (lambda (module)
+ (or (member module ncx)
+ (every (lambda (i)
+ (member i nci))
+ (map edge-down (mod-down-ls module)))))
+ external)
+ (format #t "AC_DEFUN([AUTOFRISK_CHECKS],[\n\n")
+ (>>strong strong)
+ (format #t "\n~A=~S\n\n" *pww* "")
+ (>>weak (fold (lambda (module so-far)
+ (append so-far (mod-down-ls module)))
+ (list)
+ weak))
+ (>>programs (cfg 'programs))
+ (format #t "AC_SUBST(~A)\n])\n\n" *pww*))))
+
+(define (>>summary)
+ (format #t
+ (symbol->string
+ '#{
+AC_DEFUN([AUTOFRISK_SUMMARY],[
+if test ! "$~A" = "" ; then
+ p=" ***"
+ echo "$p"
+ echo "$p NOTE:"
+ echo "$p The following modules probably won't work:"
+ echo "$p $~A"
+ echo "$p They can be installed anyway, and will work if their"
+ echo "$p dependencies are installed later. Please see README."
+ echo "$p"
+fi
+])
+})
+ *pww* *pww*))
+
+(define (autofrisk . args)
+ (let ((file (if (null? args) "modules.af" (car args))))
+ (or (file-exists? file)
+ (error "could not find input file:" file))
+ (with-output-to-file (format #f "~A.m4" file)
+ (lambda ()
+ (>>checks (read-scheme-source-silently file))
+ (>>summary)))))
+
+(define main autofrisk)
+
+;; Local variables:
+;; eval: (put 'receive 'scheme-indent-function 2)
+;; End:
+
+;;; autofrisk ends here
+;;; Compile --- Command-line Guile Scheme compiler -*- coding: iso-8859-1 -*-
+
+;; Copyright 2005, 2008, 2009, 2010, 2011, 2014 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Ludovic Courts <ludo@gnu.org>
+;;; Author: Andy Wingo <wingo@pobox.com>
+
+;;; Commentary:
+
+;; Usage: compile [ARGS]
+;;
+;; A command-line interface to the Guile compiler.
+
+;;; Code:
+
+(define-module (scripts compile)
+ #\use-module ((system base compile) #\select (compile-file))
+ #\use-module (system base target)
+ #\use-module (system base message)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-13)
+ #\use-module (srfi srfi-37)
+ #\use-module (ice-9 format)
+ #\export (compile))
+
+(define %summary "Compile a file.")
+
+
+(define (fail . messages)
+ (format (current-error-port) "error: ~{~a~}~%" messages)
+ (exit 1))
+
+(define %options
+ ;; Specifications of the command-line options.
+ (list (option '(#\h "help") #f #f
+ (lambda (opt name arg result)
+ (alist-cons 'help? #t result)))
+ (option '("version") #f #f
+ (lambda (opt name arg result)
+ (show-version)
+ (exit 0)))
+
+ (option '(#\L "load-path") #t #f
+ (lambda (opt name arg result)
+ (let ((load-path (assoc-ref result 'load-path)))
+ (alist-cons 'load-path (cons arg load-path)
+ result))))
+ (option '(#\o "output") #t #f
+ (lambda (opt name arg result)
+ (if (assoc-ref result 'output-file)
+ (fail "`-o' option cannot be specified more than once")
+ (alist-cons 'output-file arg result))))
+
+ (option '(#\W "warn") #t #f
+ (lambda (opt name arg result)
+ (if (string=? arg "help")
+ (begin
+ (show-warning-help)
+ (exit 0))
+ (let ((warnings (assoc-ref result 'warnings)))
+ (alist-cons 'warnings
+ (cons (string->symbol arg) warnings)
+ (alist-delete 'warnings result))))))
+
+ (option '(#\O "optimize") #f #f
+ (lambda (opt name arg result)
+ (alist-cons 'optimize? #t result)))
+ (option '(#\f "from") #t #f
+ (lambda (opt name arg result)
+ (if (assoc-ref result 'from)
+ (fail "`--from' option cannot be specified more than once")
+ (alist-cons 'from (string->symbol arg) result))))
+ (option '(#\t "to") #t #f
+ (lambda (opt name arg result)
+ (if (assoc-ref result 'to)
+ (fail "`--to' option cannot be specified more than once")
+ (alist-cons 'to (string->symbol arg) result))))
+ (option '(#\T "target") #t #f
+ (lambda (opt name arg result)
+ (if (assoc-ref result 'target)
+ (fail "`--target' option cannot be specified more than once")
+ (alist-cons 'target arg result))))))
+
+(define (parse-args args)
+ "Parse argument list @var{args} and return an alist with all the relevant
+options."
+ (args-fold args %options
+ (lambda (opt name arg result)
+ (format (current-error-port) "~A: unrecognized option" name)
+ (exit 1))
+ (lambda (file result)
+ (let ((input-files (assoc-ref result 'input-files)))
+ (alist-cons 'input-files (cons file input-files)
+ result)))
+
+ ;; default option values
+ '((input-files)
+ (load-path)
+ (warnings unsupported-warning))))
+
+(define (show-version)
+ (format #t "compile (GNU Guile) ~A~%" (version))
+ (format #t "Copyright (C) 2009, 2011 Free Software Foundation, Inc.
+License LGPLv3+: GNU LGPL version 3 or later <http://gnu.org/licenses/lgpl.html>.
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.~%"))
+
+(define (show-warning-help)
+ (format #t "The available warning types are:~%~%")
+ (for-each (lambda (wt)
+ (format #t " ~22A ~A~%"
+ (format #f "`~A'" (warning-type-name wt))
+ (warning-type-description wt)))
+ %warning-types)
+ (format #t "~%"))
+
+
+(define (compile . args)
+ (let* ((options (parse-args args))
+ (help? (assoc-ref options 'help?))
+ (compile-opts (let ((o `(#\warnings
+ ,(assoc-ref options 'warnings))))
+ (if (assoc-ref options 'optimize?)
+ (cons #\O o)
+ o)))
+ (from (or (assoc-ref options 'from) 'scheme))
+ (to (or (assoc-ref options 'to) 'objcode))
+ (target (or (assoc-ref options 'target) %host-type))
+ (input-files (assoc-ref options 'input-files))
+ (output-file (assoc-ref options 'output-file))
+ (load-path (assoc-ref options 'load-path)))
+ (if (or help? (null? input-files))
+ (begin
+ (format #t "Usage: compile [OPTION] FILE...
+Compile each Guile source file FILE into a Guile object.
+
+ -h, --help print this help message
+
+ -L, --load-path=DIR add DIR to the front of the module load path
+ -o, --output=OFILE write output to OFILE
+
+ -W, --warn=WARNING emit warnings of type WARNING; use `--warn=help'
+ for a list of available warnings
+
+ -f, --from=LANG specify a source language other than `scheme'
+ -t, --to=LANG specify a target language other than `objcode'
+ -T, --target=TRIPLET produce bytecode for host TRIPLET
+
+Note that auto-compilation will be turned off.
+
+Report bugs to <~A>.~%"
+ %guile-bug-report-address)
+ (exit 0)))
+
+ (set! %load-path (append load-path %load-path))
+ (set! %load-should-auto-compile #f)
+
+ (if (and output-file
+ (or (null? input-files)
+ (not (null? (cdr input-files)))))
+ (fail "`-o' option can only be specified "
+ "when compiling a single file"))
+
+ ;; Install a SIGINT handler. As a side effect, this gives unwind
+ ;; handlers an opportunity to run upon SIGINT; this includes that of
+ ;; 'call-with-output-file/atomic', called by 'compile-file', which
+ ;; removes the temporary output file.
+ (sigaction SIGINT
+ (lambda args
+ (fail "interrupted by the user")))
+
+ (for-each (lambda (file)
+ (format #t "wrote `~A'\n"
+ (with-fluids ((*current-warning-prefix* ""))
+ (with-target target
+ (lambda ()
+ (compile-file file
+ #\output-file output-file
+ #\from from
+ #\to to
+ #\opts compile-opts))))))
+ input-files)))
+
+(define main compile)
+;;; Disassemble --- Disassemble .go files into something human-readable
+
+;; Copyright 2005, 2008, 2009, 2011, 2014 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Ludovic Courtès <ludo@gnu.org>
+;;; Author: Andy Wingo <wingo@pobox.com>
+
+;;; Commentary:
+
+;; Usage: disassemble [ARGS]
+
+;;; Code:
+
+(define-module (scripts disassemble)
+ #\use-module (system vm objcode)
+ #\use-module ((language assembly disassemble) #\prefix asm\:)
+ #\export (disassemble))
+
+(define %summary "Disassemble a compiled .go file.")
+
+(define (disassemble . files)
+ (for-each (lambda (file)
+ (asm:disassemble (load-objcode file)))
+ files))
+
+(define main disassemble)
+;;; display-commentary --- As advertized
+
+;; Copyright (C) 2001, 2006, 2011 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Thien-Thi Nguyen
+
+;;; Commentary:
+
+;; Usage: display-commentary REF1 REF2 ...
+;;
+;; Display Commentary section from REF1, REF2 and so on.
+;; Each REF may be a filename or module name (list of symbols).
+;; In the latter case, a filename is computed by searching `%load-path'.
+
+;;; Code:
+
+(define-module (scripts display-commentary)
+ \:use-module (ice-9 documentation)
+ \:export (display-commentary))
+
+(define %summary "Display the Commentary section from a file or module.")
+
+(define (display-commentary-one file)
+ (format #t "~A commentary:\n~A" file (file-commentary file)))
+
+(define (module-name->filename-frag ls) ; todo: export or move
+ (let ((ls (map symbol->string ls)))
+ (let loop ((ls (cdr ls)) (acc (car ls)))
+ (if (null? ls)
+ acc
+ (loop (cdr ls) (string-append acc "/" (car ls)))))))
+
+(define (display-module-commentary module-name)
+ (cond ((%search-load-path (module-name->filename-frag module-name))
+ => (lambda (file)
+ (format #t "module ~A\n" module-name)
+ (display-commentary-one file)))))
+
+(define (display-commentary . refs)
+ (for-each (lambda (ref)
+ (cond ((string? ref)
+ (if (equal? 0 (string-index ref #\())
+ (display-module-commentary
+ (with-input-from-string ref read))
+ (display-commentary-one ref)))
+ ((list? ref)
+ (display-module-commentary ref))))
+ refs))
+
+(define main display-commentary)
+
+;;; display-commentary ends here
+;;; doc-snarf --- Extract documentation from source files
+
+;; Copyright (C) 2001, 2006, 2011 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Martin Grabmueller
+
+;;; Commentary:
+
+;; Usage: doc-snarf FILE
+;;
+;; This program reads in a Scheme source file and extracts docstrings
+;; in the format specified below. Additionally, a procedure protoype
+;; is infered from the procedure definition line starting with
+;; (define... ).
+;;
+;; Currently, two output modi are implemented: texinfo and plaintext.
+;; Default is plaintext, texinfo can be switched on with the
+;; `--texinfo, -t' command line option.
+;;
+;; Format: A docstring can span multiple lines and a docstring line
+;; begins with `;; ' (two semicoli and a space). A docstring is ended
+;; by either a line beginning with (define ...) or one or more lines
+;; beginning with `;;-' (two semicoli and a dash). These lines are
+;; called `options' and begin with a keyword, followed by a colon and
+;; a string.
+;;
+;; Additionally, "standard internal docstrings" (for Scheme source) are
+;; recognized and output as "options". The output formatting is likely
+;; to change in the future.
+;;
+;; Example:
+
+;; This procedure foos, or bars, depending on the argument @var{braz}.
+;;-Author: Martin Grabmueller
+(define (foo/bar braz)
+ (if braz 'foo 'bar))
+
+;;; Which results in the following docstring if texinfo output is
+;;; enabled:
+
+;; TODO: Convert option lines to alist.
+;; More parameterization.
+;; (maybe) Use in Guile build itself.
+
+(define doc-snarf-version "0.0.2") ; please update before publishing!
+
+;;; Code:
+
+(define-module (scripts doc-snarf)
+ \:use-module (ice-9 getopt-long)
+ \:use-module (ice-9 regex)
+ \:use-module (ice-9 string-fun)
+ \:use-module (ice-9 rdelim)
+ \:export (doc-snarf))
+
+(define %summary "Snarf out documentation from a file.")
+
+(define command-synopsis
+ '((version (single-char #\v) (value #f))
+ (help (single-char #\h) (value #f))
+ (output (single-char #\o) (value #t))
+ (texinfo (single-char #\t) (value #f))
+ (lang (single-char #\l) (value #t))))
+
+;; Display version information and exit.
+;;-ttn-mod: use var
+(define (display-version)
+ (display "doc-snarf ") (display doc-snarf-version) (newline))
+
+;; Display the usage help message and exit.
+;;-ttn-mod: change option "source" to "lang"
+(define (display-help)
+ (display "Usage: doc-snarf [options...] inputfile\n")
+ (display " --help, -h Show this usage information\n")
+ (display " --version, -v Show version information\n")
+ (display
+ " --output=FILE, -o Specify output file [default=stdout]\n")
+ (display " --texinfo, -t Format output as texinfo\n")
+ (display " --lang=[c,scheme], -l Specify the input language\n"))
+
+;; Main program.
+;;-ttn-mod: canonicalize lang
+(define (doc-snarf . args)
+ (let ((options (getopt-long (cons "doc-snarf" args) command-synopsis)))
+ (let ((help-wanted (option-ref options 'help #f))
+ (version-wanted (option-ref options 'version #f))
+ (texinfo-wanted (option-ref options 'texinfo #f))
+ (lang (string->symbol
+ (string-downcase (option-ref options 'lang "scheme")))))
+ (cond
+ (version-wanted (display-version))
+ (help-wanted (display-help))
+ (else
+ (let ((input (option-ref options '() #f))
+ (output (option-ref options 'output #f)))
+ (if
+ ;; Bonard B. Timmons III says `(pair? input)' alone is sufficient.
+ ;; (and input (pair? input))
+ (pair? input)
+ (snarf-file (car input) output texinfo-wanted lang)
+ (display-help))))))))
+
+(define main doc-snarf)
+
+;; Supported languages and their parameters. Each element has form:
+;; (LANG DOC-START DOC-END DOC-PREFIX OPT-PREFIX SIG-START STD-INT-DOC?)
+;; LANG is a symbol, STD-INT-DOC? is a boolean indicating whether or not
+;; LANG supports "standard internal docstring" (a string after the formals),
+;; everything else is a string specifying a regexp.
+;;-ttn-mod: new var
+(define supported-languages
+ '((c
+ "^/\\*(.*)"
+ "^ \\*/"
+ "^ \\* (.*)"
+ "^ \\*-(.*)"
+ "NOTHING AT THIS TIME!!!"
+ #f
+ )
+ (scheme
+ "^;; (.*)"
+ "^;;\\."
+ "^;; (.*)"
+ "^;;-(.*)"
+ "^\\(define"
+ #t
+ )))
+
+;; Get @var{lang}'s @var{parameter}. Both args are symbols.
+;;-ttn-mod: new proc
+(define (lang-parm lang parm)
+ (list-ref (assq-ref supported-languages lang)
+ (case parm
+ ((docstring-start) 0)
+ ((docstring-end) 1)
+ ((docstring-prefix) 2)
+ ((option-prefix) 3)
+ ((signature-start) 4)
+ ((std-int-doc?) 5))))
+
+;; Snarf all docstrings from the file @var{input} and write them to
+;; file @var{output}. Use texinfo format for the output if
+;; @var{texinfo?} is true.
+;;-ttn-mod: don't use string comparison, consult table instead
+(define (snarf-file input output texinfo? lang)
+ (or (memq lang (map car supported-languages))
+ (error "doc-snarf: input language must be c or scheme."))
+ (write-output (snarf input lang) output
+ (if texinfo? format-texinfo format-plain)))
+
+;; fixme: this comment is required to trigger standard internal
+;; docstring snarfing... ideally, it wouldn't be necessary.
+;;-ttn-mod: new proc, from snarf-docs (aren't these names fun?)
+(define (find-std-int-doc line input-port)
+ "Unread @var{line} from @var{input-port}, then read in the entire form and
+return the standard internal docstring if found. Return #f if not."
+ (unread-string line input-port) ; ugh
+ (let ((form (read input-port)))
+ (cond ((and (list? form) ; (define (PROC ARGS) "DOC" ...)
+ (< 3 (length form))
+ (eq? 'define (car form))
+ (pair? (cadr form))
+ (symbol? (caadr form))
+ (string? (caddr form)))
+ (caddr form))
+ ((and (list? form) ; (define VAR (lambda ARGS "DOC" ...))
+ (< 2 (length form))
+ (eq? 'define (car form))
+ (symbol? (cadr form))
+ (list? (caddr form))
+ (< 3 (length (caddr form)))
+ (eq? 'lambda (car (caddr form)))
+ (string? (caddr (caddr form))))
+ (caddr (caddr form)))
+ (else #f))))
+
+;; Split @var{string} into lines, adding @var{prefix} to each.
+;;-ttn-mod: new proc
+(define (split-prefixed string prefix)
+ (separate-fields-discarding-char
+ #\newline string
+ (lambda lines
+ (map (lambda (line)
+ (string-append prefix line))
+ lines))))
+
+;; snarf input-file output-file
+;; Extract docstrings from the input file @var{input}, presumed
+;; to be written in language @var{lang}.
+;;-Author: Martin Grabmueller <mgrabmue@cs.tu-berlin.de>
+;;-Created: 2001-02-17
+;;-ttn-mod: regluarize lang parm lookup, add "std int doc" snarfing (2 places)
+(define (snarf input-file lang)
+ (let* ((i-p (open-input-file input-file))
+ (parm-regexp (lambda (parm) (make-regexp (lang-parm lang parm))))
+ (docstring-start (parm-regexp 'docstring-start))
+ (docstring-end (parm-regexp 'docstring-end))
+ (docstring-prefix (parm-regexp 'docstring-prefix))
+ (option-prefix (parm-regexp 'option-prefix))
+ (signature-start (parm-regexp 'signature-start))
+ (augmented-options
+ (lambda (line i-p options)
+ (let ((int-doc (and (lang-parm lang 'std-int-doc?)
+ (let ((d (find-std-int-doc line i-p)))
+ (and d (split-prefixed d "internal: "))))))
+ (if int-doc
+ (append (reverse int-doc) options)
+ options)))))
+
+ (let lp ((line (read-line i-p)) (state 'neutral) (doc-strings '())
+ (options '()) (entries '()) (lno 0))
+ (cond
+ ((eof-object? line)
+ (close-input-port i-p)
+ (reverse entries))
+
+ ;; State 'neutral: we're currently not within a docstring or
+ ;; option section
+ ((eq? state 'neutral)
+ (let ((m (regexp-exec docstring-start line)))
+ (if m
+ (lp (read-line i-p) 'doc-string
+ (list (match:substring m 1)) '() entries (+ lno 1))
+ (lp (read-line i-p) state '() '() entries (+ lno 1)))))
+
+ ;; State 'doc-string: we have started reading a docstring and
+ ;; are waiting for more, for options or for a define.
+ ((eq? state 'doc-string)
+ (let ((m0 (regexp-exec docstring-prefix line))
+ (m1 (regexp-exec option-prefix line))
+ (m2 (regexp-exec signature-start line))
+ (m3 (regexp-exec docstring-end line)))
+ (cond
+ (m0
+ (lp (read-line i-p) 'doc-string
+ (cons (match:substring m0 1) doc-strings) '() entries
+ (+ lno 1)))
+ (m1
+ (lp (read-line i-p) 'options
+ doc-strings (cons (match:substring m1 1) options) entries
+ (+ lno 1)))
+ (m2
+ (let ((options (augmented-options line i-p options))) ; ttn-mod
+ (lp (read-line i-p) 'neutral '() '()
+ (cons (parse-entry doc-strings options line input-file lno)
+ entries)
+ (+ lno 1))))
+ (m3
+ (lp (read-line i-p) 'neutral '() '()
+ (cons (parse-entry doc-strings options #f input-file lno)
+ entries)
+ (+ lno 1)))
+ (else
+ (lp (read-line i-p) 'neutral '() '() entries (+ lno 1))))))
+
+ ;; State 'options: We're waiting for more options or for a
+ ;; define.
+ ((eq? state 'options)
+ (let ((m1 (regexp-exec option-prefix line))
+ (m2 (regexp-exec signature-start line))
+ (m3 (regexp-exec docstring-end line)))
+ (cond
+ (m1
+ (lp (read-line i-p) 'options
+ doc-strings (cons (match:substring m1 1) options) entries
+ (+ lno 1)))
+ (m2
+ (let ((options (augmented-options line i-p options))) ; ttn-mod
+ (lp (read-line i-p) 'neutral '() '()
+ (cons (parse-entry doc-strings options line input-file lno)
+ entries)
+ (+ lno 1))))
+ (m3
+ (lp (read-line i-p) 'neutral '() '()
+ (cons (parse-entry doc-strings options #f input-file lno)
+ entries)
+ (+ lno 1)))
+ (else
+ (lp (read-line i-p) 'neutral '() '() entries (+ lno 1))))))))))
+
+(define (make-entry symbol signature docstrings options filename line)
+ (vector 'entry symbol signature docstrings options filename line))
+(define (entry-symbol e)
+ (vector-ref e 1))
+(define (entry-signature e)
+ (vector-ref e 2))
+(define (entry-docstrings e)
+ (vector-ref e 3))
+(define (entry-options e)
+ (vector-ref e 4))
+(define (entry-filename e)
+ (vector-ref e 5))
+(define (entry-line e)
+ "This docstring will not be snarfed, unfortunately..."
+ (vector-ref e 6))
+
+;; Create a docstring entry from the docstring line list
+;; @var{doc-strings}, the option line list @var{options} and the
+;; define line @var{def-line}
+(define (parse-entry docstrings options def-line filename line-no)
+; (write-line docstrings)
+ (cond
+ (def-line
+ (make-entry (get-symbol def-line)
+ (make-prototype def-line) (reverse docstrings)
+ (reverse options) filename
+ (+ (- line-no (length docstrings) (length options)) 1)))
+ ((> (length docstrings) 0)
+ (make-entry (string->symbol (car (reverse docstrings)))
+ (car (reverse docstrings))
+ (cdr (reverse docstrings))
+ (reverse options) filename
+ (+ (- line-no (length docstrings) (length options)) 1)))
+ (else
+ (make-entry 'foo "" (reverse docstrings) (reverse options) filename
+ (+ (- line-no (length docstrings) (length options)) 1)))))
+
+;; Create a string which is a procedure prototype. The necessary
+;; information for constructing the prototype is taken from the line
+;; @var{def-line}, which is a line starting with @code{(define...}.
+(define (make-prototype def-line)
+ (call-with-input-string
+ def-line
+ (lambda (s-p)
+ (let* ((paren (read-char s-p))
+ (keyword (read s-p))
+ (tmp (read s-p)))
+ (cond
+ ((pair? tmp)
+ (join-symbols tmp))
+ ((symbol? tmp)
+ (symbol->string tmp))
+ (else
+ ""))))))
+
+(define (get-symbol def-line)
+ (call-with-input-string
+ def-line
+ (lambda (s-p)
+ (let* ((paren (read-char s-p))
+ (keyword (read s-p))
+ (tmp (read s-p)))
+ (cond
+ ((pair? tmp)
+ (car tmp))
+ ((symbol? tmp)
+ tmp)
+ (else
+ 'foo))))))
+
+;; Append the symbols in the string list @var{s}, separated with a
+;; space character.
+(define (join-symbols s)
+ (cond ((null? s)
+ "")
+ ((symbol? s)
+ (string-append ". " (symbol->string s)))
+ ((null? (cdr s))
+ (symbol->string (car s)))
+ (else
+ (string-append (symbol->string (car s)) " " (join-symbols (cdr s))))))
+
+;; Write @var{entries} to @var{output-file} using @var{writer}.
+;; @var{writer} is a proc that takes one entry.
+;; If @var{output-file} is #f, write to stdout.
+;;-ttn-mod: new proc
+(define (write-output entries output-file writer)
+ (with-output-to-port (cond (output-file (open-output-file output-file))
+ (else (current-output-port)))
+ (lambda () (for-each writer entries))))
+
+;; Write an @var{entry} using texinfo format.
+;;-ttn-mod: renamed from `texinfo-output', distilled
+(define (format-texinfo entry)
+ (display "\n\f")
+ (display (entry-symbol entry))
+ (newline)
+ (display "@c snarfed from ")
+ (display (entry-filename entry))
+ (display ":")
+ (display (entry-line entry))
+ (newline)
+ (display "@deffn procedure ")
+ (display (entry-signature entry))
+ (newline)
+ (for-each (lambda (s) (write-line s))
+ (entry-docstrings entry))
+ (for-each (lambda (s) (display "@c ") (write-line s))
+ (entry-options entry))
+ (write-line "@end deffn"))
+
+;; Write an @var{entry} using plain format.
+;;-ttn-mod: renamed from `texinfo-output', distilled
+(define (format-plain entry)
+ (display "Procedure: ")
+ (display (entry-signature entry))
+ (newline)
+ (for-each (lambda (s) (write-line s))
+ (entry-docstrings entry))
+ (for-each (lambda (s) (display ";; ") (write-line s))
+ (entry-options entry))
+ (display "Snarfed from ")
+ (display (entry-filename entry))
+ (display ":")
+ (display (entry-line entry))
+ (newline)
+ (write-line "\f"))
+
+;;; doc-snarf ends here
+;;; frisk --- Grok the module interfaces of a body of files
+
+;; Copyright (C) 2002, 2006, 2010, 2011 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
+
+;;; Commentary:
+
+;; Usage: frisk [options] file ...
+;;
+;; Analyze FILE... module interfaces in aggregate (as a "body"),
+;; and display a summary. Modules that are `define-module'd are
+;; considered "internal" (and those not, "external"). When module X
+;; uses module Y, X is said to be "(a) downstream of" Y, and Y is
+;; "(an) upstream of" X.
+;;
+;; Normally, the summary displays external modules and their internal
+;; downstreams, as this is the usual question asked by a body. There
+;; are several options that modify this output.
+;;
+;; -u, --upstream show upstream edges
+;; -d, --downstream show downstream edges (default)
+;; -i, --internal show internal modules
+;; -x, --external show external modules (default)
+;;
+;; If given both `upstream' and `downstream' options ("frisk -ud"), the
+;; output is formatted: "C MODULE --- UP-LS --- DOWN-LS", where C is
+;; either `i' or `x', and each element of UP-LS and DOWN-LS is (TYPE
+;; MODULE-NAME ...).
+;;
+;; In all other cases, the "C MODULE" occupies its own line, and
+;; subsequent lines list the up- or downstream edges, respectively,
+;; indented by some non-zero amount of whitespace.
+;;
+;; Top-level `use-modules' (or `load' or 'primitive-load') forms in a
+;; file that do not follow a `define-module' result an edge where the
+;; downstream is the "default module", normally `(guile-user)'. This
+;; can be set to another value by using:
+;;
+;; -m, --default-module MOD set MOD as the default module
+
+;; Usage from a Scheme Program: (use-modules (scripts frisk))
+;;
+;; Module export list:
+;; (frisk . args)
+;; (make-frisker . options) => (lambda (files) ...) [see below]
+;; (mod-up-ls module) => upstream edges
+;; (mod-down-ls module) => downstream edges
+;; (mod-int? module) => is the module internal?
+;; (edge-type edge) => symbol: {regular,autoload,computed}
+;; (edge-up edge) => upstream module
+;; (edge-down edge) => downstream module
+;;
+;; OPTIONS is an alist. Recognized keys are:
+;; default-module
+;;
+;; `make-frisker' returns a procedure that takes a list of files, the
+;; FRISKER. FRISKER returns a closure, REPORT, that takes one of the
+;; keys:
+;; modules -- entire list of modules
+;; internal -- list of internal modules
+;; external -- list of external modules
+;; i-up -- list of modules upstream of internal modules
+;; x-up -- list of modules upstream of external modules
+;; i-down -- list of modules downstream of internal modules
+;; x-down -- list of modules downstream of external modules
+;; edges -- list of edges
+;; Note that `x-up' should always be null, since by (lack of!)
+;; definition, we only know external modules by reference.
+;;
+;; The module and edge objects managed by REPORT can be examined in
+;; detail by using the other (self-explanatory) procedures. Be careful
+;; not to confuse a freshly consed list of symbols, like `(a b c)' with
+;; the module `(a b c)'. If you want to find the module by that name,
+;; try: (cond ((member '(a b c) (REPORT 'modules)) => car)).
+
+;; TODO: Make "frisk -ud" output less ugly.
+;; Consider default module as internal; add option to invert.
+;; Support `edge-misc' data.
+
+;;; Code:
+
+(define-module (scripts frisk)
+ \:autoload (ice-9 getopt-long) (getopt-long)
+ \:use-module ((srfi srfi-1) \:select (filter remove))
+ \:export (frisk
+ make-frisker
+ mod-up-ls mod-down-ls mod-int?
+ edge-type edge-up edge-down))
+
+(define %include-in-guild-list #f)
+(define %summary "Show dependency information for a module.")
+
+(define *default-module* '(guile-user))
+
+(define (grok-proc default-module note-use!)
+ (lambda (filename)
+ (let* ((p (open-file filename "r"))
+ (next (lambda () (read p)))
+ (ferret (lambda (use) ;;; handle "((foo bar) \:select ...)"
+ (let ((maybe (car use)))
+ (if (list? maybe)
+ maybe
+ use))))
+ (curmod #f))
+ (let loop ((form (next)))
+ (cond ((eof-object? form))
+ ((not (list? form)) (loop (next)))
+ (else (case (car form)
+ ((define-module)
+ (let ((module (cadr form)))
+ (set! curmod module)
+ (note-use! 'def module #f)
+ (let loop ((ls form))
+ (or (null? ls)
+ (case (car ls)
+ ((#\use-module \:use-module)
+ (note-use! 'regular module (ferret (cadr ls)))
+ (loop (cddr ls)))
+ ((#\autoload \:autoload)
+ (note-use! 'autoload module (cadr ls))
+ (loop (cdddr ls)))
+ (else (loop (cdr ls))))))))
+ ((use-modules)
+ (for-each (lambda (use)
+ (note-use! 'regular
+ (or curmod default-module)
+ (ferret use)))
+ (cdr form)))
+ ((load primitive-load)
+ (note-use! 'computed
+ (or curmod default-module)
+ (let ((file (cadr form)))
+ (if (string? file)
+ file
+ (format #f "[computed in ~A]"
+ filename))))))
+ (loop (next))))))))
+
+(define up-ls (make-object-property)) ; list
+(define dn-ls (make-object-property)) ; list
+(define int? (make-object-property)) ; defined via `define-module'
+
+(define mod-up-ls up-ls)
+(define mod-down-ls dn-ls)
+(define mod-int? int?)
+
+(define (i-or-x module)
+ (if (int? module) 'i 'x))
+
+(define edge-type (make-object-property)) ; symbol
+
+(define (make-edge type up down)
+ (let ((new (cons up down)))
+ (set! (edge-type new) type)
+ new))
+
+(define edge-up car)
+(define edge-down cdr)
+
+(define (up-ls+! m new) (set! (up-ls m) (cons new (up-ls m))))
+(define (dn-ls+! m new) (set! (dn-ls m) (cons new (dn-ls m))))
+
+(define (make-body alist)
+ (lambda (key)
+ (assq-ref alist key)))
+
+(define (scan default-module files)
+ (let* ((modules (list))
+ (edges (list))
+ (intern (lambda (module)
+ (cond ((member module modules) => car)
+ (else (set! (up-ls module) (list))
+ (set! (dn-ls module) (list))
+ (set! modules (cons module modules))
+ module))))
+ (grok (grok-proc default-module
+ (lambda (type d u)
+ (let ((d (intern d)))
+ (if (eq? type 'def)
+ (set! (int? d) #t)
+ (let* ((u (intern u))
+ (edge (make-edge type u d)))
+ (set! edges (cons edge edges))
+ (up-ls+! d edge)
+ (dn-ls+! u edge))))))))
+ (for-each grok files)
+ (make-body
+ `((modules . ,modules)
+ (internal . ,(filter int? modules))
+ (external . ,(remove int? modules))
+ (i-up . ,(filter int? (map edge-down edges)))
+ (x-up . ,(remove int? (map edge-down edges)))
+ (i-down . ,(filter int? (map edge-up edges)))
+ (x-down . ,(remove int? (map edge-up edges)))
+ (edges . ,edges)))))
+
+(define (make-frisker . options)
+ (let ((default-module (or (assq-ref options 'default-module)
+ *default-module*)))
+ (lambda (files)
+ (scan default-module files))))
+
+(define (dump-updown modules)
+ (for-each (lambda (m)
+ (format #t "~A ~A --- ~A --- ~A\n"
+ (i-or-x m) m
+ (map (lambda (edge)
+ (cons (edge-type edge)
+ (edge-up edge)))
+ (up-ls m))
+ (map (lambda (edge)
+ (cons (edge-type edge)
+ (edge-down edge)))
+ (dn-ls m))))
+ modules))
+
+(define (dump-up modules)
+ (for-each (lambda (m)
+ (format #t "~A ~A\n" (i-or-x m) m)
+ (for-each (lambda (edge)
+ (format #t "\t\t\t ~A\t~A\n"
+ (edge-type edge) (edge-up edge)))
+ (up-ls m)))
+ modules))
+
+(define (dump-down modules)
+ (for-each (lambda (m)
+ (format #t "~A ~A\n" (i-or-x m) m)
+ (for-each (lambda (edge)
+ (format #t "\t\t\t ~A\t~A\n"
+ (edge-type edge) (edge-down edge)))
+ (dn-ls m)))
+ modules))
+
+(define (frisk . args)
+ (let* ((parsed-opts (getopt-long
+ (cons "frisk" args) ;;; kludge
+ '((upstream (single-char #\u))
+ (downstream (single-char #\d))
+ (internal (single-char #\i))
+ (external (single-char #\x))
+ (default-module
+ (single-char #\m)
+ (value #t)))))
+ (=u (option-ref parsed-opts 'upstream #f))
+ (=d (option-ref parsed-opts 'downstream #f))
+ (=i (option-ref parsed-opts 'internal #f))
+ (=x (option-ref parsed-opts 'external #f))
+ (files (option-ref parsed-opts '() (list)))
+ (report ((make-frisker
+ `(default-module
+ . ,(option-ref parsed-opts 'default-module
+ *default-module*)))
+ files))
+ (modules (report 'modules))
+ (internal (report 'internal))
+ (external (report 'external))
+ (edges (report 'edges)))
+ (format #t "~A ~A, ~A ~A (~A ~A, ~A ~A), ~A ~A\n\n"
+ (length files) "files"
+ (length modules) "modules"
+ (length internal) "internal"
+ (length external) "external"
+ (length edges) "edges")
+ ((cond ((and =u =d) dump-updown)
+ (=u dump-up)
+ (else dump-down))
+ (cond ((and =i =x) modules)
+ (=i internal)
+ (else external)))))
+
+(define main frisk)
+
+;;; frisk ends here
+;;; generate-autoload --- Display define-module form with autoload info
+
+;; Copyright (C) 2001, 2006, 2011 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Thien-Thi Nguyen
+
+;;; Commentary:
+
+;; Usage: generate-autoload [OPTIONS] FILE1 FILE2 ...
+;;
+;; The autoload form is displayed to standard output:
+;;
+;; (define-module (guile-user)
+;; :autoload (ZAR FOO) (FOO-1 FOO-2 ...)
+;; :
+;; :
+;; :autoload (ZAR BAR) (BAR-1 BAR-2 ...))
+;;
+;; For each file, a symbol triggers an autoload if it is found in one
+;; of these situations:
+;; - in the `:export' clause of a `define-module' form
+;; - in a top-level `export' or `export-syntax' form
+;; - in a `define-public' form
+;; - in a `defmacro-public' form
+;;
+;; The module name is inferred from the `define-module' form. If either the
+;; module name or the exports list cannot be determined, no autoload entry is
+;; generated for that file.
+;;
+;; Options:
+;; --target MODULE-NAME -- Use MODULE-NAME instead of `(guile-user)'.
+;; Note that some shells may require you to
+;; quote the argument to handle parentheses
+;; and spaces.
+;;
+;; Usage examples from Scheme code as a module:
+;; (use-modules (scripts generate-autoload))
+;; (generate-autoload "generate-autoload")
+;; (generate-autoload "--target" "(my module)" "generate-autoload")
+;; (apply generate-autoload "--target" "(my module)" '("foo" "bar" "baz"))
+
+;;; Code:
+
+(define-module (scripts generate-autoload)
+ \:export (generate-autoload))
+
+(define %include-in-guild-list #f)
+(define %summary "Generate #\autoload clauses for a module.")
+
+(define (autoload-info file)
+ (let ((p (open-input-file file)))
+ (let loop ((form (read p)) (module-name #f) (exports '()))
+ (if (eof-object? form)
+ (and module-name
+ (not (null? exports))
+ (list module-name exports)) ; ret
+ (cond ((and (list? form)
+ (< 1 (length form))
+ (eq? 'define-module (car form)))
+ (loop (read p)
+ (cadr form)
+ (cond ((member '\:export form)
+ => (lambda (val)
+ (append (cadr val) exports)))
+ (else exports))))
+ ((and (list? form)
+ (< 1 (length form))
+ (memq (car form) '(export export-syntax)))
+ (loop (read p)
+ module-name
+ (append (cdr form) exports)))
+ ((and (list? form)
+ (< 2 (length form))
+ (eq? 'define-public (car form))
+ (list? (cadr form))
+ (symbol? (caadr form)))
+ (loop (read p)
+ module-name
+ (cons (caadr form) exports)))
+ ((and (list? form)
+ (< 2 (length form))
+ (eq? 'define-public (car form))
+ (symbol? (cadr form)))
+ (loop (read p)
+ module-name
+ (cons (cadr form) exports)))
+ ((and (list? form)
+ (< 3 (length form))
+ (eq? 'defmacro-public (car form))
+ (symbol? (cadr form)))
+ (loop (read p)
+ module-name
+ (cons (cadr form) exports)))
+ (else (loop (read p) module-name exports)))))))
+
+(define (generate-autoload . args)
+ (let* ((module-count 0)
+ (syms-count 0)
+ (target-override (cond ((member "--target" args) => cadr)
+ (else #f)))
+ (files (if target-override (cddr args) (cdr args))))
+ (display ";;; do not edit --- generated ")
+ (display (strftime "%Y-%m-%d %H:%M:%S" (localtime (current-time))))
+ (newline)
+ (display "(define-module ")
+ (display (or target-override "(guile-user)"))
+ (for-each (lambda (file)
+ (cond ((autoload-info file)
+ => (lambda (info)
+ (and info
+ (apply (lambda (module-name exports)
+ (set! module-count (1+ module-count))
+ (set! syms-count (+ (length exports)
+ syms-count))
+ (for-each display
+ (list "\n :autoload "
+ module-name " "
+ exports)))
+ info))))))
+ files)
+ (display ")")
+ (newline)
+ (for-each display (list " ;;; "
+ syms-count " symbols in "
+ module-count " modules\n"))))
+
+(define main generate-autoload)
+
+;;; generate-autoload ends here
+;;; Help --- Show help on guild commands
+
+;;;; Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free
+;;;; Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;;;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; Usage: help
+;;
+;; Show help for Guild scripts.
+
+;;; Code:
+
+(define-module (scripts help)
+ #\use-module (ice-9 format)
+ #\use-module (ice-9 documentation)
+ #\use-module ((srfi srfi-1) #\select (fold append-map))
+ #\export (show-help show-summary show-usage main))
+
+(define %summary "Show a brief help message.")
+(define %synopsis "help\nhelp --all\nhelp COMMAND")
+(define %help "
+Show help on guild commands. With --all, show arcane incantations as
+well. With COMMAND, show more detailed help for a particular command.
+")
+
+
+(define (directory-files dir)
+ (if (and (file-exists? dir) (file-is-directory? dir))
+ (let ((dir-stream (opendir dir)))
+ (let loop ((new (readdir dir-stream))
+ (acc '()))
+ (if (eof-object? new)
+ (begin
+ (closedir dir-stream)
+ acc)
+ (loop (readdir dir-stream)
+ (if (or (string=? "." new) ; ignore
+ (string=? ".." new)) ; ignore
+ acc
+ (cons new acc))))))
+ '()))
+
+(define (strip-extensions path)
+ (or-map (lambda (ext)
+ (and
+ (string-suffix? ext path)
+ ;; We really can't be adding e.g. ChangeLog-2008 to the set
+ ;; of runnable scripts, just because "" is a valid
+ ;; extension, by default. So hack around that here.
+ (not (string-null? ext))
+ (substring path 0
+ (- (string-length path) (string-length ext)))))
+ (append %load-compiled-extensions %load-extensions)))
+
+(define (unique l)
+ (cond ((null? l) l)
+ ((null? (cdr l)) l)
+ ((equal? (car l) (cadr l)) (unique (cdr l)))
+ (else (cons (car l) (unique (cdr l))))))
+
+(define (find-submodules head)
+ (let ((shead (map symbol->string head)))
+ (unique
+ (sort
+ (append-map (lambda (path)
+ (fold (lambda (x rest)
+ (let ((stripped (strip-extensions x)))
+ (if stripped (cons stripped rest) rest)))
+ '()
+ (directory-files
+ (fold (lambda (x y) (in-vicinity y x)) path shead))))
+ %load-path)
+ string<?))))
+
+(define (list-commands all?)
+ (display "\\
+Usage: guild COMMAND [ARGS]
+Run command-line scripts provided by GNU Guile and related programs.
+
+Commands:
+")
+
+ (for-each
+ (lambda (name)
+ (let* ((modname `(scripts ,(string->symbol name)))
+ (mod (resolve-module modname #\ensure #f))
+ (summary (and mod (and=> (module-variable mod '%summary)
+ variable-ref))))
+ (if (and mod
+ (or all?
+ (let ((v (module-variable mod '%include-in-guild-list)))
+ (if v (variable-ref v) #t))))
+ (if summary
+ (format #t " ~A ~23t~a\n" name summary)
+ (format #t " ~A\n" name)))))
+ (find-submodules '(scripts)))
+ (format #t "
+For help on a specific command, try \"guild help COMMAND\".
+
+Report guild bugs to ~a
+GNU Guile home page: <http://www.gnu.org/software/guile/>
+General help using GNU software: <http://www.gnu.org/gethelp/>
+For complete documentation, run: info guile 'Using Guile Tools'
+" %guile-bug-report-address))
+
+(define (module-commentary mod)
+ (file-commentary
+ (%search-load-path (module-filename mod))))
+
+(define (module-command-name mod)
+ (symbol->string (car (last-pair (module-name mod)))))
+
+(define* (show-usage mod #\optional (port (current-output-port)))
+ (let ((usages (string-split
+ (let ((var (module-variable mod '%synopsis)))
+ (if var
+ (variable-ref var)
+ (string-append (module-command-name mod)
+ " OPTION...")))
+ #\newline)))
+ (display "Usage: guild " port)
+ (display (car usages))
+ (newline port)
+ (for-each (lambda (u)
+ (display " guild " port)
+ (display u port)
+ (newline port))
+ (cdr usages))))
+
+(define* (show-summary mod #\optional (port (current-output-port)))
+ (let ((var (module-variable mod '%summary)))
+ (if var
+ (begin
+ (display (variable-ref var) port)
+ (newline port)))))
+
+(define* (show-help mod #\optional (port (current-output-port)))
+ (show-usage mod port)
+ (show-summary mod port)
+ (cond
+ ((module-variable mod '%help)
+ => (lambda (var)
+ (display (variable-ref var) port)
+ (newline port)))
+ ((module-commentary mod)
+ => (lambda (commentary)
+ (newline port)
+ (display commentary port)))
+ (else
+ (format #t "No documentation found for command \"~a\".\n"
+ (module-command-name mod)))))
+
+(define %mod (current-module))
+(define (main . args)
+ (cond
+ ((null? args)
+ (list-commands #f))
+ ((or (equal? args '("--all")) (equal? args '("-a")))
+ (list-commands #t))
+ ((and (null? (cdr args)) (not (string-prefix? "-" (car args))))
+ ;; help for particular command
+ (let ((name (car args)))
+ (cond
+ ((resolve-module `(scripts ,(string->symbol name)) #\ensure #f)
+ => (lambda (mod)
+ (show-help mod)
+ (exit 0)))
+ (else
+ (format #t "No command named \"~a\".\n" name)
+ (exit 1)))))
+ (else
+ (show-help %mod (current-error-port))
+ (exit 1))))
+;;; lint --- Preemptive checks for coding errors in Guile Scheme code
+
+;; Copyright (C) 2002, 2006, 2011 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Neil Jerram
+
+;;; Commentary:
+
+;; Usage: lint FILE1 FILE2 ...
+;;
+;; Perform various preemptive checks for coding errors in Guile Scheme
+;; code.
+;;
+;; Right now, there is only one check available, for unresolved free
+;; variables. The intention is that future lint-like checks will be
+;; implemented by adding to this script file.
+;;
+;; Unresolved free variables
+;; -------------------------
+;;
+;; Free variables are those whose definitions come from outside the
+;; module under investigation. In Guile, these definitions are
+;; imported from other modules using `#\use-module' forms.
+;;
+;; This tool scans the specified files for unresolved free variables -
+;; i.e. variables for which you may have forgotten the appropriate
+;; `#\use-module', or for which the module that is supposed to export
+;; them forgot to.
+;;
+;; It isn't guaranteed that the scan will find absolutely all such
+;; errors. Quoted (and quasiquoted) expressions are skipped, since
+;; they are most commonly used to describe constant data, not code, so
+;; code that is explicitly evaluated using `eval' will not be checked.
+;; For example, the `unresolved-var' in `(eval 'unresolved-var
+;; (current-module))' would be missed.
+;;
+;; False positives are also possible. Firstly, the tool doesn't
+;; understand all possible forms of implicit quoting; in particular,
+;; it doesn't detect and expand uses of macros. Secondly, it picks up
+;; explicit compatibility code like `(if (defined? 'x) (define y x))'.
+;; Thirdly, there are occasional oddities like `next-method'.
+;; However, the number of false positives for realistic code is
+;; hopefully small enough that they can be individually considered and
+;; ignored.
+;;
+;; Example
+;; -------
+;;
+;; Note: most of the unresolved variables found in this example are
+;; false positives, as you would hope. => scope for improvement.
+;;
+;; $ guild lint `guild`
+;; No unresolved free variables in PROGRAM
+;; No unresolved free variables in autofrisk
+;; No unresolved free variables in display-commentary
+;; Unresolved free variables in doc-snarf:
+;; doc-snarf-version
+;; No unresolved free variables in frisk
+;; No unresolved free variables in generate-autoload
+;; No unresolved free variables in lint
+;; No unresolved free variables in punify
+;; No unresolved free variables in read-scheme-source
+;; Unresolved free variables in snarf-check-and-output-texi:
+;; name
+;; pos
+;; line
+;; x
+;; rest
+;; ...
+;; do-argpos
+;; do-command
+;; do-args
+;; type
+;; num
+;; file
+;; do-arglist
+;; req
+;; opt
+;; var
+;; command
+;; do-directive
+;; s
+;; ?
+;; No unresolved free variables in use2dot
+
+;;; Code:
+
+(define-module (scripts lint)
+ #\use-module (ice-9 common-list)
+ #\use-module (ice-9 format)
+ #\export (lint))
+
+(define %include-in-guild-list #f)
+(define %summary "Check for bugs and style errors in a Scheme file.")
+
+(define (lint filename)
+ (let ((module-name (scan-file-for-module-name filename))
+ (free-vars (uniq (scan-file-for-free-variables filename))))
+ (let ((module (resolve-module module-name))
+ (all-resolved? #t))
+ (format #t "Resolved module: ~S\n" module)
+ (let loop ((free-vars free-vars))
+ (or (null? free-vars)
+ (begin
+ (catch #t
+ (lambda ()
+ (eval (car free-vars) module))
+ (lambda args
+ (if all-resolved?
+ (format #t
+ "Unresolved free variables in ~A:\n"
+ filename))
+ (write-char #\tab)
+ (write (car free-vars))
+ (newline)
+ (set! all-resolved? #f)))
+ (loop (cdr free-vars)))))
+ (if all-resolved?
+ (format #t
+ "No unresolved free variables in ~A\n"
+ filename)))))
+
+(define (scan-file-for-module-name filename)
+ (with-input-from-file filename
+ (lambda ()
+ (let loop ((x (read)))
+ (cond ((eof-object? x) #f)
+ ((and (pair? x)
+ (eq? (car x) 'define-module))
+ (cadr x))
+ (else (loop (read))))))))
+
+(define (scan-file-for-free-variables filename)
+ (with-input-from-file filename
+ (lambda ()
+ (let loop ((x (read)) (fvlists '()))
+ (if (eof-object? x)
+ (apply append fvlists)
+ (loop (read) (cons (detect-free-variables x '()) fvlists)))))))
+
+; guile> (detect-free-variables '(let ((a 1)) a) '())
+; ()
+; guile> (detect-free-variables '(let ((a 1)) b) '())
+; (b)
+; guile> (detect-free-variables '(let ((a 1) (b a)) b) '())
+; (a)
+; guile> (detect-free-variables '(let* ((a 1) (b a)) b) '())
+; ()
+; guile> (detect-free-variables '(define a 1) '())
+; ()
+; guile> (detect-free-variables '(define a b) '())
+; (b)
+; guile> (detect-free-variables '(define (a b c) b) '())
+; ()
+; guile> (detect-free-variables '(define (a b c) e) '())
+; (e)
+
+(define (detect-free-variables x locals)
+ ;; Given an expression @var{x} and a list @var{locals} of local
+ ;; variables (symbols) that are in scope for @var{x}, return a list
+ ;; of free variable symbols.
+ (cond ((symbol? x)
+ (if (memq x locals) '() (list x)))
+
+ ((pair? x)
+ (case (car x)
+ ((define-module define-generic quote quasiquote)
+ ;; No code of interest in these expressions.
+ '())
+
+ ((let letrec)
+ ;; Check for named let. If there is a name, transform the
+ ;; expression so that it looks like an unnamed let with
+ ;; the name as one of the bindings.
+ (if (symbol? (cadr x))
+ (set-cdr! x (cons (cons (list (cadr x) #f) (caddr x))
+ (cdddr x))))
+ ;; Unnamed let processing.
+ (let ((letrec? (eq? (car x) 'letrec))
+ (locals-for-let-body (append locals (map car (cadr x)))))
+ (append (apply append
+ (map (lambda (binding)
+ (detect-free-variables (cadr binding)
+ (if letrec?
+ locals-for-let-body
+ locals)))
+ (cadr x)))
+ (apply append
+ (map (lambda (bodyform)
+ (detect-free-variables bodyform
+ locals-for-let-body))
+ (cddr x))))))
+
+ ((let* and-let*)
+ ;; Handle bindings recursively.
+ (if (null? (cadr x))
+ (apply append
+ (map (lambda (bodyform)
+ (detect-free-variables bodyform locals))
+ (cddr x)))
+ (append (detect-free-variables (cadr (caadr x)) locals)
+ (detect-free-variables `(let* ,(cdadr x) ,@(cddr x))
+ (cons (caaadr x) locals)))))
+
+ ((define define-public define-macro)
+ (if (pair? (cadr x))
+ (begin
+ (set! locals (cons (caadr x) locals))
+ (detect-free-variables `(lambda ,(cdadr x) ,@(cddr x))
+ locals))
+ (begin
+ (set! locals (cons (cadr x) locals))
+ (detect-free-variables (caddr x) locals))))
+
+ ((lambda lambda*)
+ (let ((locals-for-lambda-body (let loop ((locals locals)
+ (args (cadr x)))
+ (cond ((null? args) locals)
+ ((pair? args)
+ (loop (cons (car args) locals)
+ (cdr args)))
+ (else
+ (cons args locals))))))
+ (apply append
+ (map (lambda (bodyform)
+ (detect-free-variables bodyform
+ locals-for-lambda-body))
+ (cddr x)))))
+
+ ((receive)
+ (let ((locals-for-receive-body (append locals (cadr x))))
+ (apply append
+ (detect-free-variables (caddr x) locals)
+ (map (lambda (bodyform)
+ (detect-free-variables bodyform
+ locals-for-receive-body))
+ (cdddr x)))))
+
+ ((define-method define*)
+ (let ((locals-for-method-body (let loop ((locals locals)
+ (args (cdadr x)))
+ (cond ((null? args) locals)
+ ((pair? args)
+ (loop (cons (if (pair? (car args))
+ (caar args)
+ (car args))
+ locals)
+ (cdr args)))
+ (else
+ (cons args locals))))))
+ (apply append
+ (map (lambda (bodyform)
+ (detect-free-variables bodyform
+ locals-for-method-body))
+ (cddr x)))))
+
+ ((define-class)
+ ;; Avoid picking up slot names at the start of slot
+ ;; definitions.
+ (apply append
+ (map (lambda (slot/option)
+ (detect-free-variables-noncar (if (pair? slot/option)
+ (cdr slot/option)
+ slot/option)
+ locals))
+ (cdddr x))))
+
+ ((case)
+ (apply append
+ (detect-free-variables (cadr x) locals)
+ (map (lambda (case)
+ (detect-free-variables (cdr case) locals))
+ (cddr x))))
+
+ ((unquote unquote-splicing else =>)
+ (detect-free-variables-noncar (cdr x) locals))
+
+ (else (append (detect-free-variables (car x) locals)
+ (detect-free-variables-noncar (cdr x) locals)))))
+
+ (else '())))
+
+(define (detect-free-variables-noncar x locals)
+ ;; Given an expression @var{x} and a list @var{locals} of local
+ ;; variables (symbols) that are in scope for @var{x}, return a list
+ ;; of free variable symbols.
+ (cond ((symbol? x)
+ (if (memq x locals) '() (list x)))
+
+ ((pair? x)
+ (case (car x)
+ ((=>)
+ (detect-free-variables-noncar (cdr x) locals))
+
+ (else (append (detect-free-variables (car x) locals)
+ (detect-free-variables-noncar (cdr x) locals)))))
+
+ (else '())))
+
+(define (main . files)
+ (for-each lint files))
+
+;;; lint ends here
+;;; List --- List scripts that can be invoked by guild -*- coding: iso-8859-1 -*-
+
+;;;; Copyright (C) 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free
+;;;; Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;;;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; Usage: list
+;;
+;; List scripts that can be invoked by guild.
+
+;;; Code:
+
+(define-module (scripts list)
+ #\use-module (srfi srfi-1)
+ #\export (list-scripts))
+
+(define %include-in-guild-list #f)
+(define %summary "An alias for \"help\".")
+
+
+(define (directory-files dir)
+ (if (and (file-exists? dir) (file-is-directory? dir))
+ (let ((dir-stream (opendir dir)))
+ (let loop ((new (readdir dir-stream))
+ (acc '()))
+ (if (eof-object? new)
+ (begin
+ (closedir dir-stream)
+ acc)
+ (loop (readdir dir-stream)
+ (if (or (string=? "." new) ; ignore
+ (string=? ".." new)) ; ignore
+ acc
+ (cons new acc))))))
+ '()))
+
+(define (strip-extensions path)
+ (or-map (lambda (ext)
+ (and
+ (string-suffix? ext path)
+ ;; We really can't be adding e.g. ChangeLog-2008 to the set
+ ;; of runnable scripts, just because "" is a valid
+ ;; extension, by default. So hack around that here.
+ (not (string-null? ext))
+ (substring path 0
+ (- (string-length path) (string-length ext)))))
+ (append %load-compiled-extensions %load-extensions)))
+
+(define (unique l)
+ (cond ((null? l) l)
+ ((null? (cdr l)) l)
+ ((equal? (car l) (cadr l)) (unique (cdr l)))
+ (else (cons (car l) (unique (cdr l))))))
+
+(define (find-submodules head)
+ (let ((shead (map symbol->string head)))
+ (unique
+ (sort
+ (append-map (lambda (path)
+ (fold (lambda (x rest)
+ (let ((stripped (strip-extensions x)))
+ (if stripped (cons stripped rest) rest)))
+ '()
+ (directory-files
+ (fold (lambda (x y) (in-vicinity y x)) path shead))))
+ %load-path)
+ string<?))))
+
+(define (list-scripts . args)
+ (for-each (lambda (x)
+ ;; would be nice to show a summary.
+ (format #t "~A\n" x))
+ (find-submodules '(scripts))))
+
+(define (main . args)
+ (apply (@@ (scripts help) main) args))
+;;; punify --- Display Scheme code w/o unnecessary comments / whitespace
+
+;; Copyright (C) 2001, 2006 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Thien-Thi Nguyen
+
+;;; Commentary:
+
+;; Usage: punify FILE1 FILE2 ...
+;;
+;; Each file's forms are read and written to stdout.
+;; The effect is to remove comments and much non-essential whitespace.
+;; This is useful when installing Scheme source to space-limited media.
+;;
+;; Example:
+;; $ wc ./punify ; ./punify ./punify | wc
+;; 89 384 3031 ./punify
+;; 0 42 920
+;;
+;; TODO: Read from stdin.
+;; Handle vectors.
+;; Identifier punification.
+
+;;; Code:
+
+(define-module (scripts punify)
+ \:export (punify))
+
+(define %include-in-guild-list #f)
+(define %summary "Strip comments and whitespace from a Scheme file.")
+
+(define (write-punily form)
+ (cond ((and (list? form) (not (null? form)))
+ (let ((first (car form)))
+ (display "(")
+ (write-punily first)
+ (let loop ((ls (cdr form)) (last-was-list? (list? first)))
+ (if (null? ls)
+ (display ")")
+ (let* ((new-first (car ls))
+ (this-is-list? (list? new-first)))
+ (and (not last-was-list?)
+ (not this-is-list?)
+ (display " "))
+ (write-punily new-first)
+ (loop (cdr ls) this-is-list?))))))
+ ((and (symbol? form)
+ (let ((ls (string->list (symbol->string form))))
+ (and (char=? (car ls) #\:)
+ (not (memq #\space ls))
+ (list->string (cdr ls)))))
+ => (lambda (symbol-name-after-colon)
+ (display #\:)
+ (display symbol-name-after-colon)))
+ (else (write form))))
+
+(define (punify-one file)
+ (with-input-from-file file
+ (lambda ()
+ (let ((toke (lambda () (read (current-input-port)))))
+ (let loop ((form (toke)))
+ (or (eof-object? form)
+ (begin
+ (write-punily form)
+ (loop (toke)))))))))
+
+(define (punify . args)
+ (for-each punify-one args))
+
+(define main punify)
+
+;;; punify ends here
+;;; read-rfc822 --- Validate RFC822 file by displaying it to stdout
+
+;; Copyright (C) 2002, 2004, 2006, 2011 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
+
+;;; Commentary:
+
+;; Usage: read-rfc822 FILE
+;;
+;; Read FILE, assumed to be in RFC822 format, and display it to stdout.
+;; This is not very interesting, admittedly.
+;;
+;; For Scheme programming, this module exports two procs:
+;; (read-rfc822 . args) ; only first arg used
+;; (read-rfc822-silently port)
+;;
+;; Parse FILE (a string) or PORT, respectively, and return a query proc that
+;; takes a symbol COMP, and returns the message component COMP. Supported
+;; values for COMP (and the associated query return values) are:
+;; from -- #f (reserved for future mbox support)
+;; headers -- alist of (HEADER-SYMBOL . "VALUE-STRING") pairs, in order
+;; body -- rest of the mail message, a string
+;; body-lines -- rest of the mail message, as a list of lines
+;; Any other query results in a "bad component" error.
+;;
+;; TODO: Add "-m" option (mbox support).
+
+;;; Code:
+
+(define-module (scripts read-rfc822)
+ \:use-module (ice-9 regex)
+ \:use-module (ice-9 rdelim)
+ \:autoload (srfi srfi-13) (string-join)
+ \:export (read-rfc822 read-rfc822-silently))
+
+(define %include-in-guild-list #f)
+(define %summary "Validate an RFC822-style file.")
+
+(define from-line-rx (make-regexp "^From "))
+(define header-name-rx (make-regexp "^([^:]+):[ \t]*"))
+(define header-cont-rx (make-regexp "^[ \t]+"))
+
+(define option #f) ; for future "-m"
+
+(define (drain-message port)
+ (let loop ((line (read-line port)) (acc '()))
+ (cond ((eof-object? line)
+ (reverse acc))
+ ((and option (regexp-exec from-line-rx line))
+ (for-each (lambda (c)
+ (unread-char c port))
+ (cons #\newline
+ (reverse (string->list line))))
+ (reverse acc))
+ (else
+ (loop (read-line port) (cons line acc))))))
+
+(define (parse-message port)
+ (let* ((from (and option
+ (match:suffix (regexp-exec from-line-rx
+ (read-line port)))))
+ (body-lines #f)
+ (body #f)
+ (headers '())
+ (add-header! (lambda (reversed-hlines)
+ (let* ((hlines (reverse reversed-hlines))
+ (first (car hlines))
+ (m (regexp-exec header-name-rx first))
+ (name (string->symbol (match:substring m 1)))
+ (data (string-join
+ (cons (substring first (match:end m))
+ (cdr hlines))
+ " ")))
+ (set! headers (acons name data headers))))))
+ ;; "From " is only one line
+ (let loop ((line (read-line port)) (current-header #f))
+ (cond ((string-null? line)
+ (and current-header (add-header! current-header))
+ (set! body-lines (drain-message port)))
+ ((regexp-exec header-cont-rx line)
+ => (lambda (m)
+ (loop (read-line port)
+ (cons (match:suffix m) current-header))))
+ (else
+ (and current-header (add-header! current-header))
+ (loop (read-line port) (list line)))))
+ (set! headers (reverse headers))
+ (lambda (component)
+ (case component
+ ((from) from)
+ ((body-lines) body-lines)
+ ((headers) headers)
+ ((body) (or body
+ (begin (set! body (string-join body-lines "\n" 'suffix))
+ body)))
+ (else (error "bad component:" component))))))
+
+(define (read-rfc822-silently port)
+ (parse-message port))
+
+(define (display-rfc822 parse)
+ (cond ((parse 'from) => (lambda (from) (format #t "From ~A\n" from))))
+ (for-each (lambda (header)
+ (format #t "~A: ~A\n" (car header) (cdr header)))
+ (parse 'headers))
+ (format #t "\n~A" (parse 'body)))
+
+(define (read-rfc822 . args)
+ (let ((parse (read-rfc822-silently (open-file (car args) OPEN_READ))))
+ (display-rfc822 parse))
+ #t)
+
+(define main read-rfc822)
+
+;;; read-rfc822 ends here
+;;; read-scheme-source --- Read a file, recognizing scheme forms and comments
+
+;; Copyright (C) 2001, 2006, 2011 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Thien-Thi Nguyen
+
+;;; Commentary:
+
+;; Usage: read-scheme-source FILE1 FILE2 ...
+;;
+;; This program parses each FILE and writes to stdout sexps that describe the
+;; top-level structures of the file: scheme forms, single-line comments, and
+;; hash-bang comments. You can further process these (to associate comments
+;; w/ scheme forms as a kind of documentation, for example).
+;;
+;; The output sexps have one of these forms:
+;;
+;; (quote (filename FILENAME))
+;;
+;; (quote (comment :leading-semicolons N
+;; :text LINE))
+;;
+;; (quote (whitespace :text LINE))
+;;
+;; (quote (hash-bang-comment :line LINUM
+;; :line-count N
+;; :text-list (LINE1 LINE2 ...)))
+;;
+;; (quote (following-form-properties :line LINUM
+;; :line-count N)
+;; :type TYPE
+;; :signature SIGNATURE
+;; :std-int-doc DOCSTRING))
+;;
+;; SEXP
+;;
+;; The first four are straightforward (both FILENAME and LINE are strings sans
+;; newline, while LINUM and N are integers). The last two always go together,
+;; in that order. SEXP is scheme code processed only by `read' and then
+;; `write'.
+;;
+;; The :type field may be omitted if the form is not recognized. Otherwise,
+;; TYPE may be one of: procedure, alias, define-module, variable.
+;;
+;; The :signature field may be omitted if the form is not a procedure.
+;; Otherwise, SIGNATURE is a list showing the procedure's signature.
+;;
+;; If the type is `procedure' and the form has a standard internal docstring
+;; (first body form a string), that is extracted in full -- including any
+;; embedded newlines -- and recorded by field :std-int-doc.
+;;
+;;
+;; Usage from a program: The output list of sexps can be retrieved by scheme
+;; programs w/o having to capture stdout, like so:
+;;
+;; (use-modules (scripts read-scheme-source))
+;; (define source-forms (read-scheme-source-silently "FILE1" "FILE2" ...))
+;;
+;; There are also two convenience procs exported for use by Scheme programs:
+;;
+;; (clump FORMS) --- filter FORMS combining contiguous comment forms that
+;; have the same number of leading semicolons.
+;;
+;; (quoted? SYM FORM) --- see if FORM looks like: "(quote (SYM ...))", parse
+;; the ":tags", and return alist of (TAG . VAL) elems.
+;;
+;; TODO: Add option "--clump-comments", maybe w/ different clumping styles.
+;; Make `annotate!' extensible.
+
+;;; Code:
+
+(define-module (scripts read-scheme-source)
+ \:use-module (ice-9 rdelim)
+ \:export (read-scheme-source
+ read-scheme-source-silently
+ quoted?
+ clump))
+
+(define %include-in-guild-list #f)
+(define %summary "Print a parsed representation of a Scheme file.")
+
+;; Try to figure out what FORM is and its various attributes.
+;; Call proc NOTE! with key (a symbol) and value.
+;;
+(define (annotate! form note!)
+ (cond ((and (list? form)
+ (< 2 (length form))
+ (eq? 'define (car form))
+ (pair? (cadr form))
+ (symbol? (caadr form)))
+ (note! '\:type 'procedure)
+ (note! '\:signature (cadr form))
+ (and (< 3 (length form))
+ (string? (caddr form))
+ (note! '\:std-int-doc (caddr form))))
+ ((and (list? form)
+ (< 2 (length form))
+ (eq? 'define (car form))
+ (symbol? (cadr form))
+ (list? (caddr form))
+ (< 3 (length (caddr form)))
+ (eq? 'lambda (car (caddr form)))
+ (string? (caddr (caddr form))))
+ (note! '\:type 'procedure)
+ (note! '\:signature (cons (cadr form) (cadr (caddr form))))
+ (note! '\:std-int-doc (caddr (caddr form))))
+ ((and (list? form)
+ (= 3 (length form))
+ (eq? 'define (car form))
+ (symbol? (cadr form))
+ (symbol? (caddr form)))
+ (note! '\:type 'alias))
+ ((and (list? form)
+ (eq? 'define-module (car form)))
+ (note! '\:type 'define-module))
+ ;; Add other types here.
+ (else (note! '\:type 'variable))))
+
+;; Process FILE, calling NB! on parsed top-level elements.
+;; Recognized: #!-!# and regular comments in addition to normal forms.
+;;
+(define (process file nb!)
+ (nb! `'(filename ,file))
+ (let ((hash-bang-rx (make-regexp "^#!"))
+ (bang-hash-rx (make-regexp "^!#"))
+ (all-comment-rx (make-regexp "^[ \t]*(;+)"))
+ (all-whitespace-rx (make-regexp "^[ \t]*$"))
+ (p (open-input-file file)))
+ (let loop ((n (1+ (port-line p))) (line (read-line p)))
+ (or (not n)
+ (eof-object? line)
+ (begin
+ (cond ((regexp-exec hash-bang-rx line)
+ (let loop ((line (read-line p))
+ (text (list line)))
+ (if (or (eof-object? line)
+ (regexp-exec bang-hash-rx line))
+ (nb! `'(hash-bang-comment
+ \:line ,n
+ \:line-count ,(1+ (length text))
+ \:text-list ,(reverse
+ (cons line text))))
+ (loop (read-line p)
+ (cons line text)))))
+ ((regexp-exec all-whitespace-rx line)
+ (nb! `'(whitespace \:text ,line)))
+ ((regexp-exec all-comment-rx line)
+ => (lambda (m)
+ (nb! `'(comment
+ \:leading-semicolons
+ ,(let ((m1 (vector-ref m 1)))
+ (- (cdr m1) (car m1)))
+ \:text ,line))))
+ (else
+ (unread-string line p)
+ (let* ((form (read p))
+ (count (- (port-line p) n))
+ (props (let* ((props '())
+ (prop+ (lambda args
+ (set! props
+ (append props args)))))
+ (annotate! form prop+)
+ props)))
+ (or (= count 1) ; ugh
+ (begin
+ (read-line p)
+ (set! count (1+ count))))
+ (nb! `'(following-form-properties
+ \:line ,n
+ \:line-count ,count
+ ,@props))
+ (nb! form))))
+ (loop (1+ (port-line p)) (read-line p)))))))
+
+;;; entry points
+
+(define (read-scheme-source-silently . files)
+ "See commentary in module (scripts read-scheme-source)."
+ (let* ((res '()))
+ (for-each (lambda (file)
+ (process file (lambda (e) (set! res (cons e res)))))
+ files)
+ (reverse res)))
+
+(define (read-scheme-source . files)
+ "See commentary in module (scripts read-scheme-source)."
+ (for-each (lambda (file)
+ (process file (lambda (e) (write e) (newline))))
+ files))
+
+;; Recognize: (quote (SYM :TAG1 VAL1 :TAG2 VAL2 ...))
+;; and return alist: ((TAG1 . VAL1) (TAG2 . VAL2) ...)
+;; where the tags are symbols.
+;;
+(define (quoted? sym form)
+ (and (list? form)
+ (= 2 (length form))
+ (eq? 'quote (car form))
+ (let ((inside (cadr form)))
+ (and (list? inside)
+ (< 0 (length inside))
+ (eq? sym (car inside))
+ (let loop ((ls (cdr inside)) (alist '()))
+ (if (null? ls)
+ alist ; retval
+ (let ((first (car ls)))
+ (or (symbol? first)
+ (error "bad list!"))
+ (loop (cddr ls)
+ (acons (string->symbol
+ (substring (symbol->string first) 1))
+ (cadr ls)
+ alist)))))))))
+
+;; Filter FORMS, combining contiguous comment forms that have the same number
+;; of leading semicolons. Do not include in them whitespace lines.
+;; Whitespace lines outside of such comment groupings are ignored, as are
+;; hash-bang comments. All other forms are passed through unchanged.
+;;
+(define (clump forms)
+ (let loop ((forms forms) (acc '()) (pass-this-one-through? #f))
+ (if (null? forms)
+ (reverse acc) ; retval
+ (let ((form (car forms)))
+ (cond (pass-this-one-through?
+ (loop (cdr forms) (cons form acc) #f))
+ ((quoted? 'following-form-properties form)
+ (loop (cdr forms) (cons form acc) #t))
+ ((quoted? 'whitespace form) ;;; ignore
+ (loop (cdr forms) acc #f))
+ ((quoted? 'hash-bang-comment form) ;;; ignore for now
+ (loop (cdr forms) acc #f))
+ ((quoted? 'comment form)
+ => (lambda (alist)
+ (let cloop ((inner-forms (cdr forms))
+ (level (assq-ref alist 'leading-semicolons))
+ (text (list (assq-ref alist 'text))))
+ (let ((up (lambda ()
+ (loop inner-forms
+ (cons (cons level (reverse text))
+ acc)
+ #f))))
+ (if (null? inner-forms)
+ (up)
+ (let ((inner-form (car inner-forms)))
+ (cond ((quoted? 'comment inner-form)
+ => (lambda (inner-alist)
+ (let ((new-level
+ (assq-ref
+ inner-alist
+ 'leading-semicolons)))
+ (if (= new-level level)
+ (cloop (cdr inner-forms)
+ level
+ (cons (assq-ref
+ inner-alist
+ 'text)
+ text))
+ (up)))))
+ (else (up)))))))))
+ (else (loop (cdr forms) (cons form acc) #f)))))))
+
+;;; script entry point
+
+(define main read-scheme-source)
+
+;;; read-scheme-source ends here
+;;; read-text-outline --- Read a text outline and display it as a sexp
+
+;; Copyright (C) 2002, 2006, 2011 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
+
+;;; Commentary:
+
+;; Usage: read-text-outline OUTLINE
+;;
+;; Scan OUTLINE file and display a list of trees, the structure of
+;; each reflecting the "levels" in OUTLINE. The recognized outline
+;; format (used to indicate outline headings) is zero or more pairs of
+;; leading spaces followed by "-". Something like:
+;;
+;; - a 0
+;; - b 1
+;; - c 2
+;; - d 1
+;; - e 0
+;; - f 1
+;; - g 2
+;; - h 1
+;;
+;; In this example the levels are shown to the right. The output for
+;; such a file would be the single line:
+;;
+;; (("a" ("b" "c") "d") ("e" ("f" "g") "h"))
+;;
+;; Basically, anything at the beginning of a list is a parent, and the
+;; remaining elements of that list are its children.
+;;
+;;
+;; Usage from a Scheme program: These two procs are exported:
+;;
+;; (read-text-outline . args) ; only first arg is used
+;; (read-text-outline-silently port)
+;; (make-text-outline-reader re specs)
+;;
+;; `make-text-outline-reader' returns a proc that reads from PORT and
+;; returns a list of trees (similar to `read-text-outline-silently').
+;;
+;; RE is a regular expression (string) that is used to identify a header
+;; line of the outline (as opposed to a whitespace line or intervening
+;; text). RE must begin w/ a sub-expression to match the "level prefix"
+;; of the line. You can use `level-submatch-number' in SPECS (explained
+;; below) to specify a number other than 1, the default.
+;;
+;; Normally, the level of the line is taken directly as the length of
+;; its level prefix. This often results in adjacent levels not mapping
+;; to adjacent numbers, which confuses the tree-building portion of the
+;; program, which expects top-level to be 0, first sub-level to be 1,
+;; etc. You can use `level-substring-divisor' or `compute-level' in
+;; SPECS to specify a constant scaling factor or specify a completely
+;; alternative procedure, respectively.
+;;
+;; SPECS is an alist which may contain the following key/value pairs:
+;;
+;; - level-submatch-number NUMBER
+;; - level-substring-divisor NUMBER
+;; - compute-level PROC
+;; - body-submatch-number NUMBER
+;; - extra-fields ((FIELD-1 . SUBMATCH-1) (FIELD-2 . SUBMATCH-2) ...)
+;;
+;; The PROC value associated with key `compute-level' should take a
+;; Scheme match structure (as returned by `regexp-exec') and return a
+;; number, the normalized level for that line. If this is specified,
+;; it takes precedence over other level-computation methods.
+;;
+;; Use `body-submatch-number' if RE specifies the whole body, or if you
+;; want to make use of the extra fields parsing. The `extra-fields'
+;; value is a sub-alist, whose keys name additional fields that are to
+;; be recognized. These fields along with `level' are set as object
+;; properties of the final string ("body") that is consed into the tree.
+;; If a field name ends in "?" the field value is set to be #t if there
+;; is a match and the result is not an empty string, and #f otherwise.
+;;
+;;
+;; Bugs and caveats:
+;;
+;; (1) Only the first file specified on the command line is scanned.
+;; (2) TAB characters at the beginnings of lines are not recognized.
+;; (3) Outlines that "skip" levels signal an error. In other words,
+;; this will fail:
+;;
+;; - a 0
+;; - b 1
+;; - c 3 <-- skipped 2 -- error!
+;; - d 1
+;;
+;;
+;; TODO: Determine what's the right thing to do for skips.
+;; Handle TABs.
+;; Make line format customizable via longopts.
+
+;;; Code:
+
+(define-module (scripts read-text-outline)
+ \:export (read-text-outline
+ read-text-outline-silently
+ make-text-outline-reader)
+ \:use-module (ice-9 regex)
+ \:autoload (ice-9 rdelim) (read-line)
+ \:autoload (ice-9 getopt-long) (getopt-long))
+
+(define %include-in-guild-list #f)
+(define %summary "Convert textual outlines to s-expressions.")
+
+(define (?? symbol)
+ (let ((name (symbol->string symbol)))
+ (string=? "?" (substring name (1- (string-length name))))))
+
+(define (msub n)
+ (lambda (m)
+ (match:substring m n)))
+
+(define (??-predicates pair)
+ (cons (car pair)
+ (if (?? (car pair))
+ (lambda (m)
+ (not (string=? "" (match:substring m (cdr pair)))))
+ (msub (cdr pair)))))
+
+(define (make-line-parser re specs)
+ (let* ((rx (let ((fc (substring re 0 1)))
+ (make-regexp (if (string=? "^" fc)
+ re
+ (string-append "^" re)))))
+ (check (lambda (key)
+ (assq-ref specs key)))
+ (level-substring (msub (or (check 'level-submatch-number) 1)))
+ (extract-level (cond ((check 'compute-level)
+ => (lambda (proc)
+ (lambda (m)
+ (proc m))))
+ ((check 'level-substring-divisor)
+ => (lambda (n)
+ (lambda (m)
+ (/ (string-length (level-substring m))
+ n))))
+ (else
+ (lambda (m)
+ (string-length (level-substring m))))))
+ (extract-body (cond ((check 'body-submatch-number)
+ => msub)
+ (else
+ (lambda (m) (match:suffix m)))))
+ (misc-props! (cond ((check 'extra-fields)
+ => (lambda (alist)
+ (let ((new (map ??-predicates alist)))
+ (lambda (obj m)
+ (for-each
+ (lambda (pair)
+ (set-object-property!
+ obj (car pair)
+ ((cdr pair) m)))
+ new)))))
+ (else
+ (lambda (obj m) #t)))))
+ ;; retval
+ (lambda (line)
+ (cond ((regexp-exec rx line)
+ => (lambda (m)
+ (let ((level (extract-level m))
+ (body (extract-body m)))
+ (set-object-property! body 'level level)
+ (misc-props! body m)
+ body)))
+ (else #f)))))
+
+(define (make-text-outline-reader re specs)
+ (let ((parse-line (make-line-parser re specs)))
+ ;; retval
+ (lambda (port)
+ (let* ((all '(start))
+ (pchain (list))) ; parents chain
+ (let loop ((line (read-line port))
+ (prev-level -1) ; how this relates to the first input
+ ; level determines whether or not we
+ ; start in "sibling" or "child" mode.
+ ; in the end, `start' is ignored and
+ ; it's much easier to ignore parents
+ ; than siblings (sometimes). this is
+ ; not to encourage ignorance, however.
+ (tp all)) ; tail pointer
+ (or (eof-object? line)
+ (cond ((parse-line line)
+ => (lambda (w)
+ (let* ((words (list w))
+ (level (object-property w 'level))
+ (diff (- level prev-level)))
+ (cond
+
+ ;; sibling
+ ((zero? diff)
+ ;; just extend the chain
+ (set-cdr! tp words))
+
+ ;; child
+ ((positive? diff)
+ (or (= 1 diff)
+ (error "unhandled diff not 1:" diff line))
+ ;; parent may be contacted by uncle later (kids
+ ;; these days!) so save its level
+ (set-object-property! tp 'level prev-level)
+ (set! pchain (cons tp pchain))
+ ;; "push down" car into hierarchy
+ (set-car! tp (cons (car tp) words)))
+
+ ;; uncle
+ ((negative? diff)
+ ;; prune back to where levels match
+ (do ((p pchain (cdr p)))
+ ((= level (object-property (car p) 'level))
+ (set! pchain p)))
+ ;; resume at this level
+ (set-cdr! (car pchain) words)
+ (set! pchain (cdr pchain))))
+
+ (loop (read-line port) level words))))
+ (else (loop (read-line port) prev-level tp)))))
+ (set! all (car all))
+ (if (eq? 'start all)
+ '() ; wasteland
+ (cdr all))))))
+
+(define read-text-outline-silently
+ (make-text-outline-reader "(([ ][ ])*)- *"
+ '((level-substring-divisor . 2))))
+
+(define (read-text-outline . args)
+ (write (read-text-outline-silently (open-file (car args) "r")))
+ (newline)
+ #t) ; exit val
+
+(define main read-text-outline)
+
+;;; read-text-outline ends here
+;;; scan-api --- Scan and group interpreter and libguile interface elements
+
+;; Copyright (C) 2002, 2006, 2011 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
+
+;;; Commentary:
+
+;; Usage: scan-api GUILE SOFILE [GROUPINGS ...]
+;;
+;; Invoke GUILE, an executable guile interpreter, and use nm(1) on SOFILE, a
+;; shared-object library, to determine available interface elements, and
+;; display them to stdout as an alist:
+;;
+;; ((meta ...) (interface ...))
+;;
+;; The meta fields are `GUILE_LOAD_PATH', `LTDL_LIBRARY_PATH', `guile'
+;; `libguileinterface', `sofile' and `groups'. The interface elements are in
+;; turn sub-alists w/ keys `groups' and `scan-data'. Interface elements
+;; initially belong in one of two groups `Scheme' or `C' (but not both --
+;; signal error if that happens).
+;;
+;; Optional GROUPINGS ... are files each containing a single "grouping
+;; definition" alist with each entry of the form:
+;;
+;; (NAME (description "DESCRIPTION") (members SYM...))
+;;
+;; All of the SYM... should be proper subsets of the interface. In addition
+;; to `description' and `members' forms, the entry may optionally include:
+;;
+;; (grok USE-MODULES (lambda (x) CODE))
+;;
+;; where CODE implements a group-membership predicate to be applied to `x', a
+;; symbol. [When evaluated, CODE can assume (use-modules MODULE) has been
+;; executed where MODULE is an element of USE-MODULES, a list. [NOT YET
+;; IMPLEMENTED!]]
+;;
+;; Currently, there are two convenience predicates that operate on `x':
+;; (in-group? x GROUP)
+;; (name-prefix? x PREFIX)
+;;
+;; TODO: Allow for concurrent Scheme/C membership.
+;; Completely separate reporting.
+
+;;; Code:
+
+(define-module (scripts scan-api)
+ \:use-module (ice-9 popen)
+ \:use-module (ice-9 rdelim)
+ \:use-module (ice-9 regex)
+ \:export (scan-api))
+
+(define %include-in-guild-list #f)
+(define %summary "Generate an API description for a Guile extension.")
+
+(define put set-object-property!)
+(define get object-property)
+
+(define (add-props object . args)
+ (let loop ((args args))
+ (if (null? args)
+ object ; retval
+ (let ((key (car args))
+ (value (cadr args)))
+ (put object key value)
+ (loop (cddr args))))))
+
+(define (scan re command match)
+ (let ((rx (make-regexp re))
+ (port (open-pipe command OPEN_READ)))
+ (let loop ((line (read-line port)))
+ (or (eof-object? line)
+ (begin
+ (cond ((regexp-exec rx line) => match))
+ (loop (read-line port)))))))
+
+(define (scan-Scheme! ht guile)
+ (scan "^.guile.+: ([^ \t]+)([ \t]+(.+))*$"
+ (format #f "~A -c '~S ~S'"
+ guile
+ '(use-modules (ice-9 session))
+ '(apropos "."))
+ (lambda (m)
+ (let ((x (string->symbol (match:substring m 1))))
+ (put x 'Scheme (or (match:substring m 3)
+ ""))
+ (hashq-set! ht x #t)))))
+
+(define (scan-C! ht sofile)
+ (scan "^[0-9a-fA-F]+ ([B-TV-Z]) (.+)$"
+ (format #f "nm ~A" sofile)
+ (lambda (m)
+ (let ((x (string->symbol (match:substring m 2))))
+ (put x 'C (string->symbol (match:substring m 1)))
+ (and (hashq-get-handle ht x)
+ (error "both Scheme and C:" x))
+ (hashq-set! ht x #t)))))
+
+(define THIS-MODULE (current-module))
+
+(define (in-group? x group)
+ (memq group (get x 'groups)))
+
+(define (name-prefix? x prefix)
+ (string-match (string-append "^" prefix) (symbol->string x)))
+
+(define (add-group-name! x name)
+ (put x 'groups (cons name (get x 'groups))))
+
+(define (make-grok-proc name form)
+ (let* ((predicate? (eval form THIS-MODULE))
+ (p (lambda (x)
+ (and (predicate? x)
+ (add-group-name! x name)))))
+ (put p 'name name)
+ p))
+
+(define (make-members-proc name members)
+ (let ((p (lambda (x)
+ (and (memq x members)
+ (add-group-name! x name)))))
+ (put p 'name name)
+ p))
+
+(define (make-grouper files) ; \/^^^o/ . o
+ (let ((hook (make-hook 1))) ; /\____\
+ (for-each
+ (lambda (file)
+ (for-each
+ (lambda (gdef)
+ (let ((name (car gdef))
+ (members (assq-ref gdef 'members))
+ (grok (assq-ref gdef 'grok)))
+ (or members grok
+ (error "bad grouping, must have `members' or `grok'"))
+ (add-hook! hook
+ (if grok
+ (add-props (make-grok-proc name (cadr grok))
+ 'description
+ (assq-ref gdef 'description))
+ (make-members-proc name members))
+ #t))) ; append
+ (read (open-file file OPEN_READ))))
+ files)
+ hook))
+
+(define (scan-api . args)
+ (let ((guile (list-ref args 0))
+ (sofile (list-ref args 1))
+ (grouper (false-if-exception (make-grouper (cddr args))))
+ (ht (make-hash-table 3331)))
+ (scan-Scheme! ht guile)
+ (scan-C! ht sofile)
+ (let ((all (sort (hash-fold (lambda (key value prior-result)
+ (add-props
+ key
+ 'string (symbol->string key)
+ 'scan-data (or (get key 'Scheme)
+ (get key 'C))
+ 'groups (if (get key 'Scheme)
+ '(Scheme)
+ '(C)))
+ (and grouper (run-hook grouper key))
+ (cons key prior-result))
+ '()
+ ht)
+ (lambda (a b)
+ (string<? (get a 'string)
+ (get b 'string))))))
+ (format #t ";;; generated by scan-api -- do not edit!\n\n")
+ (format #t "(\n")
+ (format #t "(meta\n")
+ (format #t " (GUILE_LOAD_PATH . ~S)\n"
+ (or (getenv "GUILE_LOAD_PATH") ""))
+ (format #t " (LTDL_LIBRARY_PATH . ~S)\n"
+ (or (getenv "LTDL_LIBRARY_PATH") ""))
+ (format #t " (guile . ~S)\n" guile)
+ (format #t " (libguileinterface . ~S)\n"
+ (let ((i #f))
+ (scan "(.+)"
+ (format #f "~A -c '(display ~A)'"
+ guile
+ '(assq-ref %guile-build-info
+ 'libguileinterface))
+ (lambda (m) (set! i (match:substring m 1))))
+ i))
+ (format #t " (sofile . ~S)\n" sofile)
+ (format #t " ~A\n"
+ (cons 'groups (append (if grouper
+ (map (lambda (p) (get p 'name))
+ (hook->list grouper))
+ '())
+ '(Scheme C))))
+ (format #t ") ;; end of meta\n")
+ (format #t "(interface\n")
+ (for-each (lambda (x)
+ (format #t "(~A ~A (scan-data ~S))\n"
+ x
+ (cons 'groups (get x 'groups))
+ (get x 'scan-data)))
+ all)
+ (format #t ") ;; end of interface\n")
+ (format #t ") ;; eof\n")))
+ #t)
+
+(define main scan-api)
+
+;;; scan-api ends here
+;;; snarf-check-and-output-texi --- called by the doc snarfer.
+
+;; Copyright (C) 2001, 2002, 2006, 2011, 2014 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Michael Livshin
+
+;;; Code:
+
+(define-module (scripts snarf-check-and-output-texi)
+ \:use-module (ice-9 streams)
+ \:use-module (ice-9 match)
+ \:export (snarf-check-and-output-texi))
+
+(define %include-in-guild-list #f)
+(define %summary "Transform snarfed .doc files into texinfo documentation.")
+
+;;; why aren't these in some module?
+
+(define-macro (when cond . body)
+ `(if ,cond (begin ,@body)))
+
+(define-macro (unless cond . body)
+ `(if (not ,cond) (begin ,@body)))
+
+(define *manual-flag* #f)
+
+(define (snarf-check-and-output-texi . flags)
+ (if (member "--manual" flags)
+ (set! *manual-flag* #t))
+ (process-stream (current-input-port)))
+
+(define (process-stream port)
+ (let loop ((input (stream-map (match-lambda
+ (('id . s)
+ (cons 'id (string->symbol s)))
+ (('int_dec . s)
+ (cons 'int (string->number s)))
+ (('int_oct . s)
+ (cons 'int (string->number s 8)))
+ (('int_hex . s)
+ (cons 'int (string->number s 16)))
+ ((and x (? symbol?))
+ (cons x x))
+ ((and x (? string?))
+ (cons 'string x))
+ (x x))
+ (make-stream (lambda (s)
+ (let loop ((s s))
+ (cond
+ ((stream-null? s) #t)
+ ((memq (stream-car s) '(eol hash))
+ (loop (stream-cdr s)))
+ (else (cons (stream-car s) (stream-cdr s))))))
+ (port->stream port read)))))
+
+ (unless (stream-null? input)
+ (let ((token (stream-car input)))
+ (if (eq? (car token) 'snarf_cookie)
+ (dispatch-top-cookie (stream-cdr input)
+ loop)
+ (loop (stream-cdr input)))))))
+
+(define (dispatch-top-cookie input cont)
+
+ (when (stream-null? input)
+ (error 'syntax "premature end of file"))
+
+ (let ((token (stream-car input)))
+ (cond
+ ((eq? (car token) 'brace_open)
+ (consume-multiline (stream-cdr input)
+ cont))
+ (else
+ (consume-upto-cookie process-singleline
+ input
+ cont)))))
+
+(define (consume-upto-cookie process input cont)
+ (let loop ((acc '()) (input input))
+
+ (when (stream-null? input)
+ (error 'syntax "premature end of file in directive context"))
+
+ (let ((token (stream-car input)))
+ (cond
+ ((eq? (car token) 'snarf_cookie)
+ (process (reverse! acc))
+ (cont (stream-cdr input)))
+
+ (else (loop (cons token acc) (stream-cdr input)))))))
+
+(define (consume-multiline input cont)
+ (begin-multiline)
+
+ (let loop ((input input))
+
+ (when (stream-null? input)
+ (error 'syntax "premature end of file in multiline context"))
+
+ (let ((token (stream-car input)))
+ (cond
+ ((eq? (car token) 'brace_close)
+ (end-multiline)
+ (cont (stream-cdr input)))
+
+ (else (consume-upto-cookie process-multiline-directive
+ input
+ loop))))))
+
+(define *file* #f)
+(define *line* #f)
+(define *c-function-name* #f)
+(define *function-name* #f)
+(define *snarf-type* #f)
+(define *args* #f)
+(define *sig* #f)
+(define *docstring* #f)
+
+(define (begin-multiline)
+ (set! *file* #f)
+ (set! *line* #f)
+ (set! *c-function-name* #f)
+ (set! *function-name* #f)
+ (set! *snarf-type* #f)
+ (set! *args* #f)
+ (set! *sig* #f)
+ (set! *docstring* #f))
+
+(define *primitive-deffnx-signature* "@deffnx {Scheme Procedure} ")
+(define *primitive-deffnx-sig-length* (string-length *primitive-deffnx-signature*))
+
+(define (end-multiline)
+ (let* ((req (car *sig*))
+ (opt (cadr *sig*))
+ (var (caddr *sig*))
+ (all (+ req opt var)))
+ (if (and (not (eqv? *snarf-type* 'register))
+ (not (= (length *args*) all)))
+ (error (format #f "~A:~A: ~A's C implementation takes ~A args (should take ~A)"
+ *file* *line* *function-name* (length *args*) all)))
+ (let ((nice-sig
+ (if (eq? *snarf-type* 'register)
+ *function-name*
+ (with-output-to-string
+ (lambda ()
+ (format #t "~A" *function-name*)
+ (let loop-req ((args *args*) (r 0))
+ (if (< r req)
+ (begin
+ (format #t " ~A" (car args))
+ (loop-req (cdr args) (+ 1 r)))
+ (let loop-opt ((o 0) (args args) (tail '()))
+ (if (< o opt)
+ (begin
+ (format #t " [~A" (car args))
+ (loop-opt (+ 1 o) (cdr args) (cons #\] tail)))
+ (begin
+ (if (> var 0)
+ (format #t " . ~A"
+ (car args)))
+ (let loop-tail ((tail tail))
+ (if (not (null? tail))
+ (begin
+ (format #t "~A" (car tail))
+ (loop-tail (cdr tail))))))))))))))
+ (scm-deffnx
+ (if (and *manual-flag* (eq? *snarf-type* 'primitive))
+ (with-output-to-string
+ (lambda ()
+ (format #t "@deffnx {C Function} ~A (" *c-function-name*)
+ (unless (null? *args*)
+ (format #t "~A" (car *args*))
+ (let loop ((args (cdr *args*)))
+ (unless (null? args)
+ (format #t ", ~A" (car args))
+ (loop (cdr args)))))
+ (format #t ")\n")))
+ #f)))
+ (format #t "\n ~A\n" *function-name*)
+ (format #t "@c snarfed from ~A:~A\n" *file* *line*)
+ (format #t "@deffn {Scheme Procedure} ~A\n" nice-sig)
+ (let loop ((strings *docstring*) (scm-deffnx scm-deffnx))
+ (cond ((null? strings))
+ ((or (not scm-deffnx)
+ (and (>= (string-length (car strings))
+ *primitive-deffnx-sig-length*)
+ (string=? (substring (car strings)
+ 0 *primitive-deffnx-sig-length*)
+ *primitive-deffnx-signature*)))
+ (display (car strings))
+ (loop (cdr strings) scm-deffnx))
+ (else (display scm-deffnx)
+ (loop strings #f))))
+ (display "\n")
+ (display "@end deffn\n"))))
+
+(define (texi-quote s)
+ (let rec ((i 0))
+ (if (= i (string-length s))
+ ""
+ (string-append (let ((ss (substring s i (+ i 1))))
+ (if (string=? ss "@")
+ "@@"
+ ss))
+ (rec (+ i 1))))))
+
+(define (process-multiline-directive l)
+
+ (define do-args
+ (match-lambda
+
+ (('(paren_close . paren_close))
+ '())
+
+ (('(comma . comma) rest ...)
+ (do-args rest))
+
+ (('(id . SCM) ('id . name) rest ...)
+ (cons name (do-args rest)))
+
+ (x (error (format #f "invalid argument syntax: ~A" (map cdr x))))))
+
+ (define do-arglist
+ (match-lambda
+
+ (('(paren_open . paren_open) '(id . void) '(paren_close . paren_close))
+ '())
+
+ (('(paren_open . paren_open) rest ...)
+ (do-args rest))
+
+ (x (error (format #f "invalid arglist syntax: ~A" (map cdr x))))))
+
+ (define do-command
+ (match-lambda
+
+ (('cname ('id . name))
+ (set! *c-function-name* (texi-quote (symbol->string name))))
+
+ (('fname ('string . name) ...)
+ (set! *function-name* (texi-quote (apply string-append name))))
+
+ (('type ('id . type))
+ (set! *snarf-type* type))
+
+ (('type ('int . num))
+ (set! *snarf-type* num))
+
+ (('location ('string . file) ('int . line))
+ (set! *file* file)
+ (set! *line* line))
+
+ (('arglist rest ...)
+ (set! *args* (do-arglist rest)))
+
+ (('argsig ('int . req) ('int . opt) ('int . var))
+ (set! *sig* (list req opt var)))
+
+ (x (error (format #f "unknown doc attribute: ~A" x)))))
+
+ (define do-directive
+ (match-lambda
+
+ ((('id . command) rest ...)
+ (do-command (cons command rest)))
+
+ ((('string . string) ...)
+ (set! *docstring* string))
+
+ (x (error (format #f "unknown doc attribute syntax: ~A" x)))))
+
+ (do-directive l))
+
+(define (process-singleline l)
+
+ (define do-argpos
+ (match-lambda
+ ((('id . name) ('int . pos) ('int . line))
+ (let ((idx (list-index *args* name)))
+ (when idx
+ (unless (= (+ idx 1) pos)
+ (display (format #f "~A:~A: wrong position for argument ~A: ~A (should be ~A)\n"
+ *file* line name pos (+ idx 1))
+ (current-error-port))))))
+ (x #f)))
+
+ (define do-command
+ (match-lambda
+ (('(id . argpos) rest ...)
+ (do-argpos rest))
+ (x (error (format #f "unknown check: ~A" x)))))
+
+ (when *function-name*
+ (do-command l)))
+
+(define main snarf-check-and-output-texi)
+;;; snarf-guile-m4-docs --- Parse guile.m4 comments for texi documentation
+
+;; Copyright (C) 2002, 2006, 2011 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
+
+;;; Commentary:
+
+;; Usage: snarf-guile-m4-docs FILE
+;;
+;; Grep FILE for comments preceding macro definitions, massage
+;; them into valid texi, and display to stdout. For each comment,
+;; lines preceding "^# Usage:" are discarded.
+;;
+;; TODO: Generalize.
+
+;;; Code:
+
+(define-module (scripts snarf-guile-m4-docs)
+ \:use-module (ice-9 rdelim)
+ \:export (snarf-guile-m4-docs))
+
+(define %include-in-guild-list #f)
+(define %summary "Snarf out texinfo documentation from .m4 files.")
+
+(define (display-texi lines)
+ (display "@deffn {Autoconf Macro}")
+ (for-each (lambda (line)
+ (display (cond ((and (>= (string-length line) 2)
+ (string=? "# " (substring line 0 2)))
+ (substring line 2))
+ ((string=? "#" (substring line 0 1))
+ (substring line 1))
+ (else line)))
+ (newline))
+ lines)
+ (display "@end deffn")
+ (newline) (newline))
+
+(define (prefix? line sub)
+ (false-if-exception
+ (string=? sub (substring line 0 (string-length sub)))))
+
+(define (massage-usage line)
+ (let loop ((line (string->list line)) (acc '()))
+ (if (null? line)
+ (list (list->string (reverse acc)))
+ (loop (cdr line)
+ (cons (case (car line)
+ ((#\( #\) #\,) #\space)
+ (else (car line)))
+ acc)))))
+
+(define (snarf-guile-m4-docs . args)
+ (let* ((p (open-file (car args) "r"))
+ (next (lambda () (read-line p))))
+ (let loop ((line (next)) (acc #f))
+ (or (eof-object? line)
+ (cond ((prefix? line "# Usage:")
+ (loop (next) (massage-usage (substring line 8))))
+ ((prefix? line "AC_DEFUN")
+ (display-texi (reverse acc))
+ (loop (next) #f))
+ ((and acc (prefix? line "#"))
+ (loop (next) (cons line acc)))
+ (else
+ (loop (next) #f)))))))
+
+(define main snarf-guile-m4-docs)
+
+;;; snarf-guile-m4-docs ends here
+;;; summarize-guile-TODO --- Display Guile TODO list in various ways
+
+;; Copyright (C) 2002, 2006, 2010, 2011 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
+
+;;; Commentary:
+
+;; Usage: summarize-guile-TODO TODOFILE
+;;
+;; The TODOFILE is typically Guile's (see workbook/tasks/README)
+;; presumed to serve as our signal to ourselves (lest we want real
+;; bosses hassling us) wrt to the overt message "items to do" as well as
+;; the messages that can be inferred from its structure.
+;;
+;; This program reads TODOFILE and displays interpretations on its
+;; structure, including registered markers and ownership, in various
+;; ways.
+;;
+;; A primary interest in any task is its parent task. The output
+;; summarization by default lists every item and its parent chain.
+;; Top-level parents are not items. You can use these command-line
+;; options to modify the selection and display (selection criteria
+;; are ANDed together):
+;;
+;; -i, --involved USER -- select USER-involved items
+;; -p, --personal USER -- select USER-responsible items
+;; -t, --todo -- select unfinished items (status "-")
+;; -d, --done -- select finished items (status "+")
+;; -r, --review -- select review items (marker "R")
+;;
+;; -w, --who -- also show who is associated w/ the item
+;; -n, --no-parent -- do not show parent chain
+;;
+;;
+;; Usage from a Scheme program:
+;; (summarize-guile-TODO . args) ; uses first arg only
+;;
+;;
+;; Bugs: (1) Markers are scanned in sequence: D R X N%. This means "XD"
+;; and the like are completely dropped. However, such strings
+;; are unlikely to be used if the markers are chosen to be
+;; somewhat exclusive, which is currently the case for D R X.
+;; N% used w/ these needs to be something like: "D25%" (this
+;; means discussion accounts for 1/4 of the task).
+;;
+;; TODO: Implement more various ways. (Patches welcome.)
+;; Add support for ORing criteria.
+
+;;; Code:
+(debug-enable 'backtrace)
+
+(define-module (scripts summarize-guile-TODO)
+ \:use-module (scripts read-text-outline)
+ \:use-module (ice-9 getopt-long)
+ \:autoload (srfi srfi-13) (string-tokenize) ; string library
+ \:autoload (srfi srfi-14) (char-set) ; string library
+ \:autoload (ice-9 common-list) (remove-if-not)
+ \:export (summarize-guile-TODO))
+
+(define %include-in-guild-list #f)
+(define %summary "A quaint relic of the past.")
+
+(define put set-object-property!)
+(define get object-property)
+
+(define (as-leaf x)
+ (cond ((get x 'who)
+ => (lambda (who)
+ (put x 'who
+ (map string->symbol
+ (string-tokenize who (char-set #\:)))))))
+ (cond ((get x 'pct-done)
+ => (lambda (pct-done)
+ (put x 'pct-done (string->number pct-done)))))
+ x)
+
+(define (hang-by-the-leaves trees)
+ (let ((leaves '()))
+ (letrec ((hang (lambda (tree parent)
+ (if (list? tree)
+ (begin
+ (put (car tree) 'parent parent)
+ (for-each (lambda (child)
+ (hang child (car tree)))
+ (cdr tree)))
+ (begin
+ (put tree 'parent parent)
+ (set! leaves (cons (as-leaf tree) leaves)))))))
+ (for-each (lambda (tree)
+ (hang tree #f))
+ trees))
+ leaves))
+
+(define (read-TODO file)
+ (hang-by-the-leaves
+ ((make-text-outline-reader
+ "(([ ][ ])*)([-+])(D*)(R*)(X*)(([0-9]+)%)* *([^[]*)(\\[(.*)\\])*"
+ '((level-substring-divisor . 2)
+ (body-submatch-number . 9)
+ (extra-fields . ((status . 3)
+ (design? . 4)
+ (review? . 5)
+ (extblock? . 6)
+ (pct-done . 8)
+ (who . 11)))))
+ (open-file file "r"))))
+
+(define (select-items p items)
+ (let ((sub '()))
+ (cond ((option-ref p 'involved #f)
+ => (lambda (u)
+ (let ((u (string->symbol u)))
+ (set! sub (cons
+ (lambda (x)
+ (and (get x 'who)
+ (memq u (get x 'who))))
+ sub))))))
+ (cond ((option-ref p 'personal #f)
+ => (lambda (u)
+ (let ((u (string->symbol u)))
+ (set! sub (cons
+ (lambda (x)
+ (cond ((get x 'who)
+ => (lambda (ls)
+ (eq? (car (reverse ls))
+ u)))
+ (else #f)))
+ sub))))))
+ (for-each (lambda (pair)
+ (cond ((option-ref p (car pair) #f)
+ (set! sub (cons (cdr pair) sub)))))
+ `((todo . ,(lambda (x) (string=? (get x 'status) "-")))
+ (done . ,(lambda (x) (string=? (get x 'status) "+")))
+ (review . ,(lambda (x) (get x 'review?)))))
+ (let loop ((sub (reverse sub)) (items items))
+ (if (null? sub)
+ (reverse items)
+ (loop (cdr sub) (remove-if-not (car sub) items))))))
+
+(define (make-display-item show-who? show-parent?)
+ (let ((show-who
+ (if show-who?
+ (lambda (item)
+ (cond ((get item 'who)
+ => (lambda (who) (format #f " ~A" who)))
+ (else "")))
+ (lambda (item) "")))
+ (show-parents
+ (if show-parent?
+ (lambda (item)
+ (let loop ((parent (get item 'parent)) (indent 2))
+ (and parent
+ (begin
+ (format #t "under : ~A~A\n"
+ (make-string indent #\space)
+ parent)
+ (loop (get parent 'parent) (+ 2 indent))))))
+ (lambda (item) #t))))
+ (lambda (item)
+ (format #t "status: ~A~A~A~A~A~A\nitem : ~A\n"
+ (get item 'status)
+ (if (get item 'design?) "D" "")
+ (if (get item 'review?) "R" "")
+ (if (get item 'extblock?) "X" "")
+ (cond ((get item 'pct-done)
+ => (lambda (pct-done)
+ (format #f " ~A%" pct-done)))
+ (else ""))
+ (show-who item)
+ item)
+ (show-parents item))))
+
+(define (display-items p items)
+ (let ((display-item (make-display-item (option-ref p 'who #f)
+ (not (option-ref p 'no-parent #f))
+ )))
+ (for-each display-item items)))
+
+(define (summarize-guile-TODO . args)
+ (let ((p (getopt-long (cons "summarize-guile-TODO" args)
+ '((who (single-char #\w))
+ (no-parent (single-char #\n))
+ (involved (single-char #\i)
+ (value #t))
+ (personal (single-char #\p)
+ (value #t))
+ (todo (single-char #\t))
+ (done (single-char #\d))
+ (review (single-char #\r))
+ ;; Add options here.
+ ))))
+ (display-items p (select-items p (read-TODO (car (option-ref p '() #f))))))
+ #t) ; exit val
+
+(define main summarize-guile-TODO)
+
+;;; summarize-guile-TODO ends here
+;;; use2dot --- Display module dependencies as a DOT specification
+
+;; Copyright (C) 2001, 2006, 2011 Free Software Foundation, Inc.
+;;
+;; 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, 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 software; see the file COPYING.LESSER. If
+;; not, write to the Free Software Foundation, Inc., 51 Franklin
+;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Thien-Thi Nguyen
+
+;;; Commentary:
+
+;; Usage: use2dot [OPTIONS] [FILE ...]
+;; Display to stdout a DOT specification that describes module dependencies
+;; in FILEs.
+;;
+;; A top-level `use-modules' form or a `:use-module' `define-module'-component
+;; results in a "solid" style edge.
+;;
+;; An `:autoload' `define-module'-component results in a "dotted" style edge
+;; with label "N" indicating that N names are responsible for triggering the
+;; autoload. [The "N" label is not implemented.]
+;;
+;; A top-level `load' or `primitive-load' form results in a a "bold" style
+;; edge to a node named with either the file name if the `load' argument is a
+;; string, or "[computed in FILE]" otherwise.
+;;
+;; Options:
+;; -m, --default-module MOD -- Set MOD as the default module (for top-level
+;; `use-modules' forms that do not follow some
+;; `define-module' form in a file). MOD should be
+;; be a list or `#f', in which case such top-level
+;; `use-modules' forms are effectively ignored.
+;; Default value: `(guile-user)'.
+
+;;; Code:
+
+(define-module (scripts use2dot)
+ \:autoload (ice-9 getopt-long) (getopt-long)
+ \:use-module ((srfi srfi-13) \:select (string-join))
+ \:use-module ((scripts frisk)
+ \:select (make-frisker edge-type edge-up edge-down))
+ \:export (use2dot))
+
+(define %summary "Print a module's dependencies in graphviz format.")
+
+(define *default-module* '(guile-user))
+
+(define (q s) ; quote
+ (format #f "~S" s))
+
+(define (vv pairs) ; => ("var=val" ...)
+ (map (lambda (pair)
+ (format #f "~A=~A" (car pair) (cdr pair)))
+ pairs))
+
+(define (>>header)
+ (format #t "digraph use2dot {\n")
+ (for-each (lambda (s) (format #t " ~A;\n" s))
+ (vv `((label . ,(q "Guile Module Dependencies"))
+ ;;(rankdir . LR)
+ ;;(size . ,(q "7.5,10"))
+ (ratio . fill)
+ ;;(nodesep . ,(q "0.05"))
+ ))))
+
+(define (>>body edges)
+ (for-each
+ (lambda (edge)
+ (format #t " \"~A\" -> \"~A\"" (edge-down edge) (edge-up edge))
+ (cond ((case (edge-type edge)
+ ((autoload) '((style . dotted) (fontsize . 5)))
+ ((computed) '((style . bold)))
+ (else #f))
+ => (lambda (etc)
+ (format #t " [~A]" (string-join (vv etc) ",")))))
+ (format #t ";\n"))
+ edges))
+
+(define (>>footer)
+ (format #t "}"))
+
+(define (>> edges)
+ (>>header)
+ (>>body edges)
+ (>>footer))
+
+(define (use2dot . args)
+ (let* ((parsed-args (getopt-long (cons "use2dot" args) ;;; kludge
+ '((default-module
+ (single-char #\m) (value #t)))))
+ (=m (option-ref parsed-args 'default-module *default-module*))
+ (scan (make-frisker `(default-module . ,=m)))
+ (files (option-ref parsed-args '() '())))
+ (>> (reverse ((scan files) 'edges)))))
+
+(define main use2dot)
+
+;;; use2dot ends here
+;;; srfi-1.scm --- List Library
+
+;; Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2009, 2010, 2011 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Some parts from the reference implementation, which is
+;;; Copyright (c) 1998, 1999 by Olin Shivers. You may do as you please with
+;;; this code as long as you do not remove this copyright notice or
+;;; hold me liable for its use.
+
+;;; Author: Martin Grabmueller <mgrabmue@cs.tu-berlin.de>
+;;; Date: 2001-06-06
+
+;;; Commentary:
+
+;; This is an implementation of SRFI-1 (List Library).
+;;
+;; All procedures defined in SRFI-1, which are not already defined in
+;; the Guile core library, are exported. The procedures in this
+;; implementation work, but they have not been tuned for speed or
+;; memory usage.
+;;
+;; This module is fully documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-1)
+ \:export (
+;;; Constructors
+ ;; cons <= in the core
+ ;; list <= in the core
+ xcons
+ ;; cons* <= in the core
+ ;; make-list <= in the core
+ list-tabulate
+ list-copy
+ circular-list
+ ;; iota ; Extended.
+
+;;; Predicates
+ proper-list?
+ circular-list?
+ dotted-list?
+ ;; pair? <= in the core
+ ;; null? <= in the core
+ null-list?
+ not-pair?
+ list=
+
+;;; Selectors
+ ;; car <= in the core
+ ;; cdr <= in the core
+ ;; caar <= in the core
+ ;; cadr <= in the core
+ ;; cdar <= in the core
+ ;; cddr <= in the core
+ ;; caaar <= in the core
+ ;; caadr <= in the core
+ ;; cadar <= in the core
+ ;; caddr <= in the core
+ ;; cdaar <= in the core
+ ;; cdadr <= in the core
+ ;; cddar <= in the core
+ ;; cdddr <= in the core
+ ;; caaaar <= in the core
+ ;; caaadr <= in the core
+ ;; caadar <= in the core
+ ;; caaddr <= in the core
+ ;; cadaar <= in the core
+ ;; cadadr <= in the core
+ ;; caddar <= in the core
+ ;; cadddr <= in the core
+ ;; cdaaar <= in the core
+ ;; cdaadr <= in the core
+ ;; cdadar <= in the core
+ ;; cdaddr <= in the core
+ ;; cddaar <= in the core
+ ;; cddadr <= in the core
+ ;; cdddar <= in the core
+ ;; cddddr <= in the core
+ ;; list-ref <= in the core
+ first
+ second
+ third
+ fourth
+ fifth
+ sixth
+ seventh
+ eighth
+ ninth
+ tenth
+ car+cdr
+ take
+ drop
+ take-right
+ drop-right
+ take!
+ drop-right!
+ split-at
+ split-at!
+ last
+ ;; last-pair <= in the core
+
+;;; Miscelleneous: length, append, concatenate, reverse, zip & count
+ ;; length <= in the core
+ length+
+ ;; append <= in the core
+ ;; append! <= in the core
+ concatenate
+ concatenate!
+ ;; reverse <= in the core
+ ;; reverse! <= in the core
+ append-reverse
+ append-reverse!
+ zip
+ unzip1
+ unzip2
+ unzip3
+ unzip4
+ unzip5
+ count
+
+;;; Fold, unfold & map
+ fold
+ fold-right
+ pair-fold
+ pair-fold-right
+ reduce
+ reduce-right
+ unfold
+ unfold-right
+ ;; map ; Extended.
+ ;; for-each ; Extended.
+ append-map
+ append-map!
+ map!
+ ;; map-in-order ; Extended.
+ pair-for-each
+ filter-map
+
+;;; Filtering & partitioning
+ ;; filter <= in the core
+ partition
+ remove
+ ;; filter! <= in the core
+ partition!
+ remove!
+
+;;; Searching
+ find
+ find-tail
+ take-while
+ take-while!
+ drop-while
+ span
+ span!
+ break
+ break!
+ any
+ every
+ ;; list-index ; Extended.
+ ;; member ; Extended.
+ ;; memq <= in the core
+ ;; memv <= in the core
+
+;;; Deletion
+ ;; delete ; Extended.
+ ;; delete! ; Extended.
+ delete-duplicates
+ delete-duplicates!
+
+;;; Association lists
+ ;; assoc ; Extended.
+ ;; assq <= in the core
+ ;; assv <= in the core
+ alist-cons
+ alist-copy
+ alist-delete
+ alist-delete!
+
+;;; Set operations on lists
+ lset<=
+ lset=
+ lset-adjoin
+ lset-union
+ lset-intersection
+ lset-difference
+ lset-xor
+ lset-diff+intersection
+ lset-union!
+ lset-intersection!
+ lset-difference!
+ lset-xor!
+ lset-diff+intersection!
+
+;;; Primitive side-effects
+ ;; set-car! <= in the core
+ ;; set-cdr! <= in the core
+ )
+ \:re-export (cons list cons* make-list pair? null?
+ car cdr caar cadr cdar cddr
+ caaar caadr cadar caddr cdaar cdadr cddar cdddr
+ caaaar caaadr caadar caaddr cadaar cadadr caddar cadddr
+ cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar cddddr
+ list-ref last-pair length append append! reverse reverse!
+ filter filter! memq memv assq assv set-car! set-cdr!)
+ \:replace (iota map for-each map-in-order list-copy list-index member
+ delete delete! assoc)
+ )
+
+(cond-expand-provide (current-module) '(srfi-1))
+
+;; Load the compiled primitives from the shared library.
+;;
+(load-extension (string-append "libguile-" (effective-version))
+ "scm_init_srfi_1")
+
+
+;;; Constructors
+
+(define (xcons d a)
+ "Like `cons', but with interchanged arguments. Useful mostly when passed to
+higher-order procedures."
+ (cons a d))
+
+(define (wrong-type-arg caller arg)
+ (scm-error 'wrong-type-arg (symbol->string caller)
+ "Wrong type argument: ~S" (list arg) '()))
+
+(define-syntax-rule (check-arg pred arg caller)
+ (if (not (pred arg))
+ (wrong-type-arg 'caller arg)))
+
+(define (out-of-range proc arg)
+ (scm-error 'out-of-range proc
+ "Value out of range: ~A" (list arg) (list arg)))
+
+;; the srfi spec doesn't seem to forbid inexact integers.
+(define (non-negative-integer? x) (and (integer? x) (>= x 0)))
+
+(define (list-tabulate n init-proc)
+ "Return an N-element list, where each list element is produced by applying the
+procedure INIT-PROC to the corresponding list index. The order in which
+INIT-PROC is applied to the indices is not specified."
+ (check-arg non-negative-integer? n list-tabulate)
+ (let lp ((n n) (acc '()))
+ (if (<= n 0)
+ acc
+ (lp (- n 1) (cons (init-proc (- n 1)) acc)))))
+
+(define (circular-list elt1 . elts)
+ (set! elts (cons elt1 elts))
+ (set-cdr! (last-pair elts) elts)
+ elts)
+
+(define* (iota count #\optional (start 0) (step 1))
+ (check-arg non-negative-integer? count iota)
+ (let lp ((n 0) (acc '()))
+ (if (= n count)
+ (reverse! acc)
+ (lp (+ n 1) (cons (+ start (* n step)) acc)))))
+
+;;; Predicates
+
+(define (proper-list? x)
+ (list? x))
+
+(define (circular-list? x)
+ (if (not-pair? x)
+ #f
+ (let lp ((hare (cdr x)) (tortoise x))
+ (if (not-pair? hare)
+ #f
+ (let ((hare (cdr hare)))
+ (if (not-pair? hare)
+ #f
+ (if (eq? hare tortoise)
+ #t
+ (lp (cdr hare) (cdr tortoise)))))))))
+
+(define (dotted-list? x)
+ (cond
+ ((null? x) #f)
+ ((not-pair? x) #t)
+ (else
+ (let lp ((hare (cdr x)) (tortoise x))
+ (cond
+ ((null? hare) #f)
+ ((not-pair? hare) #t)
+ (else
+ (let ((hare (cdr hare)))
+ (cond
+ ((null? hare) #f)
+ ((not-pair? hare) #t)
+ ((eq? hare tortoise) #f)
+ (else
+ (lp (cdr hare) (cdr tortoise)))))))))))
+
+(define (null-list? x)
+ (cond
+ ((proper-list? x)
+ (null? x))
+ ((circular-list? x)
+ #f)
+ (else
+ (error "not a proper list in null-list?"))))
+
+(define (not-pair? x)
+ "Return #t if X is not a pair, #f otherwise.
+
+This is shorthand notation `(not (pair? X))' and is supposed to be used for
+end-of-list checking in contexts where dotted lists are allowed."
+ (not (pair? x)))
+
+(define (list= elt= . rest)
+ (define (lists-equal a b)
+ (let lp ((a a) (b b))
+ (cond ((null? a)
+ (null? b))
+ ((null? b)
+ #f)
+ (else
+ (and (elt= (car a) (car b))
+ (lp (cdr a) (cdr b)))))))
+
+ (check-arg procedure? elt= list=)
+ (or (null? rest)
+ (let lp ((lists rest))
+ (or (null? (cdr lists))
+ (and (lists-equal (car lists) (cadr lists))
+ (lp (cdr lists)))))))
+
+;;; Selectors
+
+(define first car)
+(define second cadr)
+(define third caddr)
+(define fourth cadddr)
+(define (fifth x) (car (cddddr x)))
+(define (sixth x) (cadr (cddddr x)))
+(define (seventh x) (caddr (cddddr x)))
+(define (eighth x) (cadddr (cddddr x)))
+(define (ninth x) (car (cddddr (cddddr x))))
+(define (tenth x) (cadr (cddddr (cddddr x))))
+
+(define (car+cdr x)
+ "Return two values, the `car' and the `cdr' of PAIR."
+ (values (car x) (cdr x)))
+
+(define take list-head)
+(define drop list-tail)
+
+;;; TAKE-RIGHT and DROP-RIGHT work by getting two pointers into the list,
+;;; off by K, then chasing down the list until the lead pointer falls off
+;;; the end. Note that they diverge for circular lists.
+
+(define (take-right lis k)
+ (let lp ((lag lis) (lead (drop lis k)))
+ (if (pair? lead)
+ (lp (cdr lag) (cdr lead))
+ lag)))
+
+(define (drop-right lis k)
+ (let lp ((lag lis) (lead (drop lis k)) (result '()))
+ (if (pair? lead)
+ (lp (cdr lag) (cdr lead) (cons (car lag) result))
+ (reverse result))))
+
+(define (take! lst i)
+ "Linear-update variant of `take'."
+ (if (= i 0)
+ '()
+ (let ((tail (drop lst (- i 1))))
+ (set-cdr! tail '())
+ lst)))
+
+(define (drop-right! lst i)
+ "Linear-update variant of `drop-right'."
+ (let ((tail (drop lst i)))
+ (if (null? tail)
+ '()
+ (let loop ((prev lst)
+ (tail (cdr tail)))
+ (if (null? tail)
+ (if (pair? prev)
+ (begin
+ (set-cdr! prev '())
+ lst)
+ lst)
+ (loop (cdr prev)
+ (cdr tail)))))))
+
+(define (split-at lst i)
+ "Return two values, a list of the elements before index I in LST, and
+a list of those after."
+ (if (< i 0)
+ (out-of-range 'split-at i)
+ (let lp ((l lst) (n i) (acc '()))
+ (if (<= n 0)
+ (values (reverse! acc) l)
+ (lp (cdr l) (- n 1) (cons (car l) acc))))))
+
+(define (split-at! lst i)
+ "Linear-update variant of `split-at'."
+ (cond ((< i 0)
+ (out-of-range 'split-at! i))
+ ((= i 0)
+ (values '() lst))
+ (else
+ (let lp ((l lst) (n (- i 1)))
+ (if (<= n 0)
+ (let ((tmp (cdr l)))
+ (set-cdr! l '())
+ (values lst tmp))
+ (lp (cdr l) (- n 1)))))))
+
+(define (last pair)
+ "Return the last element of the non-empty, finite list PAIR."
+ (car (last-pair pair)))
+
+;;; Miscelleneous: length, append, concatenate, reverse, zip & count
+
+(define (zip clist1 . rest)
+ (let lp ((l (cons clist1 rest)) (acc '()))
+ (if (any null? l)
+ (reverse! acc)
+ (lp (map cdr l) (cons (map car l) acc)))))
+
+
+(define (unzip1 l)
+ (map first l))
+(define (unzip2 l)
+ (values (map first l) (map second l)))
+(define (unzip3 l)
+ (values (map first l) (map second l) (map third l)))
+(define (unzip4 l)
+ (values (map first l) (map second l) (map third l) (map fourth l)))
+(define (unzip5 l)
+ (values (map first l) (map second l) (map third l) (map fourth l)
+ (map fifth l)))
+
+;;; Fold, unfold & map
+
+(define (fold kons knil list1 . rest)
+ "Apply PROC to the elements of LIST1 ... LISTN to build a result, and return
+that result. See the manual for details."
+ (check-arg procedure? kons fold)
+ (if (null? rest)
+ (let f ((knil knil) (list1 list1))
+ (if (null? list1)
+ knil
+ (f (kons (car list1) knil) (cdr list1))))
+ (let f ((knil knil) (lists (cons list1 rest)))
+ (if (any null? lists)
+ knil
+ (let ((cars (map car lists))
+ (cdrs (map cdr lists)))
+ (f (apply kons (append! cars (list knil))) cdrs))))))
+
+(define (fold-right kons knil clist1 . rest)
+ (check-arg procedure? kons fold-right)
+ (if (null? rest)
+ (let loop ((lst (reverse clist1))
+ (result knil))
+ (if (null? lst)
+ result
+ (loop (cdr lst)
+ (kons (car lst) result))))
+ (let loop ((lists (map reverse (cons clist1 rest)))
+ (result knil))
+ (if (any1 null? lists)
+ result
+ (loop (map cdr lists)
+ (apply kons (append! (map car lists) (list result))))))))
+
+(define (pair-fold kons knil clist1 . rest)
+ (check-arg procedure? kons pair-fold)
+ (if (null? rest)
+ (let f ((knil knil) (list1 clist1))
+ (if (null? list1)
+ knil
+ (let ((tail (cdr list1)))
+ (f (kons list1 knil) tail))))
+ (let f ((knil knil) (lists (cons clist1 rest)))
+ (if (any null? lists)
+ knil
+ (let ((tails (map cdr lists)))
+ (f (apply kons (append! lists (list knil))) tails))))))
+
+
+(define (pair-fold-right kons knil clist1 . rest)
+ (check-arg procedure? kons pair-fold-right)
+ (if (null? rest)
+ (let f ((list1 clist1))
+ (if (null? list1)
+ knil
+ (kons list1 (f (cdr list1)))))
+ (let f ((lists (cons clist1 rest)))
+ (if (any null? lists)
+ knil
+ (apply kons (append! lists (list (f (map cdr lists)))))))))
+
+(define* (unfold p f g seed #\optional (tail-gen (lambda (x) '())))
+ (define (reverse+tail lst seed)
+ (let loop ((lst lst)
+ (result (tail-gen seed)))
+ (if (null? lst)
+ result
+ (loop (cdr lst)
+ (cons (car lst) result)))))
+
+ (check-arg procedure? p unfold)
+ (check-arg procedure? f unfold)
+ (check-arg procedure? g unfold)
+ (check-arg procedure? tail-gen unfold)
+ (let loop ((seed seed)
+ (result '()))
+ (if (p seed)
+ (reverse+tail result seed)
+ (loop (g seed)
+ (cons (f seed) result)))))
+
+(define* (unfold-right p f g seed #\optional (tail '()))
+ (check-arg procedure? p unfold-right)
+ (check-arg procedure? f unfold-right)
+ (check-arg procedure? g unfold-right)
+ (let uf ((seed seed) (lis tail))
+ (if (p seed)
+ lis
+ (uf (g seed) (cons (f seed) lis)))))
+
+(define (reduce f ridentity lst)
+ "`reduce' is a variant of `fold', where the first call to F is on two
+elements from LST, rather than one element and a given initial value.
+If LST is empty, RIDENTITY is returned. If LST has just one element
+then that's the return value."
+ (check-arg procedure? f reduce)
+ (if (null? lst)
+ ridentity
+ (fold f (car lst) (cdr lst))))
+
+(define (reduce-right f ridentity lst)
+ "`reduce-right' is a variant of `fold-right', where the first call to
+F is on two elements from LST, rather than one element and a given
+initial value. If LST is empty, RIDENTITY is returned. If LST
+has just one element then that's the return value."
+ (reduce f ridentity (reverse lst)))
+
+(define map
+ (case-lambda
+ ((f l)
+ (check-arg procedure? f map)
+ (let map1 ((hare l) (tortoise l) (move? #f) (out '()))
+ (if (pair? hare)
+ (if move?
+ (if (eq? tortoise hare)
+ (scm-error 'wrong-type-arg "map" "Circular list: ~S"
+ (list l) #f)
+ (map1 (cdr hare) (cdr tortoise) #f
+ (cons (f (car hare)) out)))
+ (map1 (cdr hare) tortoise #t
+ (cons (f (car hare)) out)))
+ (if (null? hare)
+ (reverse! out)
+ (scm-error 'wrong-type-arg "map" "Not a list: ~S"
+ (list l) #f)))))
+
+ ((f l1 . rest)
+ (check-arg procedure? f map)
+ (let ((len (fold (lambda (ls len)
+ (let ((ls-len (length+ ls)))
+ (if len
+ (if ls-len (min ls-len len) len)
+ ls-len)))
+ (length+ l1)
+ rest)))
+ (if (not len)
+ (scm-error 'wrong-type-arg "map"
+ "Args do not contain a proper (finite) list: ~S"
+ (list (cons l1 rest)) #f))
+ (let mapn ((l1 l1) (rest rest) (len len) (out '()))
+ (if (zero? len)
+ (reverse! out)
+ (mapn (cdr l1) (map cdr rest) (1- len)
+ (cons (apply f (car l1) (map car rest)) out))))))))
+
+(define map-in-order map)
+
+(define for-each
+ (case-lambda
+ ((f l)
+ (check-arg procedure? f for-each)
+ (let for-each1 ((hare l) (tortoise l) (move? #f))
+ (if (pair? hare)
+ (if move?
+ (if (eq? tortoise hare)
+ (scm-error 'wrong-type-arg "for-each" "Circular list: ~S"
+ (list l) #f)
+ (begin
+ (f (car hare))
+ (for-each1 (cdr hare) (cdr tortoise) #f)))
+ (begin
+ (f (car hare))
+ (for-each1 (cdr hare) tortoise #t)))
+
+ (if (not (null? hare))
+ (scm-error 'wrong-type-arg "for-each" "Not a list: ~S"
+ (list l) #f)))))
+
+ ((f l1 . rest)
+ (check-arg procedure? f for-each)
+ (let ((len (fold (lambda (ls len)
+ (let ((ls-len (length+ ls)))
+ (if len
+ (if ls-len (min ls-len len) len)
+ ls-len)))
+ (length+ l1)
+ rest)))
+ (if (not len)
+ (scm-error 'wrong-type-arg "for-each"
+ "Args do not contain a proper (finite) list: ~S"
+ (list (cons l1 rest)) #f))
+ (let for-eachn ((l1 l1) (rest rest) (len len))
+ (if (> len 0)
+ (begin
+ (apply f (car l1) (map car rest))
+ (for-eachn (cdr l1) (map cdr rest) (1- len)))))))))
+
+(define (append-map f clist1 . rest)
+ (concatenate (apply map f clist1 rest)))
+
+(define (append-map! f clist1 . rest)
+ (concatenate! (apply map f clist1 rest)))
+
+;; OPTIMIZE-ME: Re-use cons cells of list1
+(define map! map)
+
+(define (filter-map proc list1 . rest)
+ "Apply PROC to the elements of LIST1... and return a list of the
+results as per SRFI-1 `map', except that any #f results are omitted from
+the list returned."
+ (check-arg procedure? proc filter-map)
+ (if (null? rest)
+ (let lp ((l list1)
+ (rl '()))
+ (if (null? l)
+ (reverse! rl)
+ (let ((res (proc (car l))))
+ (if res
+ (lp (cdr l) (cons res rl))
+ (lp (cdr l) rl)))))
+ (let lp ((l (cons list1 rest))
+ (rl '()))
+ (if (any1 null? l)
+ (reverse! rl)
+ (let ((res (apply proc (map car l))))
+ (if res
+ (lp (map cdr l) (cons res rl))
+ (lp (map cdr l) rl)))))))
+
+(define (pair-for-each f clist1 . rest)
+ (check-arg procedure? f pair-for-each)
+ (if (null? rest)
+ (let lp ((l clist1))
+ (if (null? l)
+ (if #f #f)
+ (begin
+ (f l)
+ (lp (cdr l)))))
+ (let lp ((l (cons clist1 rest)))
+ (if (any1 null? l)
+ (if #f #f)
+ (begin
+ (apply f l)
+ (lp (map cdr l)))))))
+
+
+;;; Searching
+
+(define (take-while pred ls)
+ "Return a new list which is the longest initial prefix of LS whose
+elements all satisfy the predicate PRED."
+ (check-arg procedure? pred take-while)
+ (cond ((null? ls) '())
+ ((not (pred (car ls))) '())
+ (else
+ (let ((result (list (car ls))))
+ (let lp ((ls (cdr ls)) (p result))
+ (cond ((null? ls) result)
+ ((not (pred (car ls))) result)
+ (else
+ (set-cdr! p (list (car ls)))
+ (lp (cdr ls) (cdr p)))))))))
+
+(define (take-while! pred lst)
+ "Linear-update variant of `take-while'."
+ (check-arg procedure? pred take-while!)
+ (let loop ((prev #f)
+ (rest lst))
+ (cond ((null? rest)
+ lst)
+ ((pred (car rest))
+ (loop rest (cdr rest)))
+ (else
+ (if (pair? prev)
+ (begin
+ (set-cdr! prev '())
+ lst)
+ '())))))
+
+(define (drop-while pred lst)
+ "Drop the longest initial prefix of LST whose elements all satisfy the
+predicate PRED."
+ (check-arg procedure? pred drop-while)
+ (let loop ((lst lst))
+ (cond ((null? lst)
+ '())
+ ((pred (car lst))
+ (loop (cdr lst)))
+ (else lst))))
+
+(define (span pred lst)
+ "Return two values, the longest initial prefix of LST whose elements
+all satisfy the predicate PRED, and the remainder of LST."
+ (check-arg procedure? pred span)
+ (let lp ((lst lst) (rl '()))
+ (if (and (not (null? lst))
+ (pred (car lst)))
+ (lp (cdr lst) (cons (car lst) rl))
+ (values (reverse! rl) lst))))
+
+(define (span! pred list)
+ "Linear-update variant of `span'."
+ (check-arg procedure? pred span!)
+ (let loop ((prev #f)
+ (rest list))
+ (cond ((null? rest)
+ (values list '()))
+ ((pred (car rest))
+ (loop rest (cdr rest)))
+ (else
+ (if (pair? prev)
+ (begin
+ (set-cdr! prev '())
+ (values list rest))
+ (values '() list))))))
+
+(define (break pred clist)
+ "Return two values, the longest initial prefix of LST whose elements
+all fail the predicate PRED, and the remainder of LST."
+ (check-arg procedure? pred break)
+ (let lp ((clist clist) (rl '()))
+ (if (or (null? clist)
+ (pred (car clist)))
+ (values (reverse! rl) clist)
+ (lp (cdr clist) (cons (car clist) rl)))))
+
+(define (break! pred list)
+ "Linear-update variant of `break'."
+ (check-arg procedure? pred break!)
+ (let loop ((l list)
+ (prev #f))
+ (cond ((null? l)
+ (values list '()))
+ ((pred (car l))
+ (if (pair? prev)
+ (begin
+ (set-cdr! prev '())
+ (values list l))
+ (values '() list)))
+ (else
+ (loop (cdr l) l)))))
+
+(define (any pred ls . lists)
+ (check-arg procedure? pred any)
+ (if (null? lists)
+ (any1 pred ls)
+ (let lp ((lists (cons ls lists)))
+ (cond ((any1 null? lists)
+ #f)
+ ((any1 null? (map cdr lists))
+ (apply pred (map car lists)))
+ (else
+ (or (apply pred (map car lists)) (lp (map cdr lists))))))))
+
+(define (any1 pred ls)
+ (let lp ((ls ls))
+ (cond ((null? ls)
+ #f)
+ ((null? (cdr ls))
+ (pred (car ls)))
+ (else
+ (or (pred (car ls)) (lp (cdr ls)))))))
+
+(define (every pred ls . lists)
+ (check-arg procedure? pred every)
+ (if (null? lists)
+ (every1 pred ls)
+ (let lp ((lists (cons ls lists)))
+ (cond ((any1 null? lists)
+ #t)
+ ((any1 null? (map cdr lists))
+ (apply pred (map car lists)))
+ (else
+ (and (apply pred (map car lists)) (lp (map cdr lists))))))))
+
+(define (every1 pred ls)
+ (let lp ((ls ls))
+ (cond ((null? ls)
+ #t)
+ ((null? (cdr ls))
+ (pred (car ls)))
+ (else
+ (and (pred (car ls)) (lp (cdr ls)))))))
+
+(define (list-index pred clist1 . rest)
+ "Return the index of the first set of elements, one from each of
+CLIST1 ... CLISTN, that satisfies PRED."
+ (check-arg procedure? pred list-index)
+ (if (null? rest)
+ (let lp ((l clist1) (i 0))
+ (if (null? l)
+ #f
+ (if (pred (car l))
+ i
+ (lp (cdr l) (+ i 1)))))
+ (let lp ((lists (cons clist1 rest)) (i 0))
+ (cond ((any1 null? lists)
+ #f)
+ ((apply pred (map car lists)) i)
+ (else
+ (lp (map cdr lists) (+ i 1)))))))
+
+;;; Association lists
+
+(define alist-cons acons)
+
+(define (alist-copy alist)
+ "Return a copy of ALIST, copying both the pairs comprising the list
+and those making the associations."
+ (let lp ((a alist)
+ (rl '()))
+ (if (null? a)
+ (reverse! rl)
+ (lp (cdr a) (alist-cons (caar a) (cdar a) rl)))))
+
+(define* (alist-delete key alist #\optional (k= equal?))
+ (check-arg procedure? k= alist-delete)
+ (let lp ((a alist) (rl '()))
+ (if (null? a)
+ (reverse! rl)
+ (if (k= key (caar a))
+ (lp (cdr a) rl)
+ (lp (cdr a) (cons (car a) rl))))))
+
+(define* (alist-delete! key alist #\optional (k= equal?))
+ (alist-delete key alist k=)) ; XXX:optimize
+
+;;; Delete / assoc / member
+
+(define* (member x ls #\optional (= equal?))
+ (cond
+ ;; This might be performance-sensitive, so punt on the check here,
+ ;; relying on memq/memv to check that = is a procedure.
+ ((eq? = eq?) (memq x ls))
+ ((eq? = eqv?) (memv x ls))
+ (else
+ (check-arg procedure? = member)
+ (find-tail (lambda (y) (= x y)) ls))))
+
+;;; Set operations on lists
+
+(define (lset<= = . rest)
+ (check-arg procedure? = lset<=)
+ (if (null? rest)
+ #t
+ (let lp ((f (car rest)) (r (cdr rest)))
+ (or (null? r)
+ (and (every (lambda (el) (member el (car r) =)) f)
+ (lp (car r) (cdr r)))))))
+
+(define (lset= = . rest)
+ (check-arg procedure? = lset<=)
+ (if (null? rest)
+ #t
+ (let lp ((f (car rest)) (r (cdr rest)))
+ (or (null? r)
+ (and (every (lambda (el) (member el (car r) =)) f)
+ (every (lambda (el) (member el f (lambda (x y) (= y x)))) (car r))
+ (lp (car r) (cdr r)))))))
+
+;; It's not quite clear if duplicates among the `rest' elements are meant to
+;; be cast out. The spec says `=' is called as (= lstelem restelem),
+;; suggesting perhaps not, but the reference implementation shows the "list"
+;; at each stage as including those elements already added. The latter
+;; corresponds to what's described for lset-union, so that's what's done.
+;;
+(define (lset-adjoin = list . rest)
+ "Add to LIST any of the elements of REST not already in the list.
+These elements are `cons'ed onto the start of LIST (so the return shares
+a common tail with LIST), but the order they're added is unspecified.
+
+The given `=' procedure is used for comparing elements, called
+as `(@var{=} listelem elem)', i.e., the second argument is one of the
+given REST parameters."
+ ;; If `=' is `eq?' or `eqv?', users won't be able to tell which arg is
+ ;; first, so we can pass the raw procedure through to `member',
+ ;; allowing `memq' / `memv' to be selected.
+ (define pred
+ (if (or (eq? = eq?) (eq? = eqv?))
+ =
+ (begin
+ (check-arg procedure? = lset-adjoin)
+ (lambda (x y) (= y x)))))
+
+ (let lp ((ans list) (rest rest))
+ (if (null? rest)
+ ans
+ (lp (if (member (car rest) ans pred)
+ ans
+ (cons (car rest) ans))
+ (cdr rest)))))
+
+(define (lset-union = . rest)
+ ;; Likewise, allow memq / memv to be used if possible.
+ (define pred
+ (if (or (eq? = eq?) (eq? = eqv?))
+ =
+ (begin
+ (check-arg procedure? = lset-union)
+ (lambda (x y) (= y x)))))
+
+ (fold (lambda (lis ans) ; Compute ANS + LIS.
+ (cond ((null? lis) ans) ; Don't copy any lists
+ ((null? ans) lis) ; if we don't have to.
+ ((eq? lis ans) ans)
+ (else
+ (fold (lambda (elt ans)
+ (if (member elt ans pred)
+ ans
+ (cons elt ans)))
+ ans lis))))
+ '()
+ rest))
+
+(define (lset-intersection = list1 . rest)
+ (check-arg procedure? = lset-intersection)
+ (let lp ((l list1) (acc '()))
+ (if (null? l)
+ (reverse! acc)
+ (if (every (lambda (ll) (member (car l) ll =)) rest)
+ (lp (cdr l) (cons (car l) acc))
+ (lp (cdr l) acc)))))
+
+(define (lset-difference = list1 . rest)
+ (check-arg procedure? = lset-difference)
+ (if (null? rest)
+ list1
+ (let lp ((l list1) (acc '()))
+ (if (null? l)
+ (reverse! acc)
+ (if (any (lambda (ll) (member (car l) ll =)) rest)
+ (lp (cdr l) acc)
+ (lp (cdr l) (cons (car l) acc)))))))
+
+;(define (fold kons knil list1 . rest)
+
+(define (lset-xor = . rest)
+ (check-arg procedure? = lset-xor)
+ (fold (lambda (lst res)
+ (let lp ((l lst) (acc '()))
+ (if (null? l)
+ (let lp0 ((r res) (acc acc))
+ (if (null? r)
+ (reverse! acc)
+ (if (member (car r) lst =)
+ (lp0 (cdr r) acc)
+ (lp0 (cdr r) (cons (car r) acc)))))
+ (if (member (car l) res =)
+ (lp (cdr l) acc)
+ (lp (cdr l) (cons (car l) acc))))))
+ '()
+ rest))
+
+(define (lset-diff+intersection = list1 . rest)
+ (check-arg procedure? = lset-diff+intersection)
+ (let lp ((l list1) (accd '()) (acci '()))
+ (if (null? l)
+ (values (reverse! accd) (reverse! acci))
+ (let ((appears (every (lambda (ll) (member (car l) ll =)) rest)))
+ (if appears
+ (lp (cdr l) accd (cons (car l) acci))
+ (lp (cdr l) (cons (car l) accd) acci))))))
+
+
+(define (lset-union! = . rest)
+ (check-arg procedure? = lset-union!)
+ (apply lset-union = rest)) ; XXX:optimize
+
+(define (lset-intersection! = list1 . rest)
+ (check-arg procedure? = lset-intersection!)
+ (apply lset-intersection = list1 rest)) ; XXX:optimize
+
+(define (lset-xor! = . rest)
+ (check-arg procedure? = lset-xor!)
+ (apply lset-xor = rest)) ; XXX:optimize
+
+(define (lset-diff+intersection! = list1 . rest)
+ (check-arg procedure? = lset-diff+intersection!)
+ (apply lset-diff+intersection = list1 rest)) ; XXX:optimize
+
+;;; srfi-1.scm ends here
+;;; srfi-10.scm --- Hash-Comma Reader Extension
+
+;; Copyright (C) 2001, 2002, 2006 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This module implements the syntax extension #,(), also called
+;; hash-comma, which is defined in SRFI-10.
+;;
+;; The support for SRFI-10 consists of the procedure
+;; `define-reader-ctor' for defining new reader constructors and the
+;; read syntax form
+;;
+;; #,(<ctor> <datum> ...)
+;;
+;; where <ctor> must be a symbol for which a read constructor was
+;; defined previously.
+;;
+;; Example:
+;;
+;; (define-reader-ctor 'file open-input-file)
+;; (define f '#,(file "/etc/passwd"))
+;; (read-line f)
+;; =>
+;; "root:x:0:0:root:/root:/bin/bash"
+;;
+;; Please note the quote before the #,(file ...) expression. This is
+;; necessary because ports are not self-evaluating in Guile.
+;;
+;; This module is fully documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-10)
+ \:use-module (ice-9 rdelim)
+ \:export (define-reader-ctor))
+
+(cond-expand-provide (current-module) '(srfi-10))
+
+;; This hash table stores the association between comma-hash tags and
+;; the corresponding constructor procedures.
+;;
+(define reader-ctors (make-hash-table 31))
+
+;; This procedure installs the procedure @var{proc} as the constructor
+;; for the comma-hash tag @var{symbol}.
+;;
+(define (define-reader-ctor symbol proc)
+ (hashq-set! reader-ctors symbol proc)
+ (if #f #f)) ; Return unspecified value.
+
+;; Retrieve the constructor procedure for the tag @var{symbol} or
+;; throw an error if no such tag is defined.
+;;
+(define (lookup symbol)
+ (let ((p (hashq-ref reader-ctors symbol #f)))
+ (if (procedure? p)
+ p
+ (error "unknown hash-comma tag " symbol))))
+
+;; This is the actual reader extension.
+;;
+(define (hash-comma char port)
+ (let* ((obj (read port)))
+ (if (and (list? obj) (positive? (length obj)) (symbol? (car obj)))
+ (let ((p (lookup (car obj))))
+ (let ((res (apply p (cdr obj))))
+ res))
+ (error "syntax error in hash-comma expression"))))
+
+;; Install the hash extension.
+;;
+(read-hash-extend #\, hash-comma)
+
+;;; srfi-10.scm ends here
+;;; srfi-11.scm --- let-values and let*-values
+
+;; Copyright (C) 2000, 2001, 2002, 2004, 2006, 2009 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This module exports two syntax forms: let-values and let*-values.
+;;
+;; Sample usage:
+;;
+;; (let-values (((x y . z) (foo a b))
+;; ((p q) (bar c)))
+;; (baz x y z p q))
+;;
+;; This binds `x' and `y' to the first to values returned by `foo',
+;; `z' to the rest of the values from `foo', and `p' and `q' to the
+;; values returned by `bar'. All of these are available to `baz'.
+;;
+;; let*-values : let-values :: let* : let
+;;
+;; This module is fully documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-11)
+ \:export-syntax (let-values let*-values))
+
+(cond-expand-provide (current-module) '(srfi-11))
+
+;;;;;;;;;;;;;;
+;; let-values
+;;
+;; Current approach is to translate
+;;
+;; (let-values (((x y . z) (foo a b))
+;; ((p q) (bar c)))
+;; (baz x y z p q))
+;;
+;; into
+;;
+;; (call-with-values (lambda () (foo a b))
+;; (lambda (<tmp-x> <tmp-y> . <tmp-z>)
+;; (call-with-values (lambda () (bar c))
+;; (lambda (<tmp-p> <tmp-q>)
+;; (let ((x <tmp-x>)
+;; (y <tmp-y>)
+;; (z <tmp-z>)
+;; (p <tmp-p>)
+;; (q <tmp-q>))
+;; (baz x y z p q))))))
+
+;; We could really use quasisyntax here...
+(define-syntax let-values
+ (lambda (x)
+ (syntax-case x ()
+ ((_ ((binds exp)) b0 b1 ...)
+ (syntax (call-with-values (lambda () exp)
+ (lambda binds b0 b1 ...))))
+ ((_ (clause ...) b0 b1 ...)
+ (let lp ((clauses (syntax (clause ...)))
+ (ids '())
+ (tmps '()))
+ (if (null? clauses)
+ (with-syntax (((id ...) ids)
+ ((tmp ...) tmps))
+ (syntax (let ((id tmp) ...)
+ b0 b1 ...)))
+ (syntax-case (car clauses) ()
+ (((var ...) exp)
+ (with-syntax (((new-tmp ...) (generate-temporaries
+ (syntax (var ...))))
+ ((id ...) ids)
+ ((tmp ...) tmps))
+ (with-syntax ((inner (lp (cdr clauses)
+ (syntax (var ... id ...))
+ (syntax (new-tmp ... tmp ...)))))
+ (syntax (call-with-values (lambda () exp)
+ (lambda (new-tmp ...) inner))))))
+ ((vars exp)
+ (with-syntax ((((new-tmp . new-var) ...)
+ (let lp ((vars (syntax vars)))
+ (syntax-case vars ()
+ ((id . rest)
+ (acons (syntax id)
+ (car
+ (generate-temporaries (syntax (id))))
+ (lp (syntax rest))))
+ (id (acons (syntax id)
+ (car
+ (generate-temporaries (syntax (id))))
+ '())))))
+ ((id ...) ids)
+ ((tmp ...) tmps))
+ (with-syntax ((inner (lp (cdr clauses)
+ (syntax (new-var ... id ...))
+ (syntax (new-tmp ... tmp ...))))
+ (args (let lp ((tmps (syntax (new-tmp ...))))
+ (syntax-case tmps ()
+ ((id) (syntax id))
+ ((id . rest) (cons (syntax id)
+ (lp (syntax rest))))))))
+ (syntax (call-with-values (lambda () exp)
+ (lambda args inner)))))))))))))
+
+;;;;;;;;;;;;;;
+;; let*-values
+;;
+;; Current approach is to translate
+;;
+;; (let*-values (((x y z) (foo a b))
+;; ((p q) (bar c)))
+;; (baz x y z p q))
+;;
+;; into
+;;
+;; (call-with-values (lambda () (foo a b))
+;; (lambda (x y z)
+;; (call-with-values (lambda (bar c))
+;; (lambda (p q)
+;; (baz x y z p q)))))
+
+(define-syntax let*-values
+ (syntax-rules ()
+ ((let*-values () body ...)
+ (let () body ...))
+ ((let*-values ((vars-1 binding-1) (vars-2 binding-2) ...) body ...)
+ (call-with-values (lambda () binding-1)
+ (lambda vars-1
+ (let*-values ((vars-2 binding-2) ...)
+ body ...))))))
+
+;;; srfi-11.scm ends here
+;;; srfi-111.scm -- SRFI 111 Boxes
+
+;; Copyright (C) 2014 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (srfi srfi-111)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-9 gnu)
+ #\export (box box? unbox set-box!))
+
+(cond-expand-provide (current-module) '(srfi-111))
+
+(define-record-type <box>
+ (box value)
+ box?
+ (value unbox set-box!))
+
+(set-record-type-printer! <box>
+ (lambda (box port)
+ (display "#<box " port)
+ (display (number->string (object-address box) 16) port)
+ (display " value: ")
+ (write (unbox box) port)
+ (display ">" port)))
+;;; srfi-13.scm --- String Library
+
+;; Copyright (C) 2001, 2002, 2003, 2004, 2006 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This module is fully documented in the Guile Reference Manual.
+;;
+;; All procedures are in the core and are simply reexported here.
+
+;;; Code:
+
+(define-module (srfi srfi-13))
+
+(re-export
+;;; Predicates
+ string?
+ string-null?
+ string-any
+ string-every
+
+;;; Constructors
+ make-string
+ string
+ string-tabulate
+
+;;; List/string conversion
+ string->list
+ list->string
+ reverse-list->string
+ string-join
+
+;;; Selection
+ string-length
+ string-ref
+ string-copy
+ substring/shared
+ string-copy!
+ string-take string-take-right
+ string-drop string-drop-right
+ string-pad string-pad-right
+ string-trim string-trim-right
+ string-trim-both
+
+;;; Modification
+ string-set!
+ string-fill!
+
+;;; Comparison
+ string-compare
+ string-compare-ci
+ string= string<>
+ string< string>
+ string<= string>=
+ string-ci= string-ci<>
+ string-ci< string-ci>
+ string-ci<= string-ci>=
+ string-hash string-hash-ci
+
+;;; Prefixes/Suffixes
+ string-prefix-length
+ string-prefix-length-ci
+ string-suffix-length
+ string-suffix-length-ci
+ string-prefix?
+ string-prefix-ci?
+ string-suffix?
+ string-suffix-ci?
+
+;;; Searching
+ string-index
+ string-index-right
+ string-skip string-skip-right
+ string-count
+ string-contains string-contains-ci
+
+;;; Alphabetic case mapping
+ string-upcase
+ string-upcase!
+ string-downcase
+ string-downcase!
+ string-titlecase
+ string-titlecase!
+
+;;; Reverse/Append
+ string-reverse
+ string-reverse!
+ string-append
+ string-append/shared
+ string-concatenate
+ string-concatenate-reverse
+ string-concatenate/shared
+ string-concatenate-reverse/shared
+
+;;; Fold/Unfold/Map
+ string-map string-map!
+ string-fold
+ string-fold-right
+ string-unfold
+ string-unfold-right
+ string-for-each
+ string-for-each-index
+
+;;; Replicate/Rotate
+ xsubstring
+ string-xcopy!
+
+;;; Miscellaneous
+ string-replace
+ string-tokenize
+
+;;; Filtering/Deleting
+ string-filter
+ string-delete)
+
+(cond-expand-provide (current-module) '(srfi-13))
+
+;;; srfi-13.scm ends here
+;;; srfi-14.scm --- Character-set Library
+
+;; Copyright (C) 2001, 2002, 2004, 2006 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This module is fully documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-14))
+
+(re-export
+;;; General procedures
+ char-set?
+ char-set=
+ char-set<=
+ char-set-hash
+
+;;; Iterating over character sets
+ char-set-cursor
+ char-set-ref
+ char-set-cursor-next
+ end-of-char-set?
+ char-set-fold
+ char-set-unfold char-set-unfold!
+ char-set-for-each
+ char-set-map
+
+;;; Creating character sets
+ char-set-copy
+ char-set
+ list->char-set list->char-set!
+ string->char-set string->char-set!
+ char-set-filter char-set-filter!
+ ucs-range->char-set ucs-range->char-set!
+ ->char-set
+
+;;; Querying character sets
+ char-set-size
+ char-set-count
+ char-set->list
+ char-set->string
+ char-set-contains?
+ char-set-every
+ char-set-any
+
+;;; Character set algebra
+ char-set-adjoin char-set-adjoin!
+ char-set-delete char-set-delete!
+ char-set-complement
+ char-set-union
+ char-set-intersection
+ char-set-difference
+ char-set-xor
+ char-set-diff+intersection
+ char-set-complement!
+ char-set-union!
+ char-set-intersection!
+ char-set-difference!
+ char-set-xor!
+ char-set-diff+intersection!
+
+;;; Standard character sets
+ char-set:lower-case
+ char-set:upper-case
+ char-set:title-case
+ char-set:letter
+ char-set:digit
+ char-set:letter+digit
+ char-set:graphic
+ char-set:printing
+ char-set:whitespace
+ char-set:iso-control
+ char-set:punctuation
+ char-set:symbol
+ char-set:hex-digit
+ char-set:blank
+ char-set:ascii
+ char-set:empty
+ char-set:full)
+
+(cond-expand-provide (current-module) '(srfi-14))
+
+;;; srfi-14.scm ends here
+;;; srfi-16.scm --- case-lambda
+
+;; Copyright (C) 2001, 2002, 2006, 2009, 2014 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Martin Grabmueller
+
+;;; Commentary:
+
+;; Implementation of SRFI-16. `case-lambda' is a syntactic form
+;; which permits writing functions acting different according to the
+;; number of arguments passed.
+;;
+;; The syntax of the `case-lambda' form is defined in the following
+;; EBNF grammar.
+;;
+;; <case-lambda>
+;; --> (case-lambda <case-lambda-clause>)
+;; <case-lambda-clause>
+;; --> (<signature> <definition-or-command>*)
+;; <signature>
+;; --> (<identifier>*)
+;; | (<identifier>* . <identifier>)
+;; | <identifier>
+;;
+;; The value returned by a `case-lambda' form is a procedure which
+;; matches the number of actual arguments against the signatures in
+;; the various clauses, in order. The first matching clause is
+;; selected, the corresponding values from the actual parameter list
+;; are bound to the variable names in the clauses and the body of the
+;; clause is evaluated.
+
+;;; Code:
+
+(define-module (srfi srfi-16)
+ #\re-export (case-lambda))
+
+;; Case-lambda is now provided by core psyntax.
+;;; srfi-17.scm --- Generalized set!
+
+;; Copyright (C) 2001, 2002, 2003, 2006 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Matthias Koeppe <mkoeppe@mail.math.uni-magdeburg.de>
+
+;;; Commentary:
+
+;; This is an implementation of SRFI-17: Generalized set!
+;;
+;; It exports the Guile procedure `make-procedure-with-setter' under
+;; the SRFI name `getter-with-setter' and exports the standard
+;; procedures `car', `cdr', ..., `cdddr', `string-ref' and
+;; `vector-ref' as procedures with setters, as required by the SRFI.
+;;
+;; SRFI-17 was heavily criticized during its discussion period but it
+;; was finalized anyway. One issue was its concept of globally
+;; associating setter "properties" with (procedure) values, which is
+;; non-Schemy. For this reason, this implementation chooses not to
+;; provide a way to set the setter of a procedure. In fact, (set!
+;; (setter PROC) SETTER) signals an error. The only way to attach a
+;; setter to a procedure is to create a new object (a "procedure with
+;; setter") via the `getter-with-setter' procedure. This procedure is
+;; also specified in the SRFI. Using it avoids the described
+;; problems.
+;;
+;; This module is fully documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-17)
+ \:export (getter-with-setter)
+ \:replace (;; redefined standard procedures
+ setter
+ car cdr caar cadr cdar cddr caaar caadr cadar caddr cdaar
+ cdadr cddar cdddr caaaar caaadr caadar caaddr cadaar cadadr
+ caddar cadddr cdaaar cdaadr cdadar cdaddr cddaar cddadr
+ cdddar cddddr string-ref vector-ref))
+
+(cond-expand-provide (current-module) '(srfi-17))
+
+;;; Procedures
+
+(define getter-with-setter make-procedure-with-setter)
+
+(define setter
+ (getter-with-setter
+ (@ (guile) setter)
+ (lambda args
+ (error "Setting setters is not supported for a good reason."))))
+
+;;; Redefine R5RS procedures to appropriate procedures with setters
+
+(define (compose-setter setter location)
+ (lambda (obj value)
+ (setter (location obj) value)))
+
+(define car
+ (getter-with-setter (@ (guile) car)
+ set-car!))
+(define cdr
+ (getter-with-setter (@ (guile) cdr)
+ set-cdr!))
+
+(define caar
+ (getter-with-setter (@ (guile) caar)
+ (compose-setter set-car! (@ (guile) car))))
+(define cadr
+ (getter-with-setter (@ (guile) cadr)
+ (compose-setter set-car! (@ (guile) cdr))))
+(define cdar
+ (getter-with-setter (@ (guile) cdar)
+ (compose-setter set-cdr! (@ (guile) car))))
+(define cddr
+ (getter-with-setter (@ (guile) cddr)
+ (compose-setter set-cdr! (@ (guile) cdr))))
+
+(define caaar
+ (getter-with-setter (@ (guile) caaar)
+ (compose-setter set-car! (@ (guile) caar))))
+(define caadr
+ (getter-with-setter (@ (guile) caadr)
+ (compose-setter set-car! (@ (guile) cadr))))
+(define cadar
+ (getter-with-setter (@ (guile) cadar)
+ (compose-setter set-car! (@ (guile) cdar))))
+(define caddr
+ (getter-with-setter (@ (guile) caddr)
+ (compose-setter set-car! (@ (guile) cddr))))
+(define cdaar
+ (getter-with-setter (@ (guile) cdaar)
+ (compose-setter set-cdr! (@ (guile) caar))))
+(define cdadr
+ (getter-with-setter (@ (guile) cdadr)
+ (compose-setter set-cdr! (@ (guile) cadr))))
+(define cddar
+ (getter-with-setter (@ (guile) cddar)
+ (compose-setter set-cdr! (@ (guile) cdar))))
+(define cdddr
+ (getter-with-setter (@ (guile) cdddr)
+ (compose-setter set-cdr! (@ (guile) cddr))))
+
+(define caaaar
+ (getter-with-setter (@ (guile) caaaar)
+ (compose-setter set-car! (@ (guile) caaar))))
+(define caaadr
+ (getter-with-setter (@ (guile) caaadr)
+ (compose-setter set-car! (@ (guile) caadr))))
+(define caadar
+ (getter-with-setter (@ (guile) caadar)
+ (compose-setter set-car! (@ (guile) cadar))))
+(define caaddr
+ (getter-with-setter (@ (guile) caaddr)
+ (compose-setter set-car! (@ (guile) caddr))))
+(define cadaar
+ (getter-with-setter (@ (guile) cadaar)
+ (compose-setter set-car! (@ (guile) cdaar))))
+(define cadadr
+ (getter-with-setter (@ (guile) cadadr)
+ (compose-setter set-car! (@ (guile) cdadr))))
+(define caddar
+ (getter-with-setter (@ (guile) caddar)
+ (compose-setter set-car! (@ (guile) cddar))))
+(define cadddr
+ (getter-with-setter (@ (guile) cadddr)
+ (compose-setter set-car! (@ (guile) cdddr))))
+(define cdaaar
+ (getter-with-setter (@ (guile) cdaaar)
+ (compose-setter set-cdr! (@ (guile) caaar))))
+(define cdaadr
+ (getter-with-setter (@ (guile) cdaadr)
+ (compose-setter set-cdr! (@ (guile) caadr))))
+(define cdadar
+ (getter-with-setter (@ (guile) cdadar)
+ (compose-setter set-cdr! (@ (guile) cadar))))
+(define cdaddr
+ (getter-with-setter (@ (guile) cdaddr)
+ (compose-setter set-cdr! (@ (guile) caddr))))
+(define cddaar
+ (getter-with-setter (@ (guile) cddaar)
+ (compose-setter set-cdr! (@ (guile) cdaar))))
+(define cddadr
+ (getter-with-setter (@ (guile) cddadr)
+ (compose-setter set-cdr! (@ (guile) cdadr))))
+(define cdddar
+ (getter-with-setter (@ (guile) cdddar)
+ (compose-setter set-cdr! (@ (guile) cddar))))
+(define cddddr
+ (getter-with-setter (@ (guile) cddddr)
+ (compose-setter set-cdr! (@ (guile) cdddr))))
+
+(define string-ref
+ (getter-with-setter (@ (guile) string-ref)
+ string-set!))
+
+(define vector-ref
+ (getter-with-setter (@ (guile) vector-ref)
+ vector-set!))
+
+;;; srfi-17.scm ends here
+;;; srfi-18.scm --- Multithreading support
+
+;; Copyright (C) 2008, 2009, 2010, 2014 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Julian Graham <julian.graham@aya.yale.edu>
+;;; Date: 2008-04-11
+
+;;; Commentary:
+
+;; This is an implementation of SRFI-18 (Multithreading support).
+;;
+;; All procedures defined in SRFI-18, which are not already defined in
+;; the Guile core library, are exported.
+;;
+;; This module is fully documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-18)
+ \:use-module (srfi srfi-34)
+ \:export (
+
+;;; Threads
+ ;; current-thread <= in the core
+ ;; thread? <= in the core
+ make-thread
+ thread-name
+ thread-specific
+ thread-specific-set!
+ thread-start!
+ thread-yield!
+ thread-sleep!
+ thread-terminate!
+ thread-join!
+
+;;; Mutexes
+ ;; mutex? <= in the core
+ make-mutex
+ mutex-name
+ mutex-specific
+ mutex-specific-set!
+ mutex-state
+ mutex-lock!
+ mutex-unlock!
+
+;;; Condition variables
+ ;; condition-variable? <= in the core
+ make-condition-variable
+ condition-variable-name
+ condition-variable-specific
+ condition-variable-specific-set!
+ condition-variable-signal!
+ condition-variable-broadcast!
+ condition-variable-wait!
+
+;;; Time
+ current-time
+ time?
+ time->seconds
+ seconds->time
+
+ current-exception-handler
+ with-exception-handler
+ raise
+ join-timeout-exception?
+ abandoned-mutex-exception?
+ terminated-thread-exception?
+ uncaught-exception?
+ uncaught-exception-reason
+ )
+ \:re-export (current-thread thread? mutex? condition-variable?)
+ \:replace (current-time
+ make-thread
+ make-mutex
+ make-condition-variable
+ raise))
+
+(if (not (provided? 'threads))
+ (error "SRFI-18 requires Guile with threads support"))
+
+(cond-expand-provide (current-module) '(srfi-18))
+
+(define (check-arg-type pred arg caller)
+ (if (pred arg)
+ arg
+ (scm-error 'wrong-type-arg caller
+ "Wrong type argument: ~S" (list arg) '())))
+
+(define abandoned-mutex-exception (list 'abandoned-mutex-exception))
+(define join-timeout-exception (list 'join-timeout-exception))
+(define terminated-thread-exception (list 'terminated-thread-exception))
+(define uncaught-exception (list 'uncaught-exception))
+
+(define object-names (make-weak-key-hash-table))
+(define object-specifics (make-weak-key-hash-table))
+(define thread-start-conds (make-weak-key-hash-table))
+(define thread-exception-handlers (make-weak-key-hash-table))
+
+;; EXCEPTIONS
+
+(define raise (@ (srfi srfi-34) raise))
+(define (initial-handler obj)
+ (srfi-18-exception-preserver (cons uncaught-exception obj)))
+
+(define thread->exception (make-object-property))
+
+(define (srfi-18-exception-preserver obj)
+ (if (or (terminated-thread-exception? obj)
+ (uncaught-exception? obj))
+ (set! (thread->exception (current-thread)) obj)))
+
+(define (srfi-18-exception-handler key . args)
+
+ ;; SRFI 34 exceptions continue to bubble up no matter who handles them, so
+ ;; if one is caught at this level, it has already been taken care of by
+ ;; `initial-handler'.
+
+ (and (not (eq? key 'srfi-34))
+ (srfi-18-exception-preserver (if (null? args)
+ (cons uncaught-exception key)
+ (cons* uncaught-exception key args)))))
+
+(define (current-handler-stack)
+ (let ((ct (current-thread)))
+ (or (hashq-ref thread-exception-handlers ct)
+ (hashq-set! thread-exception-handlers ct (list initial-handler)))))
+
+(define (with-exception-handler handler thunk)
+ (let ((ct (current-thread))
+ (hl (current-handler-stack)))
+ (check-arg-type procedure? handler "with-exception-handler")
+ (check-arg-type thunk? thunk "with-exception-handler")
+ (hashq-set! thread-exception-handlers ct (cons handler hl))
+ (apply (@ (srfi srfi-34) with-exception-handler)
+ (list (lambda (obj)
+ (hashq-set! thread-exception-handlers ct hl)
+ (handler obj))
+ (lambda ()
+ (call-with-values thunk
+ (lambda res
+ (hashq-set! thread-exception-handlers ct hl)
+ (apply values res))))))))
+
+(define (current-exception-handler)
+ (car (current-handler-stack)))
+
+(define (join-timeout-exception? obj) (eq? obj join-timeout-exception))
+(define (abandoned-mutex-exception? obj) (eq? obj abandoned-mutex-exception))
+(define (uncaught-exception? obj)
+ (and (pair? obj) (eq? (car obj) uncaught-exception)))
+(define (uncaught-exception-reason exc)
+ (cdr (check-arg-type uncaught-exception? exc "uncaught-exception-reason")))
+(define (terminated-thread-exception? obj)
+ (eq? obj terminated-thread-exception))
+
+;; THREADS
+
+;; Create a new thread and prevent it from starting using a condition variable.
+;; Once started, install a top-level exception handler that rethrows any
+;; exceptions wrapped in an uncaught-exception wrapper.
+
+(define make-thread
+ (let ((make-cond-wrapper (lambda (thunk lcond lmutex scond smutex)
+ (lambda ()
+ (lock-mutex lmutex)
+ (signal-condition-variable lcond)
+ (lock-mutex smutex)
+ (unlock-mutex lmutex)
+ (wait-condition-variable scond smutex)
+ (unlock-mutex smutex)
+ (with-exception-handler initial-handler
+ thunk)))))
+ (lambda (thunk . name)
+ (let ((n (and (pair? name) (car name)))
+
+ (lm (make-mutex 'launch-mutex))
+ (lc (make-condition-variable 'launch-condition-variable))
+ (sm (make-mutex 'start-mutex))
+ (sc (make-condition-variable 'start-condition-variable)))
+
+ (lock-mutex lm)
+ (let ((t (call-with-new-thread (make-cond-wrapper thunk lc lm sc sm)
+ srfi-18-exception-handler)))
+ (hashq-set! thread-start-conds t (cons sm sc))
+ (and n (hashq-set! object-names t n))
+ (wait-condition-variable lc lm)
+ (unlock-mutex lm)
+ t)))))
+
+(define (thread-name thread)
+ (hashq-ref object-names (check-arg-type thread? thread "thread-name")))
+
+(define (thread-specific thread)
+ (hashq-ref object-specifics
+ (check-arg-type thread? thread "thread-specific")))
+
+(define (thread-specific-set! thread obj)
+ (hashq-set! object-specifics
+ (check-arg-type thread? thread "thread-specific-set!")
+ obj)
+ *unspecified*)
+
+(define (thread-start! thread)
+ (let ((x (hashq-ref thread-start-conds
+ (check-arg-type thread? thread "thread-start!"))))
+ (and x (let ((smutex (car x))
+ (scond (cdr x)))
+ (hashq-remove! thread-start-conds thread)
+ (lock-mutex smutex)
+ (signal-condition-variable scond)
+ (unlock-mutex smutex)))
+ thread))
+
+(define (thread-yield!) (yield) *unspecified*)
+
+(define (thread-sleep! timeout)
+ (let* ((ct (time->seconds (current-time)))
+ (t (cond ((time? timeout) (- (time->seconds timeout) ct))
+ ((number? timeout) (- timeout ct))
+ (else (scm-error 'wrong-type-arg "thread-sleep!"
+ "Wrong type argument: ~S"
+ (list timeout)
+ '()))))
+ (secs (inexact->exact (truncate t)))
+ (usecs (inexact->exact (truncate (* (- t secs) 1000000)))))
+ (and (> secs 0) (sleep secs))
+ (and (> usecs 0) (usleep usecs))
+ *unspecified*))
+
+;; A convenience function for installing exception handlers on SRFI-18
+;; primitives that resume the calling continuation after the handler is
+;; invoked -- this resolves a behavioral incompatibility with Guile's
+;; implementation of SRFI-34, which uses lazy-catch and rethrows handled
+;; exceptions. (SRFI-18, "Primitives and exceptions")
+
+(define (wrap thunk)
+ (lambda (continuation)
+ (with-exception-handler (lambda (obj)
+ ((current-exception-handler) obj)
+ (continuation))
+ thunk)))
+
+;; A pass-thru to cancel-thread that first installs a handler that throws
+;; terminated-thread exception, as per SRFI-18,
+
+(define (thread-terminate! thread)
+ (define (thread-terminate-inner!)
+ (let ((current-handler (thread-cleanup thread)))
+ (if (thunk? current-handler)
+ (set-thread-cleanup! thread
+ (lambda ()
+ (with-exception-handler initial-handler
+ current-handler)
+ (srfi-18-exception-preserver
+ terminated-thread-exception)))
+ (set-thread-cleanup! thread
+ (lambda () (srfi-18-exception-preserver
+ terminated-thread-exception))))
+ (cancel-thread thread)
+ *unspecified*))
+ (thread-terminate-inner!))
+
+(define (thread-join! thread . args)
+ (define thread-join-inner!
+ (wrap (lambda ()
+ (let ((v (apply join-thread (cons thread args)))
+ (e (thread->exception thread)))
+ (if (and (= (length args) 1) (not v))
+ (raise join-timeout-exception))
+ (if e (raise e))
+ v))))
+ (call/cc thread-join-inner!))
+
+;; MUTEXES
+;; These functions are all pass-thrus to the existing Guile implementations.
+
+(define make-mutex
+ (lambda name
+ (let ((n (and (pair? name) (car name)))
+ (m ((@ (guile) make-mutex)
+ 'unchecked-unlock
+ 'allow-external-unlock
+ 'recursive)))
+ (and n (hashq-set! object-names m n)) m)))
+
+(define (mutex-name mutex)
+ (hashq-ref object-names (check-arg-type mutex? mutex "mutex-name")))
+
+(define (mutex-specific mutex)
+ (hashq-ref object-specifics
+ (check-arg-type mutex? mutex "mutex-specific")))
+
+(define (mutex-specific-set! mutex obj)
+ (hashq-set! object-specifics
+ (check-arg-type mutex? mutex "mutex-specific-set!")
+ obj)
+ *unspecified*)
+
+(define (mutex-state mutex)
+ (let ((owner (mutex-owner mutex)))
+ (if owner
+ (if (thread-exited? owner) 'abandoned owner)
+ (if (> (mutex-level mutex) 0) 'not-owned 'not-abandoned))))
+
+(define (mutex-lock! mutex . args)
+ (define mutex-lock-inner!
+ (wrap (lambda ()
+ (catch 'abandoned-mutex-error
+ (lambda () (apply lock-mutex (cons mutex args)))
+ (lambda (key . args) (raise abandoned-mutex-exception))))))
+ (call/cc mutex-lock-inner!))
+
+(define (mutex-unlock! mutex . args)
+ (apply unlock-mutex (cons mutex args)))
+
+;; CONDITION VARIABLES
+;; These functions are all pass-thrus to the existing Guile implementations.
+
+(define make-condition-variable
+ (lambda name
+ (let ((n (and (pair? name) (car name)))
+ (m ((@ (guile) make-condition-variable))))
+ (and n (hashq-set! object-names m n)) m)))
+
+(define (condition-variable-name condition-variable)
+ (hashq-ref object-names (check-arg-type condition-variable?
+ condition-variable
+ "condition-variable-name")))
+
+(define (condition-variable-specific condition-variable)
+ (hashq-ref object-specifics (check-arg-type condition-variable?
+ condition-variable
+ "condition-variable-specific")))
+
+(define (condition-variable-specific-set! condition-variable obj)
+ (hashq-set! object-specifics
+ (check-arg-type condition-variable?
+ condition-variable
+ "condition-variable-specific-set!")
+ obj)
+ *unspecified*)
+
+(define (condition-variable-signal! cond)
+ (signal-condition-variable cond)
+ *unspecified*)
+
+(define (condition-variable-broadcast! cond)
+ (broadcast-condition-variable cond)
+ *unspecified*)
+
+;; TIME
+
+(define current-time gettimeofday)
+(define (time? obj)
+ (and (pair? obj)
+ (let ((co (car obj))) (and (integer? co) (>= co 0)))
+ (let ((co (cdr obj))) (and (integer? co) (>= co 0)))))
+
+(define (time->seconds time)
+ (and (check-arg-type time? time "time->seconds")
+ (+ (car time) (/ (cdr time) 1000000))))
+
+(define (seconds->time x)
+ (and (check-arg-type number? x "seconds->time")
+ (let ((fx (truncate x)))
+ (cons (inexact->exact fx)
+ (inexact->exact (truncate (* (- x fx) 1000000)))))))
+
+;; srfi-18.scm ends here
+;;; srfi-19.scm --- Time/Date Library
+
+;; Copyright (C) 2001-2003, 2005-2011, 2014, 2016
+;; Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Rob Browning <rlb@cs.utexas.edu>
+;;; Originally from SRFI reference implementation by Will Fitzgerald.
+
+;;; Commentary:
+
+;; This module is fully documented in the Guile Reference Manual.
+
+;;; Code:
+
+;; FIXME: I haven't checked a decent amount of this code for potential
+;; performance improvements, but I suspect that there may be some
+;; substantial ones to be realized, esp. in the later "parsing" half
+;; of the file, by rewriting the code with use of more Guile native
+;; functions that do more work in a "chunk".
+;;
+;; FIXME: mkoeppe: Time zones are treated a little simplistic in
+;; SRFI-19; they are only a numeric offset. Thus, printing time zones
+;; (LOCALE-PRINT-TIME-ZONE) can't be implemented sensibly. The
+;; functions taking an optional TZ-OFFSET should be extended to take a
+;; symbolic time-zone (like "CET"); this string should be stored in
+;; the DATE structure.
+
+(define-module (srfi srfi-19)
+ \:use-module (srfi srfi-6)
+ \:use-module (srfi srfi-8)
+ \:use-module (srfi srfi-9)
+ \:autoload (ice-9 rdelim) (read-line)
+ \:use-module (ice-9 i18n)
+ \:replace (current-time)
+ \:export (;; Constants
+ time-duration
+ time-monotonic
+ time-process
+ time-tai
+ time-thread
+ time-utc
+ ;; Current time and clock resolution
+ current-date
+ current-julian-day
+ current-modified-julian-day
+ time-resolution
+ ;; Time object and accessors
+ make-time
+ time?
+ time-type
+ time-nanosecond
+ time-second
+ set-time-type!
+ set-time-nanosecond!
+ set-time-second!
+ copy-time
+ ;; Time comparison procedures
+ time<=?
+ time<?
+ time=?
+ time>=?
+ time>?
+ ;; Time arithmetic procedures
+ time-difference
+ time-difference!
+ add-duration
+ add-duration!
+ subtract-duration
+ subtract-duration!
+ ;; Date object and accessors
+ make-date
+ date?
+ date-nanosecond
+ date-second
+ date-minute
+ date-hour
+ date-day
+ date-month
+ date-year
+ date-zone-offset
+ date-year-day
+ date-week-day
+ date-week-number
+ ;; Time/Date/Julian Day/Modified Julian Day converters
+ date->julian-day
+ date->modified-julian-day
+ date->time-monotonic
+ date->time-tai
+ date->time-utc
+ julian-day->date
+ julian-day->time-monotonic
+ julian-day->time-tai
+ julian-day->time-utc
+ modified-julian-day->date
+ modified-julian-day->time-monotonic
+ modified-julian-day->time-tai
+ modified-julian-day->time-utc
+ time-monotonic->date
+ time-monotonic->julian-day
+ time-monotonic->modified-julian-day
+ time-monotonic->time-tai
+ time-monotonic->time-tai!
+ time-monotonic->time-utc
+ time-monotonic->time-utc!
+ time-tai->date
+ time-tai->julian-day
+ time-tai->modified-julian-day
+ time-tai->time-monotonic
+ time-tai->time-monotonic!
+ time-tai->time-utc
+ time-tai->time-utc!
+ time-utc->date
+ time-utc->julian-day
+ time-utc->modified-julian-day
+ time-utc->time-monotonic
+ time-utc->time-monotonic!
+ time-utc->time-tai
+ time-utc->time-tai!
+ ;; Date to string/string to date converters.
+ date->string
+ string->date))
+
+(cond-expand-provide (current-module) '(srfi-19))
+
+(define time-tai 'time-tai)
+(define time-utc 'time-utc)
+(define time-monotonic 'time-monotonic)
+(define time-thread 'time-thread)
+(define time-process 'time-process)
+(define time-duration 'time-duration)
+
+;; FIXME: do we want to add gc time?
+;; (define time-gc 'time-gc)
+
+;;-- LOCALE dependent constants
+
+;; See date->string
+(define locale-date-time-format "~a ~b ~d ~H:~M:~S~z ~Y")
+(define locale-short-date-format "~m/~d/~y")
+(define locale-time-format "~H:~M:~S")
+(define iso-8601-date-time-format "~Y-~m-~dT~H:~M:~S~z")
+
+;;-- Miscellaneous Constants.
+;;-- only the tai-epoch-in-jd might need changing if
+;; a different epoch is used.
+
+(define nano 1000000000) ; nanoseconds in a second
+(define sid 86400) ; seconds in a day
+(define sihd 43200) ; seconds in a half day
+(define tai-epoch-in-jd 4881175/2) ; julian day number for 'the epoch'
+
+;; FIXME: should this be something other than misc-error?
+(define (time-error caller type value)
+ (if value
+ (throw 'misc-error caller "TIME-ERROR type ~A: ~S" (list type value) #f)
+ (throw 'misc-error caller "TIME-ERROR type ~A" (list type) #f)))
+
+;; A table of leap seconds
+;; See ftp://maia.usno.navy.mil/ser7/tai-utc.dat
+;; and update as necessary.
+;; this procedures reads the file in the above
+;; format and creates the leap second table
+;; it also calls the almost standard, but not R5 procedures read-line
+;; & open-input-string
+;; ie (set! leap-second-table (read-tai-utc-date "tai-utc.dat"))
+
+(define (read-tai-utc-data filename)
+ (define (convert-jd jd)
+ (* (- (inexact->exact jd) tai-epoch-in-jd) sid))
+ (define (convert-sec sec)
+ (inexact->exact sec))
+ (let ((port (open-input-file filename))
+ (table '()))
+ (let loop ((line (read-line port)))
+ (if (not (eof-object? line))
+ (begin
+ (let* ((data (read (open-input-string
+ (string-append "(" line ")"))))
+ (year (car data))
+ (jd (cadddr (cdr data)))
+ (secs (cadddr (cdddr data))))
+ (if (>= year 1972)
+ (set! table (cons
+ (cons (convert-jd jd) (convert-sec secs))
+ table)))
+ (loop (read-line port))))))
+ table))
+
+;; each entry is (tai seconds since epoch . # seconds to subtract for utc)
+;; note they go higher to lower, and end in 1972.
+(define leap-second-table
+ '((1435708800 . 36)
+ (1341100800 . 35)
+ (1230768000 . 34)
+ (1136073600 . 33)
+ (915148800 . 32)
+ (867715200 . 31)
+ (820454400 . 30)
+ (773020800 . 29)
+ (741484800 . 28)
+ (709948800 . 27)
+ (662688000 . 26)
+ (631152000 . 25)
+ (567993600 . 24)
+ (489024000 . 23)
+ (425865600 . 22)
+ (394329600 . 21)
+ (362793600 . 20)
+ (315532800 . 19)
+ (283996800 . 18)
+ (252460800 . 17)
+ (220924800 . 16)
+ (189302400 . 15)
+ (157766400 . 14)
+ (126230400 . 13)
+ (94694400 . 12)
+ (78796800 . 11)
+ (63072000 . 10)))
+
+(define (read-leap-second-table filename)
+ (set! leap-second-table (read-tai-utc-data filename)))
+
+
+(define (leap-second-delta utc-seconds)
+ (letrec ((lsd (lambda (table)
+ (cond ((>= utc-seconds (caar table))
+ (cdar table))
+ (else (lsd (cdr table)))))))
+ (if (< utc-seconds (* (- 1972 1970) 365 sid)) 0
+ (lsd leap-second-table))))
+
+
+;;; the TIME structure; creates the accessors, too.
+
+(define-record-type time
+ (make-time-unnormalized type nanosecond second)
+ time?
+ (type time-type set-time-type!)
+ (nanosecond time-nanosecond set-time-nanosecond!)
+ (second time-second set-time-second!))
+
+(define (copy-time time)
+ (make-time (time-type time) (time-nanosecond time) (time-second time)))
+
+(define (split-real r)
+ (if (integer? r)
+ (values (inexact->exact r) 0)
+ (let ((l (truncate r)))
+ (values (inexact->exact l) (- r l)))))
+
+(define (time-normalize! t)
+ (if (>= (abs (time-nanosecond t)) 1000000000)
+ (receive (int frac)
+ (split-real (time-nanosecond t))
+ (set-time-second! t (+ (time-second t)
+ (quotient int 1000000000)))
+ (set-time-nanosecond! t (+ (remainder int 1000000000)
+ frac))))
+ (if (and (positive? (time-second t))
+ (negative? (time-nanosecond t)))
+ (begin
+ (set-time-second! t (- (time-second t) 1))
+ (set-time-nanosecond! t (+ 1000000000 (time-nanosecond t))))
+ (if (and (negative? (time-second t))
+ (positive? (time-nanosecond t)))
+ (begin
+ (set-time-second! t (+ (time-second t) 1))
+ (set-time-nanosecond! t (+ 1000000000 (time-nanosecond t))))))
+ t)
+
+(define (make-time type nanosecond second)
+ (time-normalize! (make-time-unnormalized type nanosecond second)))
+
+;; Helpers
+;; FIXME: finish this and publish it?
+(define (date->broken-down-time date)
+ (let ((result (mktime 0)))
+ ;; FIXME: What should we do about leap-seconds which may overflow
+ ;; set-tm:sec?
+ (set-tm:sec result (date-second date))
+ (set-tm:min result (date-minute date))
+ (set-tm:hour result (date-hour date))
+ ;; FIXME: SRFI day ranges from 0-31. (not compatible with set-tm:mday).
+ (set-tm:mday result (date-day date))
+ (set-tm:mon result (- (date-month date) 1))
+ ;; FIXME: need to signal error on range violation.
+ (set-tm:year result (+ 1900 (date-year date)))
+ (set-tm:isdst result -1)
+ (set-tm:gmtoff result (- (date-zone-offset date)))
+ result))
+
+;;; current-time
+
+;;; specific time getters.
+
+(define (current-time-utc)
+ ;; Resolution is microseconds.
+ (let ((tod (gettimeofday)))
+ (make-time time-utc (* (cdr tod) 1000) (car tod))))
+
+(define (current-time-tai)
+ ;; Resolution is microseconds.
+ (let* ((tod (gettimeofday))
+ (sec (car tod))
+ (usec (cdr tod)))
+ (make-time time-tai
+ (* usec 1000)
+ (+ (car tod) (leap-second-delta sec)))))
+
+;;(define (current-time-ms-time time-type proc)
+;; (let ((current-ms (proc)))
+;; (make-time time-type
+;; (quotient current-ms 10000)
+;; (* (remainder current-ms 1000) 10000))))
+
+;; -- we define it to be the same as TAI.
+;; A different implemation of current-time-montonic
+;; will require rewriting all of the time-monotonic converters,
+;; of course.
+
+(define (current-time-monotonic)
+ ;; Resolution is microseconds.
+ (current-time-tai))
+
+(define (current-time-thread)
+ (time-error 'current-time 'unsupported-clock-type 'time-thread))
+
+(define ns-per-guile-tick (/ 1000000000 internal-time-units-per-second))
+
+(define (current-time-process)
+ (let ((run-time (get-internal-run-time)))
+ (make-time
+ time-process
+ (* (remainder run-time internal-time-units-per-second)
+ ns-per-guile-tick)
+ (quotient run-time internal-time-units-per-second))))
+
+;;(define (current-time-gc)
+;; (current-time-ms-time time-gc current-gc-milliseconds))
+
+(define (current-time . clock-type)
+ (let ((clock-type (if (null? clock-type) time-utc (car clock-type))))
+ (cond
+ ((eq? clock-type time-tai) (current-time-tai))
+ ((eq? clock-type time-utc) (current-time-utc))
+ ((eq? clock-type time-monotonic) (current-time-monotonic))
+ ((eq? clock-type time-thread) (current-time-thread))
+ ((eq? clock-type time-process) (current-time-process))
+ ;; ((eq? clock-type time-gc) (current-time-gc))
+ (else (time-error 'current-time 'invalid-clock-type clock-type)))))
+
+;; -- Time Resolution
+;; This is the resolution of the clock in nanoseconds.
+;; This will be implementation specific.
+
+(define (time-resolution . clock-type)
+ (let ((clock-type (if (null? clock-type) time-utc (car clock-type))))
+ (case clock-type
+ ((time-tai) 1000)
+ ((time-utc) 1000)
+ ((time-monotonic) 1000)
+ ((time-process) ns-per-guile-tick)
+ ;; ((eq? clock-type time-thread) 1000)
+ ;; ((eq? clock-type time-gc) 10000)
+ (else (time-error 'time-resolution 'invalid-clock-type clock-type)))))
+
+;; -- Time comparisons
+
+(define (time=? t1 t2)
+ ;; Arrange tests for speed and presume that t1 and t2 are actually times.
+ ;; also presume it will be rare to check two times of different types.
+ (and (= (time-second t1) (time-second t2))
+ (= (time-nanosecond t1) (time-nanosecond t2))
+ (eq? (time-type t1) (time-type t2))))
+
+(define (time>? t1 t2)
+ (or (> (time-second t1) (time-second t2))
+ (and (= (time-second t1) (time-second t2))
+ (> (time-nanosecond t1) (time-nanosecond t2)))))
+
+(define (time<? t1 t2)
+ (or (< (time-second t1) (time-second t2))
+ (and (= (time-second t1) (time-second t2))
+ (< (time-nanosecond t1) (time-nanosecond t2)))))
+
+(define (time>=? t1 t2)
+ (or (> (time-second t1) (time-second t2))
+ (and (= (time-second t1) (time-second t2))
+ (>= (time-nanosecond t1) (time-nanosecond t2)))))
+
+(define (time<=? t1 t2)
+ (or (< (time-second t1) (time-second t2))
+ (and (= (time-second t1) (time-second t2))
+ (<= (time-nanosecond t1) (time-nanosecond t2)))))
+
+;; -- Time arithmetic
+
+(define (time-difference! time1 time2)
+ (let ((sec-diff (- (time-second time1) (time-second time2)))
+ (nsec-diff (- (time-nanosecond time1) (time-nanosecond time2))))
+ (set-time-type! time1 time-duration)
+ (set-time-second! time1 sec-diff)
+ (set-time-nanosecond! time1 nsec-diff)
+ (time-normalize! time1)))
+
+(define (time-difference time1 time2)
+ (let ((result (copy-time time1)))
+ (time-difference! result time2)))
+
+(define (add-duration! t duration)
+ (if (not (eq? (time-type duration) time-duration))
+ (time-error 'add-duration 'not-duration duration)
+ (let ((sec-plus (+ (time-second t) (time-second duration)))
+ (nsec-plus (+ (time-nanosecond t) (time-nanosecond duration))))
+ (set-time-second! t sec-plus)
+ (set-time-nanosecond! t nsec-plus)
+ (time-normalize! t))))
+
+(define (add-duration t duration)
+ (let ((result (copy-time t)))
+ (add-duration! result duration)))
+
+(define (subtract-duration! t duration)
+ (if (not (eq? (time-type duration) time-duration))
+ (time-error 'add-duration 'not-duration duration)
+ (let ((sec-minus (- (time-second t) (time-second duration)))
+ (nsec-minus (- (time-nanosecond t) (time-nanosecond duration))))
+ (set-time-second! t sec-minus)
+ (set-time-nanosecond! t nsec-minus)
+ (time-normalize! t))))
+
+(define (subtract-duration time1 duration)
+ (let ((result (copy-time time1)))
+ (subtract-duration! result duration)))
+
+;; -- Converters between types.
+
+(define (priv:time-tai->time-utc! time-in time-out caller)
+ (if (not (eq? (time-type time-in) time-tai))
+ (time-error caller 'incompatible-time-types time-in))
+ (set-time-type! time-out time-utc)
+ (set-time-nanosecond! time-out (time-nanosecond time-in))
+ (set-time-second! time-out (- (time-second time-in)
+ (leap-second-delta
+ (time-second time-in))))
+ time-out)
+
+(define (time-tai->time-utc time-in)
+ (priv:time-tai->time-utc! time-in (make-time-unnormalized #f #f #f) 'time-tai->time-utc))
+
+
+(define (time-tai->time-utc! time-in)
+ (priv:time-tai->time-utc! time-in time-in 'time-tai->time-utc!))
+
+(define (priv:time-utc->time-tai! time-in time-out caller)
+ (if (not (eq? (time-type time-in) time-utc))
+ (time-error caller 'incompatible-time-types time-in))
+ (set-time-type! time-out time-tai)
+ (set-time-nanosecond! time-out (time-nanosecond time-in))
+ (set-time-second! time-out (+ (time-second time-in)
+ (leap-second-delta
+ (time-second time-in))))
+ time-out)
+
+(define (time-utc->time-tai time-in)
+ (priv:time-utc->time-tai! time-in (make-time-unnormalized #f #f #f) 'time-utc->time-tai))
+
+(define (time-utc->time-tai! time-in)
+ (priv:time-utc->time-tai! time-in time-in 'time-utc->time-tai!))
+
+;; -- these depend on time-monotonic having the same definition as time-tai!
+(define (time-monotonic->time-utc time-in)
+ (if (not (eq? (time-type time-in) time-monotonic))
+ (time-error 'time-monotonic->time-utc
+ 'incompatible-time-types time-in))
+ (let ((ntime (copy-time time-in)))
+ (set-time-type! ntime time-tai)
+ (priv:time-tai->time-utc! ntime ntime 'time-monotonic->time-utc)))
+
+(define (time-monotonic->time-utc! time-in)
+ (if (not (eq? (time-type time-in) time-monotonic))
+ (time-error 'time-monotonic->time-utc!
+ 'incompatible-time-types time-in))
+ (set-time-type! time-in time-tai)
+ (priv:time-tai->time-utc! time-in time-in 'time-monotonic->time-utc))
+
+(define (time-monotonic->time-tai time-in)
+ (if (not (eq? (time-type time-in) time-monotonic))
+ (time-error 'time-monotonic->time-tai
+ 'incompatible-time-types time-in))
+ (let ((ntime (copy-time time-in)))
+ (set-time-type! ntime time-tai)
+ ntime))
+
+(define (time-monotonic->time-tai! time-in)
+ (if (not (eq? (time-type time-in) time-monotonic))
+ (time-error 'time-monotonic->time-tai!
+ 'incompatible-time-types time-in))
+ (set-time-type! time-in time-tai)
+ time-in)
+
+(define (time-utc->time-monotonic time-in)
+ (if (not (eq? (time-type time-in) time-utc))
+ (time-error 'time-utc->time-monotonic
+ 'incompatible-time-types time-in))
+ (let ((ntime (priv:time-utc->time-tai! time-in (make-time-unnormalized #f #f #f)
+ 'time-utc->time-monotonic)))
+ (set-time-type! ntime time-monotonic)
+ ntime))
+
+(define (time-utc->time-monotonic! time-in)
+ (if (not (eq? (time-type time-in) time-utc))
+ (time-error 'time-utc->time-monotonic!
+ 'incompatible-time-types time-in))
+ (let ((ntime (priv:time-utc->time-tai! time-in time-in
+ 'time-utc->time-monotonic!)))
+ (set-time-type! ntime time-monotonic)
+ ntime))
+
+(define (time-tai->time-monotonic time-in)
+ (if (not (eq? (time-type time-in) time-tai))
+ (time-error 'time-tai->time-monotonic
+ 'incompatible-time-types time-in))
+ (let ((ntime (copy-time time-in)))
+ (set-time-type! ntime time-monotonic)
+ ntime))
+
+(define (time-tai->time-monotonic! time-in)
+ (if (not (eq? (time-type time-in) time-tai))
+ (time-error 'time-tai->time-monotonic!
+ 'incompatible-time-types time-in))
+ (set-time-type! time-in time-monotonic)
+ time-in)
+
+;; -- Date Structures
+
+;; FIXME: to be really safe, perhaps we should normalize the
+;; seconds/nanoseconds/minutes coming in to make-date...
+
+(define-record-type date
+ (make-date nanosecond second minute
+ hour day month
+ year
+ zone-offset)
+ date?
+ (nanosecond date-nanosecond set-date-nanosecond!)
+ (second date-second set-date-second!)
+ (minute date-minute set-date-minute!)
+ (hour date-hour set-date-hour!)
+ (day date-day set-date-day!)
+ (month date-month set-date-month!)
+ (year date-year set-date-year!)
+ (zone-offset date-zone-offset set-date-zone-offset!))
+
+;; gives the julian day which starts at noon.
+(define (encode-julian-day-number day month year)
+ (let* ((a (quotient (- 14 month) 12))
+ (y (- (+ year 4800) a (if (negative? year) -1 0)))
+ (m (- (+ month (* 12 a)) 3)))
+ (+ day
+ (quotient (+ (* 153 m) 2) 5)
+ (* 365 y)
+ (quotient y 4)
+ (- (quotient y 100))
+ (quotient y 400)
+ -32045)))
+
+;; gives the seconds/date/month/year
+(define (decode-julian-day-number jdn)
+ (let* ((days (inexact->exact (truncate jdn)))
+ (a (+ days 32044))
+ (b (quotient (+ (* 4 a) 3) 146097))
+ (c (- a (quotient (* 146097 b) 4)))
+ (d (quotient (+ (* 4 c) 3) 1461))
+ (e (- c (quotient (* 1461 d) 4)))
+ (m (quotient (+ (* 5 e) 2) 153))
+ (y (+ (* 100 b) d -4800 (quotient m 10))))
+ (values ; seconds date month year
+ (* (- jdn days) sid)
+ (+ e (- (quotient (+ (* 153 m) 2) 5)) 1)
+ (+ m 3 (* -12 (quotient m 10)))
+ (if (>= 0 y) (- y 1) y))))
+
+;; relies on the fact that we named our time zone accessor
+;; differently from MzScheme's....
+;; This should be written to be OS specific.
+
+(define (local-tz-offset utc-time)
+ ;; SRFI uses seconds West, but guile (and libc) use seconds East.
+ (- (tm:gmtoff (localtime (time-second utc-time)))))
+
+;; special thing -- ignores nanos
+(define (time->julian-day-number seconds tz-offset)
+ (+ (/ (+ seconds tz-offset sihd)
+ sid)
+ tai-epoch-in-jd))
+
+(define (leap-second? second)
+ (and (assoc second leap-second-table) #t))
+
+(define (time-utc->date time . tz-offset)
+ (if (not (eq? (time-type time) time-utc))
+ (time-error 'time->date 'incompatible-time-types time))
+ (let* ((offset (if (null? tz-offset)
+ (local-tz-offset time)
+ (car tz-offset)))
+ (leap-second? (leap-second? (+ offset (time-second time))))
+ (jdn (time->julian-day-number (if leap-second?
+ (- (time-second time) 1)
+ (time-second time))
+ offset)))
+
+ (call-with-values (lambda () (decode-julian-day-number jdn))
+ (lambda (secs date month year)
+ ;; secs is a real because jdn is a real in Guile;
+ ;; but it is conceptionally an integer.
+ (let* ((int-secs (inexact->exact (round secs)))
+ (hours (quotient int-secs (* 60 60)))
+ (rem (remainder int-secs (* 60 60)))
+ (minutes (quotient rem 60))
+ (seconds (remainder rem 60)))
+ (make-date (time-nanosecond time)
+ (if leap-second? (+ seconds 1) seconds)
+ minutes
+ hours
+ date
+ month
+ year
+ offset))))))
+
+(define (time-tai->date time . tz-offset)
+ (if (not (eq? (time-type time) time-tai))
+ (time-error 'time->date 'incompatible-time-types time))
+ (let* ((offset (if (null? tz-offset)
+ (local-tz-offset (time-tai->time-utc time))
+ (car tz-offset)))
+ (seconds (- (time-second time)
+ (leap-second-delta (time-second time))))
+ (leap-second? (leap-second? (+ offset seconds)))
+ (jdn (time->julian-day-number (if leap-second?
+ (- seconds 1)
+ seconds)
+ offset)))
+ (call-with-values (lambda () (decode-julian-day-number jdn))
+ (lambda (secs date month year)
+ ;; secs is a real because jdn is a real in Guile;
+ ;; but it is conceptionally an integer.
+ ;; adjust for leap seconds if necessary ...
+ (let* ((int-secs (inexact->exact (round secs)))
+ (hours (quotient int-secs (* 60 60)))
+ (rem (remainder int-secs (* 60 60)))
+ (minutes (quotient rem 60))
+ (seconds (remainder rem 60)))
+ (make-date (time-nanosecond time)
+ (if leap-second? (+ seconds 1) seconds)
+ minutes
+ hours
+ date
+ month
+ year
+ offset))))))
+
+;; this is the same as time-tai->date.
+(define (time-monotonic->date time . tz-offset)
+ (if (not (eq? (time-type time) time-monotonic))
+ (time-error 'time->date 'incompatible-time-types time))
+ (let* ((offset (if (null? tz-offset)
+ (local-tz-offset (time-monotonic->time-utc time))
+ (car tz-offset)))
+ (seconds (- (time-second time)
+ (leap-second-delta (time-second time))))
+ (leap-second? (leap-second? (+ offset seconds)))
+ (jdn (time->julian-day-number (if leap-second?
+ (- seconds 1)
+ seconds)
+ offset)))
+ (call-with-values (lambda () (decode-julian-day-number jdn))
+ (lambda (secs date month year)
+ ;; secs is a real because jdn is a real in Guile;
+ ;; but it is conceptionally an integer.
+ ;; adjust for leap seconds if necessary ...
+ (let* ((int-secs (inexact->exact (round secs)))
+ (hours (quotient int-secs (* 60 60)))
+ (rem (remainder int-secs (* 60 60)))
+ (minutes (quotient rem 60))
+ (seconds (remainder rem 60)))
+ (make-date (time-nanosecond time)
+ (if leap-second? (+ seconds 1) seconds)
+ minutes
+ hours
+ date
+ month
+ year
+ offset))))))
+
+(define (date->time-utc date)
+ (let* ((jdays (- (encode-julian-day-number (date-day date)
+ (date-month date)
+ (date-year date))
+ tai-epoch-in-jd))
+ ;; jdays is an integer plus 1/2,
+ (jdays-1/2 (inexact->exact (- jdays 1/2))))
+ (make-time
+ time-utc
+ (date-nanosecond date)
+ (+ (* jdays-1/2 24 60 60)
+ (* (date-hour date) 60 60)
+ (* (date-minute date) 60)
+ (date-second date)
+ (- (date-zone-offset date))))))
+
+(define (date->time-tai date)
+ (time-utc->time-tai! (date->time-utc date)))
+
+(define (date->time-monotonic date)
+ (time-utc->time-monotonic! (date->time-utc date)))
+
+(define (leap-year? year)
+ (or (= (modulo year 400) 0)
+ (and (= (modulo year 4) 0) (not (= (modulo year 100) 0)))))
+
+;; Map 1-based month number M to number of days in the year before the
+;; start of month M (in a non-leap year).
+(define month-assoc '((1 . 0) (2 . 31) (3 . 59) (4 . 90)
+ (5 . 120) (6 . 151) (7 . 181) (8 . 212)
+ (9 . 243) (10 . 273) (11 . 304) (12 . 334)))
+
+(define (year-day day month year)
+ (let ((days-pr (assoc month month-assoc)))
+ (if (not days-pr)
+ (time-error 'date-year-day 'invalid-month-specification month))
+ (if (and (leap-year? year) (> month 2))
+ (+ day (cdr days-pr) 1)
+ (+ day (cdr days-pr)))))
+
+(define (date-year-day date)
+ (year-day (date-day date) (date-month date) (date-year date)))
+
+;; from calendar faq
+(define (week-day day month year)
+ (let* ((a (quotient (- 14 month) 12))
+ (y (- year a))
+ (m (+ month (* 12 a) -2)))
+ (modulo (+ day
+ y
+ (quotient y 4)
+ (- (quotient y 100))
+ (quotient y 400)
+ (quotient (* 31 m) 12))
+ 7)))
+
+(define (date-week-day date)
+ (week-day (date-day date) (date-month date) (date-year date)))
+
+(define (days-before-first-week date day-of-week-starting-week)
+ (let* ((first-day (make-date 0 0 0 0
+ 1
+ 1
+ (date-year date)
+ #f))
+ (fdweek-day (date-week-day first-day)))
+ (modulo (- day-of-week-starting-week fdweek-day)
+ 7)))
+
+;; The "-1" here is a fix for the reference implementation, to make a new
+;; week start on the given day-of-week-starting-week. date-year-day returns
+;; a day starting from 1 for 1st Jan.
+;;
+(define (date-week-number date day-of-week-starting-week)
+ (quotient (- (date-year-day date)
+ 1
+ (days-before-first-week date day-of-week-starting-week))
+ 7))
+
+(define (current-date . tz-offset)
+ (let ((time (current-time time-utc)))
+ (time-utc->date
+ time
+ (if (null? tz-offset)
+ (local-tz-offset time)
+ (car tz-offset)))))
+
+;; given a 'two digit' number, find the year within 50 years +/-
+(define (natural-year n)
+ (let* ((current-year (date-year (current-date)))
+ (current-century (* (quotient current-year 100) 100)))
+ (cond
+ ((>= n 100) n)
+ ((< n 0) n)
+ ((<= (- (+ current-century n) current-year) 50) (+ current-century n))
+ (else (+ (- current-century 100) n)))))
+
+(define (date->julian-day date)
+ (let ((nanosecond (date-nanosecond date))
+ (second (date-second date))
+ (minute (date-minute date))
+ (hour (date-hour date))
+ (day (date-day date))
+ (month (date-month date))
+ (year (date-year date))
+ (offset (date-zone-offset date)))
+ (+ (encode-julian-day-number day month year)
+ (- 1/2)
+ (+ (/ (+ (- offset)
+ (* hour 60 60)
+ (* minute 60)
+ second
+ (/ nanosecond nano))
+ sid)))))
+
+(define (date->modified-julian-day date)
+ (- (date->julian-day date)
+ 4800001/2))
+
+(define (time-utc->julian-day time)
+ (if (not (eq? (time-type time) time-utc))
+ (time-error 'time->date 'incompatible-time-types time))
+ (+ (/ (+ (time-second time) (/ (time-nanosecond time) nano))
+ sid)
+ tai-epoch-in-jd))
+
+(define (time-utc->modified-julian-day time)
+ (- (time-utc->julian-day time)
+ 4800001/2))
+
+(define (time-tai->julian-day time)
+ (if (not (eq? (time-type time) time-tai))
+ (time-error 'time->date 'incompatible-time-types time))
+ (+ (/ (+ (- (time-second time)
+ (leap-second-delta (time-second time)))
+ (/ (time-nanosecond time) nano))
+ sid)
+ tai-epoch-in-jd))
+
+(define (time-tai->modified-julian-day time)
+ (- (time-tai->julian-day time)
+ 4800001/2))
+
+;; this is the same as time-tai->julian-day
+(define (time-monotonic->julian-day time)
+ (if (not (eq? (time-type time) time-monotonic))
+ (time-error 'time->date 'incompatible-time-types time))
+ (+ (/ (+ (- (time-second time)
+ (leap-second-delta (time-second time)))
+ (/ (time-nanosecond time) nano))
+ sid)
+ tai-epoch-in-jd))
+
+(define (time-monotonic->modified-julian-day time)
+ (- (time-monotonic->julian-day time)
+ 4800001/2))
+
+(define (julian-day->time-utc jdn)
+ (let ((secs (* sid (- jdn tai-epoch-in-jd))))
+ (receive (seconds parts)
+ (split-real secs)
+ (make-time time-utc
+ (* parts nano)
+ seconds))))
+
+(define (julian-day->time-tai jdn)
+ (time-utc->time-tai! (julian-day->time-utc jdn)))
+
+(define (julian-day->time-monotonic jdn)
+ (time-utc->time-monotonic! (julian-day->time-utc jdn)))
+
+(define (julian-day->date jdn . tz-offset)
+ (let* ((time (julian-day->time-utc jdn))
+ (offset (if (null? tz-offset)
+ (local-tz-offset time)
+ (car tz-offset))))
+ (time-utc->date time offset)))
+
+(define (modified-julian-day->date jdn . tz-offset)
+ (apply julian-day->date (+ jdn 4800001/2)
+ tz-offset))
+
+(define (modified-julian-day->time-utc jdn)
+ (julian-day->time-utc (+ jdn 4800001/2)))
+
+(define (modified-julian-day->time-tai jdn)
+ (julian-day->time-tai (+ jdn 4800001/2)))
+
+(define (modified-julian-day->time-monotonic jdn)
+ (julian-day->time-monotonic (+ jdn 4800001/2)))
+
+(define (current-julian-day)
+ (time-utc->julian-day (current-time time-utc)))
+
+(define (current-modified-julian-day)
+ (time-utc->modified-julian-day (current-time time-utc)))
+
+;; returns a string rep. of number N, of minimum LENGTH, padded with
+;; character PAD-WITH. If PAD-WITH is #f, no padding is done, and it's
+;; as if number->string was used. if string is longer than or equal
+;; in length to LENGTH, it's as if number->string was used.
+
+(define (padding n pad-with length)
+ (let* ((str (number->string n))
+ (str-len (string-length str)))
+ (if (or (>= str-len length)
+ (not pad-with))
+ str
+ (string-append (make-string (- length str-len) pad-with) str))))
+
+(define (last-n-digits i n)
+ (abs (remainder i (expt 10 n))))
+
+(define (locale-abbr-weekday n) (locale-day-short (+ 1 n)))
+(define (locale-long-weekday n) (locale-day (+ 1 n)))
+(define locale-abbr-month locale-month-short)
+(define locale-long-month locale-month)
+
+(define (date-reverse-lookup needle haystack-ref haystack-len
+ same?)
+ ;; Lookup NEEDLE (a string) using HAYSTACK-REF (a one argument procedure
+ ;; that returns a string corresponding to the given index) by passing it
+ ;; indices lower than HAYSTACK-LEN.
+ (let loop ((index 1))
+ (cond ((> index haystack-len) #f)
+ ((same? needle (haystack-ref index))
+ index)
+ (else (loop (+ index 1))))))
+
+(define (locale-abbr-weekday->index string)
+ (date-reverse-lookup string locale-day-short 7 string=?))
+
+(define (locale-long-weekday->index string)
+ (date-reverse-lookup string locale-day 7 string=?))
+
+(define (locale-abbr-month->index string)
+ (date-reverse-lookup string locale-abbr-month 12 string=?))
+
+(define (locale-long-month->index string)
+ (date-reverse-lookup string locale-long-month 12 string=?))
+
+
+;; FIXME: mkoeppe: Put a symbolic time zone in the date structs.
+;; Print it here instead of the numerical offset if available.
+(define (locale-print-time-zone date port)
+ (tz-printer (date-zone-offset date) port))
+
+(define (locale-am-string/pm hr)
+ (if (> hr 11) (locale-pm-string) (locale-am-string)))
+
+(define (tz-printer offset port)
+ (cond
+ ((= offset 0) (display "Z" port))
+ ((negative? offset) (display "-" port))
+ (else (display "+" port)))
+ (if (not (= offset 0))
+ (let ((hours (abs (quotient offset (* 60 60))))
+ (minutes (abs (quotient (remainder offset (* 60 60)) 60))))
+ (display (padding hours #\0 2) port)
+ (display (padding minutes #\0 2) port))))
+
+;; A table of output formatting directives.
+;; the first time is the format char.
+;; the second is a procedure that takes the date, a padding character
+;; (which might be #f), and the output port.
+;;
+(define directives
+ (list
+ (cons #\~ (lambda (date pad-with port)
+ (display #\~ port)))
+ (cons #\a (lambda (date pad-with port)
+ (display (locale-abbr-weekday (date-week-day date))
+ port)))
+ (cons #\A (lambda (date pad-with port)
+ (display (locale-long-weekday (date-week-day date))
+ port)))
+ (cons #\b (lambda (date pad-with port)
+ (display (locale-abbr-month (date-month date))
+ port)))
+ (cons #\B (lambda (date pad-with port)
+ (display (locale-long-month (date-month date))
+ port)))
+ (cons #\c (lambda (date pad-with port)
+ (display (date->string date locale-date-time-format) port)))
+ (cons #\d (lambda (date pad-with port)
+ (display (padding (date-day date)
+ #\0 2)
+ port)))
+ (cons #\D (lambda (date pad-with port)
+ (display (date->string date "~m/~d/~y") port)))
+ (cons #\e (lambda (date pad-with port)
+ (display (padding (date-day date)
+ #\Space 2)
+ port)))
+ (cons #\f (lambda (date pad-with port)
+ (if (> (date-nanosecond date)
+ nano)
+ (display (padding (+ (date-second date) 1)
+ pad-with 2)
+ port)
+ (display (padding (date-second date)
+ pad-with 2)
+ port))
+ (receive (i f)
+ (split-real (/
+ (date-nanosecond date)
+ nano 1.0))
+ (let* ((ns (number->string f))
+ (le (string-length ns)))
+ (if (> le 2)
+ (begin
+ (display (locale-decimal-point) port)
+ (display (substring ns 2 le) port)))))))
+ (cons #\h (lambda (date pad-with port)
+ (display (date->string date "~b") port)))
+ (cons #\H (lambda (date pad-with port)
+ (display (padding (date-hour date)
+ pad-with 2)
+ port)))
+ (cons #\I (lambda (date pad-with port)
+ (let ((hr (date-hour date)))
+ (if (> hr 12)
+ (display (padding (- hr 12)
+ pad-with 2)
+ port)
+ (display (padding hr
+ pad-with 2)
+ port)))))
+ (cons #\j (lambda (date pad-with port)
+ (display (padding (date-year-day date)
+ pad-with 3)
+ port)))
+ (cons #\k (lambda (date pad-with port)
+ (display (padding (date-hour date)
+ #\Space 2)
+ port)))
+ (cons #\l (lambda (date pad-with port)
+ (let ((hr (if (> (date-hour date) 12)
+ (- (date-hour date) 12) (date-hour date))))
+ (display (padding hr #\Space 2)
+ port))))
+ (cons #\m (lambda (date pad-with port)
+ (display (padding (date-month date)
+ pad-with 2)
+ port)))
+ (cons #\M (lambda (date pad-with port)
+ (display (padding (date-minute date)
+ pad-with 2)
+ port)))
+ (cons #\n (lambda (date pad-with port)
+ (newline port)))
+ (cons #\N (lambda (date pad-with port)
+ (display (padding (date-nanosecond date)
+ pad-with 7)
+ port)))
+ (cons #\p (lambda (date pad-with port)
+ (display (locale-am-string/pm (date-hour date)) port)))
+ (cons #\r (lambda (date pad-with port)
+ (display (date->string date "~I:~M:~S ~p") port)))
+ (cons #\s (lambda (date pad-with port)
+ (display (time-second (date->time-utc date)) port)))
+ (cons #\S (lambda (date pad-with port)
+ (if (> (date-nanosecond date)
+ nano)
+ (display (padding (+ (date-second date) 1)
+ pad-with 2)
+ port)
+ (display (padding (date-second date)
+ pad-with 2)
+ port))))
+ (cons #\t (lambda (date pad-with port)
+ (display #\Tab port)))
+ (cons #\T (lambda (date pad-with port)
+ (display (date->string date "~H:~M:~S") port)))
+ (cons #\U (lambda (date pad-with port)
+ (if (> (days-before-first-week date 0) 0)
+ (display (padding (+ (date-week-number date 0) 1)
+ #\0 2) port)
+ (display (padding (date-week-number date 0)
+ #\0 2) port))))
+ (cons #\V (lambda (date pad-with port)
+ (display (padding (date-week-number date 1)
+ #\0 2) port)))
+ (cons #\w (lambda (date pad-with port)
+ (display (date-week-day date) port)))
+ (cons #\x (lambda (date pad-with port)
+ (display (date->string date locale-short-date-format) port)))
+ (cons #\X (lambda (date pad-with port)
+ (display (date->string date locale-time-format) port)))
+ (cons #\W (lambda (date pad-with port)
+ (if (> (days-before-first-week date 1) 0)
+ (display (padding (+ (date-week-number date 1) 1)
+ #\0 2) port)
+ (display (padding (date-week-number date 1)
+ #\0 2) port))))
+ (cons #\y (lambda (date pad-with port)
+ (display (padding (last-n-digits
+ (date-year date) 2)
+ pad-with
+ 2)
+ port)))
+ (cons #\Y (lambda (date pad-with port)
+ (display (date-year date) port)))
+ (cons #\z (lambda (date pad-with port)
+ (tz-printer (date-zone-offset date) port)))
+ (cons #\Z (lambda (date pad-with port)
+ (locale-print-time-zone date port)))
+ (cons #\1 (lambda (date pad-with port)
+ (display (date->string date "~Y-~m-~d") port)))
+ (cons #\2 (lambda (date pad-with port)
+ (display (date->string date "~H:~M:~S~z") port)))
+ (cons #\3 (lambda (date pad-with port)
+ (display (date->string date "~H:~M:~S") port)))
+ (cons #\4 (lambda (date pad-with port)
+ (display (date->string date "~Y-~m-~dT~H:~M:~S~z") port)))
+ (cons #\5 (lambda (date pad-with port)
+ (display (date->string date "~Y-~m-~dT~H:~M:~S") port)))))
+
+
+(define (get-formatter char)
+ (let ((associated (assoc char directives)))
+ (if associated (cdr associated) #f)))
+
+(define (date-printer date index format-string str-len port)
+ (if (< index str-len)
+ (let ((current-char (string-ref format-string index)))
+ (if (not (char=? current-char #\~))
+ (begin
+ (display current-char port)
+ (date-printer date (+ index 1) format-string str-len port))
+ (if (= (+ index 1) str-len) ; bad format string.
+ (time-error 'date-printer 'bad-date-format-string
+ format-string)
+ (let ((pad-char? (string-ref format-string (+ index 1))))
+ (cond
+ ((char=? pad-char? #\-)
+ (if (= (+ index 2) str-len) ; bad format string.
+ (time-error 'date-printer
+ 'bad-date-format-string
+ format-string)
+ (let ((formatter (get-formatter
+ (string-ref format-string
+ (+ index 2)))))
+ (if (not formatter)
+ (time-error 'date-printer
+ 'bad-date-format-string
+ format-string)
+ (begin
+ (formatter date #f port)
+ (date-printer date
+ (+ index 3)
+ format-string
+ str-len
+ port))))))
+
+ ((char=? pad-char? #\_)
+ (if (= (+ index 2) str-len) ; bad format string.
+ (time-error 'date-printer
+ 'bad-date-format-string
+ format-string)
+ (let ((formatter (get-formatter
+ (string-ref format-string
+ (+ index 2)))))
+ (if (not formatter)
+ (time-error 'date-printer
+ 'bad-date-format-string
+ format-string)
+ (begin
+ (formatter date #\Space port)
+ (date-printer date
+ (+ index 3)
+ format-string
+ str-len
+ port))))))
+ (else
+ (let ((formatter (get-formatter
+ (string-ref format-string
+ (+ index 1)))))
+ (if (not formatter)
+ (time-error 'date-printer
+ 'bad-date-format-string
+ format-string)
+ (begin
+ (formatter date #\0 port)
+ (date-printer date
+ (+ index 2)
+ format-string
+ str-len
+ port))))))))))))
+
+
+(define (date->string date . format-string)
+ (let ((str-port (open-output-string))
+ (fmt-str (if (null? format-string) "~c" (car format-string))))
+ (date-printer date 0 fmt-str (string-length fmt-str) str-port)
+ (get-output-string str-port)))
+
+(define (char->int ch)
+ (case ch
+ ((#\0) 0)
+ ((#\1) 1)
+ ((#\2) 2)
+ ((#\3) 3)
+ ((#\4) 4)
+ ((#\5) 5)
+ ((#\6) 6)
+ ((#\7) 7)
+ ((#\8) 8)
+ ((#\9) 9)
+ (else (time-error 'char->int 'bad-date-template-string
+ (list "Non-integer character" ch)))))
+
+;; read an integer upto n characters long on port; upto -> #f is any length
+(define (integer-reader upto port)
+ (let loop ((accum 0) (nchars 0))
+ (let ((ch (peek-char port)))
+ (if (or (eof-object? ch)
+ (not (char-numeric? ch))
+ (and upto (>= nchars upto)))
+ accum
+ (loop (+ (* accum 10) (char->int (read-char port)))
+ (+ nchars 1))))))
+
+(define (make-integer-reader upto)
+ (lambda (port)
+ (integer-reader upto port)))
+
+;; read *exactly* n characters and convert to integer; could be padded
+(define (integer-reader-exact n port)
+ (let ((padding-ok #t))
+ (define (accum-int port accum nchars)
+ (let ((ch (peek-char port)))
+ (cond
+ ((>= nchars n) accum)
+ ((eof-object? ch)
+ (time-error 'string->date 'bad-date-template-string
+ "Premature ending to integer read."))
+ ((char-numeric? ch)
+ (set! padding-ok #f)
+ (accum-int port
+ (+ (* accum 10) (char->int (read-char port)))
+ (+ nchars 1)))
+ (padding-ok
+ (read-char port) ; consume padding
+ (accum-int port accum (+ nchars 1)))
+ (else ; padding where it shouldn't be
+ (time-error 'string->date 'bad-date-template-string
+ "Non-numeric characters in integer read.")))))
+ (accum-int port 0 0)))
+
+
+(define (make-integer-exact-reader n)
+ (lambda (port)
+ (integer-reader-exact n port)))
+
+(define (zone-reader port)
+ (let ((offset 0)
+ (positive? #f))
+ (let ((ch (read-char port)))
+ (if (eof-object? ch)
+ (time-error 'string->date 'bad-date-template-string
+ (list "Invalid time zone +/-" ch)))
+ (if (or (char=? ch #\Z) (char=? ch #\z))
+ 0
+ (begin
+ (cond
+ ((char=? ch #\+) (set! positive? #t))
+ ((char=? ch #\-) (set! positive? #f))
+ (else
+ (time-error 'string->date 'bad-date-template-string
+ (list "Invalid time zone +/-" ch))))
+ (let ((ch (read-char port)))
+ (if (eof-object? ch)
+ (time-error 'string->date 'bad-date-template-string
+ (list "Invalid time zone number" ch)))
+ (set! offset (* (char->int ch)
+ 10 60 60)))
+ (let ((ch (read-char port)))
+ (if (eof-object? ch)
+ (time-error 'string->date 'bad-date-template-string
+ (list "Invalid time zone number" ch)))
+ (set! offset (+ offset (* (char->int ch)
+ 60 60))))
+ (let ((ch (read-char port)))
+ (if (eof-object? ch)
+ (time-error 'string->date 'bad-date-template-string
+ (list "Invalid time zone number" ch)))
+ (set! offset (+ offset (* (char->int ch)
+ 10 60))))
+ (let ((ch (read-char port)))
+ (if (eof-object? ch)
+ (time-error 'string->date 'bad-date-template-string
+ (list "Invalid time zone number" ch)))
+ (set! offset (+ offset (* (char->int ch)
+ 60))))
+ (if positive? offset (- offset)))))))
+
+;; looking at a char, read the char string, run thru indexer, return index
+(define (locale-reader port indexer)
+
+ (define (read-char-string result)
+ (let ((ch (peek-char port)))
+ (if (char-alphabetic? ch)
+ (read-char-string (cons (read-char port) result))
+ (list->string (reverse! result)))))
+
+ (let* ((str (read-char-string '()))
+ (index (indexer str)))
+ (if index index (time-error 'string->date
+ 'bad-date-template-string
+ (list "Invalid string for " indexer)))))
+
+(define (make-locale-reader indexer)
+ (lambda (port)
+ (locale-reader port indexer)))
+
+(define (make-char-id-reader char)
+ (lambda (port)
+ (if (char=? char (read-char port))
+ char
+ (time-error 'string->date
+ 'bad-date-template-string
+ "Invalid character match."))))
+
+;; A List of formatted read directives.
+;; Each entry is a list.
+;; 1. the character directive;
+;; a procedure, which takes a character as input & returns
+;; 2. #t as soon as a character on the input port is acceptable
+;; for input,
+;; 3. a port reader procedure that knows how to read the current port
+;; for a value. Its one parameter is the port.
+;; 4. an optional action procedure, that takes the value (from 3.) and
+;; some object (here, always the date) and (probably) side-effects it.
+;; If no action is required, as with ~A, this element may be #f.
+
+(define read-directives
+ (let ((ireader4 (make-integer-reader 4))
+ (ireader2 (make-integer-reader 2))
+ (eireader2 (make-integer-exact-reader 2))
+ (locale-reader-abbr-weekday (make-locale-reader
+ locale-abbr-weekday->index))
+ (locale-reader-long-weekday (make-locale-reader
+ locale-long-weekday->index))
+ (locale-reader-abbr-month (make-locale-reader
+ locale-abbr-month->index))
+ (locale-reader-long-month (make-locale-reader
+ locale-long-month->index))
+ (char-fail (lambda (ch) #t)))
+
+ (list
+ (list #\~ char-fail (make-char-id-reader #\~) #f)
+ (list #\a char-alphabetic? locale-reader-abbr-weekday #f)
+ (list #\A char-alphabetic? locale-reader-long-weekday #f)
+ (list #\b char-alphabetic? locale-reader-abbr-month
+ (lambda (val object)
+ (set-date-month! object val)))
+ (list #\B char-alphabetic? locale-reader-long-month
+ (lambda (val object)
+ (set-date-month! object val)))
+ (list #\d char-numeric? ireader2 (lambda (val object)
+ (set-date-day!
+ object val)))
+ (list #\e char-fail eireader2 (lambda (val object)
+ (set-date-day! object val)))
+ (list #\h char-alphabetic? locale-reader-abbr-month
+ (lambda (val object)
+ (set-date-month! object val)))
+ (list #\H char-numeric? ireader2 (lambda (val object)
+ (set-date-hour! object val)))
+ (list #\k char-fail eireader2 (lambda (val object)
+ (set-date-hour! object val)))
+ (list #\m char-numeric? ireader2 (lambda (val object)
+ (set-date-month! object val)))
+ (list #\M char-numeric? ireader2 (lambda (val object)
+ (set-date-minute!
+ object val)))
+ (list #\S char-numeric? ireader2 (lambda (val object)
+ (set-date-second! object val)))
+ (list #\y char-fail eireader2
+ (lambda (val object)
+ (set-date-year! object (natural-year val))))
+ (list #\Y char-numeric? ireader4 (lambda (val object)
+ (set-date-year! object val)))
+ (list #\z (lambda (c)
+ (or (char=? c #\Z)
+ (char=? c #\z)
+ (char=? c #\+)
+ (char=? c #\-)))
+ zone-reader (lambda (val object)
+ (set-date-zone-offset! object val))))))
+
+(define (priv:string->date date index format-string str-len port template-string)
+ (define (skip-until port skipper)
+ (let ((ch (peek-char port)))
+ (if (eof-object? ch)
+ (time-error 'string->date 'bad-date-format-string template-string)
+ (if (not (skipper ch))
+ (begin (read-char port) (skip-until port skipper))))))
+ (if (< index str-len)
+ (let ((current-char (string-ref format-string index)))
+ (if (not (char=? current-char #\~))
+ (let ((port-char (read-char port)))
+ (if (or (eof-object? port-char)
+ (not (char=? current-char port-char)))
+ (time-error 'string->date
+ 'bad-date-format-string template-string))
+ (priv:string->date date
+ (+ index 1)
+ format-string
+ str-len
+ port
+ template-string))
+ ;; otherwise, it's an escape, we hope
+ (if (> (+ index 1) str-len)
+ (time-error 'string->date
+ 'bad-date-format-string template-string)
+ (let* ((format-char (string-ref format-string (+ index 1)))
+ (format-info (assoc format-char read-directives)))
+ (if (not format-info)
+ (time-error 'string->date
+ 'bad-date-format-string template-string)
+ (begin
+ (let ((skipper (cadr format-info))
+ (reader (caddr format-info))
+ (actor (cadddr format-info)))
+ (skip-until port skipper)
+ (let ((val (reader port)))
+ (if (eof-object? val)
+ (time-error 'string->date
+ 'bad-date-format-string
+ template-string)
+ (if actor (actor val date))))
+ (priv:string->date date
+ (+ index 2)
+ format-string
+ str-len
+ port
+ template-string))))))))))
+
+(define (string->date input-string template-string)
+ (define (date-ok? date)
+ (and (date-nanosecond date)
+ (date-second date)
+ (date-minute date)
+ (date-hour date)
+ (date-day date)
+ (date-month date)
+ (date-year date)
+ (date-zone-offset date)))
+ (let ((newdate (make-date 0 0 0 0 #f #f #f #f)))
+ (priv:string->date newdate
+ 0
+ template-string
+ (string-length template-string)
+ (open-input-string input-string)
+ template-string)
+ (if (not (date-zone-offset newdate))
+ (begin
+ ;; this is necessary to get DST right -- as far as we can
+ ;; get it right (think of the double/missing hour in the
+ ;; night when we are switching between normal time and DST).
+ (set-date-zone-offset! newdate
+ (local-tz-offset
+ (make-time time-utc 0 0)))
+ (set-date-zone-offset! newdate
+ (local-tz-offset
+ (date->time-utc newdate)))))
+ (if (date-ok? newdate)
+ newdate
+ (time-error
+ 'string->date
+ 'bad-date-format-string
+ (list "Incomplete date read. " newdate template-string)))))
+
+;;; srfi-19.scm ends here
+;;; srfi-2.scm --- and-let*
+
+;; Copyright (C) 2001, 2002, 2006 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This module is fully documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-2)
+ \:use-module (ice-9 and-let-star)
+ \:re-export-syntax (and-let*))
+
+(cond-expand-provide (current-module) '(srfi-2))
+
+;;; srfi-2.scm ends here
+;;; srfi-26.scm --- specializing parameters without currying.
+
+;; Copyright (C) 2002, 2006, 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (srfi srfi-26)
+ \:export (cut cute))
+
+(cond-expand-provide (current-module) '(srfi-26))
+
+(define-syntax cut
+ (lambda (stx)
+ (syntax-case stx ()
+ ((cut slot0 slot1+ ...)
+ (let loop ((slots #'(slot0 slot1+ ...))
+ (params '())
+ (args '()))
+ (if (null? slots)
+ #`(lambda #,(reverse params) #,(reverse args))
+ (let ((s (car slots))
+ (rest (cdr slots)))
+ (with-syntax (((var) (generate-temporaries '(var))))
+ (syntax-case s (<> <___>)
+ (<>
+ (loop rest (cons #'var params) (cons #'var args)))
+ (<___>
+ (if (pair? rest)
+ (error "<___> not on the end of cut expression"))
+ #`(lambda #,(append (reverse params) #'var)
+ (apply #,@(reverse (cons #'var args)))))
+ (else
+ (loop rest params (cons s args))))))))))))
+
+(define-syntax cute
+ (lambda (stx)
+ (syntax-case stx ()
+ ((cute slots ...)
+ (let loop ((slots #'(slots ...))
+ (bindings '())
+ (arguments '()))
+ (define (process-hole)
+ (loop (cdr slots) bindings (cons (car slots) arguments)))
+ (if (null? slots)
+ #`(let #,bindings
+ (cut #,@(reverse arguments)))
+ (syntax-case (car slots) (<> <___>)
+ (<> (process-hole))
+ (<___> (process-hole))
+ (expr
+ (with-syntax (((t) (generate-temporaries '(t))))
+ (loop (cdr slots)
+ (cons #'(t expr) bindings)
+ (cons #'t arguments)))))))))))
+;;; srfi-27.scm --- Sources of Random Bits
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library. If not, see
+;; <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module is fully documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-27)
+ #\export (random-integer
+ random-real
+ default-random-source
+ make-random-source
+ random-source?
+ random-source-state-ref
+ random-source-state-set!
+ random-source-randomize!
+ random-source-pseudo-randomize!
+ random-source-make-integers
+ random-source-make-reals)
+ #\use-module (srfi srfi-9))
+
+(cond-expand-provide (current-module) '(srfi-27))
+
+(define-record-type \:random-source
+ (%make-random-source state)
+ random-source?
+ (state random-source-state set-random-source-state!))
+
+(define (make-random-source)
+ (%make-random-source (seed->random-state 0)))
+
+(define (random-source-state-ref s)
+ (random-state->datum (random-source-state s)))
+
+(define (random-source-state-set! s state)
+ (set-random-source-state! s (datum->random-state state)))
+
+(define (random-source-randomize! s)
+ (let ((time (gettimeofday)))
+ (set-random-source-state! s (seed->random-state
+ (+ (* (car time) 1e6) (cdr time))))))
+
+(define (random-source-pseudo-randomize! s i j)
+ (set-random-source-state! s (seed->random-state (i+j->seed i j))))
+
+(define (i+j->seed i j)
+ (logior (ash (spread i 2) 1)
+ (spread j 2)))
+
+(define (spread n amount)
+ (let loop ((result 0) (n n) (shift 0))
+ (if (zero? n)
+ result
+ (loop (logior result
+ (ash (logand n 1) shift))
+ (ash n -1)
+ (+ shift amount)))))
+
+(define (random-source-make-integers s)
+ (lambda (n)
+ (random n (random-source-state s))))
+
+(define random-source-make-reals
+ (case-lambda
+ ((s)
+ (lambda ()
+ (let loop ()
+ (let ((x (random:uniform (random-source-state s))))
+ (if (zero? x)
+ (loop)
+ x)))))
+ ((s unit)
+ (or (and (real? unit) (< 0 unit 1))
+ (error "unit must be real between 0 and 1" unit))
+ (random-source-make-reals s))))
+
+(define default-random-source (make-random-source))
+(define random-integer (random-source-make-integers default-random-source))
+(define random-real (random-source-make-reals default-random-source))
+;;; srfi-28.scm --- Basic Format Strings
+
+;; Copyright (C) 2014 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This module provides a wrapper for simple-format that always outputs
+;; to a string.
+;;
+;; This module is documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-28)
+ #\replace (format))
+
+(define (format message . args)
+ (apply simple-format #f message args))
+
+(cond-expand-provide (current-module) '(srfi-28))
+;;; srfi-31.scm --- special form for recursive evaluation
+
+;; Copyright (C) 2004, 2006, 2012 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Original author: Rob Browning <rlb@defaultvalue.org>
+
+(define-module (srfi srfi-31)
+ #\export (rec))
+
+(cond-expand-provide (current-module) '(srfi-31))
+
+(define-syntax rec
+ (syntax-rules ()
+ "Return the given object, defined in a lexical environment where
+NAME is bound to itself."
+ ((_ (name . formals) body ...) ; procedure
+ (letrec ((name (lambda formals body ...)))
+ name))
+ ((_ name expr) ; arbitrary object
+ (letrec ((name expr))
+ name))))
+;;; srfi-34.scm --- Exception handling for programs
+
+;; Copyright (C) 2003, 2006, 2008, 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Neil Jerram <neil@ossau.uklinux.net>
+
+;;; Commentary:
+
+;; This is an implementation of SRFI-34: Exception Handling for
+;; Programs. For documentation please see the SRFI-34 document; this
+;; module is not yet documented at all in the Guile manual.
+
+;;; Code:
+
+(define-module (srfi srfi-34)
+ #\export (with-exception-handler)
+ #\replace (raise)
+ #\export-syntax (guard))
+
+(cond-expand-provide (current-module) '(srfi-34))
+
+(define throw-key 'srfi-34)
+
+(define (with-exception-handler handler thunk)
+ "Returns the result(s) of invoking THUNK. HANDLER must be a
+procedure that accepts one argument. It is installed as the current
+exception handler for the dynamic extent (as determined by
+dynamic-wind) of the invocation of THUNK."
+ (with-throw-handler throw-key
+ thunk
+ (lambda (key obj)
+ (handler obj))))
+
+(define (raise obj)
+ "Invokes the current exception handler on OBJ. The handler is
+called in the dynamic environment of the call to raise, except that
+the current exception handler is that in place for the call to
+with-exception-handler that installed the handler being called. The
+handler's continuation is otherwise unspecified."
+ (throw throw-key obj))
+
+(define-syntax guard
+ (syntax-rules (else)
+ "Syntax: (guard (<var> <clause1> <clause2> ...) <body>)
+Each <clause> should have the same form as a `cond' clause.
+
+Semantics: Evaluating a guard form evaluates <body> with an exception
+handler that binds the raised object to <var> and within the scope of
+that binding evaluates the clauses as if they were the clauses of a
+cond expression. That implicit cond expression is evaluated with the
+continuation and dynamic environment of the guard expression. If
+every <clause>'s <test> evaluates to false and there is no else
+clause, then raise is re-invoked on the raised object within the
+dynamic environment of the original call to raise except that the
+current exception handler is that of the guard expression."
+ ((guard (var clause ... (else e e* ...)) body body* ...)
+ (catch throw-key
+ (lambda () body body* ...)
+ (lambda (key var)
+ (cond clause ...
+ (else e e* ...)))))
+ ((guard (var clause clause* ...) body body* ...)
+ (catch throw-key
+ (lambda () body body* ...)
+ (lambda (key var)
+ (cond clause clause* ...
+ (else (throw key var))))))))
+
+
+;;; (srfi srfi-34) ends here.
+;;; srfi-35.scm --- Conditions -*- coding: utf-8 -*-
+
+;; Copyright (C) 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Ludovic Courtès <ludo@gnu.org>
+
+;;; Commentary:
+
+;; This is an implementation of SRFI-35, "Conditions". Conditions are a
+;; means to convey information about exceptional conditions between parts of
+;; a program.
+
+;;; Code:
+
+(define-module (srfi srfi-35)
+ #\use-module (srfi srfi-1)
+ #\export (make-condition-type condition-type?
+ make-condition condition? condition-has-type? condition-ref
+ make-compound-condition extract-condition
+ define-condition-type condition
+ &condition
+ &message message-condition? condition-message
+ &serious serious-condition?
+ &error error?))
+
+(cond-expand-provide (current-module) '(srfi-35))
+
+
+;;;
+;;; Condition types.
+;;;
+
+(define %condition-type-vtable
+ ;; The vtable of all condition types.
+ ;; vtable fields: vtable, self, printer
+ ;; user fields: id, parent, all-field-names
+ (let ((s (make-vtable (string-append standard-vtable-fields "prprpr")
+ (lambda (ct port)
+ (format port "#<condition-type ~a ~a>"
+ (condition-type-id ct)
+ (number->string (object-address ct)
+ 16))))))
+ (set-struct-vtable-name! s 'condition-type)
+ s))
+
+(define (%make-condition-type layout id parent all-fields)
+ (let ((struct (make-struct %condition-type-vtable 0
+ (make-struct-layout layout) ;; layout
+ print-condition ;; printer
+ id parent all-fields)))
+
+ ;; Hack to associate STRUCT with a name, providing a better name for
+ ;; GOOPS classes as returned by `class-of' et al.
+ (set-struct-vtable-name! struct (cond ((symbol? id) id)
+ ((string? id) (string->symbol id))
+ (else (string->symbol ""))))
+ struct))
+
+(define (condition-type? obj)
+ "Return true if OBJ is a condition type."
+ (and (struct? obj)
+ (eq? (struct-vtable obj)
+ %condition-type-vtable)))
+
+(define (condition-type-id ct)
+ (and (condition-type? ct)
+ (struct-ref ct (+ vtable-offset-user 0))))
+
+(define (condition-type-parent ct)
+ (and (condition-type? ct)
+ (struct-ref ct (+ vtable-offset-user 1))))
+
+(define (condition-type-all-fields ct)
+ (and (condition-type? ct)
+ (struct-ref ct (+ vtable-offset-user 2))))
+
+
+(define (struct-layout-for-condition field-names)
+ ;; Return a string denoting the layout required to hold the fields listed
+ ;; in FIELD-NAMES.
+ (let loop ((field-names field-names)
+ (layout '("pr")))
+ (if (null? field-names)
+ (string-concatenate/shared layout)
+ (loop (cdr field-names)
+ (cons "pr" layout)))))
+
+(define (print-condition c port)
+ ;; Print condition C to PORT in a way similar to how records print:
+ ;; #<condition TYPE [FIELD: VALUE ...] ADDRESS>.
+ (define (field-values)
+ (let* ((type (struct-vtable c))
+ (strings (fold (lambda (field result)
+ (cons (format #f "~A: ~S" field
+ (condition-ref c field))
+ result))
+ '()
+ (condition-type-all-fields type))))
+ (string-join (reverse strings) " ")))
+
+ (format port "#<condition ~a [~a] ~a>"
+ (condition-type-id (condition-type c))
+ (field-values)
+ (number->string (object-address c) 16)))
+
+(define (make-condition-type id parent field-names)
+ "Return a new condition type named ID, inheriting from PARENT, and with the
+fields whose names are listed in FIELD-NAMES. FIELD-NAMES must be a list of
+symbols and must not contain names already used by PARENT or one of its
+supertypes."
+ (if (symbol? id)
+ (if (condition-type? parent)
+ (let ((parent-fields (condition-type-all-fields parent)))
+ (if (and (every symbol? field-names)
+ (null? (lset-intersection eq?
+ field-names parent-fields)))
+ (let* ((all-fields (append parent-fields field-names))
+ (layout (struct-layout-for-condition all-fields)))
+ (%make-condition-type layout
+ id parent all-fields))
+ (error "invalid condition type field names"
+ field-names)))
+ (error "parent is not a condition type" parent))
+ (error "condition type identifier is not a symbol" id)))
+
+(define (make-compound-condition-type id parents)
+ ;; Return a compound condition type made of the types listed in PARENTS.
+ ;; All fields from PARENTS are kept, even same-named ones, since they are
+ ;; needed by `extract-condition'.
+ (cond ((null? parents)
+ (error "`make-compound-condition-type' passed empty parent list"
+ id))
+ ((null? (cdr parents))
+ (car parents))
+ (else
+ (let* ((all-fields (append-map condition-type-all-fields
+ parents))
+ (layout (struct-layout-for-condition all-fields)))
+ (%make-condition-type layout
+ id
+ parents ;; list of parents!
+ all-fields)))))
+
+
+;;;
+;;; Conditions.
+;;;
+
+(define (condition? c)
+ "Return true if C is a condition."
+ (and (struct? c)
+ (condition-type? (struct-vtable c))))
+
+(define (condition-type c)
+ (and (struct? c)
+ (let ((vtable (struct-vtable c)))
+ (if (condition-type? vtable)
+ vtable
+ #f))))
+
+(define (condition-has-type? c type)
+ "Return true if condition C has type TYPE."
+ (if (and (condition? c) (condition-type? type))
+ (let loop ((ct (condition-type c)))
+ (or (eq? ct type)
+ (and ct
+ (let ((parent (condition-type-parent ct)))
+ (if (list? parent)
+ (any loop parent) ;; compound condition
+ (loop (condition-type-parent ct)))))))
+ (throw 'wrong-type-arg "condition-has-type?"
+ "Wrong type argument")))
+
+(define (condition-ref c field-name)
+ "Return the value of the field named FIELD-NAME from condition C."
+ (if (condition? c)
+ (if (symbol? field-name)
+ (let* ((type (condition-type c))
+ (fields (condition-type-all-fields type))
+ (index (list-index (lambda (name)
+ (eq? name field-name))
+ fields)))
+ (if index
+ (struct-ref c index)
+ (error "invalid field name" field-name)))
+ (error "field name is not a symbol" field-name))
+ (throw 'wrong-type-arg "condition-ref"
+ "Wrong type argument: ~S" c)))
+
+(define (make-condition-from-values type values)
+ (apply make-struct type 0 values))
+
+(define (make-condition type . field+value)
+ "Return a new condition of type TYPE with fields initialized as specified
+by FIELD+VALUE, a sequence of field names (symbols) and values."
+ (if (condition-type? type)
+ (let* ((all-fields (condition-type-all-fields type))
+ (inits (fold-right (lambda (field inits)
+ (let ((v (memq field field+value)))
+ (if (pair? v)
+ (cons (cadr v) inits)
+ (error "field not specified"
+ field))))
+ '()
+ all-fields)))
+ (make-condition-from-values type inits))
+ (throw 'wrong-type-arg "make-condition"
+ "Wrong type argument: ~S" type)))
+
+(define (make-compound-condition . conditions)
+ "Return a new compound condition composed of CONDITIONS."
+ (let* ((types (map condition-type conditions))
+ (ct (make-compound-condition-type 'compound types))
+ (inits (append-map (lambda (c)
+ (let ((ct (condition-type c)))
+ (map (lambda (f)
+ (condition-ref c f))
+ (condition-type-all-fields ct))))
+ conditions)))
+ (make-condition-from-values ct inits)))
+
+(define (extract-condition c type)
+ "Return a condition of condition type TYPE with the field values specified
+by C."
+
+ (define (first-field-index parents)
+ ;; Return the index of the first field of TYPE within C.
+ (let loop ((parents parents)
+ (index 0))
+ (let ((parent (car parents)))
+ (cond ((null? parents)
+ #f)
+ ((eq? parent type)
+ index)
+ ((pair? parent)
+ (or (loop parent index)
+ (loop (cdr parents)
+ (+ index
+ (apply + (map condition-type-all-fields
+ parent))))))
+ (else
+ (let ((shift (length (condition-type-all-fields parent))))
+ (loop (cdr parents)
+ (+ index shift))))))))
+
+ (define (list-fields start-index field-names)
+ ;; Return a list of the form `(FIELD-NAME VALUE...)'.
+ (let loop ((index start-index)
+ (field-names field-names)
+ (result '()))
+ (if (null? field-names)
+ (reverse! result)
+ (loop (+ 1 index)
+ (cdr field-names)
+ (cons* (struct-ref c index)
+ (car field-names)
+ result)))))
+
+ (if (and (condition? c) (condition-type? type))
+ (let* ((ct (condition-type c))
+ (parent (condition-type-parent ct)))
+ (cond ((eq? type ct)
+ c)
+ ((pair? parent)
+ ;; C is a compound condition.
+ (let ((field-index (first-field-index parent)))
+ ;;(format #t "field-index: ~a ~a~%" field-index
+ ;; (list-fields field-index
+ ;; (condition-type-all-fields type)))
+ (apply make-condition type
+ (list-fields field-index
+ (condition-type-all-fields type)))))
+ (else
+ ;; C does not have type TYPE.
+ #f)))
+ (throw 'wrong-type-arg "extract-condition"
+ "Wrong type argument")))
+
+
+;;;
+;;; Syntax.
+;;;
+
+(define-syntax-rule (define-condition-type name parent pred (field-name field-accessor) ...)
+ (begin
+ (define name
+ (make-condition-type 'name parent '(field-name ...)))
+ (define (pred c)
+ (condition-has-type? c name))
+ (define (field-accessor c)
+ (condition-ref c 'field-name))
+ ...))
+
+(define-syntax-rule (compound-condition (type ...) (field ...))
+ ;; Create a compound condition using `make-compound-condition-type'.
+ (condition ((make-compound-condition-type '%compound `(,type ...))
+ field ...)))
+
+(define-syntax condition-instantiation
+ ;; Build the `(make-condition type ...)' call.
+ (syntax-rules ()
+ ((_ type (out ...))
+ (make-condition type out ...))
+ ((_ type (out ...) (field-name field-value) rest ...)
+ (condition-instantiation type (out ... 'field-name field-value) rest ...))))
+
+(define-syntax condition
+ (syntax-rules ()
+ ((_ (type field ...))
+ (condition-instantiation type () field ...))
+ ((_ (type field ...) ...)
+ (compound-condition (type ...) (field ... ...)))))
+
+
+;;;
+;;; Standard condition types.
+;;;
+
+(define &condition
+ ;; The root condition type.
+ (make-struct %condition-type-vtable 0
+ (make-struct-layout "")
+ (lambda (c port)
+ (display "<&condition>"))
+ '&condition #f '() '()))
+
+(define-condition-type &message &condition
+ message-condition?
+ (message condition-message))
+
+(define-condition-type &serious &condition
+ serious-condition?)
+
+(define-condition-type &error &serious
+ error?)
+
+;;; srfi-35.scm ends here
+;;; srfi-37.scm --- args-fold
+
+;; Copyright (C) 2007, 2008, 2013 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+;;; Commentary:
+;;
+;; To use this module with Guile, use (cdr (program-arguments)) as
+;; the ARGS argument to `args-fold'. Here is a short example:
+;;
+;; (args-fold (cdr (program-arguments))
+;; (let ((display-and-exit-proc
+;; (lambda (msg)
+;; (lambda (opt name arg)
+;; (display msg) (quit) (values)))))
+;; (list (option '(#\v "version") #f #f
+;; (display-and-exit-proc "Foo version 42.0\n"))
+;; (option '(#\h "help") #f #f
+;; (display-and-exit-proc
+;; "Usage: foo scheme-file ..."))))
+;; (lambda (opt name arg)
+;; (error "Unrecognized option `~A'" name))
+;; (lambda (op) (load op) (values)))
+;;
+;;; Code:
+
+
+;;;; Module definition & exports
+(define-module (srfi srfi-37)
+ #\use-module (srfi srfi-9)
+ #\export (option option-names option-required-arg?
+ option-optional-arg? option-processor
+ args-fold))
+
+(cond-expand-provide (current-module) '(srfi-37))
+
+;;;; args-fold and periphery procedures
+
+;;; An option as answered by `option'. `names' is a list of
+;;; characters and strings, representing associated short-options and
+;;; long-options respectively that should use this option's
+;;; `processor' in an `args-fold' call.
+;;;
+;;; `required-arg?' and `optional-arg?' are mutually exclusive
+;;; booleans and indicate whether an argument must be or may be
+;;; provided. Besides the obvious, this affects semantics of
+;;; short-options, as short-options with a required or optional
+;;; argument cannot be followed by other short options in the same
+;;; program-arguments string, as they will be interpreted collectively
+;;; as the option's argument.
+;;;
+;;; `processor' is called when this option is encountered. It should
+;;; accept the containing option, the element of `names' (by `equal?')
+;;; encountered, the option's argument (or #f if none), and the seeds
+;;; as variadic arguments, answering the new seeds as values.
+(define-record-type srfi-37:option
+ (option names required-arg? optional-arg? processor)
+ option?
+ (names option-names)
+ (required-arg? option-required-arg?)
+ (optional-arg? option-optional-arg?)
+ (processor option-processor))
+
+(define (error-duplicate-option option-name)
+ (scm-error 'program-error "args-fold"
+ "Duplicate option name `~A~A'"
+ (list (if (char? option-name) #\- "--")
+ option-name)
+ #f))
+
+(define (build-options-lookup options)
+ "Answer an `equal?' Guile hash-table that maps OPTIONS' names back
+to the containing options, signalling an error if a name is
+encountered more than once."
+ (let ((lookup (make-hash-table (* 2 (length options)))))
+ (for-each
+ (lambda (opt)
+ (for-each (lambda (name)
+ (let ((assoc (hash-create-handle!
+ lookup name #f)))
+ (if (cdr assoc)
+ (error-duplicate-option (car assoc))
+ (set-cdr! assoc opt))))
+ (option-names opt)))
+ options)
+ lookup))
+
+(define (args-fold args options unrecognized-option-proc
+ operand-proc . seeds)
+ "Answer the results of folding SEEDS as multiple values against the
+program-arguments in ARGS, as decided by the OPTIONS'
+`option-processor's, UNRECOGNIZED-OPTION-PROC, and OPERAND-PROC."
+ (let ((lookup (build-options-lookup options)))
+ ;; I don't like Guile's `error' here
+ (define (error msg . args)
+ (scm-error 'misc-error "args-fold" msg args #f))
+
+ (define (mutate-seeds! procedure . params)
+ (set! seeds (call-with-values
+ (lambda ()
+ (apply procedure (append params seeds)))
+ list)))
+
+ ;; Clean up the rest of ARGS, assuming they're all operands.
+ (define (rest-operands)
+ (for-each (lambda (arg) (mutate-seeds! operand-proc arg))
+ args)
+ (set! args '()))
+
+ ;; Call OPT's processor with OPT, NAME, an argument to be decided,
+ ;; and the seeds. Depending on OPT's *-arg? specification, get
+ ;; the parameter by calling REQ-ARG-PROC or OPT-ARG-PROC thunks;
+ ;; if no argument is allowed, call NO-ARG-PROC thunk.
+ (define (invoke-option-processor
+ opt name req-arg-proc opt-arg-proc no-arg-proc)
+ (mutate-seeds!
+ (option-processor opt) opt name
+ (cond ((option-required-arg? opt) (req-arg-proc))
+ ((option-optional-arg? opt) (opt-arg-proc))
+ (else (no-arg-proc) #f))))
+
+ ;; Compute and answer a short option argument, advancing ARGS as
+ ;; necessary, for the short option whose character is at POSITION
+ ;; in the current ARG.
+ (define (short-option-argument position)
+ (cond ((< (1+ position) (string-length (car args)))
+ (let ((result (substring (car args) (1+ position))))
+ (set! args (cdr args))
+ result))
+ ((pair? (cdr args))
+ (let ((result (cadr args)))
+ (set! args (cddr args))
+ result))
+ ((pair? args)
+ (set! args (cdr args))
+ #f)
+ (else #f)))
+
+ ;; Interpret the short-option at index POSITION in (car ARGS),
+ ;; followed by the remaining short options in (car ARGS).
+ (define (short-option position)
+ (if (>= position (string-length (car args)))
+ (begin
+ (set! args (cdr args))
+ (next-arg))
+ (let* ((opt-name (string-ref (car args) position))
+ (option-here (hash-ref lookup opt-name)))
+ (cond ((not option-here)
+ (mutate-seeds! unrecognized-option-proc
+ (option (list opt-name) #f #f
+ unrecognized-option-proc)
+ opt-name #f)
+ (short-option (1+ position)))
+ (else
+ (invoke-option-processor
+ option-here opt-name
+ (lambda ()
+ (or (short-option-argument position)
+ (error "Missing required argument after `-~A'" opt-name)))
+ (lambda ()
+ ;; edge case: -xo -zf or -xo -- where opt-name=#\o
+ ;; GNU getopt_long resolves these like I do
+ (short-option-argument position))
+ (lambda () #f))
+ (if (not (or (option-required-arg? option-here)
+ (option-optional-arg? option-here)))
+ (short-option (1+ position))))))))
+
+ ;; Process the long option in (car ARGS). We make the
+ ;; interesting, possibly non-standard assumption that long option
+ ;; names might contain #\=, so keep looking for more #\= in (car
+ ;; ARGS) until we find a named option in lookup.
+ (define (long-option)
+ (let ((arg (car args)))
+ (let place-=-after ((start-pos 2))
+ (let* ((index (string-index arg #\= start-pos))
+ (opt-name (substring arg 2 (or index (string-length arg))))
+ (option-here (hash-ref lookup opt-name)))
+ (if (not option-here)
+ ;; look for a later #\=, unless there can't be one
+ (if index
+ (place-=-after (1+ index))
+ (mutate-seeds!
+ unrecognized-option-proc
+ (option (list opt-name) #f #f unrecognized-option-proc)
+ opt-name #f))
+ (invoke-option-processor
+ option-here opt-name
+ (lambda ()
+ (if index
+ (substring arg (1+ index))
+ (error "Missing required argument after `--~A'" opt-name)))
+ (lambda () (and index (substring arg (1+ index))))
+ (lambda ()
+ (if index
+ (error "Extraneous argument after `--~A'" opt-name))))))))
+ (set! args (cdr args)))
+
+ ;; Process the remaining in ARGS. Basically like calling
+ ;; `args-fold', but without having to regenerate `lookup' and the
+ ;; funcs above.
+ (define (next-arg)
+ (if (null? args)
+ (apply values seeds)
+ (let ((arg (car args)))
+ (cond ((or (not (char=? #\- (string-ref arg 0)))
+ (= 1 (string-length arg))) ;"-"
+ (mutate-seeds! operand-proc arg)
+ (set! args (cdr args)))
+ ((char=? #\- (string-ref arg 1))
+ (if (= 2 (string-length arg)) ;"--"
+ (begin (set! args (cdr args)) (rest-operands))
+ (long-option)))
+ (else (short-option 1)))
+ (next-arg))))
+
+ (next-arg)))
+
+;;; srfi-37.scm ends here
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+;; Copyright (C) Ray Dillinger 2003. All Rights Reserved.
+;;
+;; Contains code based upon Alex Shinn's public-domain implementation of
+;; `read-with-shared-structure' found in Chicken's SRFI 38 egg.
+
+;; Permission is hereby granted, free of charge, to any person obtaining
+;; a copy of this software and associated documentation files (the
+;; "Software"), to deal in the Software without restriction, including
+;; without limitation the rights to use, copy, modify, merge, publish,
+;; distribute, sublicense, and/or sell copies of the Software, and to
+;; permit persons to whom the Software is furnished to do so, subject to
+;; the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+(define-module (srfi srfi-38)
+ #\export (write-with-shared-structure
+ read-with-shared-structure)
+ #\use-module (rnrs bytevectors)
+ #\use-module (srfi srfi-8)
+ #\use-module (srfi srfi-69)
+ #\use-module (system vm trap-state))
+
+(cond-expand-provide (current-module) '(srfi-38))
+
+;; A printer that shows all sharing of substructures. Uses the Common
+;; Lisp print-circle notation: #n# refers to a previous substructure
+;; labeled with #n=. Takes O(n^2) time.
+
+;; Code attributed to Al Petrofsky, modified by Ray Dillinger.
+
+;; Modified in 2010 by Andreas Rottmann to use SRFI 69 hashtables,
+;; making the time O(n), and adding some of Guile's data types to the
+;; `interesting' objects.
+
+(define* (write-with-shared-structure obj
+ #\optional
+ (outport (current-output-port))
+ (optarg #f))
+
+ ;; We only track duplicates of pairs, vectors, strings, bytevectors,
+ ;; structs (which subsume R6RS and SRFI-9 records), ports and (native)
+ ;; hash-tables. We ignore zero-length vectors and strings because
+ ;; r5rs doesn't guarantee that eq? treats them sanely (and they aren't
+ ;; very interesting anyway).
+
+ (define (interesting? obj)
+ (or (pair? obj)
+ (and (vector? obj) (not (zero? (vector-length obj))))
+ (and (string? obj) (not (zero? (string-length obj))))
+ (bytevector? obj)
+ (struct? obj)
+ (port? obj)
+ (hash-table? obj)))
+
+ ;; (write-obj OBJ STATE):
+ ;;
+ ;; STATE is a hashtable which has an entry for each interesting part
+ ;; of OBJ. The associated value will be:
+ ;;
+ ;; -- a number if the part has been given one,
+ ;; -- #t if the part will need to be assigned a number but has not been yet,
+ ;; -- #f if the part will not need a number.
+ ;; The entry `counter' in STATE should be the most recently
+ ;; assigned number.
+ ;;
+ ;; Mutates STATE for any parts that had numbers assigned.
+ (define (write-obj obj state)
+ (define (write-interesting)
+ (cond ((pair? obj)
+ (display "(" outport)
+ (write-obj (car obj) state)
+ (let write-cdr ((obj (cdr obj)))
+ (cond ((and (pair? obj) (not (hash-table-ref state obj)))
+ (display " " outport)
+ (write-obj (car obj) state)
+ (write-cdr (cdr obj)))
+ ((null? obj)
+ (display ")" outport))
+ (else
+ (display " . " outport)
+ (write-obj obj state)
+ (display ")" outport)))))
+ ((vector? obj)
+ (display "#(" outport)
+ (let ((len (vector-length obj)))
+ (write-obj (vector-ref obj 0) state)
+ (let write-vec ((i 1))
+ (cond ((= i len) (display ")" outport))
+ (else (display " " outport)
+ (write-obj (vector-ref obj i) state)
+ (write-vec (+ i 1)))))))
+ ;; else it's a string
+ (else (write obj outport))))
+ (cond ((interesting? obj)
+ (let ((val (hash-table-ref state obj)))
+ (cond ((not val) (write-interesting))
+ ((number? val)
+ (begin (display "#" outport)
+ (write val outport)
+ (display "#" outport)))
+ (else
+ (let ((n (+ 1 (hash-table-ref state 'counter))))
+ (display "#" outport)
+ (write n outport)
+ (display "=" outport)
+ (hash-table-set! state 'counter n)
+ (hash-table-set! state obj n)
+ (write-interesting))))))
+ (else
+ (write obj outport))))
+
+ ;; Scan computes the initial value of the hash table, which maps each
+ ;; interesting part of the object to #t if it occurs multiple times,
+ ;; #f if only once.
+ (define (scan obj state)
+ (cond ((not (interesting? obj)))
+ ((hash-table-exists? state obj)
+ (hash-table-set! state obj #t))
+ (else
+ (hash-table-set! state obj #f)
+ (cond ((pair? obj)
+ (scan (car obj) state)
+ (scan (cdr obj) state))
+ ((vector? obj)
+ (let ((len (vector-length obj)))
+ (do ((i 0 (+ 1 i)))
+ ((= i len))
+ (scan (vector-ref obj i) state))))))))
+
+ (let ((state (make-hash-table eq?)))
+ (scan obj state)
+ (hash-table-set! state 'counter 0)
+ (write-obj obj state)))
+
+;; A reader that understands the output of the above writer. This has
+;; been written by Andreas Rottmann to re-use Guile's built-in reader,
+;; with inspiration from Alex Shinn's public-domain implementation of
+;; `read-with-shared-structure' found in Chicken's SRFI 38 egg.
+
+(define* (read-with-shared-structure #\optional (port (current-input-port)))
+ (let ((parts-table (make-hash-table eqv?)))
+
+ ;; reads chars that match PRED and returns them as a string.
+ (define (read-some-chars pred initial)
+ (let iter ((chars initial))
+ (let ((c (peek-char port)))
+ (if (or (eof-object? c) (not (pred c)))
+ (list->string (reverse chars))
+ (iter (cons (read-char port) chars))))))
+
+ (define (read-hash c port)
+ (let* ((n (string->number (read-some-chars char-numeric? (list c))))
+ (c (read-char port))
+ (thunk (hash-table-ref/default parts-table n #f)))
+ (case c
+ ((#\=)
+ (if thunk
+ (error "Double declaration of part " n))
+ (let* ((cell (list #f))
+ (thunk (lambda () (car cell))))
+ (hash-table-set! parts-table n thunk)
+ (let ((obj (read port)))
+ (set-car! cell obj)
+ obj)))
+ ((#\#)
+ (or thunk
+ (error "Use of undeclared part " n)))
+ (else
+ (error "Malformed shared part specifier")))))
+
+ (with-fluid* %read-hash-procedures (fluid-ref %read-hash-procedures)
+ (lambda ()
+ (for-each (lambda (digit)
+ (read-hash-extend digit read-hash))
+ '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9))
+ (let ((result (read port)))
+ (if (< 0 (hash-table-size parts-table))
+ (patch! result))
+ result)))))
+
+(define (hole? x) (procedure? x))
+(define (fill-hole x) (if (hole? x) (fill-hole (x)) x))
+
+(define (patch! x)
+ (cond
+ ((pair? x)
+ (if (hole? (car x)) (set-car! x (fill-hole (car x))) (patch! (car x)))
+ (if (hole? (cdr x)) (set-cdr! x (fill-hole (cdr x))) (patch! (cdr x))))
+ ((vector? x)
+ (do ((i (- (vector-length x) 1) (- i 1)))
+ ((< i 0))
+ (let ((elt (vector-ref x i)))
+ (if (hole? elt)
+ (vector-set! x i (fill-hole elt))
+ (patch! elt)))))))
+;;; srfi-39.scm --- Parameter objects
+
+;; Copyright (C) 2004, 2005, 2006, 2008, 2011 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Jose Antonio Ortega Ruiz <jao@gnu.org>
+;;; Date: 2004-05-05
+
+;;; Commentary:
+
+;; This is an implementation of SRFI-39 (Parameter objects).
+;;
+;; The implementation is based on Guile's fluid objects, and is, therefore,
+;; thread-safe (parameters are thread-local).
+;;
+;; In addition to the forms defined in SRFI-39 (`make-parameter',
+;; `parameterize'), a new procedure `with-parameters*' is provided.
+;; This procedures is analogous to `with-fluids*' but taking as first
+;; argument a list of parameter objects instead of a list of fluids.
+;;
+
+;;; Code:
+
+(define-module (srfi srfi-39)
+ ;; helper procedure not in srfi-39.
+ #\export (with-parameters*)
+ #\re-export (make-parameter
+ parameterize
+ current-input-port current-output-port current-error-port))
+
+(cond-expand-provide (current-module) '(srfi-39))
+
+(define (with-parameters* params values thunk)
+ (let more ((params params)
+ (values values)
+ (fluids '()) ;; fluids from each of PARAMS
+ (convs '())) ;; VALUES with conversion proc applied
+ (if (null? params)
+ (with-fluids* fluids convs thunk)
+ (more (cdr params) (cdr values)
+ (cons (parameter-fluid (car params)) fluids)
+ (cons ((parameter-converter (car params)) (car values)) convs)))))
+;;; srfi-4.scm --- Homogeneous Numeric Vector Datatypes
+
+;; Copyright (C) 2001, 2002, 2004, 2006, 2009, 2010,
+;; 2012, 2014 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Martin Grabmueller <mgrabmue@cs.tu-berlin.de>
+
+;;; Commentary:
+
+;; This module exports the homogeneous numeric vector procedures as
+;; defined in SRFI-4. They are fully documented in the Guile
+;; Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-4)
+ #\use-module (rnrs bytevectors)
+ #\export (;; Unsigned 8-bit vectors.
+ u8vector? make-u8vector u8vector u8vector-length u8vector-ref
+ u8vector-set! u8vector->list list->u8vector
+
+ ;; Signed 8-bit vectors.
+ s8vector? make-s8vector s8vector s8vector-length s8vector-ref
+ s8vector-set! s8vector->list list->s8vector
+
+ ;; Unsigned 16-bit vectors.
+ u16vector? make-u16vector u16vector u16vector-length u16vector-ref
+ u16vector-set! u16vector->list list->u16vector
+
+ ;; Signed 16-bit vectors.
+ s16vector? make-s16vector s16vector s16vector-length s16vector-ref
+ s16vector-set! s16vector->list list->s16vector
+
+ ;; Unsigned 32-bit vectors.
+ u32vector? make-u32vector u32vector u32vector-length u32vector-ref
+ u32vector-set! u32vector->list list->u32vector
+
+ ;; Signed 32-bit vectors.
+ s32vector? make-s32vector s32vector s32vector-length s32vector-ref
+ s32vector-set! s32vector->list list->s32vector
+
+ ;; Unsigned 64-bit vectors.
+ u64vector? make-u64vector u64vector u64vector-length u64vector-ref
+ u64vector-set! u64vector->list list->u64vector
+
+ ;; Signed 64-bit vectors.
+ s64vector? make-s64vector s64vector s64vector-length s64vector-ref
+ s64vector-set! s64vector->list list->s64vector
+
+ ;; 32-bit floating point vectors.
+ f32vector? make-f32vector f32vector f32vector-length f32vector-ref
+ f32vector-set! f32vector->list list->f32vector
+
+ ;; 64-bit floating point vectors.
+ f64vector? make-f64vector f64vector f64vector-length f64vector-ref
+ f64vector-set! f64vector->list list->f64vector))
+
+(cond-expand-provide (current-module) '(srfi-4))
+
+;; Need quasisyntax to do this effectively using syntax-case
+(define-macro (define-bytevector-type tag infix size)
+ `(begin
+ (define (,(symbol-append tag 'vector?) obj)
+ (and (bytevector? obj) (eq? (array-type obj) ',tag)))
+ (define (,(symbol-append 'make- tag 'vector) len . fill)
+ (apply make-srfi-4-vector ',tag len fill))
+ (define (,(symbol-append tag 'vector-length) v)
+ (let ((len (/ (bytevector-length v) ,size)))
+ (if (integer? len)
+ len
+ (error "fractional length" v ',tag ,size))))
+ (define (,(symbol-append tag 'vector) . elts)
+ (,(symbol-append 'list-> tag 'vector) elts))
+ (define (,(symbol-append 'list-> tag 'vector) elts)
+ (let* ((len (length elts))
+ (v (,(symbol-append 'make- tag 'vector) len)))
+ (let lp ((i 0) (elts elts))
+ (if (and (< i len) (pair? elts))
+ (begin
+ (,(symbol-append tag 'vector-set!) v i (car elts))
+ (lp (1+ i) (cdr elts)))
+ v))))
+ (define (,(symbol-append tag 'vector->list) v)
+ (let lp ((i (1- (,(symbol-append tag 'vector-length) v))) (elts '()))
+ (if (< i 0)
+ elts
+ (lp (1- i) (cons (,(symbol-append tag 'vector-ref) v i) elts)))))
+ (define (,(symbol-append tag 'vector-ref) v i)
+ (,(symbol-append 'bytevector- infix '-ref) v (* i ,size)))
+ (define (,(symbol-append tag 'vector-set!) v i x)
+ (,(symbol-append 'bytevector- infix '-set!) v (* i ,size) x))
+ (define (,(symbol-append tag 'vector-set!) v i x)
+ (,(symbol-append 'bytevector- infix '-set!) v (* i ,size) x))))
+
+(define-bytevector-type u8 u8 1)
+(define-bytevector-type s8 s8 1)
+(define-bytevector-type u16 u16-native 2)
+(define-bytevector-type s16 s16-native 2)
+(define-bytevector-type u32 u32-native 4)
+(define-bytevector-type s32 s32-native 4)
+(define-bytevector-type u64 u64-native 8)
+(define-bytevector-type s64 s64-native 8)
+(define-bytevector-type f32 ieee-single-native 4)
+(define-bytevector-type f64 ieee-double-native 8)
+;;; Extensions to SRFI-4
+
+;; Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; Extensions to SRFI-4. Fully documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-4 gnu)
+ #\use-module (rnrs bytevectors)
+ #\use-module (srfi srfi-4)
+ #\export (;; Complex numbers with 32- and 64-bit components.
+ c32vector? make-c32vector c32vector c32vector-length c32vector-ref
+ c32vector-set! c32vector->list list->c32vector
+
+ c64vector? make-c64vector c64vector c64vector-length c64vector-ref
+ c64vector-set! c64vector->list list->c64vector
+
+ make-srfi-4-vector
+
+ ;; Somewhat polymorphic conversions.
+ any->u8vector any->s8vector any->u16vector any->s16vector
+ any->u32vector any->s32vector any->u64vector any->s64vector
+ any->f32vector any->f64vector any->c32vector any->c64vector))
+
+
+(define make-srfi-4-vector (@@ (srfi srfi-4) make-srfi-4-vector))
+
+(define (bytevector-c32-native-ref v i)
+ (make-rectangular (bytevector-ieee-single-native-ref v i)
+ (bytevector-ieee-single-native-ref v (+ i 4))))
+(define (bytevector-c32-native-set! v i x)
+ (bytevector-ieee-single-native-set! v i (real-part x))
+ (bytevector-ieee-single-native-set! v (+ i 4) (imag-part x)))
+(define (bytevector-c64-native-ref v i)
+ (make-rectangular (bytevector-ieee-double-native-ref v i)
+ (bytevector-ieee-double-native-ref v (+ i 8))))
+(define (bytevector-c64-native-set! v i x)
+ (bytevector-ieee-double-native-set! v i (real-part x))
+ (bytevector-ieee-double-native-set! v (+ i 8) (imag-part x)))
+
+((@@ (srfi srfi-4) define-bytevector-type) c32 c32-native 8)
+((@@ (srfi srfi-4) define-bytevector-type) c64 c64-native 16)
+
+(define-macro (define-any->vector . tags)
+ `(begin
+ ,@(map (lambda (tag)
+ `(define (,(symbol-append 'any-> tag 'vector) obj)
+ (cond ((,(symbol-append tag 'vector?) obj) obj)
+ ((pair? obj) (,(symbol-append 'list-> tag 'vector) obj))
+ ((and (array? obj) (eqv? 1 (array-rank obj)))
+ (let* ((len (array-length obj))
+ (v (,(symbol-append 'make- tag 'vector) len)))
+ (let lp ((i 0))
+ (if (< i len)
+ (begin
+ (,(symbol-append tag 'vector-set!)
+ v i (array-ref obj i))
+ (lp (1+ i)))
+ v))))
+ (else (scm-error 'wrong-type-arg #f "" '() (list obj))))))
+ tags)))
+
+(define-any->vector u8 s8 u16 s16 u32 s32 u64 s64 f32 f64 c32 c64)
+;;; srfi-41.scm -- SRFI 41 streams
+
+;; Copyright (c) 2007 Philip L. Bewig
+;; Copyright (c) 2011, 2012, 2013 Free Software Foundation, Inc.
+
+;; Permission is hereby granted, free of charge, to any person obtaining
+;; a copy of this software and associated documentation files (the
+;; "Software"), to deal in the Software without restriction, including
+;; without limitation the rights to use, copy, modify, merge, publish,
+;; distribute, sublicense, and/or sell copies of the Software, and to
+;; permit persons to whom the Software is furnished to do so, subject to
+;; the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+;; OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+(define-module (srfi srfi-41)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-8)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-9 gnu)
+ #\use-module (srfi srfi-26)
+ #\use-module (ice-9 match)
+ #\export (stream-null stream-cons stream? stream-null? stream-pair?
+ stream-car stream-cdr stream-lambda define-stream
+ list->stream port->stream stream stream->list stream-append
+ stream-concat stream-constant stream-drop stream-drop-while
+ stream-filter stream-fold stream-for-each stream-from
+ stream-iterate stream-length stream-let stream-map
+ stream-match stream-of stream-range stream-ref stream-reverse
+ stream-scan stream-take stream-take-while stream-unfold
+ stream-unfolds stream-zip))
+
+(cond-expand-provide (current-module) '(srfi-41))
+
+;;; Private supporting functions and macros.
+
+(define-syntax-rule (must pred obj func msg args ...)
+ (let ((item obj))
+ (unless (pred item)
+ (throw 'wrong-type-arg func msg (list args ...) (list item)))))
+
+(define-syntax-rule (must-not pred obj func msg args ...)
+ (let ((item obj))
+ (when (pred item)
+ (throw 'wrong-type-arg func msg (list args ...) (list item)))))
+
+(define-syntax-rule (must-every pred objs func msg args ...)
+ (let ((flunk (remove pred objs)))
+ (unless (null? flunk)
+ (throw 'wrong-type-arg func msg (list args ...) flunk))))
+
+(define-syntax-rule (first-value expr)
+ (receive (first . _) expr
+ first))
+
+(define-syntax-rule (second-value expr)
+ (receive (first second . _) expr
+ second))
+
+(define-syntax-rule (third-value expr)
+ (receive (first second third . _) expr
+ third))
+
+(define-syntax define-syntax*
+ (syntax-rules ()
+ ((_ (name . args) body ...)
+ (define-syntax name (lambda* args body ...)))
+ ((_ name syntax)
+ (define-syntax name syntax))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Here we include a copy of the code of srfi-45.scm (but with renamed
+;; identifiers), in order to create a new promise type that's disjoint
+;; from the promises created by srfi-45. Ideally this should be done
+;; using a 'make-promise-type' macro that instantiates a copy of this
+;; code, but a psyntax bug in Guile 2.0 prevents this from working
+;; properly: <http://bugs.gnu.org/13995>. So for now, we duplicate the
+;; code.
+
+;; Copyright (C) 2010, 2011 Free Software Foundation, Inc.
+;; Copyright (C) 2003 André van Tonder. All Rights Reserved.
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+(define-record-type stream-promise (make-stream-promise val) stream-promise?
+ (val stream-promise-val stream-promise-val-set!))
+
+(define-record-type stream-value (make-stream-value tag proc) stream-value?
+ (tag stream-value-tag stream-value-tag-set!)
+ (proc stream-value-proc stream-value-proc-set!))
+
+(define-syntax-rule (stream-lazy exp)
+ (make-stream-promise (make-stream-value 'lazy (lambda () exp))))
+
+(define (stream-eager x)
+ (make-stream-promise (make-stream-value 'eager x)))
+
+(define-syntax-rule (stream-delay exp)
+ (stream-lazy (stream-eager exp)))
+
+(define (stream-force promise)
+ (let ((content (stream-promise-val promise)))
+ (case (stream-value-tag content)
+ ((eager) (stream-value-proc content))
+ ((lazy) (let* ((promise* ((stream-value-proc content)))
+ (content (stream-promise-val promise)))
+ (if (not (eqv? (stream-value-tag content) 'eager))
+ (begin (stream-value-tag-set! content
+ (stream-value-tag (stream-promise-val promise*)))
+ (stream-value-proc-set! content
+ (stream-value-proc (stream-promise-val promise*)))
+ (stream-promise-val-set! promise* content)))
+ (stream-force promise))))))
+
+;;
+;; End of the copy of the code from srfi-45.scm
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; Primitive stream functions and macros: (streams primitive)
+
+(define stream? stream-promise?)
+
+(define %stream-null (cons 'stream 'null))
+(define stream-null (stream-eager %stream-null))
+
+(define (stream-null? obj)
+ (and (stream-promise? obj)
+ (eqv? (stream-force obj) %stream-null)))
+
+(define-record-type stream-pare (make-stream-pare kar kdr) stream-pare?
+ (kar stream-kar)
+ (kdr stream-kdr))
+
+(define (stream-pair? obj)
+ (and (stream-promise? obj) (stream-pare? (stream-force obj))))
+
+(define-syntax-rule (stream-cons obj strm)
+ (stream-eager (make-stream-pare (stream-delay obj) (stream-lazy strm))))
+
+(define (stream-car strm)
+ (must stream? strm 'stream-car "non-stream")
+ (let ((pare (stream-force strm)))
+ (must stream-pare? pare 'stream-car "null stream")
+ (stream-force (stream-kar pare))))
+
+(define (stream-cdr strm)
+ (must stream? strm 'stream-cdr "non-stream")
+ (let ((pare (stream-force strm)))
+ (must stream-pare? pare 'stream-cdr "null stream")
+ (stream-kdr pare)))
+
+(define-syntax-rule (stream-lambda formals body0 body1 ...)
+ (lambda formals (stream-lazy (begin body0 body1 ...))))
+
+(define* (stream-promise-visit promise #\key on-eager on-lazy)
+ (define content (stream-promise-val promise))
+ (case (stream-value-tag content)
+ ((eager) (on-eager (stream-value-proc content)))
+ ((lazy) (on-lazy (stream-value-proc content)))))
+
+(set-record-type-printer! stream-promise
+ (lambda (strm port)
+ (display "#<stream" port)
+ (let loop ((strm strm))
+ (stream-promise-visit strm
+ #\on-eager (lambda (pare)
+ (cond ((eq? pare %stream-null)
+ (write-char #\> port))
+ (else
+ (write-char #\space port)
+ (stream-promise-visit (stream-kar pare)
+ #\on-eager (cut write <> port)
+ #\on-lazy (lambda (_) (write-char #\? port)))
+ (loop (stream-kdr pare)))))
+ #\on-lazy (lambda (_) (display " ...>" port))))))
+
+;;; Derived stream functions and macros: (streams derived)
+
+(define-syntax-rule (define-stream (name . formal) body0 body1 ...)
+ (define name (stream-lambda formal body0 body1 ...)))
+
+(define-syntax-rule (stream-let tag ((name val) ...) body1 body2 ...)
+ ((letrec ((tag (stream-lambda (name ...) body1 body2 ...))) tag) val ...))
+
+(define (list->stream objs)
+ (define (list? x)
+ (or (proper-list? x) (circular-list? x)))
+ (must list? objs 'list->stream "non-list argument")
+ (stream-let recur ((objs objs))
+ (if (null? objs) stream-null
+ (stream-cons (car objs) (recur (cdr objs))))))
+
+(define* (port->stream #\optional (port (current-input-port)))
+ (must input-port? port 'port->stream "non-input-port argument")
+ (stream-let recur ()
+ (let ((c (read-char port)))
+ (if (eof-object? c) stream-null
+ (stream-cons c (recur))))))
+
+(define-syntax stream
+ (syntax-rules ()
+ ((_) stream-null)
+ ((_ x y ...) (stream-cons x (stream y ...)))))
+
+;; Common helper for the various eager-folding functions, such as
+;; stream-fold, stream-drop, stream->list, stream-length, etc.
+(define-inlinable (stream-fold-aux proc base strm limit)
+ (do ((val base (and proc (proc val (stream-car strm))))
+ (strm strm (stream-cdr strm))
+ (limit limit (and limit (1- limit))))
+ ((or (and limit (zero? limit)) (stream-null? strm))
+ (values val strm limit))))
+
+(define stream->list
+ (case-lambda
+ ((strm) (stream->list #f strm))
+ ((n strm)
+ (must stream? strm 'stream->list "non-stream argument")
+ (when n
+ (must integer? n 'stream->list "non-integer count")
+ (must exact? n 'stream->list "inexact count")
+ (must-not negative? n 'stream->list "negative count"))
+ (reverse! (first-value (stream-fold-aux xcons '() strm n))))))
+
+(define (stream-append . strms)
+ (must-every stream? strms 'stream-append "non-stream argument")
+ (stream-let recur ((strms strms))
+ (if (null? strms) stream-null
+ (let ((strm (car strms)))
+ (if (stream-null? strm) (recur (cdr strms))
+ (stream-cons (stream-car strm)
+ (recur (cons (stream-cdr strm) (cdr strms)))))))))
+
+(define (stream-concat strms)
+ (must stream? strms 'stream-concat "non-stream argument")
+ (stream-let recur ((strms strms))
+ (if (stream-null? strms) stream-null
+ (let ((strm (stream-car strms)))
+ (must stream? strm 'stream-concat "non-stream object in input stream")
+ (if (stream-null? strm) (recur (stream-cdr strms))
+ (stream-cons (stream-car strm)
+ (recur (stream-cons (stream-cdr strm)
+ (stream-cdr strms)))))))))
+
+(define stream-constant
+ (case-lambda
+ (() stream-null)
+ (objs (list->stream (apply circular-list objs)))))
+
+(define-syntax* (stream-do x)
+ (define (end x)
+ (syntax-case x ()
+ (() #'(if #f #f))
+ ((result) #'result)
+ ((result ...) #'(begin result ...))))
+ (define (var-step v s)
+ (syntax-case s ()
+ (() v)
+ ((e) #'e)
+ (_ (syntax-violation 'stream-do "bad step expression" x s))))
+
+ (syntax-case x ()
+ ((_ ((var init . step) ...)
+ (test result ...)
+ expr ...)
+ (with-syntax ((result (end #'(result ...)))
+ ((step ...) (map var-step #'(var ...) #'(step ...))))
+ #'(stream-let loop ((var init) ...)
+ (if test result
+ (begin
+ expr ...
+ (loop step ...))))))))
+
+(define (stream-drop n strm)
+ (must integer? n 'stream-drop "non-integer argument")
+ (must exact? n 'stream-drop "inexact argument")
+ (must-not negative? n 'stream-drop "negative argument")
+ (must stream? strm 'stream-drop "non-stream argument")
+ (second-value (stream-fold-aux #f #f strm n)))
+
+(define (stream-drop-while pred? strm)
+ (must procedure? pred? 'stream-drop-while "non-procedural argument")
+ (must stream? strm 'stream-drop-while "non-stream argument")
+ (stream-do ((strm strm (stream-cdr strm)))
+ ((or (stream-null? strm) (not (pred? (stream-car strm)))) strm)))
+
+(define (stream-filter pred? strm)
+ (must procedure? pred? 'stream-filter "non-procedural argument")
+ (must stream? strm 'stream-filter "non-stream argument")
+ (stream-let recur ((strm strm))
+ (cond ((stream-null? strm) stream-null)
+ ((pred? (stream-car strm))
+ (stream-cons (stream-car strm) (recur (stream-cdr strm))))
+ (else (recur (stream-cdr strm))))))
+
+(define (stream-fold proc base strm)
+ (must procedure? proc 'stream-fold "non-procedural argument")
+ (must stream? strm 'stream-fold "non-stream argument")
+ (first-value (stream-fold-aux proc base strm #f)))
+
+(define stream-for-each
+ (case-lambda
+ ((proc strm)
+ (must procedure? proc 'stream-for-each "non-procedural argument")
+ (must stream? strm 'stream-for-each "non-stream argument")
+ (do ((strm strm (stream-cdr strm)))
+ ((stream-null? strm))
+ (proc (stream-car strm))))
+ ((proc strm . rest)
+ (let ((strms (cons strm rest)))
+ (must procedure? proc 'stream-for-each "non-procedural argument")
+ (must-every stream? strms 'stream-for-each "non-stream argument")
+ (do ((strms strms (map stream-cdr strms)))
+ ((any stream-null? strms))
+ (apply proc (map stream-car strms)))))))
+
+(define* (stream-from first #\optional (step 1))
+ (must number? first 'stream-from "non-numeric starting number")
+ (must number? step 'stream-from "non-numeric step size")
+ (stream-let recur ((first first))
+ (stream-cons first (recur (+ first step)))))
+
+(define (stream-iterate proc base)
+ (must procedure? proc 'stream-iterate "non-procedural argument")
+ (stream-let recur ((base base))
+ (stream-cons base (recur (proc base)))))
+
+(define (stream-length strm)
+ (must stream? strm 'stream-length "non-stream argument")
+ (- -1 (third-value (stream-fold-aux #f #f strm -1))))
+
+(define stream-map
+ (case-lambda
+ ((proc strm)
+ (must procedure? proc 'stream-map "non-procedural argument")
+ (must stream? strm 'stream-map "non-stream argument")
+ (stream-let recur ((strm strm))
+ (if (stream-null? strm) stream-null
+ (stream-cons (proc (stream-car strm))
+ (recur (stream-cdr strm))))))
+ ((proc strm . rest)
+ (let ((strms (cons strm rest)))
+ (must procedure? proc 'stream-map "non-procedural argument")
+ (must-every stream? strms 'stream-map "non-stream argument")
+ (stream-let recur ((strms strms))
+ (if (any stream-null? strms) stream-null
+ (stream-cons (apply proc (map stream-car strms))
+ (recur (map stream-cdr strms)))))))))
+
+(define-syntax* (stream-match x)
+ (define (make-matcher x)
+ (syntax-case x ()
+ (() #'(? stream-null?))
+ (rest (identifier? #'rest) #'rest)
+ ((var . rest) (identifier? #'var)
+ (with-syntax ((next (make-matcher #'rest)))
+ #'(? (negate stream-null?)
+ (= stream-car var)
+ (= stream-cdr next))))))
+ (define (make-guarded x fail)
+ (syntax-case (list x fail) ()
+ (((expr) _) #'expr)
+ (((guard expr) fail) #'(if guard expr (fail)))))
+
+ (syntax-case x ()
+ ((_ strm-expr (pat . expr) ...)
+ (with-syntax (((fail ...) (generate-temporaries #'(pat ...))))
+ (with-syntax (((matcher ...) (map make-matcher #'(pat ...)))
+ ((expr ...) (map make-guarded #'(expr ...) #'(fail ...))))
+ #'(let ((strm strm-expr))
+ (must stream? strm 'stream-match "non-stream argument")
+ (match strm (matcher (=> fail) expr) ...)))))))
+
+(define-syntax-rule (stream-of expr rest ...)
+ (stream-of-aux expr stream-null rest ...))
+
+(define-syntax stream-of-aux
+ (syntax-rules (in is)
+ ((_ expr base)
+ (stream-cons expr base))
+ ((_ expr base (var in stream) rest ...)
+ (stream-let recur ((strm stream))
+ (if (stream-null? strm) base
+ (let ((var (stream-car strm)))
+ (stream-of-aux expr (recur (stream-cdr strm)) rest ...)))))
+ ((_ expr base (var is exp) rest ...)
+ (let ((var exp)) (stream-of-aux expr base rest ...)))
+ ((_ expr base pred? rest ...)
+ (if pred? (stream-of-aux expr base rest ...) base))))
+
+(define* (stream-range first past #\optional step)
+ (must number? first 'stream-range "non-numeric starting number")
+ (must number? past 'stream-range "non-numeric ending number")
+ (when step
+ (must number? step 'stream-range "non-numeric step size"))
+ (let* ((step (or step (if (< first past) 1 -1)))
+ (lt? (if (< 0 step) < >)))
+ (stream-let recur ((first first))
+ (if (lt? first past)
+ (stream-cons first (recur (+ first step)))
+ stream-null))))
+
+(define (stream-ref strm n)
+ (must stream? strm 'stream-ref "non-stream argument")
+ (must integer? n 'stream-ref "non-integer argument")
+ (must exact? n 'stream-ref "inexact argument")
+ (must-not negative? n 'stream-ref "negative argument")
+ (let ((res (stream-drop n strm)))
+ (must-not stream-null? res 'stream-ref "beyond end of stream")
+ (stream-car res)))
+
+(define (stream-reverse strm)
+ (must stream? strm 'stream-reverse "non-stream argument")
+ (stream-do ((strm strm (stream-cdr strm))
+ (rev stream-null (stream-cons (stream-car strm) rev)))
+ ((stream-null? strm) rev)))
+
+(define (stream-scan proc base strm)
+ (must procedure? proc 'stream-scan "non-procedural argument")
+ (must stream? strm 'stream-scan "non-stream argument")
+ (stream-let recur ((base base) (strm strm))
+ (if (stream-null? strm) (stream base)
+ (stream-cons base (recur (proc base (stream-car strm))
+ (stream-cdr strm))))))
+
+(define (stream-take n strm)
+ (must stream? strm 'stream-take "non-stream argument")
+ (must integer? n 'stream-take "non-integer argument")
+ (must exact? n 'stream-take "inexact argument")
+ (must-not negative? n 'stream-take "negative argument")
+ (stream-let recur ((n n) (strm strm))
+ (if (or (zero? n) (stream-null? strm)) stream-null
+ (stream-cons (stream-car strm) (recur (1- n) (stream-cdr strm))))))
+
+(define (stream-take-while pred? strm)
+ (must procedure? pred? 'stream-take-while "non-procedural argument")
+ (must stream? strm 'stream-take-while "non-stream argument")
+ (stream-let recur ((strm strm))
+ (cond ((stream-null? strm) stream-null)
+ ((pred? (stream-car strm))
+ (stream-cons (stream-car strm) (recur (stream-cdr strm))))
+ (else stream-null))))
+
+(define (stream-unfold mapper pred? generator base)
+ (must procedure? mapper 'stream-unfold "non-procedural mapper")
+ (must procedure? pred? 'stream-unfold "non-procedural pred?")
+ (must procedure? generator 'stream-unfold "non-procedural generator")
+ (stream-let recur ((base base))
+ (if (pred? base)
+ (stream-cons (mapper base) (recur (generator base)))
+ stream-null)))
+
+(define (stream-unfolds gen seed)
+ (define-stream (generator-stream seed)
+ (receive (next . items) (gen seed)
+ (stream-cons (list->vector items) (generator-stream next))))
+ (define-stream (make-result-stream genstrm index)
+ (define head (vector-ref (stream-car genstrm) index))
+ (define-stream (tail) (make-result-stream (stream-cdr genstrm) index))
+ (match head
+ (() stream-null)
+ (#f (tail))
+ ((item) (stream-cons item (tail)))
+ ((? list? items) (stream-append (list->stream items) (tail)))))
+
+ (must procedure? gen 'stream-unfolds "non-procedural argument")
+ (let ((genstrm (generator-stream seed)))
+ (apply values (list-tabulate (vector-length (stream-car genstrm))
+ (cut make-result-stream genstrm <>)))))
+
+(define (stream-zip strm . rest)
+ (let ((strms (cons strm rest)))
+ (must-every stream? strms 'stream-zip "non-stream argument")
+ (stream-let recur ((strms strms))
+ (if (any stream-null? strms) stream-null
+ (stream-cons (map stream-car strms) (recur (map stream-cdr strms)))))))
+;;; srfi-42.scm --- Eager comprehensions
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library. If not, see
+;; <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module is not yet documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-42)
+ #\export (\:
+ \:-dispatch-ref
+ \:-dispatch-set!
+ \:char-range
+ \:dispatched
+ \:do
+ \:generator-proc
+ \:integers
+ \:let
+ \:list
+ \:parallel
+ \:port
+ \:range
+ \:real-range
+ \:string
+ \:until
+ \:vector
+ \:while
+ any?-ec
+ append-ec
+ dispatch-union
+ do-ec
+ every?-ec
+ first-ec
+ fold-ec
+ fold3-ec
+ last-ec
+ list-ec
+ make-initial-\:-dispatch
+ max-ec
+ min-ec
+ product-ec
+ string-append-ec
+ string-ec
+ sum-ec
+ vector-ec
+ vector-of-length-ec))
+
+(cond-expand-provide (current-module) '(srfi-42))
+
+(include-from-path "srfi/srfi-42/ec.scm")
+; <PLAINTEXT>
+; Eager Comprehensions in [outer..inner|expr]-Convention
+; ======================================================
+;
+; sebastian.egner@philips.com, Eindhoven, The Netherlands, 26-Dec-2007
+; Scheme R5RS (incl. macros), SRFI-23 (error).
+;
+; Loading the implementation into Scheme48 0.57:
+; ,open srfi-23
+; ,load ec.scm
+;
+; Loading the implementation into PLT/DrScheme 317:
+; ; File > Open ... "ec.scm", click Execute
+;
+; Loading the implementation into SCM 5d7:
+; (require 'macro) (require 'record)
+; (load "ec.scm")
+;
+; Implementation comments:
+; * All local (not exported) identifiers are named ec-<something>.
+; * This implementation focuses on portability, performance,
+; readability, and simplicity roughly in this order. Design
+; decisions related to performance are taken for Scheme48.
+; * Alternative implementations, Comments and Warnings are
+; mentioned after the definition with a heading.
+
+
+; ==========================================================================
+; The fundamental comprehension do-ec
+; ==========================================================================
+;
+; All eager comprehensions are reduced into do-ec and
+; all generators are reduced to :do.
+;
+; We use the following short names for syntactic variables
+; q - qualifier
+; cc - current continuation, thing to call at the end;
+; the CPS is (m (cc ...) arg ...) -> (cc ... expr ...)
+; cmd - an expression being evaluated for its side-effects
+; expr - an expression
+; gen - a generator of an eager comprehension
+; ob - outer binding
+; oc - outer command
+; lb - loop binding
+; ne1? - not-end1? (before the payload)
+; ib - inner binding
+; ic - inner command
+; ne2? - not-end2? (after the payload)
+; ls - loop step
+; etc - more arguments of mixed type
+
+
+; (do-ec q ... cmd)
+; handles nested, if/not/and/or, begin, :let, and calls generator
+; macros in CPS to transform them into fully decorated :do.
+; The code generation for a :do is delegated to do-ec:do.
+
+(define-syntax do-ec
+ (syntax-rules (nested if not and or begin \:do let)
+
+ ; explicit nesting -> implicit nesting
+ ((do-ec (nested q ...) etc ...)
+ (do-ec q ... etc ...) )
+
+ ; implicit nesting -> fold do-ec
+ ((do-ec q1 q2 etc1 etc ...)
+ (do-ec q1 (do-ec q2 etc1 etc ...)) )
+
+ ; no qualifiers at all -> evaluate cmd once
+ ((do-ec cmd)
+ (begin cmd (if #f #f)) )
+
+; now (do-ec q cmd) remains
+
+ ; filter -> make conditional
+ ((do-ec (if test) cmd)
+ (if test (do-ec cmd)) )
+ ((do-ec (not test) cmd)
+ (if (not test) (do-ec cmd)) )
+ ((do-ec (and test ...) cmd)
+ (if (and test ...) (do-ec cmd)) )
+ ((do-ec (or test ...) cmd)
+ (if (or test ...) (do-ec cmd)) )
+
+ ; begin -> make a sequence
+ ((do-ec (begin etc ...) cmd)
+ (begin etc ... (do-ec cmd)) )
+
+ ; fully decorated :do-generator -> delegate to do-ec:do
+ ((do-ec (#\:do olet lbs ne1? ilet ne2? lss) cmd)
+ (do-ec:do cmd (#\:do olet lbs ne1? ilet ne2? lss)) )
+
+; anything else -> call generator-macro in CPS; reentry at (*)
+
+ ((do-ec (g arg1 arg ...) cmd)
+ (g (do-ec:do cmd) arg1 arg ...) )))
+
+
+; (do-ec:do cmd (#\:do olet lbs ne1? ilet ne2? lss))
+; generates code for a single fully decorated :do-generator
+; with cmd as payload, taking care of special cases.
+
+(define-syntax do-ec:do
+ (syntax-rules (#\:do let)
+
+ ; reentry point (*) -> generate code
+ ((do-ec:do cmd
+ (#\:do (let obs oc ...)
+ lbs
+ ne1?
+ (let ibs ic ...)
+ ne2?
+ (ls ...) ))
+ (ec-simplify
+ (let obs
+ oc ...
+ (let loop lbs
+ (ec-simplify
+ (if ne1?
+ (ec-simplify
+ (let ibs
+ ic ...
+ cmd
+ (ec-simplify
+ (if ne2?
+ (loop ls ...) )))))))))) ))
+
+
+; (ec-simplify <expression>)
+; generates potentially more efficient code for <expression>.
+; The macro handles if, (begin <command>*), and (let () <command>*)
+; and takes care of special cases.
+
+(define-syntax ec-simplify
+ (syntax-rules (if not let begin)
+
+; one- and two-sided if
+
+ ; literal <test>
+ ((ec-simplify (if #t consequent))
+ consequent )
+ ((ec-simplify (if #f consequent))
+ (if #f #f) )
+ ((ec-simplify (if #t consequent alternate))
+ consequent )
+ ((ec-simplify (if #f consequent alternate))
+ alternate )
+
+ ; (not (not <test>))
+ ((ec-simplify (if (not (not test)) consequent))
+ (ec-simplify (if test consequent)) )
+ ((ec-simplify (if (not (not test)) consequent alternate))
+ (ec-simplify (if test consequent alternate)) )
+
+; (let () <command>*)
+
+ ; empty <binding spec>*
+ ((ec-simplify (let () command ...))
+ (ec-simplify (begin command ...)) )
+
+; begin
+
+ ; flatten use helper (ec-simplify 1 done to-do)
+ ((ec-simplify (begin command ...))
+ (ec-simplify 1 () (command ...)) )
+ ((ec-simplify 1 done ((begin to-do1 ...) to-do2 ...))
+ (ec-simplify 1 done (to-do1 ... to-do2 ...)) )
+ ((ec-simplify 1 (done ...) (to-do1 to-do ...))
+ (ec-simplify 1 (done ... to-do1) (to-do ...)) )
+
+ ; exit helper
+ ((ec-simplify 1 () ())
+ (if #f #f) )
+ ((ec-simplify 1 (command) ())
+ command )
+ ((ec-simplify 1 (command1 command ...) ())
+ (begin command1 command ...) )
+
+; anything else
+
+ ((ec-simplify expression)
+ expression )))
+
+
+; ==========================================================================
+; The special generators :do, :let, :parallel, :while, and :until
+; ==========================================================================
+
+(define-syntax \:do
+ (syntax-rules ()
+
+ ; full decorated -> continue with cc, reentry at (*)
+ ((#\:do (cc ...) olet lbs ne1? ilet ne2? lss)
+ (cc ... (#\:do olet lbs ne1? ilet ne2? lss)) )
+
+ ; short form -> fill in default values
+ ((#\:do cc lbs ne1? lss)
+ (#\:do cc (let ()) lbs ne1? (let ()) #t lss) )))
+
+
+(define-syntax \:let
+ (syntax-rules (index)
+ ((\:let cc var (index i) expression)
+ (#\:do cc (let ((var expression) (i 0))) () #t (let ()) #f ()) )
+ ((\:let cc var expression)
+ (#\:do cc (let ((var expression))) () #t (let ()) #f ()) )))
+
+
+(define-syntax \:parallel
+ (syntax-rules (#\:do)
+ ((\:parallel cc)
+ cc )
+ ((\:parallel cc (g arg1 arg ...) gen ...)
+ (g (\:parallel-1 cc (gen ...)) arg1 arg ...) )))
+
+; (\:parallel-1 cc (to-do ...) result [ next ] )
+; iterates over to-do by converting the first generator into
+; the :do-generator next and merging next into result.
+
+(define-syntax \:parallel-1 ; used as
+ (syntax-rules (#\:do let)
+
+ ; process next element of to-do, reentry at (**)
+ ((\:parallel-1 cc ((g arg1 arg ...) gen ...) result)
+ (g (\:parallel-1 cc (gen ...) result) arg1 arg ...) )
+
+ ; reentry point (**) -> merge next into result
+ ((\:parallel-1
+ cc
+ gens
+ (#\:do (let (ob1 ...) oc1 ...)
+ (lb1 ...)
+ ne1?1
+ (let (ib1 ...) ic1 ...)
+ ne2?1
+ (ls1 ...) )
+ (#\:do (let (ob2 ...) oc2 ...)
+ (lb2 ...)
+ ne1?2
+ (let (ib2 ...) ic2 ...)
+ ne2?2
+ (ls2 ...) ))
+ (\:parallel-1
+ cc
+ gens
+ (#\:do (let (ob1 ... ob2 ...) oc1 ... oc2 ...)
+ (lb1 ... lb2 ...)
+ (and ne1?1 ne1?2)
+ (let (ib1 ... ib2 ...) ic1 ... ic2 ...)
+ (and ne2?1 ne2?2)
+ (ls1 ... ls2 ...) )))
+
+ ; no more gens -> continue with cc, reentry at (*)
+ ((\:parallel-1 (cc ...) () result)
+ (cc ... result) )))
+
+(define-syntax \:while
+ (syntax-rules ()
+ ((\:while cc (g arg1 arg ...) test)
+ (g (\:while-1 cc test) arg1 arg ...) )))
+
+; (\:while-1 cc test (#\:do ...))
+; modifies the fully decorated :do-generator such that it
+; runs while test is a true value.
+; The original implementation just replaced ne1? by
+; (and ne1? test) as follows:
+;
+; (define-syntax \:while-1
+; (syntax-rules (#\:do)
+; ((\:while-1 cc test (#\:do olet lbs ne1? ilet ne2? lss))
+; (#\:do cc olet lbs (and ne1? test) ilet ne2? lss) )))
+;
+; Bug #1:
+; Unfortunately, this code is wrong because ne1? may depend
+; in the inner bindings introduced in ilet, but ne1? is evaluated
+; outside of the inner bindings. (Refer to the specification of
+; :do to see the structure.)
+; The problem manifests itself (as sunnan@handgranat.org
+; observed, 25-Apr-2005) when the :list-generator is modified:
+;
+; (do-ec (\:while (\:list x '(1 2)) (= x 1)) (display x)).
+;
+; In order to generate proper code, we introduce temporary
+; variables saving the values of the inner bindings. The inner
+; bindings are executed in a new ne1?, which also evaluates ne1?
+; outside the scope of the inner bindings, then the inner commands
+; are executed (possibly changing the variables), and then the
+; values of the inner bindings are saved and (and ne1? test) is
+; returned. In the new ilet, the inner variables are bound and
+; initialized and their values are restored. So we construct:
+;
+; (let (ob .. (ib-tmp #f) ...)
+; oc ...
+; (let loop (lb ...)
+; (if (let (ne1?-value ne1?)
+; (let ((ib-var ib-rhs) ...)
+; ic ...
+; (set! ib-tmp ib-var) ...)
+; (and ne1?-value test))
+; (let ((ib-var ib-tmp) ...)
+; /payload/
+; (if ne2?
+; (loop ls ...) )))))
+;
+; Bug #2:
+; Unfortunately, the above expansion is still incorrect (as Jens-Axel
+; Soegaard pointed out, 4-Jun-2007) because ib-rhs are evaluated even
+; if ne1?-value is #f, indicating that the loop has ended.
+; The problem manifests itself in the following example:
+;
+; (do-ec (\:while (\:list x '(1)) #t) (display x))
+;
+; Which iterates :list beyond exhausting the list '(1).
+;
+; For the fix, we follow Jens-Axel's approach of guarding the evaluation
+; of ib-rhs with a check on ne1?-value.
+
+(define-syntax \:while-1
+ (syntax-rules (#\:do let)
+ ((\:while-1 cc test (#\:do olet lbs ne1? ilet ne2? lss))
+ (\:while-2 cc test () () () (#\:do olet lbs ne1? ilet ne2? lss)))))
+
+(define-syntax \:while-2
+ (syntax-rules (#\:do let)
+ ((\:while-2 cc
+ test
+ (ib-let ...)
+ (ib-save ...)
+ (ib-restore ...)
+ (#\:do olet
+ lbs
+ ne1?
+ (let ((ib-var ib-rhs) ib ...) ic ...)
+ ne2?
+ lss))
+ (\:while-2 cc
+ test
+ (ib-let ... (ib-tmp #f))
+ (ib-save ... (ib-var ib-rhs))
+ (ib-restore ... (ib-var ib-tmp))
+ (#\:do olet
+ lbs
+ ne1?
+ (let (ib ...) ic ... (set! ib-tmp ib-var))
+ ne2?
+ lss)))
+ ((\:while-2 cc
+ test
+ (ib-let ...)
+ (ib-save ...)
+ (ib-restore ...)
+ (#\:do (let (ob ...) oc ...) lbs ne1? (let () ic ...) ne2? lss))
+ (#\:do cc
+ (let (ob ... ib-let ...) oc ...)
+ lbs
+ (let ((ne1?-value ne1?))
+ (and ne1?-value
+ (let (ib-save ...)
+ ic ...
+ test)))
+ (let (ib-restore ...))
+ ne2?
+ lss))))
+
+
+(define-syntax \:until
+ (syntax-rules ()
+ ((\:until cc (g arg1 arg ...) test)
+ (g (\:until-1 cc test) arg1 arg ...) )))
+
+(define-syntax \:until-1
+ (syntax-rules (#\:do)
+ ((\:until-1 cc test (#\:do olet lbs ne1? ilet ne2? lss))
+ (#\:do cc olet lbs ne1? ilet (and ne2? (not test)) lss) )))
+
+
+; ==========================================================================
+; The typed generators :list :string :vector etc.
+; ==========================================================================
+
+(define-syntax \:list
+ (syntax-rules (index)
+ ((\:list cc var (index i) arg ...)
+ (\:parallel cc (\:list var arg ...) (\:integers i)) )
+ ((\:list cc var arg1 arg2 arg ...)
+ (\:list cc var (append arg1 arg2 arg ...)) )
+ ((\:list cc var arg)
+ (#\:do cc
+ (let ())
+ ((t arg))
+ (not (null? t))
+ (let ((var (car t))))
+ #t
+ ((cdr t)) ))))
+
+
+(define-syntax \:string
+ (syntax-rules (index)
+ ((\:string cc var (index i) arg)
+ (#\:do cc
+ (let ((str arg) (len 0))
+ (set! len (string-length str)))
+ ((i 0))
+ (< i len)
+ (let ((var (string-ref str i))))
+ #t
+ ((+ i 1)) ))
+ ((\:string cc var (index i) arg1 arg2 arg ...)
+ (\:string cc var (index i) (string-append arg1 arg2 arg ...)) )
+ ((\:string cc var arg1 arg ...)
+ (\:string cc var (index i) arg1 arg ...) )))
+
+; Alternative: An implementation in the style of :vector can also
+; be used for :string. However, it is less interesting as the
+; overhead of string-append is much less than for 'vector-append'.
+
+
+(define-syntax \:vector
+ (syntax-rules (index)
+ ((\:vector cc var arg)
+ (\:vector cc var (index i) arg) )
+ ((\:vector cc var (index i) arg)
+ (#\:do cc
+ (let ((vec arg) (len 0))
+ (set! len (vector-length vec)))
+ ((i 0))
+ (< i len)
+ (let ((var (vector-ref vec i))))
+ #t
+ ((+ i 1)) ))
+
+ ((\:vector cc var (index i) arg1 arg2 arg ...)
+ (\:parallel cc (\:vector cc var arg1 arg2 arg ...) (\:integers i)) )
+ ((\:vector cc var arg1 arg2 arg ...)
+ (#\:do cc
+ (let ((vec #f)
+ (len 0)
+ (vecs (ec-:vector-filter (list arg1 arg2 arg ...))) ))
+ ((k 0))
+ (if (< k len)
+ #t
+ (if (null? vecs)
+ #f
+ (begin (set! vec (car vecs))
+ (set! vecs (cdr vecs))
+ (set! len (vector-length vec))
+ (set! k 0)
+ #t )))
+ (let ((var (vector-ref vec k))))
+ #t
+ ((+ k 1)) ))))
+
+(define (ec-:vector-filter vecs)
+ (if (null? vecs)
+ '()
+ (if (zero? (vector-length (car vecs)))
+ (ec-:vector-filter (cdr vecs))
+ (cons (car vecs) (ec-:vector-filter (cdr vecs))) )))
+
+; Alternative: A simpler implementation for :vector uses vector->list
+; append and :list in the multi-argument case. Please refer to the
+; 'design.scm' for more details.
+
+
+(define-syntax \:integers
+ (syntax-rules (index)
+ ((\:integers cc var (index i))
+ (#\:do cc ((var 0) (i 0)) #t ((+ var 1) (+ i 1))) )
+ ((\:integers cc var)
+ (#\:do cc ((var 0)) #t ((+ var 1))) )))
+
+
+(define-syntax \:range
+ (syntax-rules (index)
+
+ ; handle index variable and add optional args
+ ((\:range cc var (index i) arg1 arg ...)
+ (\:parallel cc (\:range var arg1 arg ...) (\:integers i)) )
+ ((\:range cc var arg1)
+ (\:range cc var 0 arg1 1) )
+ ((\:range cc var arg1 arg2)
+ (\:range cc var arg1 arg2 1) )
+
+; special cases (partially evaluated by hand from general case)
+
+ ((\:range cc var 0 arg2 1)
+ (#\:do cc
+ (let ((b arg2))
+ (if (not (and (integer? b) (exact? b)))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" 0 b 1 )))
+ ((var 0))
+ (< var b)
+ (let ())
+ #t
+ ((+ var 1)) ))
+
+ ((\:range cc var 0 arg2 -1)
+ (#\:do cc
+ (let ((b arg2))
+ (if (not (and (integer? b) (exact? b)))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" 0 b 1 )))
+ ((var 0))
+ (> var b)
+ (let ())
+ #t
+ ((- var 1)) ))
+
+ ((\:range cc var arg1 arg2 1)
+ (#\:do cc
+ (let ((a arg1) (b arg2))
+ (if (not (and (integer? a) (exact? a)
+ (integer? b) (exact? b) ))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" a b 1 )) )
+ ((var a))
+ (< var b)
+ (let ())
+ #t
+ ((+ var 1)) ))
+
+ ((\:range cc var arg1 arg2 -1)
+ (#\:do cc
+ (let ((a arg1) (b arg2) (s -1) (stop 0))
+ (if (not (and (integer? a) (exact? a)
+ (integer? b) (exact? b) ))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" a b -1 )) )
+ ((var a))
+ (> var b)
+ (let ())
+ #t
+ ((- var 1)) ))
+
+; the general case
+
+ ((\:range cc var arg1 arg2 arg3)
+ (#\:do cc
+ (let ((a arg1) (b arg2) (s arg3) (stop 0))
+ (if (not (and (integer? a) (exact? a)
+ (integer? b) (exact? b)
+ (integer? s) (exact? s) ))
+ (error
+ "arguments of :range are not exact integer "
+ "(use :real-range?)" a b s ))
+ (if (zero? s)
+ (error "step size must not be zero in :range") )
+ (set! stop (+ a (* (max 0 (ceiling (/ (- b a) s))) s))) )
+ ((var a))
+ (not (= var stop))
+ (let ())
+ #t
+ ((+ var s)) ))))
+
+; Comment: The macro :range inserts some code to make sure the values
+; are exact integers. This overhead has proven very helpful for
+; saving users from themselves.
+
+
+(define-syntax \:real-range
+ (syntax-rules (index)
+
+ ; add optional args and index variable
+ ((\:real-range cc var arg1)
+ (\:real-range cc var (index i) 0 arg1 1) )
+ ((\:real-range cc var (index i) arg1)
+ (\:real-range cc var (index i) 0 arg1 1) )
+ ((\:real-range cc var arg1 arg2)
+ (\:real-range cc var (index i) arg1 arg2 1) )
+ ((\:real-range cc var (index i) arg1 arg2)
+ (\:real-range cc var (index i) arg1 arg2 1) )
+ ((\:real-range cc var arg1 arg2 arg3)
+ (\:real-range cc var (index i) arg1 arg2 arg3) )
+
+ ; the fully qualified case
+ ((\:real-range cc var (index i) arg1 arg2 arg3)
+ (#\:do cc
+ (let ((a arg1) (b arg2) (s arg3) (istop 0))
+ (if (not (and (real? a) (real? b) (real? s)))
+ (error "arguments of :real-range are not real" a b s) )
+ (if (and (exact? a) (or (not (exact? b)) (not (exact? s))))
+ (set! a (exact->inexact a)) )
+ (set! istop (/ (- b a) s)) )
+ ((i 0))
+ (< i istop)
+ (let ((var (+ a (* s i)))))
+ #t
+ ((+ i 1)) ))))
+
+; Comment: The macro :real-range adapts the exactness of the start
+; value in case any of the other values is inexact. This is a
+; precaution to avoid (list-ec (\: x 0 3.0) x) => '(0 1.0 2.0).
+
+
+(define-syntax \:char-range
+ (syntax-rules (index)
+ ((\:char-range cc var (index i) arg1 arg2)
+ (\:parallel cc (\:char-range var arg1 arg2) (\:integers i)) )
+ ((\:char-range cc var arg1 arg2)
+ (#\:do cc
+ (let ((imax (char->integer arg2))))
+ ((i (char->integer arg1)))
+ (<= i imax)
+ (let ((var (integer->char i))))
+ #t
+ ((+ i 1)) ))))
+
+; Warning: There is no R5RS-way to implement the :char-range generator
+; because the integers obtained by char->integer are not necessarily
+; consecutive. We simply assume this anyhow for illustration.
+
+
+(define-syntax \:port
+ (syntax-rules (index)
+ ((\:port cc var (index i) arg1 arg ...)
+ (\:parallel cc (\:port var arg1 arg ...) (\:integers i)) )
+ ((\:port cc var arg)
+ (\:port cc var arg read) )
+ ((\:port cc var arg1 arg2)
+ (#\:do cc
+ (let ((port arg1) (read-proc arg2)))
+ ((var (read-proc port)))
+ (not (eof-object? var))
+ (let ())
+ #t
+ ((read-proc port)) ))))
+
+
+; ==========================================================================
+; The typed generator :dispatched and utilities for constructing dispatchers
+; ==========================================================================
+
+(define-syntax \:dispatched
+ (syntax-rules (index)
+ ((\:dispatched cc var (index i) dispatch arg1 arg ...)
+ (\:parallel cc
+ (\:integers i)
+ (\:dispatched var dispatch arg1 arg ...) ))
+ ((\:dispatched cc var dispatch arg1 arg ...)
+ (#\:do cc
+ (let ((d dispatch)
+ (args (list arg1 arg ...))
+ (g #f)
+ (empty (list #f)) )
+ (set! g (d args))
+ (if (not (procedure? g))
+ (error "unrecognized arguments in dispatching"
+ args
+ (d '()) )))
+ ((var (g empty)))
+ (not (eq? var empty))
+ (let ())
+ #t
+ ((g empty)) ))))
+
+; Comment: The unique object empty is created as a newly allocated
+; non-empty list. It is compared using eq? which distinguishes
+; the object from any other object, according to R5RS 6.1.
+
+
+(define-syntax \:generator-proc
+ (syntax-rules (#\:do let)
+
+ ; call g with a variable, reentry at (**)
+ ((\:generator-proc (g arg ...))
+ (g (\:generator-proc var) var arg ...) )
+
+ ; reentry point (**) -> make the code from a single :do
+ ((\:generator-proc
+ var
+ (#\:do (let obs oc ...)
+ ((lv li) ...)
+ ne1?
+ (let ((i v) ...) ic ...)
+ ne2?
+ (ls ...)) )
+ (ec-simplify
+ (let obs
+ oc ...
+ (let ((lv li) ... (ne2 #t))
+ (ec-simplify
+ (let ((i #f) ...) ; v not yet valid
+ (lambda (empty)
+ (if (and ne1? ne2)
+ (ec-simplify
+ (begin
+ (set! i v) ...
+ ic ...
+ (let ((value var))
+ (ec-simplify
+ (if ne2?
+ (ec-simplify
+ (begin (set! lv ls) ...) )
+ (set! ne2 #f) ))
+ value )))
+ empty ))))))))
+
+ ; silence warnings of some macro expanders
+ ((\:generator-proc var)
+ (error "illegal macro call") )))
+
+
+(define (dispatch-union d1 d2)
+ (lambda (args)
+ (let ((g1 (d1 args)) (g2 (d2 args)))
+ (if g1
+ (if g2
+ (if (null? args)
+ (append (if (list? g1) g1 (list g1))
+ (if (list? g2) g2 (list g2)) )
+ (error "dispatching conflict" args (d1 '()) (d2 '())) )
+ g1 )
+ (if g2 g2 #f) ))))
+
+
+; ==========================================================================
+; The dispatching generator :
+; ==========================================================================
+
+(define (make-initial-\:-dispatch)
+ (lambda (args)
+ (case (length args)
+ ((0) 'SRFI42)
+ ((1) (let ((a1 (car args)))
+ (cond
+ ((list? a1)
+ (\:generator-proc (\:list a1)) )
+ ((string? a1)
+ (\:generator-proc (\:string a1)) )
+ ((vector? a1)
+ (\:generator-proc (\:vector a1)) )
+ ((and (integer? a1) (exact? a1))
+ (\:generator-proc (\:range a1)) )
+ ((real? a1)
+ (\:generator-proc (\:real-range a1)) )
+ ((input-port? a1)
+ (\:generator-proc (\:port a1)) )
+ (else
+ #f ))))
+ ((2) (let ((a1 (car args)) (a2 (cadr args)))
+ (cond
+ ((and (list? a1) (list? a2))
+ (\:generator-proc (\:list a1 a2)) )
+ ((and (string? a1) (string? a1))
+ (\:generator-proc (\:string a1 a2)) )
+ ((and (vector? a1) (vector? a2))
+ (\:generator-proc (\:vector a1 a2)) )
+ ((and (integer? a1) (exact? a1) (integer? a2) (exact? a2))
+ (\:generator-proc (\:range a1 a2)) )
+ ((and (real? a1) (real? a2))
+ (\:generator-proc (\:real-range a1 a2)) )
+ ((and (char? a1) (char? a2))
+ (\:generator-proc (\:char-range a1 a2)) )
+ ((and (input-port? a1) (procedure? a2))
+ (\:generator-proc (\:port a1 a2)) )
+ (else
+ #f ))))
+ ((3) (let ((a1 (car args)) (a2 (cadr args)) (a3 (caddr args)))
+ (cond
+ ((and (list? a1) (list? a2) (list? a3))
+ (\:generator-proc (\:list a1 a2 a3)) )
+ ((and (string? a1) (string? a1) (string? a3))
+ (\:generator-proc (\:string a1 a2 a3)) )
+ ((and (vector? a1) (vector? a2) (vector? a3))
+ (\:generator-proc (\:vector a1 a2 a3)) )
+ ((and (integer? a1) (exact? a1)
+ (integer? a2) (exact? a2)
+ (integer? a3) (exact? a3))
+ (\:generator-proc (\:range a1 a2 a3)) )
+ ((and (real? a1) (real? a2) (real? a3))
+ (\:generator-proc (\:real-range a1 a2 a3)) )
+ (else
+ #f ))))
+ (else
+ (letrec ((every?
+ (lambda (pred args)
+ (if (null? args)
+ #t
+ (and (pred (car args))
+ (every? pred (cdr args)) )))))
+ (cond
+ ((every? list? args)
+ (\:generator-proc (\:list (apply append args))) )
+ ((every? string? args)
+ (\:generator-proc (\:string (apply string-append args))) )
+ ((every? vector? args)
+ (\:generator-proc (\:list (apply append (map vector->list args)))) )
+ (else
+ #f )))))))
+
+(define \\:-dispatch
+ (make-initial-\:-dispatch) )
+
+(define (\\:-dispatch-ref)
+ \:-dispatch )
+
+(define (\\:-dispatch-set! dispatch)
+ (if (not (procedure? dispatch))
+ (error "not a procedure" dispatch) )
+ (set! \:-dispatch dispatch) )
+
+(define-syntax \:
+ (syntax-rules (index)
+ ((\: cc var (index i) arg1 arg ...)
+ (\:dispatched cc var (index i) \:-dispatch arg1 arg ...) )
+ ((\: cc var arg1 arg ...)
+ (\:dispatched cc var \:-dispatch arg1 arg ...) )))
+
+
+; ==========================================================================
+; The utility comprehensions fold-ec, fold3-ec
+; ==========================================================================
+
+(define-syntax fold3-ec
+ (syntax-rules (nested)
+ ((fold3-ec x0 (nested q1 ...) q etc1 etc2 etc3 etc ...)
+ (fold3-ec x0 (nested q1 ... q) etc1 etc2 etc3 etc ...) )
+ ((fold3-ec x0 q1 q2 etc1 etc2 etc3 etc ...)
+ (fold3-ec x0 (nested q1 q2) etc1 etc2 etc3 etc ...) )
+ ((fold3-ec x0 expression f1 f2)
+ (fold3-ec x0 (nested) expression f1 f2) )
+
+ ((fold3-ec x0 qualifier expression f1 f2)
+ (let ((result #f) (empty #t))
+ (do-ec qualifier
+ (let ((value expression)) ; don't duplicate
+ (if empty
+ (begin (set! result (f1 value))
+ (set! empty #f) )
+ (set! result (f2 value result)) )))
+ (if empty x0 result) ))))
+
+
+(define-syntax fold-ec
+ (syntax-rules (nested)
+ ((fold-ec x0 (nested q1 ...) q etc1 etc2 etc ...)
+ (fold-ec x0 (nested q1 ... q) etc1 etc2 etc ...) )
+ ((fold-ec x0 q1 q2 etc1 etc2 etc ...)
+ (fold-ec x0 (nested q1 q2) etc1 etc2 etc ...) )
+ ((fold-ec x0 expression f2)
+ (fold-ec x0 (nested) expression f2) )
+
+ ((fold-ec x0 qualifier expression f2)
+ (let ((result x0))
+ (do-ec qualifier (set! result (f2 expression result)))
+ result ))))
+
+
+; ==========================================================================
+; The comprehensions list-ec string-ec vector-ec etc.
+; ==========================================================================
+
+(define-syntax list-ec
+ (syntax-rules ()
+ ((list-ec etc1 etc ...)
+ (reverse (fold-ec '() etc1 etc ... cons)) )))
+
+; Alternative: Reverse can safely be replaced by reverse! if you have it.
+;
+; Alternative: It is possible to construct the result in the correct order
+; using set-cdr! to add at the tail. This removes the overhead of copying
+; at the end, at the cost of more book-keeping.
+
+
+(define-syntax append-ec
+ (syntax-rules ()
+ ((append-ec etc1 etc ...)
+ (apply append (list-ec etc1 etc ...)) )))
+
+(define-syntax string-ec
+ (syntax-rules ()
+ ((string-ec etc1 etc ...)
+ (list->string (list-ec etc1 etc ...)) )))
+
+; Alternative: For very long strings, the intermediate list may be a
+; problem. A more space-aware implementation collect the characters
+; in an intermediate list and when this list becomes too large it is
+; converted into an intermediate string. At the end, the intermediate
+; strings are concatenated with string-append.
+
+
+(define-syntax string-append-ec
+ (syntax-rules ()
+ ((string-append-ec etc1 etc ...)
+ (apply string-append (list-ec etc1 etc ...)) )))
+
+(define-syntax vector-ec
+ (syntax-rules ()
+ ((vector-ec etc1 etc ...)
+ (list->vector (list-ec etc1 etc ...)) )))
+
+; Comment: A similar approach as for string-ec can be used for vector-ec.
+; However, the space overhead for the intermediate list is much lower
+; than for string-ec and as there is no vector-append, the intermediate
+; vectors must be copied explicitly.
+
+(define-syntax vector-of-length-ec
+ (syntax-rules (nested)
+ ((vector-of-length-ec k (nested q1 ...) q etc1 etc ...)
+ (vector-of-length-ec k (nested q1 ... q) etc1 etc ...) )
+ ((vector-of-length-ec k q1 q2 etc1 etc ...)
+ (vector-of-length-ec k (nested q1 q2) etc1 etc ...) )
+ ((vector-of-length-ec k expression)
+ (vector-of-length-ec k (nested) expression) )
+
+ ((vector-of-length-ec k qualifier expression)
+ (let ((len k))
+ (let ((vec (make-vector len))
+ (i 0) )
+ (do-ec qualifier
+ (if (< i len)
+ (begin (vector-set! vec i expression)
+ (set! i (+ i 1)) )
+ (error "vector is too short for the comprehension") ))
+ (if (= i len)
+ vec
+ (error "vector is too long for the comprehension") ))))))
+
+
+(define-syntax sum-ec
+ (syntax-rules ()
+ ((sum-ec etc1 etc ...)
+ (fold-ec (+) etc1 etc ... +) )))
+
+(define-syntax product-ec
+ (syntax-rules ()
+ ((product-ec etc1 etc ...)
+ (fold-ec (*) etc1 etc ... *) )))
+
+(define-syntax min-ec
+ (syntax-rules ()
+ ((min-ec etc1 etc ...)
+ (fold3-ec (min) etc1 etc ... min min) )))
+
+(define-syntax max-ec
+ (syntax-rules ()
+ ((max-ec etc1 etc ...)
+ (fold3-ec (max) etc1 etc ... max max) )))
+
+(define-syntax last-ec
+ (syntax-rules (nested)
+ ((last-ec default (nested q1 ...) q etc1 etc ...)
+ (last-ec default (nested q1 ... q) etc1 etc ...) )
+ ((last-ec default q1 q2 etc1 etc ...)
+ (last-ec default (nested q1 q2) etc1 etc ...) )
+ ((last-ec default expression)
+ (last-ec default (nested) expression) )
+
+ ((last-ec default qualifier expression)
+ (let ((result default))
+ (do-ec qualifier (set! result expression))
+ result ))))
+
+
+; ==========================================================================
+; The fundamental early-stopping comprehension first-ec
+; ==========================================================================
+
+(define-syntax first-ec
+ (syntax-rules (nested)
+ ((first-ec default (nested q1 ...) q etc1 etc ...)
+ (first-ec default (nested q1 ... q) etc1 etc ...) )
+ ((first-ec default q1 q2 etc1 etc ...)
+ (first-ec default (nested q1 q2) etc1 etc ...) )
+ ((first-ec default expression)
+ (first-ec default (nested) expression) )
+
+ ((first-ec default qualifier expression)
+ (let ((result default) (stop #f))
+ (ec-guarded-do-ec
+ stop
+ (nested qualifier)
+ (begin (set! result expression)
+ (set! stop #t) ))
+ result ))))
+
+; (ec-guarded-do-ec stop (nested q ...) cmd)
+; constructs (do-ec q ... cmd) where the generators gen in q ... are
+; replaced by (\:until gen stop).
+
+(define-syntax ec-guarded-do-ec
+ (syntax-rules (nested if not and or begin)
+
+ ((ec-guarded-do-ec stop (nested (nested q1 ...) q2 ...) cmd)
+ (ec-guarded-do-ec stop (nested q1 ... q2 ...) cmd) )
+
+ ((ec-guarded-do-ec stop (nested (if test) q ...) cmd)
+ (if test (ec-guarded-do-ec stop (nested q ...) cmd)) )
+ ((ec-guarded-do-ec stop (nested (not test) q ...) cmd)
+ (if (not test) (ec-guarded-do-ec stop (nested q ...) cmd)) )
+ ((ec-guarded-do-ec stop (nested (and test ...) q ...) cmd)
+ (if (and test ...) (ec-guarded-do-ec stop (nested q ...) cmd)) )
+ ((ec-guarded-do-ec stop (nested (or test ...) q ...) cmd)
+ (if (or test ...) (ec-guarded-do-ec stop (nested q ...) cmd)) )
+
+ ((ec-guarded-do-ec stop (nested (begin etc ...) q ...) cmd)
+ (begin etc ... (ec-guarded-do-ec stop (nested q ...) cmd)) )
+
+ ((ec-guarded-do-ec stop (nested gen q ...) cmd)
+ (do-ec
+ (\:until gen stop)
+ (ec-guarded-do-ec stop (nested q ...) cmd) ))
+
+ ((ec-guarded-do-ec stop (nested) cmd)
+ (do-ec cmd) )))
+
+; Alternative: Instead of modifying the generator with :until, it is
+; possible to use call-with-current-continuation:
+;
+; (define-synatx first-ec
+; ...same as above...
+; ((first-ec default qualifier expression)
+; (call-with-current-continuation
+; (lambda (cc)
+; (do-ec qualifier (cc expression))
+; default ))) ))
+;
+; This is much simpler but not necessarily as efficient.
+
+
+; ==========================================================================
+; The early-stopping comprehensions any?-ec every?-ec
+; ==========================================================================
+
+(define-syntax any?-ec
+ (syntax-rules (nested)
+ ((any?-ec (nested q1 ...) q etc1 etc ...)
+ (any?-ec (nested q1 ... q) etc1 etc ...) )
+ ((any?-ec q1 q2 etc1 etc ...)
+ (any?-ec (nested q1 q2) etc1 etc ...) )
+ ((any?-ec expression)
+ (any?-ec (nested) expression) )
+
+ ((any?-ec qualifier expression)
+ (first-ec #f qualifier (if expression) #t) )))
+
+(define-syntax every?-ec
+ (syntax-rules (nested)
+ ((every?-ec (nested q1 ...) q etc1 etc ...)
+ (every?-ec (nested q1 ... q) etc1 etc ...) )
+ ((every?-ec q1 q2 etc1 etc ...)
+ (every?-ec (nested q1 q2) etc1 etc ...) )
+ ((every?-ec expression)
+ (every?-ec (nested) expression) )
+
+ ((every?-ec qualifier expression)
+ (first-ec #t qualifier (if (not expression)) #f) )))
+
+;;; srfi-43.scm -- SRFI 43 Vector library
+
+;; Copyright (C) 2014 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Mark H Weaver <mhw@netris.org>
+
+(define-module (srfi srfi-43)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-8)
+ #\re-export (make-vector vector vector? vector-ref vector-set!
+ vector-length)
+ #\replace (vector-copy vector-fill! list->vector vector->list)
+ #\export (vector-empty? vector= vector-unfold vector-unfold-right
+ vector-reverse-copy
+ vector-append vector-concatenate
+ vector-fold vector-fold-right
+ vector-map vector-map!
+ vector-for-each vector-count
+ vector-index vector-index-right
+ vector-skip vector-skip-right
+ vector-binary-search
+ vector-any vector-every
+ vector-swap! vector-reverse!
+ vector-copy! vector-reverse-copy!
+ reverse-vector->list
+ reverse-list->vector))
+
+(cond-expand-provide (current-module) '(srfi-43))
+
+(define (error-from who msg . args)
+ (apply error
+ (string-append (symbol->string who) ": " msg)
+ args))
+
+(define-syntax-rule (assert-nonneg-exact-integer k who)
+ (unless (and (exact-integer? k)
+ (not (negative? k)))
+ (error-from who "expected non-negative exact integer, got" k)))
+
+(define-syntax-rule (assert-procedure f who)
+ (unless (procedure? f)
+ (error-from who "expected procedure, got" f)))
+
+(define-syntax-rule (assert-vector v who)
+ (unless (vector? v)
+ (error-from who "expected vector, got" v)))
+
+(define-syntax-rule (assert-valid-index i len who)
+ (unless (and (exact-integer? i)
+ (<= 0 i len))
+ (error-from who "invalid index" i)))
+
+(define-syntax-rule (assert-valid-start start len who)
+ (unless (and (exact-integer? start)
+ (<= 0 start len))
+ (error-from who "invalid start index" start)))
+
+(define-syntax-rule (assert-valid-range start end len who)
+ (unless (and (exact-integer? start)
+ (exact-integer? end)
+ (<= 0 start end len))
+ (error-from who "invalid index range" start end)))
+
+(define-syntax-rule (assert-vectors vs who)
+ (let loop ((vs vs))
+ (unless (null? vs)
+ (assert-vector (car vs) who)
+ (loop (cdr vs)))))
+
+;; Return the length of the shortest vector in VS.
+;; VS must have at least one element.
+(define (min-length vs)
+ (let loop ((vs (cdr vs))
+ (result (vector-length (car vs))))
+ (if (null? vs)
+ result
+ (loop (cdr vs) (min result (vector-length (car vs)))))))
+
+;; Return a list of the Ith elements of the vectors in VS.
+(define (vectors-ref vs i)
+ (let loop ((vs vs) (xs '()))
+ (if (null? vs)
+ (reverse! xs)
+ (loop (cdr vs) (cons (vector-ref (car vs) i)
+ xs)))))
+
+(define vector-unfold
+ (case-lambda
+ "(vector-unfold f length initial-seed ...) -> vector
+
+The fundamental vector constructor. Create a vector whose length is
+LENGTH and iterates across each index k from 0 up to LENGTH - 1,
+applying F at each iteration to the current index and current seeds, in
+that order, to receive n + 1 values: the element to put in the kth slot
+of the new vector, and n new seeds for the next iteration. It is an
+error for the number of seeds to vary between iterations."
+ ((f len)
+ (assert-procedure f 'vector-unfold)
+ (assert-nonneg-exact-integer len 'vector-unfold)
+ (let ((v (make-vector len)))
+ (let loop ((i 0))
+ (unless (= i len)
+ (vector-set! v i (f i))
+ (loop (+ i 1))))
+ v))
+ ((f len seed)
+ (assert-procedure f 'vector-unfold)
+ (assert-nonneg-exact-integer len 'vector-unfold)
+ (let ((v (make-vector len)))
+ (let loop ((i 0) (seed seed))
+ (unless (= i len)
+ (receive (x seed) (f i seed)
+ (vector-set! v i x)
+ (loop (+ i 1) seed))))
+ v))
+ ((f len seed1 seed2)
+ (assert-procedure f 'vector-unfold)
+ (assert-nonneg-exact-integer len 'vector-unfold)
+ (let ((v (make-vector len)))
+ (let loop ((i 0) (seed1 seed1) (seed2 seed2))
+ (unless (= i len)
+ (receive (x seed1 seed2) (f i seed1 seed2)
+ (vector-set! v i x)
+ (loop (+ i 1) seed1 seed2))))
+ v))
+ ((f len . seeds)
+ (assert-procedure f 'vector-unfold)
+ (assert-nonneg-exact-integer len 'vector-unfold)
+ (let ((v (make-vector len)))
+ (let loop ((i 0) (seeds seeds))
+ (unless (= i len)
+ (receive (x . seeds) (apply f i seeds)
+ (vector-set! v i x)
+ (loop (+ i 1) seeds))))
+ v))))
+
+(define vector-unfold-right
+ (case-lambda
+ "(vector-unfold-right f length initial-seed ...) -> vector
+
+The fundamental vector constructor. Create a vector whose length is
+LENGTH and iterates across each index k from LENGTH - 1 down to 0,
+applying F at each iteration to the current index and current seeds, in
+that order, to receive n + 1 values: the element to put in the kth slot
+of the new vector, and n new seeds for the next iteration. It is an
+error for the number of seeds to vary between iterations."
+ ((f len)
+ (assert-procedure f 'vector-unfold-right)
+ (assert-nonneg-exact-integer len 'vector-unfold-right)
+ (let ((v (make-vector len)))
+ (let loop ((i (- len 1)))
+ (unless (negative? i)
+ (vector-set! v i (f i))
+ (loop (- i 1))))
+ v))
+ ((f len seed)
+ (assert-procedure f 'vector-unfold-right)
+ (assert-nonneg-exact-integer len 'vector-unfold-right)
+ (let ((v (make-vector len)))
+ (let loop ((i (- len 1)) (seed seed))
+ (unless (negative? i)
+ (receive (x seed) (f i seed)
+ (vector-set! v i x)
+ (loop (- i 1) seed))))
+ v))
+ ((f len seed1 seed2)
+ (assert-procedure f 'vector-unfold-right)
+ (assert-nonneg-exact-integer len 'vector-unfold-right)
+ (let ((v (make-vector len)))
+ (let loop ((i (- len 1)) (seed1 seed1) (seed2 seed2))
+ (unless (negative? i)
+ (receive (x seed1 seed2) (f i seed1 seed2)
+ (vector-set! v i x)
+ (loop (- i 1) seed1 seed2))))
+ v))
+ ((f len . seeds)
+ (assert-procedure f 'vector-unfold-right)
+ (assert-nonneg-exact-integer len 'vector-unfold-right)
+ (let ((v (make-vector len)))
+ (let loop ((i (- len 1)) (seeds seeds))
+ (unless (negative? i)
+ (receive (x . seeds) (apply f i seeds)
+ (vector-set! v i x)
+ (loop (- i 1) seeds))))
+ v))))
+
+(define guile-vector-copy (@ (guile) vector-copy))
+
+;; TODO: Enhance Guile core 'vector-copy' to do this.
+(define vector-copy
+ (case-lambda*
+ "(vector-copy vec [start [end [fill]]]) -> vector
+
+Allocate a new vector whose length is END - START and fills it with
+elements from vec, taking elements from vec starting at index START
+and stopping at index END. START defaults to 0 and END defaults to
+the value of (vector-length VEC). If END extends beyond the length of
+VEC, the slots in the new vector that obviously cannot be filled by
+elements from VEC are filled with FILL, whose default value is
+unspecified."
+ ((v) (guile-vector-copy v))
+ ((v start)
+ (assert-vector v 'vector-copy)
+ (let ((len (vector-length v)))
+ (assert-valid-start start len 'vector-copy)
+ (let ((result (make-vector (- len start))))
+ (vector-move-left! v start len result 0)
+ result)))
+ ((v start end #\optional (fill *unspecified*))
+ (assert-vector v 'vector-copy)
+ (let ((len (vector-length v)))
+ (unless (and (exact-integer? start)
+ (exact-integer? end)
+ (<= 0 start end))
+ (error-from 'vector-copy "invalid index range" start end))
+ (let ((result (make-vector (- end start) fill)))
+ (vector-move-left! v start (min end len) result 0)
+ result)))))
+
+(define vector-reverse-copy
+ (let ()
+ (define (%vector-reverse-copy vec start end)
+ (let* ((len (- end start))
+ (result (make-vector len)))
+ (let loop ((i 0) (j (- end 1)))
+ (unless (= i len)
+ (vector-set! result i (vector-ref vec j))
+ (loop (+ i 1) (- j 1))))
+ result))
+ (case-lambda
+ "(vector-reverse-copy vec [start [end]]) -> vector
+
+Allocate a new vector whose length is END - START and fills it with
+elements from vec, taking elements from vec in reverse order starting
+at index START and stopping at index END. START defaults to 0 and END
+defaults to the value of (vector-length VEC)."
+ ((vec)
+ (assert-vector vec 'vector-reverse-copy)
+ (%vector-reverse-copy vec 0 (vector-length vec)))
+ ((vec start)
+ (assert-vector vec 'vector-reverse-copy)
+ (let ((len (vector-length vec)))
+ (assert-valid-start start len 'vector-reverse-copy)
+ (%vector-reverse-copy vec start len)))
+ ((vec start end)
+ (assert-vector vec 'vector-reverse-copy)
+ (let ((len (vector-length vec)))
+ (assert-valid-range start end len 'vector-reverse-copy)
+ (%vector-reverse-copy vec start end))))))
+
+(define (%vector-concatenate vs)
+ (let* ((result-len (let loop ((vs vs) (len 0))
+ (if (null? vs)
+ len
+ (loop (cdr vs) (+ len (vector-length (car vs)))))))
+ (result (make-vector result-len)))
+ (let loop ((vs vs) (pos 0))
+ (unless (null? vs)
+ (let* ((v (car vs))
+ (len (vector-length v)))
+ (vector-move-left! v 0 len result pos)
+ (loop (cdr vs) (+ pos len)))))
+ result))
+
+(define vector-append
+ (case-lambda
+ "(vector-append vec ...) -> vector
+
+Return a newly allocated vector that contains all elements in order
+from the subsequent locations in VEC ..."
+ (() (vector))
+ ((v)
+ (assert-vector v 'vector-append)
+ (guile-vector-copy v))
+ ((v1 v2)
+ (assert-vector v1 'vector-append)
+ (assert-vector v2 'vector-append)
+ (let ((len1 (vector-length v1))
+ (len2 (vector-length v2)))
+ (let ((result (make-vector (+ len1 len2))))
+ (vector-move-left! v1 0 len1 result 0)
+ (vector-move-left! v2 0 len2 result len1)
+ result)))
+ (vs
+ (assert-vectors vs 'vector-append)
+ (%vector-concatenate vs))))
+
+(define (vector-concatenate vs)
+ "(vector-concatenate list-of-vectors) -> vector
+
+Append each vector in LIST-OF-VECTORS. Equivalent to:
+ (apply vector-append LIST-OF-VECTORS)"
+ (assert-vectors vs 'vector-concatenate)
+ (%vector-concatenate vs))
+
+(define (vector-empty? vec)
+ "(vector-empty? vec) -> boolean
+
+Return true if VEC is empty, i.e. its length is 0, and false if not."
+ (assert-vector vec 'vector-empty?)
+ (zero? (vector-length vec)))
+
+(define vector=
+ (let ()
+ (define (all-of-length? len vs)
+ (or (null? vs)
+ (and (= len (vector-length (car vs)))
+ (all-of-length? len (cdr vs)))))
+ (define (=up-to? i elt=? v1 v2)
+ (or (negative? i)
+ (let ((x1 (vector-ref v1 i))
+ (x2 (vector-ref v2 i)))
+ (and (or (eq? x1 x2) (elt=? x1 x2))
+ (=up-to? (- i 1) elt=? v1 v2)))))
+ (case-lambda
+ "(vector= elt=? vec ...) -> boolean
+
+Return true if the vectors VEC ... have equal lengths and equal
+elements according to ELT=?. ELT=? is always applied to two
+arguments. Element comparison must be consistent with eq?, in the
+following sense: if (eq? a b) returns true, then (elt=? a b) must also
+return true. The order in which comparisons are performed is
+unspecified."
+ ((elt=?)
+ (assert-procedure elt=? 'vector=)
+ #t)
+ ((elt=? v)
+ (assert-procedure elt=? 'vector=)
+ (assert-vector v 'vector=)
+ #t)
+ ((elt=? v1 v2)
+ (assert-procedure elt=? 'vector=)
+ (assert-vector v1 'vector=)
+ (assert-vector v2 'vector=)
+ (let ((len (vector-length v1)))
+ (and (= len (vector-length v2))
+ (=up-to? (- len 1) elt=? v1 v2))))
+ ((elt=? v1 . vs)
+ (assert-procedure elt=? 'vector=)
+ (assert-vector v1 'vector=)
+ (assert-vectors vs 'vector=)
+ (let ((len (vector-length v1)))
+ (and (all-of-length? len vs)
+ (let loop ((vs vs))
+ (or (null? vs)
+ (and (=up-to? (- len 1) elt=? v1 (car vs))
+ (loop (cdr vs)))))))))))
+
+(define vector-fold
+ (case-lambda
+ "(vector-fold kons knil vec1 vec2 ...) -> value
+
+The fundamental vector iterator. KONS is iterated over each index in
+all of the vectors, stopping at the end of the shortest; KONS is
+applied as (KONS i state (vector-ref VEC1 i) (vector-ref VEC2 i) ...)
+where STATE is the current state value, and I is the current index.
+The current state value begins with KNIL, and becomes whatever KONS
+returned at the respective iteration. The iteration is strictly
+left-to-right."
+ ((kcons knil v)
+ (assert-procedure kcons 'vector-fold)
+ (assert-vector v 'vector-fold)
+ (let ((len (vector-length v)))
+ (let loop ((i 0) (state knil))
+ (if (= i len)
+ state
+ (loop (+ i 1) (kcons i state (vector-ref v i)))))))
+ ((kcons knil v1 v2)
+ (assert-procedure kcons 'vector-fold)
+ (assert-vector v1 'vector-fold)
+ (assert-vector v2 'vector-fold)
+ (let ((len (min (vector-length v1) (vector-length v2))))
+ (let loop ((i 0) (state knil))
+ (if (= i len)
+ state
+ (loop (+ i 1)
+ (kcons i state (vector-ref v1 i) (vector-ref v2 i)))))))
+ ((kcons knil . vs)
+ (assert-procedure kcons 'vector-fold)
+ (assert-vectors vs 'vector-fold)
+ (let ((len (min-length vs)))
+ (let loop ((i 0) (state knil))
+ (if (= i len)
+ state
+ (loop (+ i 1) (apply kcons i state (vectors-ref vs i)))))))))
+
+(define vector-fold-right
+ (case-lambda
+ "(vector-fold-right kons knil vec1 vec2 ...) -> value
+
+The fundamental vector iterator. KONS is iterated over each index in
+all of the vectors, starting at the end of the shortest; KONS is
+applied as (KONS i state (vector-ref VEC1 i) (vector-ref VEC2 i) ...)
+where STATE is the current state value, and I is the current index.
+The current state value begins with KNIL, and becomes whatever KONS
+returned at the respective iteration. The iteration is strictly
+right-to-left."
+ ((kcons knil v)
+ (assert-procedure kcons 'vector-fold-right)
+ (assert-vector v 'vector-fold-right)
+ (let ((len (vector-length v)))
+ (let loop ((i (- len 1)) (state knil))
+ (if (negative? i)
+ state
+ (loop (- i 1) (kcons i state (vector-ref v i)))))))
+ ((kcons knil v1 v2)
+ (assert-procedure kcons 'vector-fold-right)
+ (assert-vector v1 'vector-fold-right)
+ (assert-vector v2 'vector-fold-right)
+ (let ((len (min (vector-length v1) (vector-length v2))))
+ (let loop ((i (- len 1)) (state knil))
+ (if (negative? i)
+ state
+ (loop (- i 1)
+ (kcons i state (vector-ref v1 i) (vector-ref v2 i)))))))
+ ((kcons knil . vs)
+ (assert-procedure kcons 'vector-fold-right)
+ (assert-vectors vs 'vector-fold-right)
+ (let ((len (min-length vs)))
+ (let loop ((i (- len 1)) (state knil))
+ (if (negative? i)
+ state
+ (loop (- i 1) (apply kcons i state (vectors-ref vs i)))))))))
+
+(define vector-map
+ (case-lambda
+ "(vector-map f vec2 vec2 ...) -> vector
+
+Return a new vector of the shortest size of the vector arguments.
+Each element at index i of the new vector is mapped from the old
+vectors by (F i (vector-ref VEC1 i) (vector-ref VEC2 i) ...). The
+dynamic order of application of F is unspecified."
+ ((f v)
+ (assert-procedure f 'vector-map)
+ (assert-vector v 'vector-map)
+ (let* ((len (vector-length v))
+ (result (make-vector len)))
+ (let loop ((i 0))
+ (unless (= i len)
+ (vector-set! result i (f i (vector-ref v i)))
+ (loop (+ i 1))))
+ result))
+ ((f v1 v2)
+ (assert-procedure f 'vector-map)
+ (assert-vector v1 'vector-map)
+ (assert-vector v2 'vector-map)
+ (let* ((len (min (vector-length v1) (vector-length v2)))
+ (result (make-vector len)))
+ (let loop ((i 0))
+ (unless (= i len)
+ (vector-set! result i (f i (vector-ref v1 i) (vector-ref v2 i)))
+ (loop (+ i 1))))
+ result))
+ ((f . vs)
+ (assert-procedure f 'vector-map)
+ (assert-vectors vs 'vector-map)
+ (let* ((len (min-length vs))
+ (result (make-vector len)))
+ (let loop ((i 0))
+ (unless (= i len)
+ (vector-set! result i (apply f i (vectors-ref vs i)))
+ (loop (+ i 1))))
+ result))))
+
+(define vector-map!
+ (case-lambda
+ "(vector-map! f vec2 vec2 ...) -> unspecified
+
+Similar to vector-map, but rather than mapping the new elements into a
+new vector, the new mapped elements are destructively inserted into
+VEC1. The dynamic order of application of F is unspecified."
+ ((f v)
+ (assert-procedure f 'vector-map!)
+ (assert-vector v 'vector-map!)
+ (let ((len (vector-length v)))
+ (let loop ((i 0))
+ (unless (= i len)
+ (vector-set! v i (f i (vector-ref v i)))
+ (loop (+ i 1))))))
+ ((f v1 v2)
+ (assert-procedure f 'vector-map!)
+ (assert-vector v1 'vector-map!)
+ (assert-vector v2 'vector-map!)
+ (let ((len (min (vector-length v1) (vector-length v2))))
+ (let loop ((i 0))
+ (unless (= i len)
+ (vector-set! v1 i (f i (vector-ref v1 i) (vector-ref v2 i)))
+ (loop (+ i 1))))))
+ ((f . vs)
+ (assert-procedure f 'vector-map!)
+ (assert-vectors vs 'vector-map!)
+ (let ((len (min-length vs))
+ (v1 (car vs)))
+ (let loop ((i 0))
+ (unless (= i len)
+ (vector-set! v1 i (apply f i (vectors-ref vs i)))
+ (loop (+ i 1))))))))
+
+(define vector-for-each
+ (case-lambda
+ "(vector-for-each f vec1 vec2 ...) -> unspecified
+
+Call (F i VEC1[i] VEC2[i] ...) for each index i less than the length
+of the shortest vector passed. The iteration is strictly
+left-to-right."
+ ((f v)
+ (assert-procedure f 'vector-for-each)
+ (assert-vector v 'vector-for-each)
+ (let ((len (vector-length v)))
+ (let loop ((i 0))
+ (unless (= i len)
+ (f i (vector-ref v i))
+ (loop (+ i 1))))))
+ ((f v1 v2)
+ (assert-procedure f 'vector-for-each)
+ (assert-vector v1 'vector-for-each)
+ (assert-vector v2 'vector-for-each)
+ (let ((len (min (vector-length v1)
+ (vector-length v2))))
+ (let loop ((i 0))
+ (unless (= i len)
+ (f i (vector-ref v1 i) (vector-ref v2 i))
+ (loop (+ i 1))))))
+ ((f . vs)
+ (assert-procedure f 'vector-for-each)
+ (assert-vectors vs 'vector-for-each)
+ (let ((len (min-length vs)))
+ (let loop ((i 0))
+ (unless (= i len)
+ (apply f i (vectors-ref vs i))
+ (loop (+ i 1))))))))
+
+(define vector-count
+ (case-lambda
+ "(vector-count pred? vec1 vec2 ...) -> exact nonnegative integer
+
+Count the number of indices i for which (PRED? VEC1[i] VEC2[i] ...)
+returns true, where i is less than the length of the shortest vector
+passed."
+ ((pred? v)
+ (assert-procedure pred? 'vector-count)
+ (assert-vector v 'vector-count)
+ (let ((len (vector-length v)))
+ (let loop ((i 0) (count 0))
+ (cond ((= i len) count)
+ ((pred? i (vector-ref v i))
+ (loop (+ i 1) (+ count 1)))
+ (else
+ (loop (+ i 1) count))))))
+ ((pred? v1 v2)
+ (assert-procedure pred? 'vector-count)
+ (assert-vector v1 'vector-count)
+ (assert-vector v2 'vector-count)
+ (let ((len (min (vector-length v1)
+ (vector-length v2))))
+ (let loop ((i 0) (count 0))
+ (cond ((= i len) count)
+ ((pred? i (vector-ref v1 i) (vector-ref v2 i))
+ (loop (+ i 1) (+ count 1)))
+ (else
+ (loop (+ i 1) count))))))
+ ((pred? . vs)
+ (assert-procedure pred? 'vector-count)
+ (assert-vectors vs 'vector-count)
+ (let ((len (min-length vs)))
+ (let loop ((i 0) (count 0))
+ (cond ((= i len) count)
+ ((apply pred? i (vectors-ref vs i))
+ (loop (+ i 1) (+ count 1)))
+ (else
+ (loop (+ i 1) count))))))))
+
+(define vector-index
+ (case-lambda
+ "(vector-index pred? vec1 vec2 ...) -> exact nonnegative integer or #f
+
+Find and return the index of the first elements in VEC1 VEC2 ... that
+satisfy PRED?. If no matching element is found by the end of the
+shortest vector, return #f."
+ ((pred? v)
+ (assert-procedure pred? 'vector-index)
+ (assert-vector v 'vector-index)
+ (let ((len (vector-length v)))
+ (let loop ((i 0))
+ (and (< i len)
+ (if (pred? (vector-ref v i))
+ i
+ (loop (+ i 1)))))))
+ ((pred? v1 v2)
+ (assert-procedure pred? 'vector-index)
+ (assert-vector v1 'vector-index)
+ (assert-vector v2 'vector-index)
+ (let ((len (min (vector-length v1)
+ (vector-length v2))))
+ (let loop ((i 0))
+ (and (< i len)
+ (if (pred? (vector-ref v1 i)
+ (vector-ref v2 i))
+ i
+ (loop (+ i 1)))))))
+ ((pred? . vs)
+ (assert-procedure pred? 'vector-index)
+ (assert-vectors vs 'vector-index)
+ (let ((len (min-length vs)))
+ (let loop ((i 0))
+ (and (< i len)
+ (if (apply pred? (vectors-ref vs i))
+ i
+ (loop (+ i 1)))))))))
+
+(define vector-index-right
+ (case-lambda
+ "(vector-index-right pred? vec1 vec2 ...) -> exact nonnegative integer or #f
+
+Find and return the index of the last elements in VEC1 VEC2 ... that
+satisfy PRED?, searching from right-to-left. If no matching element
+is found before the end of the shortest vector, return #f."
+ ((pred? v)
+ (assert-procedure pred? 'vector-index-right)
+ (assert-vector v 'vector-index-right)
+ (let ((len (vector-length v)))
+ (let loop ((i (- len 1)))
+ (and (>= i 0)
+ (if (pred? (vector-ref v i))
+ i
+ (loop (- i 1)))))))
+ ((pred? v1 v2)
+ (assert-procedure pred? 'vector-index-right)
+ (assert-vector v1 'vector-index-right)
+ (assert-vector v2 'vector-index-right)
+ (let ((len (min (vector-length v1)
+ (vector-length v2))))
+ (let loop ((i (- len 1)))
+ (and (>= i 0)
+ (if (pred? (vector-ref v1 i)
+ (vector-ref v2 i))
+ i
+ (loop (- i 1)))))))
+ ((pred? . vs)
+ (assert-procedure pred? 'vector-index-right)
+ (assert-vectors vs 'vector-index-right)
+ (let ((len (min-length vs)))
+ (let loop ((i (- len 1)))
+ (and (>= i 0)
+ (if (apply pred? (vectors-ref vs i))
+ i
+ (loop (- i 1)))))))))
+
+(define vector-skip
+ (case-lambda
+ "(vector-skip pred? vec1 vec2 ...) -> exact nonnegative integer or #f
+
+Find and return the index of the first elements in VEC1 VEC2 ... that
+do not satisfy PRED?. If no matching element is found by the end of
+the shortest vector, return #f."
+ ((pred? v)
+ (assert-procedure pred? 'vector-skip)
+ (assert-vector v 'vector-skip)
+ (let ((len (vector-length v)))
+ (let loop ((i 0))
+ (and (< i len)
+ (if (pred? (vector-ref v i))
+ (loop (+ i 1))
+ i)))))
+ ((pred? v1 v2)
+ (assert-procedure pred? 'vector-skip)
+ (assert-vector v1 'vector-skip)
+ (assert-vector v2 'vector-skip)
+ (let ((len (min (vector-length v1)
+ (vector-length v2))))
+ (let loop ((i 0))
+ (and (< i len)
+ (if (pred? (vector-ref v1 i)
+ (vector-ref v2 i))
+ (loop (+ i 1))
+ i)))))
+ ((pred? . vs)
+ (assert-procedure pred? 'vector-skip)
+ (assert-vectors vs 'vector-skip)
+ (let ((len (min-length vs)))
+ (let loop ((i 0))
+ (and (< i len)
+ (if (apply pred? (vectors-ref vs i))
+ (loop (+ i 1))
+ i)))))))
+
+(define vector-skip-right
+ (case-lambda
+ "(vector-skip-right pred? vec1 vec2 ...) -> exact nonnegative integer or #f
+
+Find and return the index of the last elements in VEC1 VEC2 ... that
+do not satisfy PRED?, searching from right-to-left. If no matching
+element is found before the end of the shortest vector, return #f."
+ ((pred? v)
+ (assert-procedure pred? 'vector-skip-right)
+ (assert-vector v 'vector-skip-right)
+ (let ((len (vector-length v)))
+ (let loop ((i (- len 1)))
+ (and (not (negative? i))
+ (if (pred? (vector-ref v i))
+ (loop (- i 1))
+ i)))))
+ ((pred? v1 v2)
+ (assert-procedure pred? 'vector-skip-right)
+ (assert-vector v1 'vector-skip-right)
+ (assert-vector v2 'vector-skip-right)
+ (let ((len (min (vector-length v1)
+ (vector-length v2))))
+ (let loop ((i (- len 1)))
+ (and (not (negative? i))
+ (if (pred? (vector-ref v1 i)
+ (vector-ref v2 i))
+ (loop (- i 1))
+ i)))))
+ ((pred? . vs)
+ (assert-procedure pred? 'vector-skip-right)
+ (assert-vectors vs 'vector-skip-right)
+ (let ((len (min-length vs)))
+ (let loop ((i (- len 1)))
+ (and (not (negative? i))
+ (if (apply pred? (vectors-ref vs i))
+ (loop (- i 1))
+ i)))))))
+
+(define vector-binary-search
+ (let ()
+ (define (%vector-binary-search vec value cmp start end)
+ (let loop ((lo start) (hi end))
+ (and (< lo hi)
+ (let* ((i (quotient (+ lo hi) 2))
+ (x (vector-ref vec i))
+ (c (cmp x value)))
+ (cond ((zero? c) i)
+ ((positive? c) (loop lo i))
+ ((negative? c) (loop (+ i 1) hi)))))))
+ (case-lambda
+ "(vector-binary-search vec value cmp [start [end]]) -> exact nonnegative integer or #f
+
+Find and return an index of VEC between START and END whose value is
+VALUE using a binary search. If no matching element is found, return
+#f. The default START is 0 and the default END is the length of VEC.
+CMP must be a procedure of two arguments such that (CMP A B) returns
+a negative integer if A < B, a positive integer if A > B, or zero if
+A = B. The elements of VEC must be sorted in non-decreasing order
+according to CMP."
+ ((vec value cmp)
+ (assert-vector vec 'vector-binary-search)
+ (assert-procedure cmp 'vector-binary-search)
+ (%vector-binary-search vec value cmp 0 (vector-length vec)))
+
+ ((vec value cmp start)
+ (assert-vector vec 'vector-binary-search)
+ (let ((len (vector-length vec)))
+ (assert-valid-start start len 'vector-binary-search)
+ (%vector-binary-search vec value cmp start len)))
+
+ ((vec value cmp start end)
+ (assert-vector vec 'vector-binary-search)
+ (let ((len (vector-length vec)))
+ (assert-valid-range start end len 'vector-binary-search)
+ (%vector-binary-search vec value cmp start end))))))
+
+(define vector-any
+ (case-lambda
+ "(vector-any pred? vec1 vec2 ...) -> value or #f
+
+Find the first parallel set of elements from VEC1 VEC2 ... for which
+PRED? returns a true value. If such a parallel set of elements
+exists, vector-any returns the value that PRED? returned for that set
+of elements. The iteration is strictly left-to-right."
+ ((pred? v)
+ (assert-procedure pred? 'vector-any)
+ (assert-vector v 'vector-any)
+ (let ((len (vector-length v)))
+ (let loop ((i 0))
+ (and (< i len)
+ (or (pred? (vector-ref v i))
+ (loop (+ i 1)))))))
+ ((pred? v1 v2)
+ (assert-procedure pred? 'vector-any)
+ (assert-vector v1 'vector-any)
+ (assert-vector v2 'vector-any)
+ (let ((len (min (vector-length v1)
+ (vector-length v2))))
+ (let loop ((i 0))
+ (and (< i len)
+ (or (pred? (vector-ref v1 i)
+ (vector-ref v2 i))
+ (loop (+ i 1)))))))
+ ((pred? . vs)
+ (assert-procedure pred? 'vector-any)
+ (assert-vectors vs 'vector-any)
+ (let ((len (min-length vs)))
+ (let loop ((i 0))
+ (and (< i len)
+ (or (apply pred? (vectors-ref vs i))
+ (loop (+ i 1)))))))))
+
+(define vector-every
+ (case-lambda
+ "(vector-every pred? vec1 vec2 ...) -> value or #f
+
+If, for every index i less than the length of the shortest vector
+argument, the set of elements VEC1[i] VEC2[i] ... satisfies PRED?,
+vector-every returns the value that PRED? returned for the last set of
+elements, at the last index of the shortest vector. The iteration is
+strictly left-to-right."
+ ((pred? v)
+ (assert-procedure pred? 'vector-every)
+ (assert-vector v 'vector-every)
+ (let ((len (vector-length v)))
+ (or (zero? len)
+ (let loop ((i 0))
+ (let ((val (pred? (vector-ref v i)))
+ (next-i (+ i 1)))
+ (if (or (not val) (= next-i len))
+ val
+ (loop next-i)))))))
+ ((pred? v1 v2)
+ (assert-procedure pred? 'vector-every)
+ (assert-vector v1 'vector-every)
+ (assert-vector v2 'vector-every)
+ (let ((len (min (vector-length v1)
+ (vector-length v2))))
+ (or (zero? len)
+ (let loop ((i 0))
+ (let ((val (pred? (vector-ref v1 i)
+ (vector-ref v2 i)))
+ (next-i (+ i 1)))
+ (if (or (not val) (= next-i len))
+ val
+ (loop next-i)))))))
+ ((pred? . vs)
+ (assert-procedure pred? 'vector-every)
+ (assert-vectors vs 'vector-every)
+ (let ((len (min-length vs)))
+ (or (zero? len)
+ (let loop ((i 0))
+ (let ((val (apply pred? (vectors-ref vs i)))
+ (next-i (+ i 1)))
+ (if (or (not val) (= next-i len))
+ val
+ (loop next-i)))))))))
+
+(define (vector-swap! vec i j)
+ "(vector-swap! vec i j) -> unspecified
+
+Swap the values of the locations in VEC at I and J."
+ (assert-vector vec 'vector-swap!)
+ (let ((len (vector-length vec)))
+ (assert-valid-index i len 'vector-swap!)
+ (assert-valid-index j len 'vector-swap!)
+ (let ((tmp (vector-ref vec i)))
+ (vector-set! vec i (vector-ref vec j))
+ (vector-set! vec j tmp))))
+
+;; TODO: Enhance Guile core 'vector-fill!' to do this.
+(define vector-fill!
+ (let ()
+ (define guile-vector-fill!
+ (@ (guile) vector-fill!))
+ (define (%vector-fill! vec fill start end)
+ (let loop ((i start))
+ (when (< i end)
+ (vector-set! vec i fill)
+ (loop (+ i 1)))))
+ (case-lambda
+ "(vector-fill! vec fill [start [end]]) -> unspecified
+
+Assign the value of every location in VEC between START and END to
+FILL. START defaults to 0 and END defaults to the length of VEC."
+ ((vec fill)
+ (guile-vector-fill! vec fill))
+ ((vec fill start)
+ (assert-vector vec 'vector-fill!)
+ (let ((len (vector-length vec)))
+ (assert-valid-start start len 'vector-fill!)
+ (%vector-fill! vec fill start len)))
+ ((vec fill start end)
+ (assert-vector vec 'vector-fill!)
+ (let ((len (vector-length vec)))
+ (assert-valid-range start end len 'vector-fill!)
+ (%vector-fill! vec fill start end))))))
+
+(define (%vector-reverse! vec start end)
+ (let loop ((i start) (j (- end 1)))
+ (when (< i j)
+ (let ((tmp (vector-ref vec i)))
+ (vector-set! vec i (vector-ref vec j))
+ (vector-set! vec j tmp)
+ (loop (+ i 1) (- j 1))))))
+
+(define vector-reverse!
+ (case-lambda
+ "(vector-reverse! vec [start [end]]) -> unspecified
+
+Destructively reverse the contents of VEC between START and END.
+START defaults to 0 and END defaults to the length of VEC."
+ ((vec)
+ (assert-vector vec 'vector-reverse!)
+ (%vector-reverse! vec 0 (vector-length vec)))
+ ((vec start)
+ (assert-vector vec 'vector-reverse!)
+ (let ((len (vector-length vec)))
+ (assert-valid-start start len 'vector-reverse!)
+ (%vector-reverse! vec start len)))
+ ((vec start end)
+ (assert-vector vec 'vector-reverse!)
+ (let ((len (vector-length vec)))
+ (assert-valid-range start end len 'vector-reverse!)
+ (%vector-reverse! vec start end)))))
+
+(define-syntax-rule (define-vector-copier! copy! docstring inner-proc)
+ (define copy!
+ (let ((%copy! inner-proc))
+ (case-lambda
+ docstring
+ ((target tstart source)
+ (assert-vector target 'copy!)
+ (assert-vector source 'copy!)
+ (let ((tlen (vector-length target))
+ (slen (vector-length source)))
+ (assert-valid-start tstart tlen 'copy!)
+ (unless (>= tlen (+ tstart slen))
+ (error-from 'copy! "would write past end of target"))
+ (%copy! target tstart source 0 slen)))
+
+ ((target tstart source sstart)
+ (assert-vector target 'copy!)
+ (assert-vector source 'copy!)
+ (let ((tlen (vector-length target))
+ (slen (vector-length source)))
+ (assert-valid-start tstart tlen 'copy!)
+ (assert-valid-start sstart slen 'copy!)
+ (unless (>= tlen (+ tstart (- slen sstart)))
+ (error-from 'copy! "would write past end of target"))
+ (%copy! target tstart source sstart slen)))
+
+ ((target tstart source sstart send)
+ (assert-vector target 'copy!)
+ (assert-vector source 'copy!)
+ (let ((tlen (vector-length target))
+ (slen (vector-length source)))
+ (assert-valid-start tstart tlen 'copy!)
+ (assert-valid-range sstart send slen 'copy!)
+ (unless (>= tlen (+ tstart (- send sstart)))
+ (error-from 'copy! "would write past end of target"))
+ (%copy! target tstart source sstart send)))))))
+
+(define-vector-copier! vector-copy!
+ "(vector-copy! target tstart source [sstart [send]]) -> unspecified
+
+Copy a block of elements from SOURCE to TARGET, both of which must be
+vectors, starting in TARGET at TSTART and starting in SOURCE at
+SSTART, ending when SEND - SSTART elements have been copied. It is an
+error for TARGET to have a length less than TSTART + (SEND - SSTART).
+SSTART defaults to 0 and SEND defaults to the length of SOURCE."
+ (lambda (target tstart source sstart send)
+ (if (< tstart sstart)
+ (vector-move-left! source sstart send target tstart)
+ (vector-move-right! source sstart send target tstart))))
+
+(define-vector-copier! vector-reverse-copy!
+ "(vector-reverse-copy! target tstart source [sstart [send]]) -> unspecified
+
+Like vector-copy!, but copy the elements in the reverse order. It is
+an error if TARGET and SOURCE are identical vectors and the TARGET and
+SOURCE ranges overlap; however, if TSTART = SSTART,
+vector-reverse-copy! behaves as (vector-reverse! TARGET TSTART SEND)
+would."
+ (lambda (target tstart source sstart send)
+ (if (and (eq? target source) (= tstart sstart))
+ (%vector-reverse! target sstart send)
+ (let loop ((i tstart) (j (- send 1)))
+ (when (>= j sstart)
+ (vector-set! target i (vector-ref source j))
+ (loop (+ i 1) (- j 1)))))))
+
+(define vector->list
+ (let ()
+ (define (%vector->list vec start end)
+ (let loop ((i (- end 1))
+ (result '()))
+ (if (< i start)
+ result
+ (loop (- i 1) (cons (vector-ref vec i) result)))))
+ (case-lambda
+ "(vector->list vec [start [end]]) -> proper-list
+
+Return a newly allocated list containing the elements in VEC between
+START and END. START defaults to 0 and END defaults to the length of
+VEC."
+ ((vec)
+ (assert-vector vec 'vector->list)
+ (%vector->list vec 0 (vector-length vec)))
+ ((vec start)
+ (assert-vector vec 'vector->list)
+ (let ((len (vector-length vec)))
+ (assert-valid-start start len 'vector->list)
+ (%vector->list vec start len)))
+ ((vec start end)
+ (assert-vector vec 'vector->list)
+ (let ((len (vector-length vec)))
+ (assert-valid-range start end len 'vector->list)
+ (%vector->list vec start end))))))
+
+(define reverse-vector->list
+ (let ()
+ (define (%reverse-vector->list vec start end)
+ (let loop ((i start)
+ (result '()))
+ (if (>= i end)
+ result
+ (loop (+ i 1) (cons (vector-ref vec i) result)))))
+ (case-lambda
+ "(reverse-vector->list vec [start [end]]) -> proper-list
+
+Return a newly allocated list containing the elements in VEC between
+START and END in reverse order. START defaults to 0 and END defaults
+to the length of VEC."
+ ((vec)
+ (assert-vector vec 'reverse-vector->list)
+ (%reverse-vector->list vec 0 (vector-length vec)))
+ ((vec start)
+ (assert-vector vec 'reverse-vector->list)
+ (let ((len (vector-length vec)))
+ (assert-valid-start start len 'reverse-vector->list)
+ (%reverse-vector->list vec start len)))
+ ((vec start end)
+ (assert-vector vec 'reverse-vector->list)
+ (let ((len (vector-length vec)))
+ (assert-valid-range start end len 'reverse-vector->list)
+ (%reverse-vector->list vec start end))))))
+
+;; TODO: change to use 'case-lambda' and improve error checking.
+(define* (list->vector lst #\optional (start 0) (end (length lst)))
+ "(list->vector proper-list [start [end]]) -> vector
+
+Return a newly allocated vector of the elements from PROPER-LIST with
+indices between START and END. START defaults to 0 and END defaults
+to the length of PROPER-LIST."
+ (let* ((len (- end start))
+ (result (make-vector len)))
+ (let loop ((i 0) (lst (drop lst start)))
+ (if (= i len)
+ result
+ (begin (vector-set! result i (car lst))
+ (loop (+ i 1) (cdr lst)))))))
+
+;; TODO: change to use 'case-lambda' and improve error checking.
+(define* (reverse-list->vector lst #\optional (start 0) (end (length lst)))
+ "(reverse-list->vector proper-list [start [end]]) -> vector
+
+Return a newly allocated vector of the elements from PROPER-LIST with
+indices between START and END, in reverse order. START defaults to 0
+and END defaults to the length of PROPER-LIST."
+ (let* ((len (- end start))
+ (result (make-vector len)))
+ (let loop ((i (- len 1)) (lst (drop lst start)))
+ (if (negative? i)
+ result
+ (begin (vector-set! result i (car lst))
+ (loop (- i 1) (cdr lst)))))))
+;;; srfi-45.scm -- Primitives for Expressing Iterative Lazy Algorithms
+
+;; Copyright (C) 2010, 2011, 2013 Free Software Foundation, Inc.
+;; Copyright (C) 2003 André van Tonder. All Rights Reserved.
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; This is the code of the reference implementation of SRFI-45, modified
+;; to use SRFI-9 and to add 'promise?' to the list of exports.
+
+;; This module is documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-45)
+ #\export (delay
+ lazy
+ force
+ eager
+ promise?)
+ #\replace (delay force promise?)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-9 gnu))
+
+(cond-expand-provide (current-module) '(srfi-45))
+
+(define-record-type promise (make-promise val) promise?
+ (val promise-val promise-val-set!))
+
+(define-record-type value (make-value tag proc) value?
+ (tag value-tag value-tag-set!)
+ (proc value-proc value-proc-set!))
+
+(define-syntax-rule (lazy exp)
+ (make-promise (make-value 'lazy (lambda () exp))))
+
+(define (eager x)
+ (make-promise (make-value 'eager x)))
+
+(define-syntax-rule (delay exp)
+ (lazy (eager exp)))
+
+(define (force promise)
+ (let ((content (promise-val promise)))
+ (case (value-tag content)
+ ((eager) (value-proc content))
+ ((lazy) (let* ((promise* ((value-proc content)))
+ (content (promise-val promise))) ; *
+ (if (not (eqv? (value-tag content) 'eager)) ; *
+ (begin (value-tag-set! content
+ (value-tag (promise-val promise*)))
+ (value-proc-set! content
+ (value-proc (promise-val promise*)))
+ (promise-val-set! promise* content)))
+ (force promise))))))
+
+;; (*) These two lines re-fetch and check the original promise in case
+;; the first line of the let* caused it to be forced. For an example
+;; where this happens, see reentrancy test 3 below.
+
+(define* (promise-visit promise #\key on-eager on-lazy)
+ (define content (promise-val promise))
+ (case (value-tag content)
+ ((eager) (on-eager (value-proc content)))
+ ((lazy) (on-lazy (value-proc content)))))
+
+(set-record-type-printer! promise
+ (lambda (promise port)
+ (promise-visit promise
+ #\on-eager (lambda (value)
+ (format port "#<promise = ~s>" value))
+ #\on-lazy (lambda (proc)
+ (format port "#<promise => ~s>" proc)))))
+;;; srfi-6.scm --- Basic String Ports
+
+;; Copyright (C) 2001, 2002, 2003, 2006, 2012 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This module is fully documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-6)
+ #\replace (open-input-string open-output-string)
+ #\re-export (get-output-string))
+
+;; SRFI-6 says nothing about encodings, and assumes that any character
+;; or string can be written to a string port. Thus, make all SRFI-6
+;; string ports Unicode capable. See <http://bugs.gnu.org/11197>.
+
+(define (open-input-string s)
+ (with-fluids ((%default-port-encoding "UTF-8"))
+ ((@ (guile) open-input-string) s)))
+
+(define (open-output-string)
+ (with-fluids ((%default-port-encoding "UTF-8"))
+ ((@ (guile) open-output-string))))
+
+(cond-expand-provide (current-module) '(srfi-6))
+
+;;; srfi-6.scm ends here
+;;; srfi-60.scm --- Integers as Bits
+
+;; Copyright (C) 2005, 2006, 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (srfi srfi-60)
+ #\export (bitwise-and
+ bitwise-ior
+ bitwise-xor
+ bitwise-not
+ any-bits-set?
+ bitwise-if bitwise-merge
+ log2-binary-factors first-set-bit
+ bit-set?
+ copy-bit
+ bit-field
+ copy-bit-field
+ arithmetic-shift
+ rotate-bit-field
+ reverse-bit-field
+ integer->list
+ list->integer
+ booleans->integer)
+ #\replace (bit-count)
+ #\re-export (logand
+ logior
+ logxor
+ integer-length
+ logtest
+ logcount
+ logbit?
+ ash))
+
+(load-extension (string-append "libguile-" (effective-version))
+ "scm_init_srfi_60")
+
+(define bitwise-and logand)
+(define bitwise-ior logior)
+(define bitwise-xor logxor)
+(define bitwise-not lognot)
+(define any-bits-set? logtest)
+(define bit-count logcount)
+
+(define (bitwise-if mask n0 n1)
+ (logior (logand mask n0)
+ (logand (lognot mask) n1)))
+(define bitwise-merge bitwise-if)
+
+(define first-set-bit log2-binary-factors)
+(define bit-set? logbit?)
+(define bit-field bit-extract)
+
+(define (copy-bit-field n newbits start end)
+ (logxor n (ash (logxor (bit-extract n start end) ;; cancel old
+ (bit-extract newbits 0 (- end start))) ;; insert new
+ start)))
+
+(define arithmetic-shift ash)
+
+(cond-expand-provide (current-module) '(srfi-60))
+;;; srfi-64.scm -- SRFI 64 - A Scheme API for test suites.
+
+;; Copyright (C) 2014 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (srfi srfi-64)
+ #\export
+ (test-begin
+ test-end test-assert test-eqv test-eq test-equal
+ test-approximate test-assert test-error test-apply test-with-runner
+ test-match-nth test-match-all test-match-any test-match-name
+ test-skip test-expect-fail test-read-eval-string
+ test-runner-group-path test-group test-group-with-cleanup
+ test-result-ref test-result-set! test-result-clear test-result-remove
+ test-result-kind test-passed?
+ test-log-to-file
+ test-runner? test-runner-reset test-runner-null
+ test-runner-simple test-runner-current test-runner-factory test-runner-get
+ test-runner-create test-runner-test-name
+ test-runner-pass-count test-runner-pass-count!
+ test-runner-fail-count test-runner-fail-count!
+ test-runner-xpass-count test-runner-xpass-count!
+ test-runner-xfail-count test-runner-xfail-count!
+ test-runner-skip-count test-runner-skip-count!
+ test-runner-group-stack test-runner-group-stack!
+ test-runner-on-test-begin test-runner-on-test-begin!
+ test-runner-on-test-end test-runner-on-test-end!
+ test-runner-on-group-begin test-runner-on-group-begin!
+ test-runner-on-group-end test-runner-on-group-end!
+ test-runner-on-final test-runner-on-final!
+ test-runner-on-bad-count test-runner-on-bad-count!
+ test-runner-on-bad-end-name test-runner-on-bad-end-name!
+ test-result-alist test-result-alist!
+ test-runner-aux-value test-runner-aux-value!
+ test-on-group-begin-simple test-on-group-end-simple
+ test-on-bad-count-simple test-on-bad-end-name-simple
+ test-on-final-simple test-on-test-end-simple
+ test-on-final-simple))
+
+(cond-expand-provide (current-module) '(srfi-64))
+
+(include-from-path "srfi/srfi-64/testing.scm")
+;; Copyright (c) 2005, 2006, 2007, 2012, 2013 Per Bothner
+;; Added "full" support for Chicken, Gauche, Guile and SISC.
+;; Alex Shinn, Copyright (c) 2005.
+;; Modified for Scheme Spheres by Álvaro Castro-Castilla, Copyright (c) 2012.
+;; Support for Guile 2 by Mark H Weaver <mhw@netris.org>, Copyright (c) 2014.
+;;
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+(cond-expand
+ (chicken
+ (require-extension syntax-case))
+ (guile-2
+ (use-modules (srfi srfi-9)
+ ;; In 2.0.9, srfi-34 and srfi-35 are not well integrated
+ ;; with either Guile's native exceptions or R6RS exceptions.
+ ;;(srfi srfi-34) (srfi srfi-35)
+ (srfi srfi-39)))
+ (guile
+ (use-modules (ice-9 syncase) (srfi srfi-9)
+ ;;(srfi srfi-34) (srfi srfi-35) - not in Guile 1.6.7
+ (srfi srfi-39)))
+ (sisc
+ (require-extension (srfi 9 34 35 39)))
+ (kawa
+ (module-compile-options warn-undefined-variable\: #t
+ warn-invoke-unknown-method\: #t)
+ (provide 'srfi-64)
+ (provide 'testing)
+ (require 'srfi-34)
+ (require 'srfi-35))
+ (else ()
+ ))
+
+(cond-expand
+ (kawa
+ (define-syntax %test-export
+ (syntax-rules ()
+ ((%test-export test-begin . other-names)
+ (module-export %test-begin . other-names)))))
+ (else
+ (define-syntax %test-export
+ (syntax-rules ()
+ ((%test-export . names) (if #f #f))))))
+
+;; List of exported names
+(%test-export
+ test-begin ;; must be listed first, since in Kawa (at least) it is "magic".
+ test-end test-assert test-eqv test-eq test-equal
+ test-approximate test-assert test-error test-apply test-with-runner
+ test-match-nth test-match-all test-match-any test-match-name
+ test-skip test-expect-fail test-read-eval-string
+ test-runner-group-path test-group test-group-with-cleanup
+ test-result-ref test-result-set! test-result-clear test-result-remove
+ test-result-kind test-passed?
+ test-log-to-file
+ ; Misc test-runner functions
+ test-runner? test-runner-reset test-runner-null
+ test-runner-simple test-runner-current test-runner-factory test-runner-get
+ test-runner-create test-runner-test-name
+ ;; test-runner field setter and getter functions - see %test-record-define:
+ test-runner-pass-count test-runner-pass-count!
+ test-runner-fail-count test-runner-fail-count!
+ test-runner-xpass-count test-runner-xpass-count!
+ test-runner-xfail-count test-runner-xfail-count!
+ test-runner-skip-count test-runner-skip-count!
+ test-runner-group-stack test-runner-group-stack!
+ test-runner-on-test-begin test-runner-on-test-begin!
+ test-runner-on-test-end test-runner-on-test-end!
+ test-runner-on-group-begin test-runner-on-group-begin!
+ test-runner-on-group-end test-runner-on-group-end!
+ test-runner-on-final test-runner-on-final!
+ test-runner-on-bad-count test-runner-on-bad-count!
+ test-runner-on-bad-end-name test-runner-on-bad-end-name!
+ test-result-alist test-result-alist!
+ test-runner-aux-value test-runner-aux-value!
+ ;; default/simple call-back functions, used in default test-runner,
+ ;; but can be called to construct more complex ones.
+ test-on-group-begin-simple test-on-group-end-simple
+ test-on-bad-count-simple test-on-bad-end-name-simple
+ test-on-final-simple test-on-test-end-simple
+ test-on-final-simple)
+
+(cond-expand
+ (srfi-9
+ (define-syntax %test-record-define
+ (syntax-rules ()
+ ((%test-record-define alloc runner? (name index setter getter) ...)
+ (define-record-type test-runner
+ (alloc)
+ runner?
+ (name setter getter) ...)))))
+ (else
+ (define %test-runner-cookie (list "test-runner"))
+ (define-syntax %test-record-define
+ (syntax-rules ()
+ ((%test-record-define alloc runner? (name index getter setter) ...)
+ (begin
+ (define (runner? obj)
+ (and (vector? obj)
+ (> (vector-length obj) 1)
+ (eq (vector-ref obj 0) %test-runner-cookie)))
+ (define (alloc)
+ (let ((runner (make-vector 23)))
+ (vector-set! runner 0 %test-runner-cookie)
+ runner))
+ (begin
+ (define (getter runner)
+ (vector-ref runner index)) ...)
+ (begin
+ (define (setter runner value)
+ (vector-set! runner index value)) ...)))))))
+
+(%test-record-define
+ %test-runner-alloc test-runner?
+ ;; Cumulate count of all tests that have passed and were expected to.
+ (pass-count 1 test-runner-pass-count test-runner-pass-count!)
+ (fail-count 2 test-runner-fail-count test-runner-fail-count!)
+ (xpass-count 3 test-runner-xpass-count test-runner-xpass-count!)
+ (xfail-count 4 test-runner-xfail-count test-runner-xfail-count!)
+ (skip-count 5 test-runner-skip-count test-runner-skip-count!)
+ (skip-list 6 %test-runner-skip-list %test-runner-skip-list!)
+ (fail-list 7 %test-runner-fail-list %test-runner-fail-list!)
+ ;; Normally #t, except when in a test-apply.
+ (run-list 8 %test-runner-run-list %test-runner-run-list!)
+ (skip-save 9 %test-runner-skip-save %test-runner-skip-save!)
+ (fail-save 10 %test-runner-fail-save %test-runner-fail-save!)
+ (group-stack 11 test-runner-group-stack test-runner-group-stack!)
+ (on-test-begin 12 test-runner-on-test-begin test-runner-on-test-begin!)
+ (on-test-end 13 test-runner-on-test-end test-runner-on-test-end!)
+ ;; Call-back when entering a group. Takes (runner suite-name count).
+ (on-group-begin 14 test-runner-on-group-begin test-runner-on-group-begin!)
+ ;; Call-back when leaving a group.
+ (on-group-end 15 test-runner-on-group-end test-runner-on-group-end!)
+ ;; Call-back when leaving the outermost group.
+ (on-final 16 test-runner-on-final test-runner-on-final!)
+ ;; Call-back when expected number of tests was wrong.
+ (on-bad-count 17 test-runner-on-bad-count test-runner-on-bad-count!)
+ ;; Call-back when name in test=end doesn't match test-begin.
+ (on-bad-end-name 18 test-runner-on-bad-end-name test-runner-on-bad-end-name!)
+ ;; Cumulate count of all tests that have been done.
+ (total-count 19 %test-runner-total-count %test-runner-total-count!)
+ ;; Stack (list) of (count-at-start . expected-count):
+ (count-list 20 %test-runner-count-list %test-runner-count-list!)
+ (result-alist 21 test-result-alist test-result-alist!)
+ ;; Field can be used by test-runner for any purpose.
+ ;; test-runner-simple uses it for a log file.
+ (aux-value 22 test-runner-aux-value test-runner-aux-value!)
+)
+
+(define (test-runner-reset runner)
+ (test-result-alist! runner '())
+ (test-runner-pass-count! runner 0)
+ (test-runner-fail-count! runner 0)
+ (test-runner-xpass-count! runner 0)
+ (test-runner-xfail-count! runner 0)
+ (test-runner-skip-count! runner 0)
+ (%test-runner-total-count! runner 0)
+ (%test-runner-count-list! runner '())
+ (%test-runner-run-list! runner #t)
+ (%test-runner-skip-list! runner '())
+ (%test-runner-fail-list! runner '())
+ (%test-runner-skip-save! runner '())
+ (%test-runner-fail-save! runner '())
+ (test-runner-group-stack! runner '()))
+
+(define (test-runner-group-path runner)
+ (reverse (test-runner-group-stack runner)))
+
+(define (%test-null-callback runner) #f)
+
+(define (test-runner-null)
+ (let ((runner (%test-runner-alloc)))
+ (test-runner-reset runner)
+ (test-runner-on-group-begin! runner (lambda (runner name count) #f))
+ (test-runner-on-group-end! runner %test-null-callback)
+ (test-runner-on-final! runner %test-null-callback)
+ (test-runner-on-test-begin! runner %test-null-callback)
+ (test-runner-on-test-end! runner %test-null-callback)
+ (test-runner-on-bad-count! runner (lambda (runner count expected) #f))
+ (test-runner-on-bad-end-name! runner (lambda (runner begin end) #f))
+ runner))
+
+;; Not part of the specification. FIXME
+;; Controls whether a log file is generated.
+(define test-log-to-file #t)
+
+(define (test-runner-simple)
+ (let ((runner (%test-runner-alloc)))
+ (test-runner-reset runner)
+ (test-runner-on-group-begin! runner test-on-group-begin-simple)
+ (test-runner-on-group-end! runner test-on-group-end-simple)
+ (test-runner-on-final! runner test-on-final-simple)
+ (test-runner-on-test-begin! runner test-on-test-begin-simple)
+ (test-runner-on-test-end! runner test-on-test-end-simple)
+ (test-runner-on-bad-count! runner test-on-bad-count-simple)
+ (test-runner-on-bad-end-name! runner test-on-bad-end-name-simple)
+ runner))
+
+(cond-expand
+ (srfi-39
+ (define test-runner-current (make-parameter #f))
+ (define test-runner-factory (make-parameter test-runner-simple)))
+ (else
+ (define %test-runner-current #f)
+ (define-syntax test-runner-current
+ (syntax-rules ()
+ ((test-runner-current)
+ %test-runner-current)
+ ((test-runner-current runner)
+ (set! %test-runner-current runner))))
+ (define %test-runner-factory test-runner-simple)
+ (define-syntax test-runner-factory
+ (syntax-rules ()
+ ((test-runner-factory)
+ %test-runner-factory)
+ ((test-runner-factory runner)
+ (set! %test-runner-factory runner))))))
+
+;; A safer wrapper to test-runner-current.
+(define (test-runner-get)
+ (let ((r (test-runner-current)))
+ (if (not r)
+ (cond-expand
+ (srfi-23 (error "test-runner not initialized - test-begin missing?"))
+ (else #t)))
+ r))
+
+(define (%test-specifier-matches spec runner)
+ (spec runner))
+
+(define (test-runner-create)
+ ((test-runner-factory)))
+
+(define (%test-any-specifier-matches list runner)
+ (let ((result #f))
+ (let loop ((l list))
+ (cond ((null? l) result)
+ (else
+ (if (%test-specifier-matches (car l) runner)
+ (set! result #t))
+ (loop (cdr l)))))))
+
+;; Returns #f, #t, or 'xfail.
+(define (%test-should-execute runner)
+ (let ((run (%test-runner-run-list runner)))
+ (cond ((or
+ (not (or (eqv? run #t)
+ (%test-any-specifier-matches run runner)))
+ (%test-any-specifier-matches
+ (%test-runner-skip-list runner)
+ runner))
+ (test-result-set! runner 'result-kind 'skip)
+ #f)
+ ((%test-any-specifier-matches
+ (%test-runner-fail-list runner)
+ runner)
+ (test-result-set! runner 'result-kind 'xfail)
+ 'xfail)
+ (else #t))))
+
+(define (%test-begin suite-name count)
+ (if (not (test-runner-current))
+ (test-runner-current (test-runner-create)))
+ (let ((runner (test-runner-current)))
+ ((test-runner-on-group-begin runner) runner suite-name count)
+ (%test-runner-skip-save! runner
+ (cons (%test-runner-skip-list runner)
+ (%test-runner-skip-save runner)))
+ (%test-runner-fail-save! runner
+ (cons (%test-runner-fail-list runner)
+ (%test-runner-fail-save runner)))
+ (%test-runner-count-list! runner
+ (cons (cons (%test-runner-total-count runner)
+ count)
+ (%test-runner-count-list runner)))
+ (test-runner-group-stack! runner (cons suite-name
+ (test-runner-group-stack runner)))))
+(cond-expand
+ (kawa
+ ;; Kawa has test-begin built in, implemented as:
+ ;; (begin
+ ;; (cond-expand (srfi-64 #!void) (else (require 'srfi-64)))
+ ;; (%test-begin suite-name [count]))
+ ;; This puts test-begin but only test-begin in the default environment.,
+ ;; which makes normal test suites loadable without non-portable commands.
+ )
+ (else
+ (define-syntax test-begin
+ (syntax-rules ()
+ ((test-begin suite-name)
+ (%test-begin suite-name #f))
+ ((test-begin suite-name count)
+ (%test-begin suite-name count))))))
+
+(define (test-on-group-begin-simple runner suite-name count)
+ (if (null? (test-runner-group-stack runner))
+ (begin
+ (display "%%%% Starting test ")
+ (display suite-name)
+ (if test-log-to-file
+ (let* ((log-file-name
+ (if (string? test-log-to-file) test-log-to-file
+ (string-append suite-name ".log")))
+ (log-file
+ (cond-expand (mzscheme
+ (open-output-file log-file-name 'truncate/replace))
+ (else (open-output-file log-file-name)))))
+ (display "%%%% Starting test " log-file)
+ (display suite-name log-file)
+ (newline log-file)
+ (test-runner-aux-value! runner log-file)
+ (display " (Writing full log to \"")
+ (display log-file-name)
+ (display "\")")))
+ (newline)))
+ (let ((log (test-runner-aux-value runner)))
+ (if (output-port? log)
+ (begin
+ (display "Group begin: " log)
+ (display suite-name log)
+ (newline log))))
+ #f)
+
+(define (test-on-group-end-simple runner)
+ (let ((log (test-runner-aux-value runner)))
+ (if (output-port? log)
+ (begin
+ (display "Group end: " log)
+ (display (car (test-runner-group-stack runner)) log)
+ (newline log))))
+ #f)
+
+(define (%test-on-bad-count-write runner count expected-count port)
+ (display "*** Total number of tests was " port)
+ (display count port)
+ (display " but should be " port)
+ (display expected-count port)
+ (display ". ***" port)
+ (newline port)
+ (display "*** Discrepancy indicates testsuite error or exceptions. ***" port)
+ (newline port))
+
+(define (test-on-bad-count-simple runner count expected-count)
+ (%test-on-bad-count-write runner count expected-count (current-output-port))
+ (let ((log (test-runner-aux-value runner)))
+ (if (output-port? log)
+ (%test-on-bad-count-write runner count expected-count log))))
+
+(define (test-on-bad-end-name-simple runner begin-name end-name)
+ (let ((msg (string-append (%test-format-line runner) "test-end " begin-name
+ " does not match test-begin " end-name)))
+ (cond-expand
+ (srfi-23 (error msg))
+ (else (display msg) (newline)))))
+
+
+(define (%test-final-report1 value label port)
+ (if (> value 0)
+ (begin
+ (display label port)
+ (display value port)
+ (newline port))))
+
+(define (%test-final-report-simple runner port)
+ (%test-final-report1 (test-runner-pass-count runner)
+ "# of expected passes " port)
+ (%test-final-report1 (test-runner-xfail-count runner)
+ "# of expected failures " port)
+ (%test-final-report1 (test-runner-xpass-count runner)
+ "# of unexpected successes " port)
+ (%test-final-report1 (test-runner-fail-count runner)
+ "# of unexpected failures " port)
+ (%test-final-report1 (test-runner-skip-count runner)
+ "# of skipped tests " port))
+
+(define (test-on-final-simple runner)
+ (%test-final-report-simple runner (current-output-port))
+ (let ((log (test-runner-aux-value runner)))
+ (if (output-port? log)
+ (%test-final-report-simple runner log))))
+
+(define (%test-format-line runner)
+ (let* ((line-info (test-result-alist runner))
+ (source-file (assq 'source-file line-info))
+ (source-line (assq 'source-line line-info))
+ (file (if source-file (cdr source-file) "")))
+ (if source-line
+ (string-append file ":"
+ (number->string (cdr source-line)) ": ")
+ "")))
+
+(define (%test-end suite-name line-info)
+ (let* ((r (test-runner-get))
+ (groups (test-runner-group-stack r))
+ (line (%test-format-line r)))
+ (test-result-alist! r line-info)
+ (if (null? groups)
+ (let ((msg (string-append line "test-end not in a group")))
+ (cond-expand
+ (srfi-23 (error msg))
+ (else (display msg) (newline)))))
+ (if (and suite-name (not (equal? suite-name (car groups))))
+ ((test-runner-on-bad-end-name r) r suite-name (car groups)))
+ (let* ((count-list (%test-runner-count-list r))
+ (expected-count (cdar count-list))
+ (saved-count (caar count-list))
+ (group-count (- (%test-runner-total-count r) saved-count)))
+ (if (and expected-count
+ (not (= expected-count group-count)))
+ ((test-runner-on-bad-count r) r group-count expected-count))
+ ((test-runner-on-group-end r) r)
+ (test-runner-group-stack! r (cdr (test-runner-group-stack r)))
+ (%test-runner-skip-list! r (car (%test-runner-skip-save r)))
+ (%test-runner-skip-save! r (cdr (%test-runner-skip-save r)))
+ (%test-runner-fail-list! r (car (%test-runner-fail-save r)))
+ (%test-runner-fail-save! r (cdr (%test-runner-fail-save r)))
+ (%test-runner-count-list! r (cdr count-list))
+ (if (null? (test-runner-group-stack r))
+ ((test-runner-on-final r) r)))))
+
+(define-syntax test-group
+ (syntax-rules ()
+ ((test-group suite-name . body)
+ (let ((r (test-runner-current)))
+ ;; Ideally should also set line-number, if available.
+ (test-result-alist! r (list (cons 'test-name suite-name)))
+ (if (%test-should-execute r)
+ (dynamic-wind
+ (lambda () (test-begin suite-name))
+ (lambda () . body)
+ (lambda () (test-end suite-name))))))))
+
+(define-syntax test-group-with-cleanup
+ (syntax-rules ()
+ ((test-group-with-cleanup suite-name form cleanup-form)
+ (test-group suite-name
+ (dynamic-wind
+ (lambda () #f)
+ (lambda () form)
+ (lambda () cleanup-form))))
+ ((test-group-with-cleanup suite-name cleanup-form)
+ (test-group-with-cleanup suite-name #f cleanup-form))
+ ((test-group-with-cleanup suite-name form1 form2 form3 . rest)
+ (test-group-with-cleanup suite-name (begin form1 form2) form3 . rest))))
+
+(define (test-on-test-begin-simple runner)
+ (let ((log (test-runner-aux-value runner)))
+ (if (output-port? log)
+ (let* ((results (test-result-alist runner))
+ (source-file (assq 'source-file results))
+ (source-line (assq 'source-line results))
+ (source-form (assq 'source-form results))
+ (test-name (assq 'test-name results)))
+ (display "Test begin:" log)
+ (newline log)
+ (if test-name (%test-write-result1 test-name log))
+ (if source-file (%test-write-result1 source-file log))
+ (if source-line (%test-write-result1 source-line log))
+ (if source-form (%test-write-result1 source-form log))))))
+
+(define-syntax test-result-ref
+ (syntax-rules ()
+ ((test-result-ref runner pname)
+ (test-result-ref runner pname #f))
+ ((test-result-ref runner pname default)
+ (let ((p (assq pname (test-result-alist runner))))
+ (if p (cdr p) default)))))
+
+(define (test-on-test-end-simple runner)
+ (let ((log (test-runner-aux-value runner))
+ (kind (test-result-ref runner 'result-kind)))
+ (if (memq kind '(fail xpass))
+ (let* ((results (test-result-alist runner))
+ (source-file (assq 'source-file results))
+ (source-line (assq 'source-line results))
+ (test-name (assq 'test-name results)))
+ (if (or source-file source-line)
+ (begin
+ (if source-file (display (cdr source-file)))
+ (display ":")
+ (if source-line (display (cdr source-line)))
+ (display ": ")))
+ (display (if (eq? kind 'xpass) "XPASS" "FAIL"))
+ (if test-name
+ (begin
+ (display " ")
+ (display (cdr test-name))))
+ (newline)))
+ (if (output-port? log)
+ (begin
+ (display "Test end:" log)
+ (newline log)
+ (let loop ((list (test-result-alist runner)))
+ (if (pair? list)
+ (let ((pair (car list)))
+ ;; Write out properties not written out by on-test-begin.
+ (if (not (memq (car pair)
+ '(test-name source-file source-line source-form)))
+ (%test-write-result1 pair log))
+ (loop (cdr list)))))))))
+
+(define (%test-write-result1 pair port)
+ (display " " port)
+ (display (car pair) port)
+ (display ": " port)
+ (write (cdr pair) port)
+ (newline port))
+
+(define (test-result-set! runner pname value)
+ (let* ((alist (test-result-alist runner))
+ (p (assq pname alist)))
+ (if p
+ (set-cdr! p value)
+ (test-result-alist! runner (cons (cons pname value) alist)))))
+
+(define (test-result-clear runner)
+ (test-result-alist! runner '()))
+
+(define (test-result-remove runner pname)
+ (let* ((alist (test-result-alist runner))
+ (p (assq pname alist)))
+ (if p
+ (test-result-alist! runner
+ (let loop ((r alist))
+ (if (eq? r p) (cdr r)
+ (cons (car r) (loop (cdr r)))))))))
+
+(define (test-result-kind . rest)
+ (let ((runner (if (pair? rest) (car rest) (test-runner-current))))
+ (test-result-ref runner 'result-kind)))
+
+(define (test-passed? . rest)
+ (let ((runner (if (pair? rest) (car rest) (test-runner-get))))
+ (memq (test-result-ref runner 'result-kind) '(pass xpass))))
+
+(define (%test-report-result)
+ (let* ((r (test-runner-get))
+ (result-kind (test-result-kind r)))
+ (case result-kind
+ ((pass)
+ (test-runner-pass-count! r (+ 1 (test-runner-pass-count r))))
+ ((fail)
+ (test-runner-fail-count! r (+ 1 (test-runner-fail-count r))))
+ ((xpass)
+ (test-runner-xpass-count! r (+ 1 (test-runner-xpass-count r))))
+ ((xfail)
+ (test-runner-xfail-count! r (+ 1 (test-runner-xfail-count r))))
+ (else
+ (test-runner-skip-count! r (+ 1 (test-runner-skip-count r)))))
+ (%test-runner-total-count! r (+ 1 (%test-runner-total-count r)))
+ ((test-runner-on-test-end r) r)))
+
+(cond-expand
+ (guile
+ (define-syntax %test-evaluate-with-catch
+ (syntax-rules ()
+ ((%test-evaluate-with-catch test-expression)
+ (catch #t
+ (lambda () test-expression)
+ (lambda (key . args)
+ (test-result-set! (test-runner-current) 'actual-error
+ (cons key args))
+ #f))))))
+ (kawa
+ (define-syntax %test-evaluate-with-catch
+ (syntax-rules ()
+ ((%test-evaluate-with-catch test-expression)
+ (try-catch test-expression
+ (ex <java.lang.Throwable>
+ (test-result-set! (test-runner-current) 'actual-error ex)
+ #f))))))
+ (srfi-34
+ (define-syntax %test-evaluate-with-catch
+ (syntax-rules ()
+ ((%test-evaluate-with-catch test-expression)
+ (guard (err (else #f)) test-expression)))))
+ (chicken
+ (define-syntax %test-evaluate-with-catch
+ (syntax-rules ()
+ ((%test-evaluate-with-catch test-expression)
+ (condition-case test-expression (ex () #f))))))
+ (else
+ (define-syntax %test-evaluate-with-catch
+ (syntax-rules ()
+ ((%test-evaluate-with-catch test-expression)
+ test-expression)))))
+
+(cond-expand
+ ((or kawa mzscheme)
+ (cond-expand
+ (mzscheme
+ (define-for-syntax (%test-syntax-file form)
+ (let ((source (syntax-source form)))
+ (cond ((string? source) file)
+ ((path? source) (path->string source))
+ (else #f)))))
+ (kawa
+ (define (%test-syntax-file form)
+ (syntax-source form))))
+ (define (%test-source-line2 form)
+ (let* ((line (syntax-line form))
+ (file (%test-syntax-file form))
+ (line-pair (if line (list (cons 'source-line line)) '())))
+ (cons (cons 'source-form (syntax-object->datum form))
+ (if file (cons (cons 'source-file file) line-pair) line-pair)))))
+ (guile-2
+ (define (%test-source-line2 form)
+ (let* ((src-props (syntax-source form))
+ (file (and src-props (assq-ref src-props 'filename)))
+ (line (and src-props (assq-ref src-props 'line)))
+ (file-alist (if file
+ `((source-file . ,file))
+ '()))
+ (line-alist (if line
+ `((source-line . ,(+ line 1)))
+ '())))
+ (datum->syntax (syntax here)
+ `((source-form . ,(syntax->datum form))
+ ,@file-alist
+ ,@line-alist)))))
+ (else
+ (define (%test-source-line2 form)
+ '())))
+
+(define (%test-on-test-begin r)
+ (%test-should-execute r)
+ ((test-runner-on-test-begin r) r)
+ (not (eq? 'skip (test-result-ref r 'result-kind))))
+
+(define (%test-on-test-end r result)
+ (test-result-set! r 'result-kind
+ (if (eq? (test-result-ref r 'result-kind) 'xfail)
+ (if result 'xpass 'xfail)
+ (if result 'pass 'fail))))
+
+(define (test-runner-test-name runner)
+ (test-result-ref runner 'test-name ""))
+
+(define-syntax %test-comp2body
+ (syntax-rules ()
+ ((%test-comp2body r comp expected expr)
+ (let ()
+ (if (%test-on-test-begin r)
+ (let ((exp expected))
+ (test-result-set! r 'expected-value exp)
+ (let ((res (%test-evaluate-with-catch expr)))
+ (test-result-set! r 'actual-value res)
+ (%test-on-test-end r (comp exp res)))))
+ (%test-report-result)))))
+
+(define (%test-approximate= error)
+ (lambda (value expected)
+ (let ((rval (real-part value))
+ (ival (imag-part value))
+ (rexp (real-part expected))
+ (iexp (imag-part expected)))
+ (and (>= rval (- rexp error))
+ (>= ival (- iexp error))
+ (<= rval (+ rexp error))
+ (<= ival (+ iexp error))))))
+
+(define-syntax %test-comp1body
+ (syntax-rules ()
+ ((%test-comp1body r expr)
+ (let ()
+ (if (%test-on-test-begin r)
+ (let ()
+ (let ((res (%test-evaluate-with-catch expr)))
+ (test-result-set! r 'actual-value res)
+ (%test-on-test-end r res))))
+ (%test-report-result)))))
+
+(cond-expand
+ ((or kawa mzscheme guile-2)
+ ;; Should be made to work for any Scheme with syntax-case
+ ;; However, I haven't gotten the quoting working. FIXME.
+ (define-syntax test-end
+ (lambda (x)
+ (syntax-case (list x (list (syntax quote) (%test-source-line2 x))) ()
+ (((mac suite-name) line)
+ (syntax
+ (%test-end suite-name line)))
+ (((mac) line)
+ (syntax
+ (%test-end #f line))))))
+ (define-syntax test-assert
+ (lambda (x)
+ (syntax-case (list x (list (syntax quote) (%test-source-line2 x))) ()
+ (((mac tname expr) line)
+ (syntax
+ (let* ((r (test-runner-get))
+ (name tname))
+ (test-result-alist! r (cons (cons 'test-name tname) line))
+ (%test-comp1body r expr))))
+ (((mac expr) line)
+ (syntax
+ (let* ((r (test-runner-get)))
+ (test-result-alist! r line)
+ (%test-comp1body r expr)))))))
+ (define (%test-comp2 comp x)
+ (syntax-case (list x (list (syntax quote) (%test-source-line2 x)) comp) ()
+ (((mac tname expected expr) line comp)
+ (syntax
+ (let* ((r (test-runner-get))
+ (name tname))
+ (test-result-alist! r (cons (cons 'test-name tname) line))
+ (%test-comp2body r comp expected expr))))
+ (((mac expected expr) line comp)
+ (syntax
+ (let* ((r (test-runner-get)))
+ (test-result-alist! r line)
+ (%test-comp2body r comp expected expr))))))
+ (define-syntax test-eqv
+ (lambda (x) (%test-comp2 (syntax eqv?) x)))
+ (define-syntax test-eq
+ (lambda (x) (%test-comp2 (syntax eq?) x)))
+ (define-syntax test-equal
+ (lambda (x) (%test-comp2 (syntax equal?) x)))
+ (define-syntax test-approximate ;; FIXME - needed for non-Kawa
+ (lambda (x)
+ (syntax-case (list x (list (syntax quote) (%test-source-line2 x))) ()
+ (((mac tname expected expr error) line)
+ (syntax
+ (let* ((r (test-runner-get))
+ (name tname))
+ (test-result-alist! r (cons (cons 'test-name tname) line))
+ (%test-comp2body r (%test-approximate= error) expected expr))))
+ (((mac expected expr error) line)
+ (syntax
+ (let* ((r (test-runner-get)))
+ (test-result-alist! r line)
+ (%test-comp2body r (%test-approximate= error) expected expr))))))))
+ (else
+ (define-syntax test-end
+ (syntax-rules ()
+ ((test-end)
+ (%test-end #f '()))
+ ((test-end suite-name)
+ (%test-end suite-name '()))))
+ (define-syntax test-assert
+ (syntax-rules ()
+ ((test-assert tname test-expression)
+ (let* ((r (test-runner-get))
+ (name tname))
+ (test-result-alist! r '((test-name . tname)))
+ (%test-comp1body r test-expression)))
+ ((test-assert test-expression)
+ (let* ((r (test-runner-get)))
+ (test-result-alist! r '())
+ (%test-comp1body r test-expression)))))
+ (define-syntax %test-comp2
+ (syntax-rules ()
+ ((%test-comp2 comp tname expected expr)
+ (let* ((r (test-runner-get))
+ (name tname))
+ (test-result-alist! r (list (cons 'test-name tname)))
+ (%test-comp2body r comp expected expr)))
+ ((%test-comp2 comp expected expr)
+ (let* ((r (test-runner-get)))
+ (test-result-alist! r '())
+ (%test-comp2body r comp expected expr)))))
+ (define-syntax test-equal
+ (syntax-rules ()
+ ((test-equal . rest)
+ (%test-comp2 equal? . rest))))
+ (define-syntax test-eqv
+ (syntax-rules ()
+ ((test-eqv . rest)
+ (%test-comp2 eqv? . rest))))
+ (define-syntax test-eq
+ (syntax-rules ()
+ ((test-eq . rest)
+ (%test-comp2 eq? . rest))))
+ (define-syntax test-approximate
+ (syntax-rules ()
+ ((test-approximate tname expected expr error)
+ (%test-comp2 (%test-approximate= error) tname expected expr))
+ ((test-approximate expected expr error)
+ (%test-comp2 (%test-approximate= error) expected expr))))))
+
+(cond-expand
+ (guile
+ (define-syntax %test-error
+ (syntax-rules ()
+ ((%test-error r etype expr)
+ (cond ((%test-on-test-begin r)
+ (let ((et etype))
+ (test-result-set! r 'expected-error et)
+ (%test-on-test-end r
+ (catch #t
+ (lambda ()
+ (test-result-set! r 'actual-value expr)
+ #f)
+ (lambda (key . args)
+ ;; TODO: decide how to specify expected
+ ;; error types for Guile.
+ (test-result-set! r 'actual-error
+ (cons key args))
+ #t)))
+ (%test-report-result))))))))
+ (mzscheme
+ (define-syntax %test-error
+ (syntax-rules ()
+ ((%test-error r etype expr)
+ (%test-comp1body r (with-handlers (((lambda (h) #t) (lambda (h) #t)))
+ (let ()
+ (test-result-set! r 'actual-value expr)
+ #f)))))))
+ (chicken
+ (define-syntax %test-error
+ (syntax-rules ()
+ ((%test-error r etype expr)
+ (%test-comp1body r (condition-case expr (ex () #t)))))))
+ (kawa
+ (define-syntax %test-error
+ (syntax-rules ()
+ ((%test-error r #t expr)
+ (cond ((%test-on-test-begin r)
+ (test-result-set! r 'expected-error #t)
+ (%test-on-test-end r
+ (try-catch
+ (let ()
+ (test-result-set! r 'actual-value expr)
+ #f)
+ (ex <java.lang.Throwable>
+ (test-result-set! r 'actual-error ex)
+ #t)))
+ (%test-report-result))))
+ ((%test-error r etype expr)
+ (if (%test-on-test-begin r)
+ (let ((et etype))
+ (test-result-set! r 'expected-error et)
+ (%test-on-test-end r
+ (try-catch
+ (let ()
+ (test-result-set! r 'actual-value expr)
+ #f)
+ (ex <java.lang.Throwable>
+ (test-result-set! r 'actual-error ex)
+ (cond ((and (instance? et <gnu.bytecode.ClassType>)
+ (gnu.bytecode.ClassType:isSubclass et <java.lang.Throwable>))
+ (instance? ex et))
+ (else #t)))))
+ (%test-report-result)))))))
+ ((and srfi-34 srfi-35)
+ (define-syntax %test-error
+ (syntax-rules ()
+ ((%test-error r etype expr)
+ (%test-comp1body r (guard (ex ((condition-type? etype)
+ (and (condition? ex) (condition-has-type? ex etype)))
+ ((procedure? etype)
+ (etype ex))
+ ((equal? etype #t)
+ #t)
+ (else #t))
+ expr #f))))))
+ (srfi-34
+ (define-syntax %test-error
+ (syntax-rules ()
+ ((%test-error r etype expr)
+ (%test-comp1body r (guard (ex (else #t)) expr #f))))))
+ (else
+ (define-syntax %test-error
+ (syntax-rules ()
+ ((%test-error r etype expr)
+ (begin
+ ((test-runner-on-test-begin r) r)
+ (test-result-set! r 'result-kind 'skip)
+ (%test-report-result)))))))
+
+(cond-expand
+ ((or kawa mzscheme guile-2)
+
+ (define-syntax test-error
+ (lambda (x)
+ (syntax-case (list x (list (syntax quote) (%test-source-line2 x))) ()
+ (((mac tname etype expr) line)
+ (syntax
+ (let* ((r (test-runner-get))
+ (name tname))
+ (test-result-alist! r (cons (cons 'test-name tname) line))
+ (%test-error r etype expr))))
+ (((mac etype expr) line)
+ (syntax
+ (let* ((r (test-runner-get)))
+ (test-result-alist! r line)
+ (%test-error r etype expr))))
+ (((mac expr) line)
+ (syntax
+ (let* ((r (test-runner-get)))
+ (test-result-alist! r line)
+ (%test-error r #t expr))))))))
+ (else
+ (define-syntax test-error
+ (syntax-rules ()
+ ((test-error name etype expr)
+ (let ((r (test-runner-get)))
+ (test-result-alist! r `((test-name . ,name)))
+ (%test-error r etype expr)))
+ ((test-error etype expr)
+ (let ((r (test-runner-get)))
+ (test-result-alist! r '())
+ (%test-error r etype expr)))
+ ((test-error expr)
+ (let ((r (test-runner-get)))
+ (test-result-alist! r '())
+ (%test-error r #t expr)))))))
+
+(define (test-apply first . rest)
+ (if (test-runner? first)
+ (test-with-runner first (apply test-apply rest))
+ (let ((r (test-runner-current)))
+ (if r
+ (let ((run-list (%test-runner-run-list r)))
+ (cond ((null? rest)
+ (%test-runner-run-list! r (reverse run-list))
+ (first)) ;; actually apply procedure thunk
+ (else
+ (%test-runner-run-list!
+ r
+ (if (eq? run-list #t) (list first) (cons first run-list)))
+ (apply test-apply rest)
+ (%test-runner-run-list! r run-list))))
+ (let ((r (test-runner-create)))
+ (test-with-runner r (apply test-apply first rest))
+ ((test-runner-on-final r) r))))))
+
+(define-syntax test-with-runner
+ (syntax-rules ()
+ ((test-with-runner runner form ...)
+ (let ((saved-runner (test-runner-current)))
+ (dynamic-wind
+ (lambda () (test-runner-current runner))
+ (lambda () form ...)
+ (lambda () (test-runner-current saved-runner)))))))
+
+;;; Predicates
+
+(define (%test-match-nth n count)
+ (let ((i 0))
+ (lambda (runner)
+ (set! i (+ i 1))
+ (and (>= i n) (< i (+ n count))))))
+
+(define-syntax test-match-nth
+ (syntax-rules ()
+ ((test-match-nth n)
+ (test-match-nth n 1))
+ ((test-match-nth n count)
+ (%test-match-nth n count))))
+
+(define (%test-match-all . pred-list)
+ (lambda (runner)
+ (let ((result #t))
+ (let loop ((l pred-list))
+ (if (null? l)
+ result
+ (begin
+ (if (not ((car l) runner))
+ (set! result #f))
+ (loop (cdr l))))))))
+
+(define-syntax test-match-all
+ (syntax-rules ()
+ ((test-match-all pred ...)
+ (%test-match-all (%test-as-specifier pred) ...))))
+
+(define (%test-match-any . pred-list)
+ (lambda (runner)
+ (let ((result #f))
+ (let loop ((l pred-list))
+ (if (null? l)
+ result
+ (begin
+ (if ((car l) runner)
+ (set! result #t))
+ (loop (cdr l))))))))
+
+(define-syntax test-match-any
+ (syntax-rules ()
+ ((test-match-any pred ...)
+ (%test-match-any (%test-as-specifier pred) ...))))
+
+;; Coerce to a predicate function:
+(define (%test-as-specifier specifier)
+ (cond ((procedure? specifier) specifier)
+ ((integer? specifier) (test-match-nth 1 specifier))
+ ((string? specifier) (test-match-name specifier))
+ (else
+ (error "not a valid test specifier"))))
+
+(define-syntax test-skip
+ (syntax-rules ()
+ ((test-skip pred ...)
+ (let ((runner (test-runner-get)))
+ (%test-runner-skip-list! runner
+ (cons (test-match-all (%test-as-specifier pred) ...)
+ (%test-runner-skip-list runner)))))))
+
+(define-syntax test-expect-fail
+ (syntax-rules ()
+ ((test-expect-fail pred ...)
+ (let ((runner (test-runner-get)))
+ (%test-runner-fail-list! runner
+ (cons (test-match-all (%test-as-specifier pred) ...)
+ (%test-runner-fail-list runner)))))))
+
+(define (test-match-name name)
+ (lambda (runner)
+ (equal? name (test-runner-test-name runner))))
+
+(define (test-read-eval-string string)
+ (let* ((port (open-input-string string))
+ (form (read port)))
+ (if (eof-object? (read-char port))
+ (cond-expand
+ (guile (eval form (current-module)))
+ (else (eval form)))
+ (cond-expand
+ (srfi-23 (error "(not at eof)"))
+ (else "error")))))
+
+;;; srfi-67.scm --- Compare Procedures
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library. If not, see
+;; <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module is not yet documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-67)
+ #\export (</<=?
+ </<?
+ <=/<=?
+ <=/<?
+ <=?
+ <?
+ =?
+ >/>=?
+ >/>?
+ >=/>=?
+ >=/>?
+ >=?
+ >?
+ boolean-compare
+ chain<=?
+ chain<?
+ chain=?
+ chain>=?
+ chain>?
+ char-compare
+ char-compare-ci
+ compare-by<
+ compare-by<=
+ compare-by=/<
+ compare-by=/>
+ compare-by>
+ compare-by>=
+ complex-compare
+ cond-compare
+ debug-compare
+ default-compare
+ if-not=?
+ if3
+ if<=?
+ if<?
+ if=?
+ if>=?
+ if>?
+ integer-compare
+ kth-largest
+ list-compare
+ list-compare-as-vector
+ max-compare
+ min-compare
+ not=?
+ number-compare
+ pair-compare
+ pair-compare-car
+ pair-compare-cdr
+ pairwise-not=?
+ rational-compare
+ real-compare
+ refine-compare
+ select-compare
+ symbol-compare
+ vector-compare
+ vector-compare-as-list)
+ #\replace (string-compare string-compare-ci)
+ #\use-module (srfi srfi-27))
+
+(cond-expand-provide (current-module) '(srfi-67))
+
+(include-from-path "srfi/srfi-67/compare.scm")
+; Copyright (c) 2011 Free Software Foundation, Inc.
+; Copyright (c) 2005 Sebastian Egner and Jens Axel S{\o}gaard.
+;
+; Permission is hereby granted, free of charge, to any person obtaining
+; a copy of this software and associated documentation files (the
+; ``Software''), to deal in the Software without restriction, including
+; without limitation the rights to use, copy, modify, merge, publish,
+; distribute, sublicense, and/or sell copies of the Software, and to
+; permit persons to whom the Software is furnished to do so, subject to
+; the following conditions:
+;
+; The above copyright notice and this permission notice shall be
+; included in all copies or substantial portions of the Software.
+;
+; THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
+; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+;
+; -----------------------------------------------------------------------
+;
+; Compare procedures SRFI (reference implementation)
+; Sebastian.Egner@philips.com, Jensaxel@soegaard.net
+; history of this file:
+; SE, 14-Oct-2004: first version
+; SE, 18-Oct-2004: 1st redesign: axioms for 'compare function'
+; SE, 29-Oct-2004: 2nd redesign: higher order reverse/map/refine/unite
+; SE, 2-Nov-2004: 3rd redesign: macros cond/refine-compare replace h.o.f's
+; SE, 10-Nov-2004: (im,re) replaced by (re,im) in complex-compare
+; SE, 11-Nov-2004: case-compare by case (not by cond); select-compare added
+; SE, 12-Jan-2005: pair-compare-cdr
+; SE, 15-Feb-2005: stricter typing for compare-<type>; pairwise-not=?
+; SE, 16-Feb-2005: case-compare -> if-compare -> if3; <? </<? chain<? etc.
+; JS, 24-Feb-2005: selection-compare added
+; SE, 25-Feb-2005: selection-compare -> kth-largest modified; if<? etc.
+; JS, 28-Feb-2005: kth-largest modified - is "stable" now
+; SE, 28-Feb-2005: simplified pairwise-not=?/kth-largest; min/max debugged
+; SE, 07-Apr-2005: compare-based type checks made explicit
+; SE, 18-Apr-2005: added (rel? compare) and eq?-test
+; SE, 16-May-2005: naming convention changed; compare-by< etc. optional x y
+
+; =============================================================================
+
+; Reference Implementation
+; ========================
+;
+; in R5RS (including hygienic macros)
+; + SRFI-16 (case-lambda)
+; + SRFI-23 (error)
+; + SRFI-27 (random-integer)
+
+; Implementation remarks:
+; * In general, the emphasis of this implementation is on correctness
+; and portability, not on efficiency.
+; * Variable arity procedures are expressed in terms of case-lambda
+; in the hope that this will produce efficient code for the case
+; where the arity is statically known at the call site.
+; * In procedures that are required to type-check their arguments,
+; we use (compare x x) for executing extra checks. This relies on
+; the assumption that eq? is used to catch this case quickly.
+; * Care has been taken to reference comparison procedures of R5RS
+; only at the time the operations here are being defined. This
+; makes it possible to redefine these operations, if need be.
+; * For the sake of efficiency, some inlining has been done by hand.
+; This is mainly expressed by macros producing defines.
+; * Identifiers of the form compare:<something> are private.
+;
+; Hints for low-level implementation:
+; * The basis of this SRFI are the atomic compare procedures,
+; i.e. boolean-compare, char-compare, etc. and the conditionals
+; if3, if=?, if<? etc., and default-compare. These should make
+; optimal use of the available type information.
+; * For the sake of speed, the reference implementation does not
+; use a LET to save the comparison value c for the ERROR call.
+; This can be fixed in a low-level implementation at no cost.
+; * Type-checks based on (compare x x) are made explicit by the
+; expression (compare:check result compare x ...).
+; * Eq? should can used to speed up built-in compare procedures,
+; but it can only be used after type-checking at least one of
+; the arguments.
+
+(define (compare:checked result compare . args)
+ (for-each (lambda (x) (compare x x)) args)
+ result)
+
+
+; 3-sided conditional
+
+(define-syntax-rule (if3 c less equal greater)
+ (case c
+ ((-1) less)
+ (( 0) equal)
+ (( 1) greater)
+ (else (error "comparison value not in {-1,0,1}"))))
+
+
+; 2-sided conditionals for comparisons
+
+(define-syntax compare:if-rel?
+ (syntax-rules ()
+ ((compare:if-rel? c-cases a-cases c consequence)
+ (compare:if-rel? c-cases a-cases c consequence (if #f #f)))
+ ((compare:if-rel? c-cases a-cases c consequence alternate)
+ (case c
+ (c-cases consequence)
+ (a-cases alternate)
+ (else (error "comparison value not in {-1,0,1}"))))))
+
+(define-syntax-rule (if=? arg ...)
+ (compare:if-rel? (0) (-1 1) arg ...))
+
+(define-syntax-rule (if<? arg ...)
+ (compare:if-rel? (-1) (0 1) arg ...))
+
+(define-syntax-rule (if>? arg ...)
+ (compare:if-rel? (1) (-1 0) arg ...))
+
+(define-syntax-rule (if<=? arg ...)
+ (compare:if-rel? (-1 0) (1) arg ...))
+
+(define-syntax-rule (if>=? arg ...)
+ (compare:if-rel? (0 1) (-1) arg ...))
+
+(define-syntax-rule (if-not=? arg ...)
+ (compare:if-rel? (-1 1) (0) arg ...))
+
+
+; predicates from compare procedures
+
+(define-syntax-rule (compare:define-rel? rel? if-rel?)
+ (define rel?
+ (case-lambda
+ (() (lambda (x y) (if-rel? (default-compare x y) #t #f)))
+ ((compare) (lambda (x y) (if-rel? (compare x y) #t #f)))
+ ((x y) (if-rel? (default-compare x y) #t #f))
+ ((compare x y)
+ (if (procedure? compare)
+ (if-rel? (compare x y) #t #f)
+ (error "not a procedure (Did you mean rel/rel??): " compare))))))
+
+(compare:define-rel? =? if=?)
+(compare:define-rel? <? if<?)
+(compare:define-rel? >? if>?)
+(compare:define-rel? <=? if<=?)
+(compare:define-rel? >=? if>=?)
+(compare:define-rel? not=? if-not=?)
+
+
+; chains of length 3
+
+(define-syntax-rule (compare:define-rel1/rel2? rel1/rel2? if-rel1? if-rel2?)
+ (define rel1/rel2?
+ (case-lambda
+ (()
+ (lambda (x y z)
+ (if-rel1? (default-compare x y)
+ (if-rel2? (default-compare y z) #t #f)
+ (compare:checked #f default-compare z))))
+ ((compare)
+ (lambda (x y z)
+ (if-rel1? (compare x y)
+ (if-rel2? (compare y z) #t #f)
+ (compare:checked #f compare z))))
+ ((x y z)
+ (if-rel1? (default-compare x y)
+ (if-rel2? (default-compare y z) #t #f)
+ (compare:checked #f default-compare z)))
+ ((compare x y z)
+ (if-rel1? (compare x y)
+ (if-rel2? (compare y z) #t #f)
+ (compare:checked #f compare z))))))
+
+(compare:define-rel1/rel2? </<? if<? if<?)
+(compare:define-rel1/rel2? </<=? if<? if<=?)
+(compare:define-rel1/rel2? <=/<? if<=? if<?)
+(compare:define-rel1/rel2? <=/<=? if<=? if<=?)
+(compare:define-rel1/rel2? >/>? if>? if>?)
+(compare:define-rel1/rel2? >/>=? if>? if>=?)
+(compare:define-rel1/rel2? >=/>? if>=? if>?)
+(compare:define-rel1/rel2? >=/>=? if>=? if>=?)
+
+
+; chains of arbitrary length
+
+(define-syntax-rule (compare:define-chain-rel? chain-rel? if-rel?)
+ (define chain-rel?
+ (case-lambda
+ ((compare)
+ #t)
+ ((compare x1)
+ (compare:checked #t compare x1))
+ ((compare x1 x2)
+ (if-rel? (compare x1 x2) #t #f))
+ ((compare x1 x2 x3)
+ (if-rel? (compare x1 x2)
+ (if-rel? (compare x2 x3) #t #f)
+ (compare:checked #f compare x3)))
+ ((compare x1 x2 . x3+)
+ (if-rel? (compare x1 x2)
+ (let chain? ((head x2) (tail x3+))
+ (if (null? tail)
+ #t
+ (if-rel? (compare head (car tail))
+ (chain? (car tail) (cdr tail))
+ (apply compare:checked #f
+ compare (cdr tail)))))
+ (apply compare:checked #f compare x3+))))))
+
+(compare:define-chain-rel? chain=? if=?)
+(compare:define-chain-rel? chain<? if<?)
+(compare:define-chain-rel? chain>? if>?)
+(compare:define-chain-rel? chain<=? if<=?)
+(compare:define-chain-rel? chain>=? if>=?)
+
+
+; pairwise inequality
+
+(define pairwise-not=?
+ (let ((= =) (<= <=))
+ (case-lambda
+ ((compare)
+ #t)
+ ((compare x1)
+ (compare:checked #t compare x1))
+ ((compare x1 x2)
+ (if-not=? (compare x1 x2) #t #f))
+ ((compare x1 x2 x3)
+ (if-not=? (compare x1 x2)
+ (if-not=? (compare x2 x3)
+ (if-not=? (compare x1 x3) #t #f)
+ #f)
+ (compare:checked #f compare x3)))
+ ((compare . x1+)
+ (let unequal? ((x x1+) (n (length x1+)) (unchecked? #t))
+ (if (< n 2)
+ (if (and unchecked? (= n 1))
+ (compare:checked #t compare (car x))
+ #t)
+ (let* ((i-pivot (random-integer n))
+ (x-pivot (list-ref x i-pivot)))
+ (let split ((i 0) (x x) (x< '()) (x> '()))
+ (if (null? x)
+ (and (unequal? x< (length x<) #f)
+ (unequal? x> (length x>) #f))
+ (if (= i i-pivot)
+ (split (+ i 1) (cdr x) x< x>)
+ (if3 (compare (car x) x-pivot)
+ (split (+ i 1) (cdr x) (cons (car x) x<) x>)
+ (if unchecked?
+ (apply compare:checked #f compare (cdr x))
+ #f)
+ (split (+ i 1) (cdr x) x< (cons (car x) x>)))))))))))))
+
+
+; min/max
+
+(define min-compare
+ (case-lambda
+ ((compare x1)
+ (compare:checked x1 compare x1))
+ ((compare x1 x2)
+ (if<=? (compare x1 x2) x1 x2))
+ ((compare x1 x2 x3)
+ (if<=? (compare x1 x2)
+ (if<=? (compare x1 x3) x1 x3)
+ (if<=? (compare x2 x3) x2 x3)))
+ ((compare x1 x2 x3 x4)
+ (if<=? (compare x1 x2)
+ (if<=? (compare x1 x3)
+ (if<=? (compare x1 x4) x1 x4)
+ (if<=? (compare x3 x4) x3 x4))
+ (if<=? (compare x2 x3)
+ (if<=? (compare x2 x4) x2 x4)
+ (if<=? (compare x3 x4) x3 x4))))
+ ((compare x1 x2 . x3+)
+ (let min ((xmin (if<=? (compare x1 x2) x1 x2)) (xs x3+))
+ (if (null? xs)
+ xmin
+ (min (if<=? (compare xmin (car xs)) xmin (car xs))
+ (cdr xs)))))))
+
+(define max-compare
+ (case-lambda
+ ((compare x1)
+ (compare:checked x1 compare x1))
+ ((compare x1 x2)
+ (if>=? (compare x1 x2) x1 x2))
+ ((compare x1 x2 x3)
+ (if>=? (compare x1 x2)
+ (if>=? (compare x1 x3) x1 x3)
+ (if>=? (compare x2 x3) x2 x3)))
+ ((compare x1 x2 x3 x4)
+ (if>=? (compare x1 x2)
+ (if>=? (compare x1 x3)
+ (if>=? (compare x1 x4) x1 x4)
+ (if>=? (compare x3 x4) x3 x4))
+ (if>=? (compare x2 x3)
+ (if>=? (compare x2 x4) x2 x4)
+ (if>=? (compare x3 x4) x3 x4))))
+ ((compare x1 x2 . x3+)
+ (let max ((xmax (if>=? (compare x1 x2) x1 x2)) (xs x3+))
+ (if (null? xs)
+ xmax
+ (max (if>=? (compare xmax (car xs)) xmax (car xs))
+ (cdr xs)))))))
+
+
+; kth-largest
+
+(define kth-largest
+ (let ((= =) (< <))
+ (case-lambda
+ ((compare k x0)
+ (case (modulo k 1)
+ ((0) (compare:checked x0 compare x0))
+ (else (error "bad index" k))))
+ ((compare k x0 x1)
+ (case (modulo k 2)
+ ((0) (if<=? (compare x0 x1) x0 x1))
+ ((1) (if<=? (compare x0 x1) x1 x0))
+ (else (error "bad index" k))))
+ ((compare k x0 x1 x2)
+ (case (modulo k 3)
+ ((0) (if<=? (compare x0 x1)
+ (if<=? (compare x0 x2) x0 x2)
+ (if<=? (compare x1 x2) x1 x2)))
+ ((1) (if3 (compare x0 x1)
+ (if<=? (compare x1 x2)
+ x1
+ (if<=? (compare x0 x2) x2 x0))
+ (if<=? (compare x0 x2) x1 x0)
+ (if<=? (compare x0 x2)
+ x0
+ (if<=? (compare x1 x2) x2 x1))))
+ ((2) (if<=? (compare x0 x1)
+ (if<=? (compare x1 x2) x2 x1)
+ (if<=? (compare x0 x2) x2 x0)))
+ (else (error "bad index" k))))
+ ((compare k x0 . x1+) ; |x1+| >= 1
+ (if (not (and (integer? k) (exact? k)))
+ (error "bad index" k))
+ (let ((n (+ 1 (length x1+))))
+ (let kth ((k (modulo k n))
+ (n n) ; = |x|
+ (rev #t) ; are x<, x=, x> reversed?
+ (x (cons x0 x1+)))
+ (let ((pivot (list-ref x (random-integer n))))
+ (let split ((x x) (x< '()) (n< 0) (x= '()) (n= 0) (x> '()) (n> 0))
+ (if (null? x)
+ (cond
+ ((< k n<)
+ (kth k n< (not rev) x<))
+ ((< k (+ n< n=))
+ (if rev
+ (list-ref x= (- (- n= 1) (- k n<)))
+ (list-ref x= (- k n<))))
+ (else
+ (kth (- k (+ n< n=)) n> (not rev) x>)))
+ (if3 (compare (car x) pivot)
+ (split (cdr x) (cons (car x) x<) (+ n< 1) x= n= x> n>)
+ (split (cdr x) x< n< (cons (car x) x=) (+ n= 1) x> n>)
+ (split (cdr x) x< n< x= n= (cons (car x) x>) (+ n> 1))))))))))))
+
+
+; compare functions from predicates
+
+(define compare-by<
+ (case-lambda
+ ((lt) (lambda (x y) (if (lt x y) -1 (if (lt y x) 1 0))))
+ ((lt x y) (if (lt x y) -1 (if (lt y x) 1 0)))))
+
+(define compare-by>
+ (case-lambda
+ ((gt) (lambda (x y) (if (gt x y) 1 (if (gt y x) -1 0))))
+ ((gt x y) (if (gt x y) 1 (if (gt y x) -1 0)))))
+
+(define compare-by<=
+ (case-lambda
+ ((le) (lambda (x y) (if (le x y) (if (le y x) 0 -1) 1)))
+ ((le x y) (if (le x y) (if (le y x) 0 -1) 1))))
+
+(define compare-by>=
+ (case-lambda
+ ((ge) (lambda (x y) (if (ge x y) (if (ge y x) 0 1) -1)))
+ ((ge x y) (if (ge x y) (if (ge y x) 0 1) -1))))
+
+(define compare-by=/<
+ (case-lambda
+ ((eq lt) (lambda (x y) (if (eq x y) 0 (if (lt x y) -1 1))))
+ ((eq lt x y) (if (eq x y) 0 (if (lt x y) -1 1)))))
+
+(define compare-by=/>
+ (case-lambda
+ ((eq gt) (lambda (x y) (if (eq x y) 0 (if (gt x y) 1 -1))))
+ ((eq gt x y) (if (eq x y) 0 (if (gt x y) 1 -1)))))
+
+; refine and extend construction
+
+(define-syntax refine-compare
+ (syntax-rules ()
+ ((refine-compare)
+ 0)
+ ((refine-compare c1)
+ c1)
+ ((refine-compare c1 c2 cs ...)
+ (if3 c1 -1 (refine-compare c2 cs ...) 1))))
+
+(define-syntax select-compare
+ (syntax-rules (else)
+ ((select-compare x y clause ...)
+ (let ((x-val x) (y-val y))
+ (select-compare (x-val y-val clause ...))))
+ ; used internally: (select-compare (x y clause ...))
+ ((select-compare (x y))
+ 0)
+ ((select-compare (x y (else c ...)))
+ (refine-compare c ...))
+ ((select-compare (x y (t? c ...) clause ...))
+ (let ((t?-val t?))
+ (let ((tx (t?-val x)) (ty (t?-val y)))
+ (if tx
+ (if ty (refine-compare c ...) -1)
+ (if ty 1 (select-compare (x y clause ...)))))))))
+
+(define-syntax cond-compare
+ (syntax-rules (else)
+ ((cond-compare)
+ 0)
+ ((cond-compare (else cs ...))
+ (refine-compare cs ...))
+ ((cond-compare ((tx ty) cs ...) clause ...)
+ (let ((tx-val tx) (ty-val ty))
+ (if tx-val
+ (if ty-val (refine-compare cs ...) -1)
+ (if ty-val 1 (cond-compare clause ...)))))))
+
+
+; R5RS atomic types
+
+(define-syntax compare:type-check
+ (syntax-rules ()
+ ((compare:type-check type? type-name x)
+ (if (not (type? x))
+ (error (string-append "not " type-name ":") x)))
+ ((compare:type-check type? type-name x y)
+ (begin (compare:type-check type? type-name x)
+ (compare:type-check type? type-name y)))))
+
+(define-syntax-rule (compare:define-by=/< compare = < type? type-name)
+ (define compare
+ (let ((= =) (< <))
+ (lambda (x y)
+ (if (type? x)
+ (if (eq? x y)
+ 0
+ (if (type? y)
+ (if (= x y) 0 (if (< x y) -1 1))
+ (error (string-append "not " type-name ":") y)))
+ (error (string-append "not " type-name ":") x))))))
+
+(define (boolean-compare x y)
+ (compare:type-check boolean? "boolean" x y)
+ (if x (if y 0 1) (if y -1 0)))
+
+(compare:define-by=/< char-compare char=? char<? char? "char")
+
+(compare:define-by=/< char-compare-ci char-ci=? char-ci<? char? "char")
+
+(compare:define-by=/< string-compare string=? string<? string? "string")
+
+(compare:define-by=/< string-compare-ci string-ci=? string-ci<? string? "string")
+
+(define (symbol-compare x y)
+ (compare:type-check symbol? "symbol" x y)
+ (string-compare (symbol->string x) (symbol->string y)))
+
+(compare:define-by=/< integer-compare = < integer? "integer")
+
+(compare:define-by=/< rational-compare = < rational? "rational")
+
+(compare:define-by=/< real-compare = < real? "real")
+
+(define (complex-compare x y)
+ (compare:type-check complex? "complex" x y)
+ (if (and (real? x) (real? y))
+ (real-compare x y)
+ (refine-compare (real-compare (real-part x) (real-part y))
+ (real-compare (imag-part x) (imag-part y)))))
+
+(define (number-compare x y)
+ (compare:type-check number? "number" x y)
+ (complex-compare x y))
+
+
+; R5RS compound data structures: dotted pair, list, vector
+
+(define (pair-compare-car compare)
+ (lambda (x y)
+ (compare (car x) (car y))))
+
+(define (pair-compare-cdr compare)
+ (lambda (x y)
+ (compare (cdr x) (cdr y))))
+
+(define pair-compare
+ (case-lambda
+
+ ; dotted pair
+ ((pair-compare-car pair-compare-cdr x y)
+ (refine-compare (pair-compare-car (car x) (car y))
+ (pair-compare-cdr (cdr x) (cdr y))))
+
+ ; possibly improper lists
+ ((compare x y)
+ (cond-compare
+ (((null? x) (null? y)) 0)
+ (((pair? x) (pair? y)) (compare (car x) (car y))
+ (pair-compare compare (cdr x) (cdr y)))
+ (else (compare x y))))
+
+ ; for convenience
+ ((x y)
+ (pair-compare default-compare x y))))
+
+(define list-compare
+ (case-lambda
+ ((compare x y empty? head tail)
+ (cond-compare
+ (((empty? x) (empty? y)) 0)
+ (else (compare (head x) (head y))
+ (list-compare compare (tail x) (tail y) empty? head tail))))
+
+ ; for convenience
+ (( x y empty? head tail)
+ (list-compare default-compare x y empty? head tail))
+ ((compare x y )
+ (list-compare compare x y null? car cdr))
+ (( x y )
+ (list-compare default-compare x y null? car cdr))))
+
+(define list-compare-as-vector
+ (case-lambda
+ ((compare x y empty? head tail)
+ (refine-compare
+ (let compare-length ((x x) (y y))
+ (cond-compare
+ (((empty? x) (empty? y)) 0)
+ (else (compare-length (tail x) (tail y)))))
+ (list-compare compare x y empty? head tail)))
+
+ ; for convenience
+ (( x y empty? head tail)
+ (list-compare-as-vector default-compare x y empty? head tail))
+ ((compare x y )
+ (list-compare-as-vector compare x y null? car cdr))
+ (( x y )
+ (list-compare-as-vector default-compare x y null? car cdr))))
+
+(define vector-compare
+ (let ((= =))
+ (case-lambda
+ ((compare x y size ref)
+ (let ((n (size x)) (m (size y)))
+ (refine-compare
+ (integer-compare n m)
+ (let compare-rest ((i 0)) ; compare x[i..n-1] y[i..n-1]
+ (if (= i n)
+ 0
+ (refine-compare (compare (ref x i) (ref y i))
+ (compare-rest (+ i 1))))))))
+
+ ; for convenience
+ (( x y size ref)
+ (vector-compare default-compare x y size ref))
+ ((compare x y )
+ (vector-compare compare x y vector-length vector-ref))
+ (( x y )
+ (vector-compare default-compare x y vector-length vector-ref)))))
+
+(define vector-compare-as-list
+ (let ((= =))
+ (case-lambda
+ ((compare x y size ref)
+ (let ((nx (size x)) (ny (size y)))
+ (let ((n (min nx ny)))
+ (let compare-rest ((i 0)) ; compare x[i..n-1] y[i..n-1]
+ (if (= i n)
+ (integer-compare nx ny)
+ (refine-compare (compare (ref x i) (ref y i))
+ (compare-rest (+ i 1))))))))
+
+ ; for convenience
+ (( x y size ref)
+ (vector-compare-as-list default-compare x y size ref))
+ ((compare x y )
+ (vector-compare-as-list compare x y vector-length vector-ref))
+ (( x y )
+ (vector-compare-as-list default-compare x y vector-length vector-ref)))))
+
+
+; default compare
+
+(define (default-compare x y)
+ (select-compare
+ x y
+ (null? 0)
+ (pair? (default-compare (car x) (car y))
+ (default-compare (cdr x) (cdr y)))
+ (boolean? (boolean-compare x y))
+ (char? (char-compare x y))
+ (string? (string-compare x y))
+ (symbol? (symbol-compare x y))
+ (number? (number-compare x y))
+ (vector? (vector-compare default-compare x y))
+ (else (error "unrecognized type in default-compare" x y))))
+
+; Note that we pass default-compare to compare-{pair,vector} explictly.
+; This makes sure recursion proceeds with this default-compare, which
+; need not be the one in the lexical scope of compare-{pair,vector}.
+
+
+; debug compare
+
+(define (debug-compare c)
+
+ (define (checked-value c x y)
+ (let ((c-xy (c x y)))
+ (if (or (eqv? c-xy -1) (eqv? c-xy 0) (eqv? c-xy 1))
+ c-xy
+ (error "compare value not in {-1,0,1}" c-xy (list c x y)))))
+
+ (define (random-boolean)
+ (zero? (random-integer 2)))
+
+ (define q ; (u v w) such that u <= v, v <= w, and not u <= w
+ '#(
+ ;x < y x = y x > y [x < z]
+ 0 0 0 ; y < z
+ 0 (z y x) (z y x) ; y = z
+ 0 (z y x) (z y x) ; y > z
+
+ ;x < y x = y x > y [x = z]
+ (y z x) (z x y) 0 ; y < z
+ (y z x) 0 (x z y) ; y = z
+ 0 (y x z) (x z y) ; y > z
+
+ ;x < y x = y x > y [x > z]
+ (x y z) (x y z) 0 ; y < z
+ (x y z) (x y z) 0 ; y = z
+ 0 0 0 ; y > z
+ ))
+
+ (let ((z? #f) (z #f)) ; stored element from previous call
+ (lambda (x y)
+ (let ((c-xx (checked-value c x x))
+ (c-yy (checked-value c y y))
+ (c-xy (checked-value c x y))
+ (c-yx (checked-value c y x)))
+ (if (not (zero? c-xx))
+ (error "compare error: not reflexive" c x))
+ (if (not (zero? c-yy))
+ (error "compare error: not reflexive" c y))
+ (if (not (zero? (+ c-xy c-yx)))
+ (error "compare error: not anti-symmetric" c x y))
+ (if z?
+ (let ((c-xz (checked-value c x z))
+ (c-zx (checked-value c z x))
+ (c-yz (checked-value c y z))
+ (c-zy (checked-value c z y)))
+ (if (not (zero? (+ c-xz c-zx)))
+ (error "compare error: not anti-symmetric" c x z))
+ (if (not (zero? (+ c-yz c-zy)))
+ (error "compare error: not anti-symmetric" c y z))
+ (let ((ijk (vector-ref q (+ c-xy (* 3 c-yz) (* 9 c-xz) 13))))
+ (if (list? ijk)
+ (apply error
+ "compare error: not transitive"
+ c
+ (map (lambda (i) (case i ((x) x) ((y) y) ((z) z)))
+ ijk)))))
+ (set! z? #t))
+ (set! z (if (random-boolean) x y)) ; randomized testing
+ c-xy))))
+;;; srfi-69.scm --- Basic hash tables
+
+;; Copyright (C) 2007 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;;; Commentary:
+
+;; My `hash' is compatible with core `hash', so I replace it.
+;; However, my `hash-table?' and `make-hash-table' are different, so
+;; importing this module will warn about them. If you don't rename my
+;; imports, you shouldn't use both my hash tables and Guile's hash
+;; tables in the same module.
+;;
+;; SRFI-13 `string-hash' and `string-hash-ci' have more arguments, but
+;; are compatible with my `string-hash' and `string-ci-hash', and are
+;; furthermore primitive in Guile, so I use them as my own.
+;;
+;; I also have the extension of allowing hash functions that require a
+;; second argument to be used as the `hash-table-hash-function', and use
+;; these in defaults to avoid an indirection in the hashx functions. The
+;; only deviation this causes is:
+;;
+;; ((hash-table-hash-function (make-hash-table)) obj)
+;; error> Wrong number of arguments to #<primitive-procedure hash>
+;;
+;; I don't think that SRFI 69 actually specifies that I *can't* do this,
+;; because it only implies the signature of a hash function by way of the
+;; named, exported hash functions. However, if this matters enough I can
+;; add a private derivation of hash-function to the srfi-69:hash-table
+;; record type, like associator is to equivalence-function.
+;;
+;; Also, outside of the issue of how weak keys and values are referenced
+;; outside the table, I always interpret key equivalence to be that of
+;; the `hash-table-equivalence-function'. For example, given the
+;; requirement that `alist->hash-table' give earlier associations
+;; priority, what should these answer?
+;;
+;; (hash-table-keys
+;; (alist->hash-table '(("xY" . 1) ("Xy" . 2)) string-ci=?))
+;;
+;; (let ((ht (make-hash-table string-ci=?)))
+;; (hash-table-set! ht "xY" 2)
+;; (hash-table-set! ht "Xy" 1)
+;; (hash-table-keys ht))
+;;
+;; My interpretation is that they can answer either ("Xy") or ("xY"),
+;; where `hash-table-values' will of course always answer (1), because
+;; the keys are the same according to the equivalence function. In this
+;; implementation, both answer ("xY"). However, I don't guarantee that
+;; this won't change in the future.
+
+;;; Code:
+
+;;;; Module definition & exports
+
+(define-module (srfi srfi-69)
+ #\use-module (srfi srfi-1) ;alist-cons,second&c,assoc
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-13) ;string-hash,string-hash-ci
+ #\use-module (ice-9 optargs)
+ #\export (;; Type constructors & predicate
+ make-hash-table hash-table? alist->hash-table
+ ;; Reflective queries
+ hash-table-equivalence-function hash-table-hash-function
+ ;; Dealing with single elements
+ hash-table-ref hash-table-ref/default hash-table-set!
+ hash-table-delete! hash-table-exists? hash-table-update!
+ hash-table-update!/default
+ ;; Dealing with the whole contents
+ hash-table-size hash-table-keys hash-table-values
+ hash-table-walk hash-table-fold hash-table->alist
+ hash-table-copy hash-table-merge!
+ ;; Hashing
+ string-ci-hash hash-by-identity)
+ #\re-export (string-hash)
+ #\replace (hash make-hash-table hash-table?))
+
+(cond-expand-provide (current-module) '(srfi-69))
+
+;;;; Internal helper macros
+
+;; Define these first, so the compiler will pick them up.
+
+;; I am a macro only for efficiency, to avoid varargs/apply.
+(define-macro (hashx-invoke hashx-proc ht-var . args)
+ "Invoke HASHX-PROC, a `hashx-*' procedure taking a hash-function,
+assoc-function, and the hash-table as first args."
+ `(,hashx-proc (hash-table-hash-function ,ht-var)
+ (ht-associator ,ht-var)
+ (ht-real-table ,ht-var)
+ . ,args))
+
+(define-macro (with-hashx-values bindings ht-var . body-forms)
+ "Bind BINDINGS to the hash-function, associator, and real-table of
+HT-VAR, while evaluating BODY-FORMS."
+ `(let ((,(first bindings) (hash-table-hash-function ,ht-var))
+ (,(second bindings) (ht-associator ,ht-var))
+ (,(third bindings) (ht-real-table ,ht-var)))
+ . ,body-forms))
+
+
+;;;; Hashing
+
+;;; The largest fixnum is in `most-positive-fixnum' in module (guile),
+;;; though not documented anywhere but libguile/numbers.c.
+
+(define (caller-with-default-size hash-fn)
+ "Answer a function that makes `most-positive-fixnum' the default
+second argument to HASH-FN, a 2-arg procedure."
+ (lambda* (obj #\optional (size most-positive-fixnum))
+ (hash-fn obj size)))
+
+(define hash (caller-with-default-size (@ (guile) hash)))
+
+(define string-ci-hash string-hash-ci)
+
+(define hash-by-identity (caller-with-default-size hashq))
+
+;;;; Reflective queries, construction, predicate
+
+(define-record-type srfi-69:hash-table
+ (make-srfi-69-hash-table real-table associator size weakness
+ equivalence-function hash-function)
+ hash-table?
+ (real-table ht-real-table)
+ (associator ht-associator)
+ ;; required for O(1) by SRFI-69. It really makes a mess of things,
+ ;; and I'd like to compute it in O(n) and memoize it because it
+ ;; doesn't seem terribly useful, but SRFI-69 is final.
+ (size ht-size ht-size!)
+ ;; required for `hash-table-copy'
+ (weakness ht-weakness)
+ ;; used only to implement hash-table-equivalence-function; I don't
+ ;; use it internally other than for `ht-associator'.
+ (equivalence-function hash-table-equivalence-function)
+ (hash-function hash-table-hash-function))
+
+(define (guess-hash-function equal-proc)
+ "Guess a hash function for EQUAL-PROC, falling back on `hash', as
+specified in SRFI-69 for `make-hash-table'."
+ (cond ((eq? equal? equal-proc) (@ (guile) hash)) ;shortcut most common case
+ ((eq? eq? equal-proc) hashq)
+ ((eq? eqv? equal-proc) hashv)
+ ((eq? string=? equal-proc) string-hash)
+ ((eq? string-ci=? equal-proc) string-ci-hash)
+ (else (@ (guile) hash))))
+
+(define (without-keyword-args rest-list)
+ "Answer REST-LIST with all keywords removed along with items that
+follow them."
+ (let lp ((acc '()) (rest-list rest-list))
+ (cond ((null? rest-list) (reverse! acc))
+ ((keyword? (first rest-list))
+ (lp acc (cddr rest-list)))
+ (else (lp (cons (first rest-list) acc) (cdr rest-list))))))
+
+(define (guile-ht-ctor weakness)
+ "Answer the Guile HT constructor for the given WEAKNESS."
+ (case weakness
+ ((#f) (@ (guile) make-hash-table))
+ ((key) make-weak-key-hash-table)
+ ((value) make-weak-value-hash-table)
+ ((key-or-value) make-doubly-weak-hash-table)
+ (else (error "Invalid weak hash table type" weakness))))
+
+(define (equivalence-proc->associator equal-proc)
+ "Answer an `assoc'-like procedure that compares the argument key to
+alist keys with EQUAL-PROC."
+ (cond ((or (eq? equal? equal-proc)
+ (eq? string=? equal-proc)) (@ (guile) assoc))
+ ((eq? eq? equal-proc) assq)
+ ((eq? eqv? equal-proc) assv)
+ (else (lambda (item alist)
+ (assoc item alist equal-proc)))))
+
+(define* (make-hash-table
+ #\optional (equal-proc equal?)
+ (hash-proc (guess-hash-function equal-proc))
+ #\key (weak #f) #\rest guile-opts)
+ "Answer a new hash table using EQUAL-PROC as the comparison
+function, and HASH-PROC as the hash function. See the reference
+manual for specifics, of which there are many."
+ (make-srfi-69-hash-table
+ (apply (guile-ht-ctor weak) (without-keyword-args guile-opts))
+ (equivalence-proc->associator equal-proc)
+ 0 weak equal-proc hash-proc))
+
+(define (alist->hash-table alist . mht-args)
+ "Convert ALIST to a hash table created with MHT-ARGS."
+ (let* ((result (apply make-hash-table mht-args))
+ (size (ht-size result)))
+ (with-hashx-values (hash-proc associator real-table) result
+ (for-each (lambda (pair)
+ (let ((handle (hashx-get-handle hash-proc associator
+ real-table (car pair))))
+ (cond ((not handle)
+ (set! size (1+ size))
+ (hashx-set! hash-proc associator real-table
+ (car pair) (cdr pair))))))
+ alist))
+ (ht-size! result size)
+ result))
+
+;;;; Accessing table items
+
+;; We use this to denote missing or unspecified values to avoid
+;; possible collision with *unspecified*.
+(define ht-unspecified (cons *unspecified* "ht-value"))
+
+(define (hash-table-ref ht key . default-thunk-lst)
+ "Lookup KEY in HT and answer the value, invoke DEFAULT-THUNK if KEY
+isn't present, or signal an error if DEFAULT-THUNK isn't provided."
+ (let ((result (hashx-invoke hashx-ref ht key ht-unspecified)))
+ (if (eq? ht-unspecified result)
+ (if (pair? default-thunk-lst)
+ ((first default-thunk-lst))
+ (error "Key not in table" key ht))
+ result)))
+
+(define (hash-table-ref/default ht key default)
+ "Lookup KEY in HT and answer the value. Answer DEFAULT if KEY isn't
+present."
+ (hashx-invoke hashx-ref ht key default))
+
+(define (hash-table-set! ht key new-value)
+ "Set KEY to NEW-VALUE in HT."
+ (let ((handle (hashx-invoke hashx-create-handle! ht key ht-unspecified)))
+ (if (eq? ht-unspecified (cdr handle))
+ (ht-size! ht (1+ (ht-size ht))))
+ (set-cdr! handle new-value))
+ *unspecified*)
+
+(define (hash-table-delete! ht key)
+ "Remove KEY's association in HT."
+ (with-hashx-values (h a real-ht) ht
+ (if (hashx-get-handle h a real-ht key)
+ (begin
+ (ht-size! ht (1- (ht-size ht)))
+ (hashx-remove! h a real-ht key))))
+ *unspecified*)
+
+(define (hash-table-exists? ht key)
+ "Return whether KEY is a key in HT."
+ (and (hashx-invoke hashx-get-handle ht key) #t))
+
+;;; `hashx-set!' duplicates the hash lookup, but we use it anyway to
+;;; avoid creating a handle in case DEFAULT-THUNK exits
+;;; `hash-table-update!' non-locally.
+(define (hash-table-update! ht key modifier . default-thunk-lst)
+ "Modify HT's value at KEY by passing its value to MODIFIER and
+setting it to the result thereof. Invoke DEFAULT-THUNK for the old
+value if KEY isn't in HT, or signal an error if DEFAULT-THUNK is not
+provided."
+ (with-hashx-values (hash-proc associator real-table) ht
+ (let ((handle (hashx-get-handle hash-proc associator real-table key)))
+ (cond (handle
+ (set-cdr! handle (modifier (cdr handle))))
+ (else
+ (hashx-set! hash-proc associator real-table key
+ (if (pair? default-thunk-lst)
+ (modifier ((car default-thunk-lst)))
+ (error "Key not in table" key ht)))
+ (ht-size! ht (1+ (ht-size ht)))))))
+ *unspecified*)
+
+(define (hash-table-update!/default ht key modifier default)
+ "Modify HT's value at KEY by passing its old value, or DEFAULT if it
+doesn't have one, to MODIFIER, and setting it to the result thereof."
+ (hash-table-update! ht key modifier (lambda () default)))
+
+;;;; Accessing whole tables
+
+(define (hash-table-size ht)
+ "Return the number of associations in HT. This is guaranteed O(1)
+for tables where #:weak was #f or not specified at creation time."
+ (if (ht-weakness ht)
+ (hash-table-fold ht (lambda (k v ans) (1+ ans)) 0)
+ (ht-size ht)))
+
+(define (hash-table-keys ht)
+ "Return a list of the keys in HT."
+ (hash-table-fold ht (lambda (k v lst) (cons k lst)) '()))
+
+(define (hash-table-values ht)
+ "Return a list of the values in HT."
+ (hash-table-fold ht (lambda (k v lst) (cons v lst)) '()))
+
+(define (hash-table-walk ht proc)
+ "Call PROC with each key and value as two arguments."
+ (hash-table-fold ht (lambda (k v unspec)
+ (call-with-values (lambda () (proc k v))
+ (lambda vals unspec)))
+ *unspecified*))
+
+(define (hash-table-fold ht f knil)
+ "Invoke (F KEY VAL PREV) for each KEY and VAL in HT, where PREV is
+the result of the previous invocation, using KNIL as the first PREV.
+Answer the final F result."
+ (hash-fold f knil (ht-real-table ht)))
+
+(define (hash-table->alist ht)
+ "Return an alist for HT."
+ (hash-table-fold ht alist-cons '()))
+
+(define (hash-table-copy ht)
+ "Answer a copy of HT."
+ (with-hashx-values (h a real-ht) ht
+ (let* ((size (hash-table-size ht)) (weak (ht-weakness ht))
+ (new-real-ht ((guile-ht-ctor weak) size)))
+ (hash-fold (lambda (k v ign) (hashx-set! h a new-real-ht k v))
+ #f real-ht)
+ (make-srfi-69-hash-table ;real,assoc,size,weak,equiv,h
+ new-real-ht a size weak
+ (hash-table-equivalence-function ht) h))))
+
+(define (hash-table-merge! ht other-ht)
+ "Add all key/value pairs from OTHER-HT to HT, overriding HT's
+mappings where present. Return HT."
+ (hash-table-fold
+ ht (lambda (k v ign) (hash-table-set! ht k v)) #f)
+ ht)
+
+;;; srfi-69.scm ends here
+;;; srfi-8.scm --- receive
+
+;; Copyright (C) 2000, 2001, 2002, 2006 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This module is fully documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-8)
+ \:use-module (ice-9 receive)
+ \:re-export-syntax (receive))
+
+(cond-expand-provide (current-module) '(srfi-8))
+
+;;; srfi-8.scm ends here
+;;; srfi-88.scm --- Keyword Objects -*- coding: utf-8 -*-
+
+;; Copyright (C) 2008, 2009, 2010 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Ludovic Courtès <ludo@gnu.org>
+
+;;; Commentary:
+
+;; This is a convenience module providing SRFI-88 "keyword object". All it
+;; does is setup the right reader option and export keyword-related
+;; convenience procedures.
+
+;;; Code:
+
+(define-module (srfi srfi-88)
+ #\re-export (keyword?)
+ #\export (keyword->string string->keyword))
+
+(cond-expand-provide (current-module) '(srfi-88))
+
+
+;; Change the keyword syntax both at compile time and run time; the latter is
+;; useful at the REPL.
+(eval-when (expand load eval)
+ (read-set! keywords 'postfix))
+
+(define (keyword->string k)
+ "Return the name of @var{k} as a string."
+ (symbol->string (keyword->symbol k)))
+
+(define (string->keyword s)
+ "Return the keyword object whose name is @var{s}."
+ (symbol->keyword (string->symbol s)))
+
+;;; Local Variables:
+;;; coding: latin-1
+;;; End:
+
+;;; srfi-88.scm ends here
+;;; srfi-9.scm --- define-record-type
+
+;; Copyright (C) 2001, 2002, 2006, 2009, 2010, 2011, 2012,
+;; 2013 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This module exports the syntactic form `define-record-type', which
+;; is the means for creating record types defined in SRFI-9.
+;;
+;; The syntax of a record type definition is:
+;;
+;; <record type definition>
+;; -> (define-record-type <type name>
+;; (<constructor name> <field tag> ...)
+;; <predicate name>
+;; <field spec> ...)
+;;
+;; <field spec> -> (<field tag> <getter name>)
+;; -> (<field tag> <getter name> <setter name>)
+;;
+;; <field tag> -> <identifier>
+;; <... name> -> <identifier>
+;;
+;; Usage example:
+;;
+;; guile> (use-modules (srfi srfi-9))
+;; guile> (define-record-type :foo (make-foo x) foo?
+;; (x get-x) (y get-y set-y!))
+;; guile> (define f (make-foo 1))
+;; guile> f
+;; #<:foo x: 1 y: #f>
+;; guile> (get-x f)
+;; 1
+;; guile> (set-y! f 2)
+;; 2
+;; guile> (get-y f)
+;; 2
+;; guile> f
+;; #<:foo x: 1 y: 2>
+;; guile> (foo? f)
+;; #t
+;; guile> (foo? 1)
+;; #f
+
+;;; Code:
+
+(define-module (srfi srfi-9)
+ #\use-module (srfi srfi-1)
+ #\use-module (system base ck)
+ #\export (define-record-type))
+
+(cond-expand-provide (current-module) '(srfi-9))
+
+;; Roll our own instead of using the public `define-inlinable'. This is
+;; because the public one has a different `make-procedure-name', so
+;; using it would require users to recompile code that uses SRFI-9. See
+;; <http://lists.gnu.org/archive/html/guile-devel/2011-04/msg00111.html>.
+;;
+
+(define-syntax-rule (define-inlinable (name formals ...) body ...)
+ (define-tagged-inlinable () (name formals ...) body ...))
+
+;; 'define-tagged-inlinable' has an additional feature: it stores a map
+;; of keys to values that can be retrieved at expansion time. This is
+;; currently used to retrieve the rtd id, field index, and record copier
+;; macro for an arbitrary getter.
+
+(define-syntax-rule (%%on-error err) err)
+
+(define %%type #f) ; a private syntax literal
+(define-syntax getter-type
+ (syntax-rules (quote)
+ ((_ s 'getter 'err)
+ (getter (%%on-error err) %%type s))))
+
+(define %%index #f) ; a private syntax literal
+(define-syntax getter-index
+ (syntax-rules (quote)
+ ((_ s 'getter 'err)
+ (getter (%%on-error err) %%index s))))
+
+(define %%copier #f) ; a private syntax literal
+(define-syntax getter-copier
+ (syntax-rules (quote)
+ ((_ s 'getter 'err)
+ (getter (%%on-error err) %%copier s))))
+
+(define-syntax define-tagged-inlinable
+ (lambda (x)
+ (define (make-procedure-name name)
+ (datum->syntax name
+ (symbol-append '% (syntax->datum name)
+ '-procedure)))
+
+ (syntax-case x ()
+ ((_ ((key value) ...) (name formals ...) body ...)
+ (identifier? #'name)
+ (with-syntax ((proc-name (make-procedure-name #'name))
+ ((args ...) (generate-temporaries #'(formals ...))))
+ #`(begin
+ (define (proc-name formals ...)
+ body ...)
+ (define-syntax name
+ (lambda (x)
+ (syntax-case x (%%on-error key ...)
+ ((_ (%%on-error err) key s) #'(ck s 'value)) ...
+ ((_ args ...)
+ #'((lambda (formals ...)
+ body ...)
+ args ...))
+ ((_ a (... ...))
+ (syntax-violation 'name "Wrong number of arguments" x))
+ (_
+ (identifier? x)
+ #'proc-name))))))))))
+
+(define (default-record-printer s p)
+ (display "#<" p)
+ (display (record-type-name (record-type-descriptor s)) p)
+ (let loop ((fields (record-type-fields (record-type-descriptor s)))
+ (off 0))
+ (cond
+ ((not (null? fields))
+ (display " " p)
+ (display (car fields) p)
+ (display ": " p)
+ (write (struct-ref s off) p)
+ (loop (cdr fields) (+ 1 off)))))
+ (display ">" p))
+
+(define (throw-bad-struct s who)
+ (throw 'wrong-type-arg who
+ "Wrong type argument: ~S" (list s)
+ (list s)))
+
+(define (make-copier-id type-name)
+ (datum->syntax type-name
+ (symbol-append '%% (syntax->datum type-name)
+ '-set-fields)))
+
+(define-syntax %%set-fields
+ (lambda (x)
+ (syntax-case x ()
+ ((_ type-name (getter-id ...) check? s (getter expr) ...)
+ (every identifier? #'(getter ...))
+ (let ((copier-name (syntax->datum (make-copier-id #'type-name)))
+ (getter+exprs #'((getter expr) ...)))
+ (define (lookup id default-expr)
+ (let ((results
+ (filter (lambda (g+e)
+ (free-identifier=? id (car g+e)))
+ getter+exprs)))
+ (case (length results)
+ ((0) default-expr)
+ ((1) (cadar results))
+ (else (syntax-violation
+ copier-name "duplicate getter" x id)))))
+ (for-each (lambda (id)
+ (or (find (lambda (getter-id)
+ (free-identifier=? id getter-id))
+ #'(getter-id ...))
+ (syntax-violation
+ copier-name "unknown getter" x id)))
+ #'(getter ...))
+ (with-syntax ((unsafe-expr
+ #`(make-struct
+ type-name 0
+ #,@(map (lambda (getter index)
+ (lookup getter #`(struct-ref s #,index)))
+ #'(getter-id ...)
+ (iota (length #'(getter-id ...)))))))
+ (if (syntax->datum #'check?)
+ #`(if (eq? (struct-vtable s) type-name)
+ unsafe-expr
+ (throw-bad-struct
+ s '#,(datum->syntax #'here copier-name)))
+ #'unsafe-expr)))))))
+
+(define-syntax %define-record-type
+ (lambda (x)
+ (define (field-identifiers field-specs)
+ (map (lambda (field-spec)
+ (syntax-case field-spec ()
+ ((name getter) #'name)
+ ((name getter setter) #'name)))
+ field-specs))
+
+ (define (getter-identifiers field-specs)
+ (map (lambda (field-spec)
+ (syntax-case field-spec ()
+ ((name getter) #'getter)
+ ((name getter setter) #'getter)))
+ field-specs))
+
+ (define (constructor form type-name constructor-spec field-names)
+ (syntax-case constructor-spec ()
+ ((ctor field ...)
+ (every identifier? #'(field ...))
+ (let ((ctor-args (map (lambda (field)
+ (let ((name (syntax->datum field)))
+ (or (memq name field-names)
+ (syntax-violation
+ (syntax-case form ()
+ ((macro . args)
+ (syntax->datum #'macro)))
+ "unknown field in constructor spec"
+ form field))
+ (cons name field)))
+ #'(field ...))))
+ #`(define-inlinable #,constructor-spec
+ (make-struct #,type-name 0
+ #,@(map (lambda (name)
+ (assq-ref ctor-args name))
+ field-names)))))))
+
+ (define (getters type-name getter-ids copier-id)
+ (map (lambda (getter index)
+ #`(define-tagged-inlinable
+ ((%%type #,type-name)
+ (%%index #,index)
+ (%%copier #,copier-id))
+ (#,getter s)
+ (if (eq? (struct-vtable s) #,type-name)
+ (struct-ref s #,index)
+ (throw-bad-struct s '#,getter))))
+ getter-ids
+ (iota (length getter-ids))))
+
+ (define (copier type-name getter-ids copier-id)
+ #`(define-syntax-rule
+ (#,copier-id check? s (getter expr) (... ...))
+ (%%set-fields #,type-name #,getter-ids
+ check? s (getter expr) (... ...))))
+
+ (define (setters type-name field-specs)
+ (filter-map (lambda (field-spec index)
+ (syntax-case field-spec ()
+ ((name getter) #f)
+ ((name getter setter)
+ #`(define-inlinable (setter s val)
+ (if (eq? (struct-vtable s) #,type-name)
+ (struct-set! s #,index val)
+ (throw-bad-struct s 'setter))))))
+ field-specs
+ (iota (length field-specs))))
+
+ (define (functional-setters copier-id field-specs)
+ (filter-map (lambda (field-spec index)
+ (syntax-case field-spec ()
+ ((name getter) #f)
+ ((name getter setter)
+ #`(define-inlinable (setter s val)
+ (#,copier-id #t s (getter val))))))
+ field-specs
+ (iota (length field-specs))))
+
+ (define (record-layout immutable? count)
+ (let ((desc (if immutable? "pr" "pw")))
+ (string-concatenate (make-list count desc))))
+
+ (syntax-case x ()
+ ((_ immutable? form type-name constructor-spec predicate-name
+ field-spec ...)
+ (let ()
+ (define (syntax-error message subform)
+ (syntax-violation (syntax-case #'form ()
+ ((macro . args) (syntax->datum #'macro)))
+ message #'form subform))
+ (and (boolean? (syntax->datum #'immutable?))
+ (or (identifier? #'type-name)
+ (syntax-error "expected type name" #'type-name))
+ (syntax-case #'constructor-spec ()
+ ((ctor args ...)
+ (every identifier? #'(ctor args ...))
+ #t)
+ (_ (syntax-error "invalid constructor spec"
+ #'constructor-spec)))
+ (or (identifier? #'predicate-name)
+ (syntax-error "expected predicate name" #'predicate-name))
+ (every (lambda (spec)
+ (syntax-case spec ()
+ ((field getter) #t)
+ ((field getter setter) #t)
+ (_ (syntax-error "invalid field spec" spec))))
+ #'(field-spec ...))))
+ (let* ((field-ids (field-identifiers #'(field-spec ...)))
+ (getter-ids (getter-identifiers #'(field-spec ...)))
+ (field-count (length field-ids))
+ (immutable? (syntax->datum #'immutable?))
+ (layout (record-layout immutable? field-count))
+ (field-names (map syntax->datum field-ids))
+ (ctor-name (syntax-case #'constructor-spec ()
+ ((ctor args ...) #'ctor)))
+ (copier-id (make-copier-id #'type-name)))
+ #`(begin
+ #,(constructor #'form #'type-name #'constructor-spec field-names)
+
+ (define type-name
+ (let ((rtd (make-struct/no-tail
+ record-type-vtable
+ '#,(datum->syntax #'here (make-struct-layout layout))
+ default-record-printer
+ 'type-name
+ '#,field-ids)))
+ (set-struct-vtable-name! rtd 'type-name)
+ (struct-set! rtd (+ 2 vtable-offset-user) #,ctor-name)
+ rtd))
+
+ (define-inlinable (predicate-name obj)
+ (and (struct? obj)
+ (eq? (struct-vtable obj) type-name)))
+
+ #,@(getters #'type-name getter-ids copier-id)
+ #,(copier #'type-name getter-ids copier-id)
+ #,@(if immutable?
+ (functional-setters copier-id #'(field-spec ...))
+ (setters #'type-name #'(field-spec ...))))))
+ ((_ immutable? form . rest)
+ (syntax-violation
+ (syntax-case #'form ()
+ ((macro . args) (syntax->datum #'macro)))
+ "invalid record definition syntax"
+ #'form)))))
+
+(define-syntax-rule (define-record-type name ctor pred fields ...)
+ (%define-record-type #f (define-record-type name ctor pred fields ...)
+ name ctor pred fields ...))
+
+;;; srfi-9.scm ends here
+;;; Extensions to SRFI-9
+
+;; Copyright (C) 2010, 2012 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; Extensions to SRFI-9. Fully documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-9 gnu)
+ #\use-module (srfi srfi-1)
+ #\use-module (system base ck)
+ #\export (set-record-type-printer!
+ define-immutable-record-type
+ set-field
+ set-fields))
+
+(define (set-record-type-printer! type proc)
+ "Set PROC as the custom printer for TYPE."
+ (struct-set! type vtable-index-printer proc))
+
+(define-syntax-rule (define-immutable-record-type name ctor pred fields ...)
+ ((@@ (srfi srfi-9) %define-record-type)
+ #t (define-immutable-record-type name ctor pred fields ...)
+ name ctor pred fields ...))
+
+(define-syntax-rule (set-field s (getter ...) expr)
+ (%set-fields #t (set-field s (getter ...) expr) ()
+ s ((getter ...) expr)))
+
+(define-syntax-rule (set-fields s . rest)
+ (%set-fields #t (set-fields s . rest) ()
+ s . rest))
+
+;;
+;; collate-set-field-specs is a helper for %set-fields
+;; thats combines all specs with the same head together.
+;;
+;; For example:
+;;
+;; SPECS: (((a b c) expr1)
+;; ((a d) expr2)
+;; ((b c) expr3)
+;; ((c) expr4))
+;;
+;; RESULT: ((a ((b c) expr1)
+;; ((d) expr2))
+;; (b ((c) expr3))
+;; (c (() expr4)))
+;;
+(define (collate-set-field-specs specs)
+ (define (insert head tail expr result)
+ (cond ((find (lambda (tree)
+ (free-identifier=? head (car tree)))
+ result)
+ => (lambda (tree)
+ `((,head (,tail ,expr)
+ ,@(cdr tree))
+ ,@(delq tree result))))
+ (else `((,head (,tail ,expr))
+ ,@result))))
+ (with-syntax (((((head . tail) expr) ...) specs))
+ (fold insert '() #'(head ...) #'(tail ...) #'(expr ...))))
+
+(define-syntax unknown-getter
+ (lambda (x)
+ (syntax-case x ()
+ ((_ orig-form getter)
+ (syntax-violation 'set-fields "unknown getter" #'orig-form #'getter)))))
+
+(define-syntax c-list
+ (lambda (x)
+ (syntax-case x (quote)
+ ((_ s 'v ...)
+ #'(ck s '(v ...))))))
+
+(define-syntax c-same-type-check
+ (lambda (x)
+ (syntax-case x (quote)
+ ((_ s 'orig-form '(path ...)
+ '(getter0 getter ...)
+ '(type0 type ...)
+ 'on-success)
+ (every (lambda (t g)
+ (or (free-identifier=? t #'type0)
+ (syntax-violation
+ 'set-fields
+ (format #f
+ "\\
+field paths ~a and ~a require one object to belong to two different record types (~a and ~a)"
+ (syntax->datum #`(path ... #,g))
+ (syntax->datum #'(path ... getter0))
+ (syntax->datum t)
+ (syntax->datum #'type0))
+ #'orig-form)))
+ #'(type ...)
+ #'(getter ...))
+ #'(ck s 'on-success)))))
+
+(define-syntax %set-fields
+ (lambda (x)
+ (with-syntax ((getter-type #'(@@ (srfi srfi-9) getter-type))
+ (getter-index #'(@@ (srfi srfi-9) getter-index))
+ (getter-copier #'(@@ (srfi srfi-9) getter-copier)))
+ (syntax-case x ()
+ ((_ check? orig-form (path-so-far ...)
+ s)
+ #'s)
+ ((_ check? orig-form (path-so-far ...)
+ s (() e))
+ #'e)
+ ((_ check? orig-form (path-so-far ...)
+ struct-expr ((head . tail) expr) ...)
+ (let ((collated-specs (collate-set-field-specs
+ #'(((head . tail) expr) ...))))
+ (with-syntax (((getter0 getter ...)
+ (map car collated-specs)))
+ (with-syntax ((err #'(unknown-getter
+ orig-form getter0)))
+ #`(ck
+ ()
+ (c-same-type-check
+ 'orig-form
+ '(path-so-far ...)
+ '(getter0 getter ...)
+ (c-list (getter-type 'getter0 'err)
+ (getter-type 'getter 'err) ...)
+ '(let ((s struct-expr))
+ ((ck () (getter-copier 'getter0 'err))
+ check?
+ s
+ #,@(map (lambda (spec)
+ (with-syntax (((head (tail expr) ...) spec))
+ (with-syntax ((err #'(unknown-getter
+ orig-form head)))
+ #'(head (%set-fields
+ check?
+ orig-form
+ (path-so-far ... head)
+ (struct-ref s (ck () (getter-index
+ 'head 'err)))
+ (tail expr) ...)))))
+ collated-specs)))))))))
+ ((_ check? orig-form (path-so-far ...)
+ s (() e) (() e*) ...)
+ (syntax-violation 'set-fields "duplicate field path"
+ #'orig-form #'(path-so-far ...)))
+ ((_ check? orig-form (path-so-far ...)
+ s ((getter ...) expr) ...)
+ (syntax-violation 'set-fields "one field path is a prefix of another"
+ #'orig-form #'(path-so-far ...)))
+ ((_ check? orig-form . rest)
+ (syntax-violation 'set-fields "invalid syntax" #'orig-form))))))
+;;; srfi-98.scm --- An interface to access environment variables
+
+;; Copyright (C) 2009 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Author: Julian Graham <julian.graham@aya.yale.edu>
+;;; Date: 2009-05-26
+
+;;; Commentary:
+
+;; This is an implementation of SRFI-98 (An interface to access environment
+;; variables).
+;;
+;; This module is fully documented in the Guile Reference Manual.
+
+;;; Code:
+
+(define-module (srfi srfi-98)
+ \:use-module (srfi srfi-1)
+ \:export (get-environment-variable
+ get-environment-variables))
+
+(cond-expand-provide (current-module) '(srfi-98))
+
+(define get-environment-variable getenv)
+(define (get-environment-variables)
+ (define (string->alist-entry str)
+ (let ((pvt (string-index str #\=))
+ (len (string-length str)))
+ (and pvt (cons (substring str 0 pvt) (substring str (+ pvt 1) len)))))
+ (filter-map string->alist-entry (environ)))
+;;;; (statprof) -- a statistical profiler for Guile
+;;;; -*-scheme-*-
+;;;;
+;;;; Copyright (C) 2009, 2010, 2011, 2015 Free Software Foundation, Inc.
+;;;; Copyright (C) 2004, 2009 Andy Wingo <wingo at pobox dot com>
+;;;; Copyright (C) 2001 Rob Browning <rlb at defaultvalue dot org>
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+;;; Commentary:
+;;
+;;@code{(statprof)} is intended to be a fairly simple
+;;statistical profiler for guile. It is in the early stages yet, so
+;;consider its output still suspect, and please report any bugs to
+;;@email{guile-devel at gnu.org}, or to me directly at @email{rlb at
+;;defaultvalue.org}.
+;;
+;;A simple use of statprof would look like this:
+;;
+;;@example
+;; (statprof-reset 0 50000 #t)
+;; (statprof-start)
+;; (do-something)
+;; (statprof-stop)
+;; (statprof-display)
+;;@end example
+;;
+;;This would reset statprof, clearing all accumulated statistics, then
+;;start profiling, run some code, stop profiling, and finally display a
+;;gprof flat-style table of statistics which will look something like
+;;this:
+;;
+;;@example
+;; % cumulative self self total
+;; time seconds seconds calls ms/call ms/call name
+;; 35.29 0.23 0.23 2002 0.11 0.11 -
+;; 23.53 0.15 0.15 2001 0.08 0.08 positive?
+;; 23.53 0.15 0.15 2000 0.08 0.08 +
+;; 11.76 0.23 0.08 2000 0.04 0.11 do-nothing
+;; 5.88 0.64 0.04 2001 0.02 0.32 loop
+;; 0.00 0.15 0.00 1 0.00 150.59 do-something
+;; ...
+;;@end example
+;;
+;;All of the numerical data with the exception of the calls column is
+;;statistically approximate. In the following column descriptions, and
+;;in all of statprof, "time" refers to execution time (both user and
+;;system), not wall clock time.
+;;
+;;@table @asis
+;;@item % time
+;;The percent of the time spent inside the procedure itself
+;;(not counting children).
+;;@item cumulative seconds
+;;The total number of seconds spent in the procedure, including
+;;children.
+;;@item self seconds
+;;The total number of seconds spent in the procedure itself (not counting
+;;children).
+;;@item calls
+;;The total number of times the procedure was called.
+;;@item self ms/call
+;;The average time taken by the procedure itself on each call, in ms.
+;;@item total ms/call
+;;The average time taken by each call to the procedure, including time
+;;spent in child functions.
+;;@item name
+;;The name of the procedure.
+;;@end table
+;;
+;;The profiler uses @code{eq?} and the procedure object itself to
+;;identify the procedures, so it won't confuse different procedures with
+;;the same name. They will show up as two different rows in the output.
+;;
+;;Right now the profiler is quite simplistic. I cannot provide
+;;call-graphs or other higher level information. What you see in the
+;;table is pretty much all there is. Patches are welcome :-)
+;;
+;;@section Implementation notes
+;;
+;;The profiler works by setting the unix profiling signal
+;;@code{ITIMER_PROF} to go off after the interval you define in the call
+;;to @code{statprof-reset}. When the signal fires, a sampling routine is
+;;run which looks at the current procedure that's executing, and then
+;;crawls up the stack, and for each procedure encountered, increments
+;;that procedure's sample count. Note that if a procedure is encountered
+;;multiple times on a given stack, it is only counted once. After the
+;;sampling is complete, the profiler resets profiling timer to fire
+;;again after the appropriate interval.
+;;
+;;Meanwhile, the profiler keeps track, via @code{get-internal-run-time},
+;;how much CPU time (system and user -- which is also what
+;;@code{ITIMER_PROF} tracks), has elapsed while code has been executing
+;;within a statprof-start/stop block.
+;;
+;;The profiler also tries to avoid counting or timing its own code as
+;;much as possible.
+;;
+;;; Code:
+
+;; When you add new features, please also add tests to ./tests/ if you
+;; have time, and then add the new files to ./run-tests. Also, if
+;; anyone's bored, there are a lot of existing API bits that don't
+;; have tests yet.
+
+;; TODO
+;;
+;; Check about profiling C functions -- does profiling primitives work?
+;; Also look into stealing code from qprof so we can sample the C stack
+;; Call graphs?
+
+(define-module (statprof)
+ #\use-module (srfi srfi-1)
+ #\autoload (ice-9 format) (format)
+ #\use-module (system vm vm)
+ #\use-module (system vm frame)
+ #\use-module (system vm program)
+ #\export (statprof-active?
+ statprof-start
+ statprof-stop
+ statprof-reset
+
+ statprof-accumulated-time
+ statprof-sample-count
+ statprof-fold-call-data
+ statprof-proc-call-data
+ statprof-call-data-name
+ statprof-call-data-calls
+ statprof-call-data-cum-samples
+ statprof-call-data-self-samples
+ statprof-call-data->stats
+
+ statprof-stats-proc-name
+ statprof-stats-%-time-in-proc
+ statprof-stats-cum-secs-in-proc
+ statprof-stats-self-secs-in-proc
+ statprof-stats-calls
+ statprof-stats-self-secs-per-call
+ statprof-stats-cum-secs-per-call
+
+ statprof-display
+ statprof-display-anomolies
+
+ statprof-fetch-stacks
+ statprof-fetch-call-tree
+
+ statprof
+ with-statprof
+
+ gcprof))
+
+
+;; This profiler tracks two numbers for every function called while
+;; it's active. It tracks the total number of calls, and the number
+;; of times the function was active when the sampler fired.
+;;
+;; Globally the profiler tracks the total time elapsed and the number
+;; of times the sampler was fired.
+;;
+;; Right now, this profiler is not per-thread and is not thread safe.
+
+(define accumulated-time #f) ; total so far.
+(define last-start-time #f) ; start-time when timer is active.
+(define sample-count #f) ; total count of sampler calls.
+(define sampling-frequency #f) ; in (seconds . microseconds)
+(define remaining-prof-time #f) ; time remaining when prof suspended.
+(define profile-level 0) ; for user start/stop nesting.
+(define %count-calls? #t) ; whether to catch apply-frame.
+(define gc-time-taken 0) ; gc time between statprof-start and
+ ; statprof-stop.
+(define record-full-stacks? #f) ; if #t, stash away the stacks
+ ; for later analysis.
+(define stacks '())
+
+;; procedure-data will be a hash where the key is the function object
+;; itself and the value is the data. The data will be a vector like
+;; this: #(name call-count cum-sample-count self-sample-count)
+(define procedure-data #f)
+
+;; If you change the call-data data structure, you need to also change
+;; sample-uncount-frame.
+(define (make-call-data proc call-count cum-sample-count self-sample-count)
+ (vector proc call-count cum-sample-count self-sample-count))
+(define (call-data-proc cd) (vector-ref cd 0))
+(define (call-data-name cd) (procedure-name (call-data-proc cd)))
+(define (call-data-printable cd)
+ (or (call-data-name cd)
+ (with-output-to-string (lambda () (write (call-data-proc cd))))))
+(define (call-data-call-count cd) (vector-ref cd 1))
+(define (call-data-cum-sample-count cd) (vector-ref cd 2))
+(define (call-data-self-sample-count cd) (vector-ref cd 3))
+
+(define (inc-call-data-call-count! cd)
+ (vector-set! cd 1 (1+ (vector-ref cd 1))))
+(define (inc-call-data-cum-sample-count! cd)
+ (vector-set! cd 2 (1+ (vector-ref cd 2))))
+(define (inc-call-data-self-sample-count! cd)
+ (vector-set! cd 3 (1+ (vector-ref cd 3))))
+
+(define-macro (accumulate-time stop-time)
+ `(set! accumulated-time
+ (+ accumulated-time 0.0 (- ,stop-time last-start-time))))
+
+(define (get-call-data proc)
+ (let ((k (if (or (not (program? proc))
+ (zero? (program-num-free-variables proc)))
+ proc
+ (program-objcode proc))))
+ (or (hashq-ref procedure-data k)
+ (let ((call-data (make-call-data proc 0 0 0)))
+ (hashq-set! procedure-data k call-data)
+ call-data))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; SIGPROF handler
+
+(define (sample-stack-procs stack)
+ (let ((stacklen (stack-length stack))
+ (hit-count-call? #f))
+
+ (if record-full-stacks?
+ (set! stacks (cons stack stacks)))
+
+ (set! sample-count (+ sample-count 1))
+ ;; Now accumulate stats for the whole stack.
+ (let loop ((frame (stack-ref stack 0))
+ (procs-seen (make-hash-table 13))
+ (self #f))
+ (cond
+ ((not frame)
+ (hash-fold
+ (lambda (proc val accum)
+ (inc-call-data-cum-sample-count!
+ (get-call-data proc)))
+ #f
+ procs-seen)
+ (and=> (and=> self get-call-data)
+ inc-call-data-self-sample-count!))
+ ((frame-procedure frame)
+ => (lambda (proc)
+ (cond
+ ((eq? proc count-call)
+ ;; We're not supposed to be sampling count-call and
+ ;; its sub-functions, so loop again with a clean
+ ;; slate.
+ (set! hit-count-call? #t)
+ (loop (frame-previous frame) (make-hash-table 13) #f))
+ (else
+ (hashq-set! procs-seen proc #t)
+ (loop (frame-previous frame)
+ procs-seen
+ (or self proc))))))
+ (else
+ (loop (frame-previous frame) procs-seen self))))
+ hit-count-call?))
+
+(define inside-profiler? #f)
+
+(define (profile-signal-handler sig)
+ (set! inside-profiler? #t)
+
+ ;; FIXME: with-statprof should be able to set an outer frame for the
+ ;; stack cut
+ (if (positive? profile-level)
+ (let* ((stop-time (get-internal-run-time))
+ ;; cut down to the signal handler. note that this will only
+ ;; work if statprof.scm is compiled; otherwise we get
+ ;; `eval' on the stack instead, because if it's not
+ ;; compiled, profile-signal-handler is a thunk that
+ ;; tail-calls eval. perhaps we should always compile the
+ ;; signal handler instead...
+ (stack (or (make-stack #t profile-signal-handler)
+ (pk 'what! (make-stack #t))))
+ (inside-apply-trap? (sample-stack-procs stack)))
+
+ (if (not inside-apply-trap?)
+ (begin
+ ;; disabling here is just a little more efficient, but
+ ;; not necessary given inside-profiler?. We can't just
+ ;; disable unconditionally at the top of this function
+ ;; and eliminate inside-profiler? because it seems to
+ ;; confuse guile wrt re-enabling the trap when
+ ;; count-call finishes.
+ (if %count-calls?
+ (set-vm-trace-level! (the-vm)
+ (1- (vm-trace-level (the-vm)))))
+ (accumulate-time stop-time)))
+
+ (setitimer ITIMER_PROF
+ 0 0
+ (car sampling-frequency)
+ (cdr sampling-frequency))
+
+ (if (not inside-apply-trap?)
+ (begin
+ (set! last-start-time (get-internal-run-time))
+ (if %count-calls?
+ (set-vm-trace-level! (the-vm)
+ (1+ (vm-trace-level (the-vm)))))))))
+
+ (set! inside-profiler? #f))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Count total calls.
+
+(define (count-call frame)
+ (if (not inside-profiler?)
+ (begin
+ (accumulate-time (get-internal-run-time))
+
+ (and=> (frame-procedure frame)
+ (lambda (proc)
+ (inc-call-data-call-count!
+ (get-call-data proc))))
+
+ (set! last-start-time (get-internal-run-time)))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (statprof-active?)
+ "Returns @code{#t} if @code{statprof-start} has been called more times
+than @code{statprof-stop}, @code{#f} otherwise."
+ (positive? profile-level))
+
+;; Do not call this from statprof internal functions -- user only.
+(define (statprof-start)
+ "Start the profiler.@code{}"
+ ;; After some head-scratching, I don't *think* I need to mask/unmask
+ ;; signals here, but if I'm wrong, please let me know.
+ (set! profile-level (+ profile-level 1))
+ (if (= profile-level 1)
+ (let* ((rpt remaining-prof-time)
+ (use-rpt? (and rpt
+ (or (positive? (car rpt))
+ (positive? (cdr rpt))))))
+ (set! remaining-prof-time #f)
+ (set! last-start-time (get-internal-run-time))
+ (set! gc-time-taken
+ (cdr (assq 'gc-time-taken (gc-stats))))
+ (if use-rpt?
+ (setitimer ITIMER_PROF 0 0 (car rpt) (cdr rpt))
+ (setitimer ITIMER_PROF
+ 0 0
+ (car sampling-frequency)
+ (cdr sampling-frequency)))
+ (if %count-calls?
+ (add-hook! (vm-apply-hook (the-vm)) count-call))
+ (set-vm-trace-level! (the-vm) (1+ (vm-trace-level (the-vm))))
+ #t)))
+
+;; Do not call this from statprof internal functions -- user only.
+(define (statprof-stop)
+ "Stop the profiler.@code{}"
+ ;; After some head-scratching, I don't *think* I need to mask/unmask
+ ;; signals here, but if I'm wrong, please let me know.
+ (set! profile-level (- profile-level 1))
+ (if (zero? profile-level)
+ (begin
+ (set! gc-time-taken
+ (- (cdr (assq 'gc-time-taken (gc-stats))) gc-time-taken))
+ (set-vm-trace-level! (the-vm) (1- (vm-trace-level (the-vm))))
+ (if %count-calls?
+ (remove-hook! (vm-apply-hook (the-vm)) count-call))
+ ;; I believe that we need to do this before getting the time
+ ;; (unless we want to make things even more complicated).
+ (set! remaining-prof-time (setitimer ITIMER_PROF 0 0 0 0))
+ (accumulate-time (get-internal-run-time))
+ (set! last-start-time #f))))
+
+(define* (statprof-reset sample-seconds sample-microseconds count-calls?
+ #\optional full-stacks?)
+ "Reset the statprof sampler interval to @var{sample-seconds} and
+@var{sample-microseconds}. If @var{count-calls?} is true, arrange to
+instrument procedure calls as well as collecting statistical profiling
+data. If @var{full-stacks?} is true, collect all sampled stacks into a
+list for later analysis.
+
+Enables traps and debugging as necessary."
+ (if (positive? profile-level)
+ (error "Can't reset profiler while profiler is running."))
+ (set! %count-calls? count-calls?)
+ (set! accumulated-time 0)
+ (set! last-start-time #f)
+ (set! sample-count 0)
+ (set! sampling-frequency (cons sample-seconds sample-microseconds))
+ (set! remaining-prof-time #f)
+ (set! procedure-data (make-hash-table 131))
+ (set! record-full-stacks? full-stacks?)
+ (set! stacks '())
+ (sigaction SIGPROF profile-signal-handler)
+ #t)
+
+(define (statprof-fold-call-data proc init)
+ "Fold @var{proc} over the call-data accumulated by statprof. Cannot be
+called while statprof is active. @var{proc} should take two arguments,
+@code{(@var{call-data} @var{prior-result})}.
+
+Note that a given proc-name may appear multiple times, but if it does,
+it represents different functions with the same name."
+ (if (positive? profile-level)
+ (error "Can't call statprof-fold-called while profiler is running."))
+
+ (hash-fold
+ (lambda (key value prior-result)
+ (proc value prior-result))
+ init
+ procedure-data))
+
+(define (statprof-proc-call-data proc)
+ "Returns the call-data associated with @var{proc}, or @code{#f} if
+none is available."
+ (if (positive? profile-level)
+ (error "Can't call statprof-fold-called while profiler is running."))
+
+ (hashq-ref procedure-data proc))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Stats
+
+(define (statprof-call-data->stats call-data)
+ "Returns an object of type @code{statprof-stats}."
+ ;; returns (vector proc-name
+ ;; %-time-in-proc
+ ;; cum-seconds-in-proc
+ ;; self-seconds-in-proc
+ ;; num-calls
+ ;; self-secs-per-call
+ ;; total-secs-per-call)
+
+ (let* ((proc-name (call-data-printable call-data))
+ (self-samples (call-data-self-sample-count call-data))
+ (cum-samples (call-data-cum-sample-count call-data))
+ (all-samples (statprof-sample-count))
+ (secs-per-sample (/ (statprof-accumulated-time)
+ (statprof-sample-count)))
+ (num-calls (and %count-calls? (statprof-call-data-calls call-data))))
+
+ (vector proc-name
+ (* (/ self-samples all-samples) 100.0)
+ (* cum-samples secs-per-sample 1.0)
+ (* self-samples secs-per-sample 1.0)
+ num-calls
+ (and num-calls ;; maybe we only sampled in children
+ (if (zero? self-samples) 0.0
+ (/ (* self-samples secs-per-sample) 1.0 num-calls)))
+ (and num-calls ;; cum-samples must be positive
+ (/ (* cum-samples secs-per-sample)
+ 1.0
+ ;; num-calls might be 0 if we entered statprof during the
+ ;; dynamic extent of the call
+ (max num-calls 1))))))
+
+(define (statprof-stats-proc-name stats) (vector-ref stats 0))
+(define (statprof-stats-%-time-in-proc stats) (vector-ref stats 1))
+(define (statprof-stats-cum-secs-in-proc stats) (vector-ref stats 2))
+(define (statprof-stats-self-secs-in-proc stats) (vector-ref stats 3))
+(define (statprof-stats-calls stats) (vector-ref stats 4))
+(define (statprof-stats-self-secs-per-call stats) (vector-ref stats 5))
+(define (statprof-stats-cum-secs-per-call stats) (vector-ref stats 6))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (stats-sorter x y)
+ (let ((diff (- (statprof-stats-self-secs-in-proc x)
+ (statprof-stats-self-secs-in-proc y))))
+ (positive?
+ (if (= diff 0)
+ (- (statprof-stats-cum-secs-in-proc x)
+ (statprof-stats-cum-secs-in-proc y))
+ diff))))
+
+(define (statprof-display . port)
+ "Displays a gprof-like summary of the statistics collected. Unless an
+optional @var{port} argument is passed, uses the current output port."
+ (if (null? port) (set! port (current-output-port)))
+
+ (cond
+ ((zero? (statprof-sample-count))
+ (format port "No samples recorded.\n"))
+ (else
+ (let* ((stats-list (statprof-fold-call-data
+ (lambda (data prior-value)
+ (cons (statprof-call-data->stats data)
+ prior-value))
+ '()))
+ (sorted-stats (sort stats-list stats-sorter)))
+
+ (define (display-stats-line stats)
+ (if %count-calls?
+ (format port "~6,2f ~9,2f ~9,2f ~7d ~8,2f ~8,2f "
+ (statprof-stats-%-time-in-proc stats)
+ (statprof-stats-cum-secs-in-proc stats)
+ (statprof-stats-self-secs-in-proc stats)
+ (statprof-stats-calls stats)
+ (* 1000 (statprof-stats-self-secs-per-call stats))
+ (* 1000 (statprof-stats-cum-secs-per-call stats)))
+ (format port "~6,2f ~9,2f ~9,2f "
+ (statprof-stats-%-time-in-proc stats)
+ (statprof-stats-cum-secs-in-proc stats)
+ (statprof-stats-self-secs-in-proc stats)))
+ (display (statprof-stats-proc-name stats) port)
+ (newline port))
+
+ (if %count-calls?
+ (begin
+ (format port "~5a ~10a ~7a ~8a ~8a ~8a ~8@a\n"
+ "% " "cumulative" "self" "" "self" "total" "")
+ (format port "~5a ~9a ~8a ~8a ~8a ~8a ~8@a\n"
+ "time" "seconds" "seconds" "calls" "ms/call" "ms/call" "name"))
+ (begin
+ (format port "~5a ~10a ~7a ~8@a\n"
+ "%" "cumulative" "self" "")
+ (format port "~5a ~10a ~7a ~8@a\n"
+ "time" "seconds" "seconds" "name")))
+
+ (for-each display-stats-line sorted-stats)
+
+ (display "---\n" port)
+ (simple-format #t "Sample count: ~A\n" (statprof-sample-count))
+ (simple-format #t "Total time: ~A seconds (~A seconds in GC)\n"
+ (statprof-accumulated-time)
+ (/ gc-time-taken 1.0 internal-time-units-per-second))))))
+
+(define (statprof-display-anomolies)
+ "A sanity check that attempts to detect anomolies in statprof's
+statistics.@code{}"
+ (statprof-fold-call-data
+ (lambda (data prior-value)
+ (if (and %count-calls?
+ (zero? (call-data-call-count data))
+ (positive? (call-data-cum-sample-count data)))
+ (simple-format #t
+ "==[~A ~A ~A]\n"
+ (call-data-name data)
+ (call-data-call-count data)
+ (call-data-cum-sample-count data))))
+ #f)
+ (simple-format #t "Total time: ~A\n" (statprof-accumulated-time))
+ (simple-format #t "Sample count: ~A\n" (statprof-sample-count)))
+
+(define (statprof-accumulated-time)
+ "Returns the time accumulated during the last statprof run.@code{}"
+ (if (positive? profile-level)
+ (error "Can't get accumulated time while profiler is running."))
+ (/ accumulated-time internal-time-units-per-second))
+
+(define (statprof-sample-count)
+ "Returns the number of samples taken during the last statprof run.@code{}"
+ (if (positive? profile-level)
+ (error "Can't get accumulated time while profiler is running."))
+ sample-count)
+
+(define statprof-call-data-name call-data-name)
+(define statprof-call-data-calls call-data-call-count)
+(define statprof-call-data-cum-samples call-data-cum-sample-count)
+(define statprof-call-data-self-samples call-data-self-sample-count)
+
+(define (statprof-fetch-stacks)
+ "Returns a list of stacks, as they were captured since the last call
+to @code{statprof-reset}.
+
+Note that stacks are only collected if the @var{full-stacks?} argument
+to @code{statprof-reset} is true."
+ stacks)
+
+(define procedure=?
+ (lambda (a b)
+ (cond
+ ((eq? a b))
+ ((and (program? a) (program? b))
+ (eq? (program-objcode a) (program-objcode b)))
+ (else
+ #f))))
+
+;; tree ::= (car n . tree*)
+
+(define (lists->trees lists equal?)
+ (let lp ((in lists) (n-terminal 0) (tails '()))
+ (cond
+ ((null? in)
+ (let ((trees (map (lambda (tail)
+ (cons (car tail)
+ (lists->trees (cdr tail) equal?)))
+ tails)))
+ (cons (apply + n-terminal (map cadr trees))
+ (sort trees
+ (lambda (a b) (> (cadr a) (cadr b)))))))
+ ((null? (car in))
+ (lp (cdr in) (1+ n-terminal) tails))
+ ((find (lambda (x) (equal? (car x) (caar in)))
+ tails)
+ => (lambda (tail)
+ (lp (cdr in)
+ n-terminal
+ (assq-set! tails
+ (car tail)
+ (cons (cdar in) (cdr tail))))))
+ (else
+ (lp (cdr in)
+ n-terminal
+ (acons (caar in) (list (cdar in)) tails))))))
+
+(define (stack->procedures stack)
+ (filter identity
+ (unfold-right (lambda (x) (not x))
+ frame-procedure
+ frame-previous
+ (stack-ref stack 0))))
+
+(define (statprof-fetch-call-tree)
+ "Return a call tree for the previous statprof run.
+
+The return value is a list of nodes, each of which is of the type:
+@code
+ node ::= (@var{proc} @var{count} . @var{nodes})
+@end code"
+ (cons #t (lists->trees (map stack->procedures stacks) procedure=?)))
+
+(define* (statprof thunk #\key (loop 1) (hz 100) (count-calls? #f)
+ (full-stacks? #f))
+ "Profile the execution of @var{thunk}, and return its return values.
+
+The stack will be sampled @var{hz} times per second, and the thunk
+itself will be called @var{loop} times.
+
+If @var{count-calls?} is true, all procedure calls will be recorded. This
+operation is somewhat expensive.
+
+If @var{full-stacks?} is true, at each sample, statprof will store away the
+whole call tree, for later analysis. Use @code{statprof-fetch-stacks} or
+@code{statprof-fetch-call-tree} to retrieve the last-stored stacks."
+ (dynamic-wind
+ (lambda ()
+ (statprof-reset (inexact->exact (floor (/ 1 hz)))
+ (inexact->exact (* 1e6 (- (/ 1 hz)
+ (floor (/ 1 hz)))))
+ count-calls?
+ full-stacks?)
+ (statprof-start))
+ (lambda ()
+ (let lp ((i loop)
+ (result '()))
+ (if (zero? i)
+ (apply values result)
+ (call-with-values thunk
+ (lambda result
+ (lp (1- i) result))))))
+ (lambda ()
+ (statprof-stop)
+ (statprof-display)
+ (set! procedure-data #f))))
+
+(define-macro (with-statprof . args)
+ "Profile the expressions in the body, and return the body's return values.
+
+Keyword arguments:
+
+@table @code
+@item #:loop
+Execute the body @var{loop} number of times, or @code{#f} for no looping
+
+default: @code{#f}
+@item #:hz
+Sampling rate
+
+default: @code{20}
+@item #:count-calls?
+Whether to instrument each function call (expensive)
+
+default: @code{#f}
+@item #:full-stacks?
+Whether to collect away all sampled stacks into a list
+
+default: @code{#f}
+@end table"
+ (define (kw-arg-ref kw args def)
+ (cond
+ ((null? args) (error "Invalid macro body"))
+ ((keyword? (car args))
+ (if (eq? (car args) kw)
+ (cadr args)
+ (kw-arg-ref kw (cddr args) def)))
+ ((eq? kw #f def) ;; asking for the body
+ args)
+ (else def))) ;; kw not found
+ `((@ (statprof) statprof)
+ (lambda () ,@(kw-arg-ref #f args #f))
+ #\loop ,(kw-arg-ref #\loop args 1)
+ #\hz ,(kw-arg-ref #\hz args 100)
+ #\count-calls? ,(kw-arg-ref #\count-calls? args #f)
+ #\full-stacks? ,(kw-arg-ref #\full-stacks? args #f)))
+
+(define* (gcprof thunk #\key (loop 1) (full-stacks? #f))
+ "Do an allocation profile of the execution of @var{thunk}.
+
+The stack will be sampled soon after every garbage collection, yielding
+an approximate idea of what is causing allocation in your program.
+
+Since GC does not occur very frequently, you may need to use the
+@var{loop} parameter, to cause @var{thunk} to be called @var{loop}
+times.
+
+If @var{full-stacks?} is true, at each sample, statprof will store away the
+whole call tree, for later analysis. Use @code{statprof-fetch-stacks} or
+@code{statprof-fetch-call-tree} to retrieve the last-stored stacks."
+
+ (define (reset)
+ (if (positive? profile-level)
+ (error "Can't reset profiler while profiler is running."))
+ (set! accumulated-time 0)
+ (set! last-start-time #f)
+ (set! sample-count 0)
+ (set! %count-calls? #f)
+ (set! procedure-data (make-hash-table 131))
+ (set! record-full-stacks? full-stacks?)
+ (set! stacks '()))
+
+ (define (gc-callback)
+ (cond
+ (inside-profiler?)
+ (else
+ (set! inside-profiler? #t)
+
+ ;; FIXME: should be able to set an outer frame for the stack cut
+ (let ((stop-time (get-internal-run-time))
+ ;; Cut down to gc-callback, and then one before (the
+ ;; after-gc async). See the note in profile-signal-handler
+ ;; also.
+ (stack (or (make-stack #t gc-callback 0 1)
+ (pk 'what! (make-stack #t)))))
+ (sample-stack-procs stack)
+ (accumulate-time stop-time)
+ (set! last-start-time (get-internal-run-time)))
+
+ (set! inside-profiler? #f))))
+
+ (define (start)
+ (set! profile-level (+ profile-level 1))
+ (if (= profile-level 1)
+ (begin
+ (set! remaining-prof-time #f)
+ (set! last-start-time (get-internal-run-time))
+ (set! gc-time-taken (cdr (assq 'gc-time-taken (gc-stats))))
+ (add-hook! after-gc-hook gc-callback)
+ (set-vm-trace-level! (the-vm) (1+ (vm-trace-level (the-vm))))
+ #t)))
+
+ (define (stop)
+ (set! profile-level (- profile-level 1))
+ (if (zero? profile-level)
+ (begin
+ (set! gc-time-taken
+ (- (cdr (assq 'gc-time-taken (gc-stats))) gc-time-taken))
+ (remove-hook! after-gc-hook gc-callback)
+ (accumulate-time (get-internal-run-time))
+ (set! last-start-time #f))))
+
+ (dynamic-wind
+ (lambda ()
+ (reset)
+ (start))
+ (lambda ()
+ (let lp ((i loop))
+ (if (not (zero? i))
+ (begin
+ (thunk)
+ (lp (1- i))))))
+ (lambda ()
+ (stop)
+ (statprof-display)
+ (set! procedure-data #f))))
+;;;; (sxml apply-templates) -- xslt-like transformation for sxml
+;;;;
+;;;; Copyright (C) 2009 Free Software Foundation, Inc.
+;;;; Copyright 2004 by Andy Wingo <wingo at pobox dot com>.
+;;;; Written 2003 by Oleg Kiselyov <oleg at pobox dot com> as apply-templates.scm.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+;;
+;; Pre-order traversal of a tree and creation of a new tree:
+;;
+;;@smallexample
+;; apply-templates:: tree x <templates> -> <new-tree>
+;;@end smallexample
+;; where
+;;@smallexample
+;; <templates> ::= (<template> ...)
+;; <template> ::= (<node-test> <node-test> ... <node-test> . <handler>)
+;; <node-test> ::= an argument to node-typeof? above
+;; <handler> ::= <tree> -> <new-tree>
+;;@end smallexample
+;;
+;; This procedure does a @emph{normal}, pre-order traversal of an SXML
+;; tree. It walks the tree, checking at each node against the list of
+;; matching templates.
+;;
+;; If the match is found (which must be unique, i.e., unambiguous), the
+;; corresponding handler is invoked and given the current node as an
+;; argument. The result from the handler, which must be a @code{<tree>},
+;; takes place of the current node in the resulting tree.
+;;
+;; The name of the function is not accidental: it resembles rather
+;; closely an @code{apply-templates} function of XSLT.
+;;
+;;; Code:
+
+(define-module (sxml apply-templates)
+ #\use-module (sxml ssax)
+ #\use-module ((sxml xpath) \:hide (filter))
+
+ #\export (apply-templates))
+
+(define (apply-templates tree templates)
+
+ ; Filter the list of templates. If a template does not
+ ; contradict the given node (that is, its head matches
+ ; the type of the node), chop off the head and keep the
+ ; rest as the result. All contradicting templates are removed.
+ (define (filter-templates node templates)
+ (cond
+ ((null? templates) templates)
+ ((not (pair? (car templates))) ; A good template must be a list
+ (filter-templates node (cdr templates)))
+ (((node-typeof? (caar templates)) node)
+ (cons (cdar templates) (filter-templates node (cdr templates))))
+ (else
+ (filter-templates node (cdr templates)))))
+
+ ; Here <templates> ::= [<template> | <handler>]
+ ; If there is a <handler> in the above list, it must
+ ; be only one. If found, return it; otherwise, return #f
+ (define (find-handler templates)
+ (and (pair? templates)
+ (cond
+ ((procedure? (car templates))
+ (if (find-handler (cdr templates))
+ (error "ambiguous template match"))
+ (car templates))
+ (else (find-handler (cdr templates))))))
+
+ (let loop ((tree tree) (active-templates '()))
+ ;(cout "active-templates: " active-templates nl "tree: " tree nl)
+ (if (nodeset? tree)
+ (map-union (lambda (a-tree) (loop a-tree active-templates)) tree)
+ (let ((still-active-templates
+ (append
+ (filter-templates tree active-templates)
+ (filter-templates tree templates))))
+ (cond
+ ;((null? still-active-templates) '())
+ ((find-handler still-active-templates) =>
+ (lambda (handler) (handler tree)))
+ ((not (pair? tree)) '())
+ (else
+ (loop (cdr tree) still-active-templates)))))))
+
+;;; arch-tag: 88cd87de-8825-4ab3-9721-cf99694fb787
+;;; templates.scm ends here
+;;;; (sxml fold) -- transformation of sxml via fold operations
+;;;;
+;;;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+;;;; Written 2007 by Andy Wingo <wingo at pobox dot com>.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+;;
+;; @code{(sxml fold)} defines a number of variants of the @dfn{fold}
+;; algorithm for use in transforming SXML trees. Additionally it defines
+;; the layout operator, @code{fold-layout}, which might be described as
+;; a context-passing variant of SSAX's @code{pre-post-order}.
+;;
+;;; Code:
+
+(define-module (sxml fold)
+ #\use-module (srfi srfi-1)
+ #\export (foldt
+ foldts
+ foldts*
+ fold-values
+ foldts*-values
+ fold-layout))
+
+(define (atom? x)
+ (not (pair? x)))
+
+(define (foldt fup fhere tree)
+ "The standard multithreaded tree fold.
+
+@var{fup} is of type [a] -> a. @var{fhere} is of type object -> a.
+"
+ (if (atom? tree)
+ (fhere tree)
+ (fup (map (lambda (kid)
+ (foldt fup fhere kid))
+ tree))))
+
+(define (foldts fdown fup fhere seed tree)
+ "The single-threaded tree fold originally defined in SSAX.
+@xref{sxml ssax,,(sxml ssax)}, for more information."
+ (if (atom? tree)
+ (fhere seed tree)
+ (fup seed
+ (fold (lambda (kid kseed)
+ (foldts fdown fup fhere kseed kid))
+ (fdown seed tree)
+ tree)
+ tree)))
+
+(define (foldts* fdown fup fhere seed tree)
+ "A variant of @ref{sxml fold foldts,,foldts} that allows pre-order
+tree rewrites. Originally defined in Andy Wingo's 2007 paper,
+@emph{Applications of fold to XML transformation}."
+ (if (atom? tree)
+ (fhere seed tree)
+ (call-with-values
+ (lambda () (fdown seed tree))
+ (lambda (kseed tree)
+ (fup seed
+ (fold (lambda (kid kseed)
+ (foldts* fdown fup fhere
+ kseed kid))
+ kseed
+ tree)
+ tree)))))
+
+(define (fold-values proc list . seeds)
+ "A variant of @ref{SRFI-1 Fold and Map, fold} that allows multi-valued
+seeds. Note that the order of the arguments differs from that of
+@code{fold}."
+ (if (null? list)
+ (apply values seeds)
+ (call-with-values
+ (lambda () (apply proc (car list) seeds))
+ (lambda seeds
+ (apply fold-values proc (cdr list) seeds)))))
+
+(define (foldts*-values fdown fup fhere tree . seeds)
+ "A variant of @ref{sxml fold foldts*,,foldts*} that allows
+multi-valued seeds. Originally defined in Andy Wingo's 2007 paper,
+@emph{Applications of fold to XML transformation}."
+ (if (atom? tree)
+ (apply fhere tree seeds)
+ (call-with-values
+ (lambda () (apply fdown tree seeds))
+ (lambda (tree . kseeds)
+ (call-with-values
+ (lambda ()
+ (apply fold-values
+ (lambda (tree . seeds)
+ (apply foldts*-values
+ fdown fup fhere tree seeds))
+ tree kseeds))
+ (lambda kseeds
+ (apply fup tree (append seeds kseeds))))))))
+
+(define (assq-ref alist key default)
+ (cond ((assq key alist) => cdr)
+ (else default)))
+
+(define (fold-layout tree bindings params layout stylesheet)
+ "A traversal combinator in the spirit of SSAX's @ref{sxml transform
+pre-post-order,,pre-post-order}.
+
+@code{fold-layout} was originally presented in Andy Wingo's 2007 paper,
+@emph{Applications of fold to XML transformation}.
+
+@example
+bindings := (<binding>...)
+binding := (<tag> <bandler-pair>...)
+ | (*default* . <post-handler>)
+ | (*text* . <text-handler>)
+tag := <symbol>
+handler-pair := (pre-layout . <pre-layout-handler>)
+ | (post . <post-handler>)
+ | (bindings . <bindings>)
+ | (pre . <pre-handler>)
+ | (macro . <macro-handler>)
+@end example
+
+@table @var
+@item pre-layout-handler
+A function of three arguments:
+
+@table @var
+@item kids
+the kids of the current node, before traversal
+@item params
+the params of the current node
+@item layout
+the layout coming into this node
+@end table
+
+@var{pre-layout-handler} is expected to use this information to return a
+layout to pass to the kids. The default implementation returns the
+layout given in the arguments.
+
+@item post-handler
+A function of five arguments:
+@table @var
+@item tag
+the current tag being processed
+@item params
+the params of the current node
+@item layout
+the layout coming into the current node, before any kids were processed
+@item klayout
+the layout after processing all of the children
+@item kids
+the already-processed child nodes
+@end table
+
+@var{post-handler} should return two values, the layout to pass to the
+next node and the final tree.
+
+@item text-handler
+@var{text-handler} is a function of three arguments:
+@table @var
+@item text
+the string
+@item params
+the current params
+@item layout
+the current layout
+@end table
+
+@var{text-handler} should return two values, the layout to pass to the
+next node and the value to which the string should transform.
+@end table
+"
+ (define (err . args)
+ (error "no binding available" args))
+ (define (fdown tree bindings pcont params layout ret)
+ (define (fdown-helper new-bindings new-layout cont)
+ (let ((cont-with-tag (lambda args
+ (apply cont (car tree) args)))
+ (bindings (if new-bindings
+ (append new-bindings bindings)
+ bindings))
+ (style-params (assq-ref stylesheet (car tree) '())))
+ (cond
+ ((null? (cdr tree))
+ (values
+ '() bindings cont-with-tag (cons style-params params) new-layout '()))
+ ((and (pair? (cadr tree)) (eq? (caadr tree) '@))
+ (let ((params (cons (append (cdadr tree) style-params) params)))
+ (values
+ (cddr tree) bindings cont-with-tag params new-layout '())))
+ (else
+ (values
+ (cdr tree) bindings cont-with-tag (cons style-params params) new-layout '())))))
+ (define (no-bindings)
+ (fdown-helper #f layout (assq-ref bindings '*default* err)))
+ (define (macro macro-handler)
+ (fdown (apply macro-handler tree)
+ bindings pcont params layout ret))
+ (define (pre pre-handler)
+ (values '() bindings
+ (lambda (params layout old-layout kids)
+ (values layout (reverse kids)))
+ params layout (apply pre-handler tree)))
+ (define (have-bindings tag-bindings)
+ (fdown-helper
+ (assq-ref tag-bindings 'bindings #f)
+ ((assq-ref tag-bindings 'pre-layout
+ (lambda (tag params layout)
+ layout))
+ tree params layout)
+ (assq-ref tag-bindings 'post
+ (assq-ref bindings '*default* err))))
+ (let ((tag-bindings (assq-ref bindings (car tree) #f)))
+ (cond
+ ((not tag-bindings) (no-bindings))
+ ((assq-ref tag-bindings 'macro #f) => macro)
+ ((assq-ref tag-bindings 'pre #f) => pre)
+ (else (have-bindings tag-bindings)))))
+ (define (fup tree bindings cont params layout ret
+ kbindings kcont kparams klayout kret)
+ (call-with-values
+ (lambda ()
+ (kcont kparams layout klayout (reverse kret)))
+ (lambda (klayout kret)
+ (values bindings cont params klayout (cons kret ret)))))
+ (define (fhere tree bindings cont params layout ret)
+ (call-with-values
+ (lambda ()
+ ((assq-ref bindings '*text* err) tree params layout))
+ (lambda (tlayout tret)
+ (values bindings cont params tlayout (cons tret ret)))))
+ (call-with-values
+ (lambda ()
+ (foldts*-values
+ fdown fup fhere tree bindings #f (cons params '()) layout '()))
+ (lambda (bindings cont params layout ret)
+ (values (car ret) layout))))
+;;; -*- mode: scheme; coding: utf-8; -*-
+;;;
+;;; Copyright (C) 2010, 2011 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 <http://www.gnu.org/licenses/>.
+
+(define-module (sxml match)
+ #\export (sxml-match
+ sxml-match-let
+ sxml-match-let*)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-11)
+ #\use-module (ice-9 control))
+
+
+;;; Commentary:
+;;;
+;;; This module provides an SXML pattern matcher, written by Jim Bender. This
+;;; allows application code to match on SXML nodes and attributes without having
+;;; to deal with the details of s-expression matching, without worrying about
+;;; the order of attributes, etc.
+;;;
+;;; It is fully documented in the Guile Reference Manual.
+;;;
+;;; Code:
+
+
+
+;;;
+;;; PLT compatibility layer.
+;;;
+
+(define-syntax-rule (syntax-object->datum stx)
+ (syntax->datum stx))
+
+(define-syntax-rule (void)
+ *unspecified*)
+
+(define (raise-syntax-error x msg obj sub)
+ (throw 'sxml-match-error x msg obj sub))
+
+(define-syntax module
+ (syntax-rules (provide require)
+ ((_ name lang (provide p_ ...) (require r_ ...)
+ body ...)
+ (begin body ...))))
+
+
+;;;
+;;; Include upstream source file.
+;;;
+
+;; This file was taken from
+;; <http://planet.plt-scheme.org/package-source/jim/sxml-match.plt/1/1/> on
+;; 2010-05-24. It was written by Jim Bender <benderjg2@aol.com> and released
+;; under the MIT/X11 license
+;; <http://www.gnu.org/licenses/license-list.html#X11License>.
+;;
+;; Modified the `sxml-match1' macro to allow multiple-value returns (upstream
+;; was notified.)
+
+(include-from-path "sxml/sxml-match.ss")
+
+;;; match.scm ends here
+;;;; (sxml simple) -- a simple interface to the SSAX parser
+;;;;
+;;;; Copyright (C) 2009, 2010, 2013 Free Software Foundation, Inc.
+;;;; Modified 2004 by Andy Wingo <wingo at pobox dot com>.
+;;;; Originally written by Oleg Kiselyov <oleg at pobox dot com> as SXML-to-HTML.scm.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+;;
+;;A simple interface to XML parsing and serialization.
+;;
+;;; Code:
+
+(define-module (sxml simple)
+ #\use-module (sxml ssax input-parse)
+ #\use-module (sxml ssax)
+ #\use-module (sxml transform)
+ #\use-module (ice-9 match)
+ #\use-module (srfi srfi-13)
+ #\export (xml->sxml sxml->xml sxml->string))
+
+;; Helpers from upstream/SSAX.scm.
+;;
+
+; ssax:reverse-collect-str LIST-OF-FRAGS -> LIST-OF-FRAGS
+; given the list of fragments (some of which are text strings)
+; reverse the list and concatenate adjacent text strings.
+; We can prove from the general case below that if LIST-OF-FRAGS
+; has zero or one element, the result of the procedure is equal?
+; to its argument. This fact justifies the shortcut evaluation below.
+(define (ssax:reverse-collect-str fragments)
+ (cond
+ ((null? fragments) '()) ; a shortcut
+ ((null? (cdr fragments)) fragments) ; see the comment above
+ (else
+ (let loop ((fragments fragments) (result '()) (strs '()))
+ (cond
+ ((null? fragments)
+ (if (null? strs) result
+ (cons (string-concatenate/shared strs) result)))
+ ((string? (car fragments))
+ (loop (cdr fragments) result (cons (car fragments) strs)))
+ (else
+ (loop (cdr fragments)
+ (cons
+ (car fragments)
+ (if (null? strs) result
+ (cons (string-concatenate/shared strs) result)))
+ '())))))))
+
+(define (read-internal-doctype-as-string port)
+ (string-concatenate/shared
+ (let loop ()
+ (let ((fragment
+ (next-token '() '(#\]) "reading internal DOCTYPE" port)))
+ (if (eqv? #\> (peek-next-char port))
+ (begin
+ (read-char port)
+ (cons fragment '()))
+ (cons* fragment "]" (loop)))))))
+
+;; Ideas for the future for this interface:
+;;
+;; * Allow doctypes to provide parsed entities
+;;
+;; * Allow validation (the ELEMENTS value from the DOCTYPE handler
+;; below)
+;;
+;; * Parse internal DTDs
+;;
+;; * Parse external DTDs
+;;
+(define* (xml->sxml #\optional (string-or-port (current-input-port)) #\key
+ (namespaces '())
+ (declare-namespaces? #t)
+ (trim-whitespace? #f)
+ (entities '())
+ (default-entity-handler #f)
+ (doctype-handler #f))
+ "Use SSAX to parse an XML document into SXML. Takes one optional
+argument, @var{string-or-port}, which defaults to the current input
+port."
+ ;; NAMESPACES: alist of PREFIX -> URI. Specifies the symbol prefix
+ ;; that the user wants on elements of a given namespace in the
+ ;; resulting SXML, regardless of the abbreviated namespaces defined in
+ ;; the document by xmlns attributes. If DECLARE-NAMESPACES? is true,
+ ;; these namespaces are treated as if they were declared in the DTD.
+
+ ;; ENTITIES: alist of SYMBOL -> STRING.
+
+ ;; NAMESPACES: list of (DOC-PREFIX . (USER-PREFIX . URI)).
+ ;; A DOC-PREFIX of #f indicates that it comes from the user.
+ ;; Otherwise, prefixes are symbols.
+ (define (munge-namespaces namespaces)
+ (map (lambda (el)
+ (match el
+ ((prefix . uri-string)
+ (cons* (and declare-namespaces? prefix)
+ prefix
+ (ssax:uri-string->symbol uri-string)))))
+ namespaces))
+
+ (define (user-namespaces)
+ (munge-namespaces namespaces))
+
+ (define (user-entities)
+ (if (and default-entity-handler
+ (not (assq '*DEFAULT* entities)))
+ (acons '*DEFAULT* default-entity-handler entities)
+ entities))
+
+ (define (name->sxml name)
+ (match name
+ ((prefix . local-part)
+ (symbol-append prefix (string->symbol ":") local-part))
+ (_ name)))
+
+ (define (doctype-continuation seed)
+ (lambda* (#\key (entities '()) (namespaces '()))
+ (values #f
+ (append entities (user-entities))
+ (append (munge-namespaces namespaces) (user-namespaces))
+ seed)))
+
+ ;; The SEED in this parser is the SXML: initialized to '() at each new
+ ;; level by the fdown handlers; built in reverse by the fhere parsers;
+ ;; and reverse-collected by the fup handlers.
+ (define parser
+ (ssax:make-parser
+ NEW-LEVEL-SEED ; fdown
+ (lambda (elem-gi attributes namespaces expected-content seed)
+ '())
+
+ FINISH-ELEMENT ; fup
+ (lambda (elem-gi attributes namespaces parent-seed seed)
+ (let ((seed (if trim-whitespace?
+ (ssax:reverse-collect-str-drop-ws seed)
+ (ssax:reverse-collect-str seed)))
+ (attrs (attlist-fold
+ (lambda (attr accum)
+ (cons (list (name->sxml (car attr)) (cdr attr))
+ accum))
+ '() attributes)))
+ (acons (name->sxml elem-gi)
+ (if (null? attrs)
+ seed
+ (cons (cons '@ attrs) seed))
+ parent-seed)))
+
+ CHAR-DATA-HANDLER ; fhere
+ (lambda (string1 string2 seed)
+ (if (string-null? string2)
+ (cons string1 seed)
+ (cons* string2 string1 seed)))
+
+ DOCTYPE
+ ;; -> ELEMS ENTITIES NAMESPACES SEED
+ ;;
+ ;; ELEMS is for validation and currently unused.
+ ;;
+ ;; ENTITIES is an alist of parsed entities (symbol -> string).
+ ;;
+ ;; NAMESPACES is as above.
+ ;;
+ ;; SEED builds up the content.
+ (lambda (port docname systemid internal-subset? seed)
+ (call-with-values
+ (lambda ()
+ (cond
+ (doctype-handler
+ (doctype-handler docname systemid
+ (and internal-subset?
+ (read-internal-doctype-as-string port))))
+ (else
+ (when internal-subset?
+ (ssax:skip-internal-dtd port))
+ (values))))
+ (doctype-continuation seed)))
+
+ UNDECL-ROOT
+ ;; This is like the DOCTYPE handler, but for documents that do not
+ ;; have a <!DOCTYPE!> entry.
+ (lambda (elem-gi seed)
+ (call-with-values
+ (lambda ()
+ (if doctype-handler
+ (doctype-handler #f #f #f)
+ (values)))
+ (doctype-continuation seed)))
+
+ PI
+ ((*DEFAULT*
+ . (lambda (port pi-tag seed)
+ (cons
+ (list '*PI* pi-tag (ssax:read-pi-body-as-string port))
+ seed))))))
+
+ (let* ((port (if (string? string-or-port)
+ (open-input-string string-or-port)
+ string-or-port))
+ (elements (reverse (parser port '()))))
+ `(*TOP* ,@elements)))
+
+(define check-name
+ (let ((*good-cache* (make-hash-table)))
+ (lambda (name)
+ (if (not (hashq-ref *good-cache* name))
+ (let* ((str (symbol->string name))
+ (i (string-index str #\:))
+ (head (or (and i (substring str 0 i)) str))
+ (tail (and i (substring str (1+ i)))))
+ (and i (string-index (substring str (1+ i)) #\:)
+ (error "Invalid QName: more than one colon" name))
+ (for-each
+ (lambda (s)
+ (and s
+ (or (char-alphabetic? (string-ref s 0))
+ (eq? (string-ref s 0) #\_)
+ (error "Invalid name starting character" s name))
+ (string-for-each
+ (lambda (c)
+ (or (char-alphabetic? c) (string-index "0123456789.-_" c)
+ (error "Invalid name character" c s name)))
+ s)))
+ (list head tail))
+ (hashq-set! *good-cache* name #t))))))
+
+;; The following two functions serialize tags and attributes. They are
+;; being used in the node handlers for the post-order function, see
+;; below.
+
+(define (attribute-value->xml value port)
+ (cond
+ ((pair? value)
+ (attribute-value->xml (car value) port)
+ (attribute-value->xml (cdr value) port))
+ ((null? value)
+ *unspecified*)
+ ((string? value)
+ (string->escaped-xml value port))
+ ((procedure? value)
+ (with-output-to-port port value))
+ (else
+ (string->escaped-xml
+ (call-with-output-string (lambda (port) (display value port)))
+ port))))
+
+(define (attribute->xml attr value port)
+ (check-name attr)
+ (display attr port)
+ (display "=\"" port)
+ (attribute-value->xml value port)
+ (display #\" port))
+
+(define (element->xml tag attrs body port)
+ (check-name tag)
+ (display #\< port)
+ (display tag port)
+ (if attrs
+ (let lp ((attrs attrs))
+ (if (pair? attrs)
+ (let ((attr (car attrs)))
+ (display #\space port)
+ (if (pair? attr)
+ (attribute->xml (car attr) (cdr attr) port)
+ (error "bad attribute" tag attr))
+ (lp (cdr attrs)))
+ (if (not (null? attrs))
+ (error "bad attributes" tag attrs)))))
+ (if (pair? body)
+ (begin
+ (display #\> port)
+ (let lp ((body body))
+ (cond
+ ((pair? body)
+ (sxml->xml (car body) port)
+ (lp (cdr body)))
+ ((null? body)
+ (display "</" port)
+ (display tag port)
+ (display ">" port))
+ (else
+ (error "bad element body" tag body)))))
+ (display " />" port)))
+
+;; FIXME: ensure name is valid
+(define (entity->xml name port)
+ (display #\& port)
+ (display name port)
+ (display #\; port))
+
+;; FIXME: ensure tag and str are valid
+(define (pi->xml tag str port)
+ (display "<?" port)
+ (display tag port)
+ (display #\space port)
+ (display str port)
+ (display "?>" port))
+
+(define* (sxml->xml tree #\optional (port (current-output-port)))
+ "Serialize the sxml tree @var{tree} as XML. The output will be written
+to the current output port, unless the optional argument @var{port} is
+present."
+ (cond
+ ((pair? tree)
+ (if (symbol? (car tree))
+ ;; An element.
+ (let ((tag (car tree)))
+ (case tag
+ ((*TOP*)
+ (sxml->xml (cdr tree) port))
+ ((*ENTITY*)
+ (if (and (list? (cdr tree)) (= (length (cdr tree)) 1))
+ (entity->xml (cadr tree) port)
+ (error "bad *ENTITY* args" (cdr tree))))
+ ((*PI*)
+ (if (and (list? (cdr tree)) (= (length (cdr tree)) 2))
+ (pi->xml (cadr tree) (caddr tree) port)
+ (error "bad *PI* args" (cdr tree))))
+ (else
+ (let* ((elems (cdr tree))
+ (attrs (and (pair? elems) (pair? (car elems))
+ (eq? '@ (caar elems))
+ (cdar elems))))
+ (element->xml tag attrs (if attrs (cdr elems) elems) port)))))
+ ;; A nodelist.
+ (for-each (lambda (x) (sxml->xml x port)) tree)))
+ ((string? tree)
+ (string->escaped-xml tree port))
+ ((null? tree) *unspecified*)
+ ((not tree) *unspecified*)
+ ((eqv? tree #t) *unspecified*)
+ ((procedure? tree)
+ (with-output-to-port port tree))
+ (else
+ (string->escaped-xml
+ (call-with-output-string (lambda (port) (display tree port)))
+ port))))
+
+(define (sxml->string sxml)
+ "Detag an sxml tree @var{sxml} into a string. Does not perform any
+formatting."
+ (string-concatenate-reverse
+ (foldts
+ (lambda (seed tree) ; fdown
+ '())
+ (lambda (seed kid-seed tree) ; fup
+ (append! kid-seed seed))
+ (lambda (seed tree) ; fhere
+ (if (string? tree) (cons tree seed) seed))
+ '()
+ sxml)))
+
+(define (make-char-quotator char-encoding)
+ (let ((bad-chars (list->char-set (map car char-encoding))))
+
+ ;; Check to see if str contains one of the characters in charset,
+ ;; from the position i onward. If so, return that character's index.
+ ;; otherwise, return #f
+ (define (index-cset str i charset)
+ (string-index str charset i))
+
+ ;; The body of the function
+ (lambda (str port)
+ (let ((bad-pos (index-cset str 0 bad-chars)))
+ (if (not bad-pos)
+ (display str port) ; str had all good chars
+ (let loop ((from 0) (to bad-pos))
+ (cond
+ ((>= from (string-length str)) *unspecified*)
+ ((not to)
+ (display (substring str from (string-length str)) port))
+ (else
+ (let ((quoted-char
+ (cdr (assv (string-ref str to) char-encoding)))
+ (new-to
+ (index-cset str (+ 1 to) bad-chars)))
+ (if (< from to)
+ (display (substring str from to) port))
+ (display quoted-char port)
+ (loop (1+ to) new-to))))))))))
+
+;; Given a string, check to make sure it does not contain characters
+;; such as '<' or '&' that require encoding. Return either the original
+;; string, or a list of string fragments with special characters
+;; replaced by appropriate character entities.
+
+(define string->escaped-xml
+ (make-char-quotator
+ '((#\< . "&lt;") (#\> . "&gt;") (#\& . "&amp;") (#\" . "&quot;"))))
+
+;;; arch-tag: 9c853b25-d82f-42ef-a959-ae26fdc7d1ac
+;;; simple.scm ends here
+
+;;;; (sxml ssax) -- the SSAX parser
+;;;;
+;;;; Copyright (C) 2009, 2010,2012,2013 Free Software Foundation, Inc.
+;;;; Modified 2004 by Andy Wingo <wingo at pobox dot com>.
+;;;; Written 2001,2002,2003,2004 by Oleg Kiselyov <oleg at pobox dot com> as SSAX.scm.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+;;
+;@subheading Functional XML parsing framework
+;@subsubheading SAX/DOM and SXML parsers with support for XML Namespaces and validation
+;
+; This is a package of low-to-high level lexing and parsing procedures
+; that can be combined to yield a SAX, a DOM, a validating parser, or
+; a parser intended for a particular document type. The procedures in
+; the package can be used separately to tokenize or parse various
+; pieces of XML documents. The package supports XML Namespaces,
+; internal and external parsed entities, user-controlled handling of
+; whitespace, and validation. This module therefore is intended to be
+; a framework, a set of "Lego blocks" you can use to build a parser
+; following any discipline and performing validation to any degree. As
+; an example of the parser construction, this file includes a
+; semi-validating SXML parser.
+
+; The present XML framework has a "sequential" feel of SAX yet a
+; "functional style" of DOM. Like a SAX parser, the framework scans the
+; document only once and permits incremental processing. An application
+; that handles document elements in order can run as efficiently as
+; possible. @emph{Unlike} a SAX parser, the framework does not require
+; an application register stateful callbacks and surrender control to
+; the parser. Rather, it is the application that can drive the framework
+; -- calling its functions to get the current lexical or syntax element.
+; These functions do not maintain or mutate any state save the input
+; port. Therefore, the framework permits parsing of XML in a pure
+; functional style, with the input port being a monad (or a linear,
+; read-once parameter).
+
+; Besides the @var{port}, there is another monad -- @var{seed}. Most of
+; the middle- and high-level parsers are single-threaded through the
+; @var{seed}. The functions of this framework do not process or affect
+; the @var{seed} in any way: they simply pass it around as an instance
+; of an opaque datatype. User functions, on the other hand, can use the
+; seed to maintain user's state, to accumulate parsing results, etc. A
+; user can freely mix his own functions with those of the framework. On
+; the other hand, the user may wish to instantiate a high-level parser:
+; @code{SSAX:make-elem-parser} or @code{SSAX:make-parser}. In the latter
+; case, the user must provide functions of specific signatures, which
+; are called at predictable moments during the parsing: to handle
+; character data, element data, or processing instructions (PI). The
+; functions are always given the @var{seed}, among other parameters, and
+; must return the new @var{seed}.
+
+; From a functional point of view, XML parsing is a combined
+; pre-post-order traversal of a "tree" that is the XML document
+; itself. This down-and-up traversal tells the user about an element
+; when its start tag is encountered. The user is notified about the
+; element once more, after all element's children have been
+; handled. The process of XML parsing therefore is a fold over the
+; raw XML document. Unlike a fold over trees defined in [1], the
+; parser is necessarily single-threaded -- obviously as elements
+; in a text XML document are laid down sequentially. The parser
+; therefore is a tree fold that has been transformed to accept an
+; accumulating parameter [1,2].
+
+; Formally, the denotational semantics of the parser can be expressed
+; as
+;@smallexample
+; parser:: (Start-tag -> Seed -> Seed) ->
+; (Start-tag -> Seed -> Seed -> Seed) ->
+; (Char-Data -> Seed -> Seed) ->
+; XML-text-fragment -> Seed -> Seed
+; parser fdown fup fchar "<elem attrs> content </elem>" seed
+; = fup "<elem attrs>" seed
+; (parser fdown fup fchar "content" (fdown "<elem attrs>" seed))
+;
+; parser fdown fup fchar "char-data content" seed
+; = parser fdown fup fchar "content" (fchar "char-data" seed)
+;
+; parser fdown fup fchar "elem-content content" seed
+; = parser fdown fup fchar "content" (
+; parser fdown fup fchar "elem-content" seed)
+;@end smallexample
+
+; Compare the last two equations with the left fold
+;@smallexample
+; fold-left kons elem:list seed = fold-left kons list (kons elem seed)
+;@end smallexample
+
+; The real parser created by @code{SSAX:make-parser} is slightly more
+; complicated, to account for processing instructions, entity
+; references, namespaces, processing of document type declaration, etc.
+
+
+; The XML standard document referred to in this module is
+; @uref{http://www.w3.org/TR/1998/REC-xml-19980210.html}
+;
+; The present file also defines a procedure that parses the text of an
+; XML document or of a separate element into SXML, an S-expression-based
+; model of an XML Information Set. SXML is also an Abstract Syntax Tree
+; of an XML document. SXML is similar but not identical to DOM; SXML is
+; particularly suitable for Scheme-based XML/HTML authoring, SXPath
+; queries, and tree transformations. See SXML.html for more details.
+; SXML is a term implementation of evaluation of the XML document [3].
+; The other implementation is context-passing.
+
+; The present frameworks fully supports the XML Namespaces Recommendation:
+; @uref{http://www.w3.org/TR/REC-xml-names/}
+; Other links:
+;@table @asis
+;@item [1]
+; Jeremy Gibbons, Geraint Jones, "The Under-appreciated Unfold,"
+; Proc. ICFP'98, 1998, pp. 273-279.
+;@item [2]
+; Richard S. Bird, The promotion and accumulation strategies in
+; transformational programming, ACM Trans. Progr. Lang. Systems,
+; 6(4):487-504, October 1984.
+;@item [3]
+; Ralf Hinze, "Deriving Backtracking Monad Transformers,"
+; Functional Pearl. Proc ICFP'00, pp. 186-197.
+;@end table
+;;
+;;; Code:
+
+(define-module (sxml ssax)
+ #\use-module (sxml ssax input-parse)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-13)
+
+ #\export (current-ssax-error-port
+ with-ssax-error-to-port
+ xml-token? xml-token-kind xml-token-head
+ make-empty-attlist attlist-add
+ attlist-null?
+ attlist-remove-top
+ attlist->alist attlist-fold
+ define-parsed-entity!
+ reset-parsed-entity-definitions!
+ ssax:uri-string->symbol
+ ssax:skip-internal-dtd
+ ssax:read-pi-body-as-string
+ ssax:reverse-collect-str-drop-ws
+ ssax:read-markup-token
+ ssax:read-cdata-body
+ ssax:read-char-ref
+ ssax:read-attributes
+ ssax:complete-start-tag
+ ssax:read-external-id
+ ssax:read-char-data
+ ssax:xml->sxml
+ ssax:make-parser
+ ssax:make-pi-parser
+ ssax:make-elem-parser))
+
+(define (parser-error port message . rest)
+ (apply throw 'parser-error port message rest))
+(define ascii->char integer->char)
+(define char->ascii char->integer)
+
+(define current-ssax-error-port
+ (make-parameter (current-error-port)))
+
+(define *current-ssax-error-port*
+ (parameter-fluid current-ssax-error-port))
+
+(define (with-ssax-error-to-port port thunk)
+ (parameterize ((current-ssax-error-port port))
+ (thunk)))
+
+(define (ssax:warn port . args)
+ (with-output-to-port (current-ssax-error-port)
+ (lambda ()
+ (display ";;; SSAX warning: ")
+ (for-each display args)
+ (newline))))
+
+(define (ucscode->string codepoint)
+ (string (integer->char codepoint)))
+
+(define char-newline #\newline)
+(define char-return #\return)
+(define char-tab #\tab)
+(define nl "\n")
+
+;; This isn't a great API, but a more proper fix will involve hacking
+;; SSAX.
+(define (reset-parsed-entity-definitions!)
+ "Restore the set of parsed entity definitions to its initial state."
+ (set! ssax:predefined-parsed-entities
+ '((amp . "&")
+ (lt . "<")
+ (gt . ">")
+ (apos . "'")
+ (quot . "\""))))
+
+(define (define-parsed-entity! entity str)
+ "Define a new parsed entity. @var{entity} should be a symbol.
+
+Instances of &@var{entity}; in XML text will be replaced with the
+string @var{str}, which will then be parsed."
+ (set! ssax:predefined-parsed-entities
+ (acons entity str ssax:predefined-parsed-entities)))
+
+;; Execute a sequence of forms and return the result of the _first_ one.
+;; Like PROG1 in Lisp. Typically used to evaluate one or more forms with
+;; side effects and return a value that must be computed before some or
+;; all of the side effects happen.
+(define-syntax begin0
+ (syntax-rules ()
+ ((begin0 form form1 ... )
+ (let ((val form)) form1 ... val))))
+
+; Like let* but allowing for multiple-value bindings
+(define-syntax let*-values
+ (syntax-rules ()
+ ((let*-values () . bodies) (begin . bodies))
+ ((let*-values (((var) initializer) . rest) . bodies)
+ (let ((var initializer)) ; a single var optimization
+ (let*-values rest . bodies)))
+ ((let*-values ((vars initializer) . rest) . bodies)
+ (call-with-values (lambda () initializer) ; the most generic case
+ (lambda vars (let*-values rest . bodies))))))
+
+;; needed for some dumb reason
+(define inc 1+)
+(define dec 1-)
+
+(define-syntax include-from-path/filtered
+ (lambda (x)
+ (define (read-filtered accept-list file)
+ (with-input-from-file (%search-load-path file)
+ (lambda ()
+ (let loop ((sexp (read)) (out '()))
+ (cond
+ ((eof-object? sexp) (reverse out))
+ ((and (pair? sexp) (memq (car sexp) accept-list))
+ (loop (read) (cons sexp out)))
+ (else
+ (loop (read) out)))))))
+ (syntax-case x ()
+ ((_ accept-list file)
+ (with-syntax (((exp ...) (datum->syntax
+ x
+ (read-filtered
+ (syntax->datum #'accept-list)
+ (syntax->datum #'file)))))
+ #'(begin exp ...))))))
+
+(include-from-path "sxml/upstream/assert.scm")
+(include-from-path/filtered
+ (define define-syntax ssax:define-labeled-arg-macro)
+ "sxml/upstream/SSAX.scm")
+;;;; (sxml ssax input-parse) -- a simple lexer
+;;;;
+;;;; Copyright (C) 2009 Free Software Foundation, Inc.
+;;;; Modified 2004 by Andy Wingo <wingo at pobox dot com>.
+;;;; Written 2003 by Oleg Kiselyov <oleg at pobox dot com> as input-parse.scm.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+;;
+;; A simple lexer.
+;;
+;; The procedures in this module surprisingly often suffice to parse an
+;; input stream. They either skip, or build and return tokens, according
+;; to inclusion or delimiting semantics. The list of characters to
+;; expect, include, or to break at may vary from one invocation of a
+;; function to another. This allows the functions to easily parse even
+;; context-sensitive languages.
+;;
+;; EOF is generally frowned on, and thrown up upon if encountered.
+;; Exceptions are mentioned specifically. The list of expected
+;; characters (characters to skip until, or break-characters) may
+;; include an EOF "character", which is to be coded as the symbol,
+;; @code{*eof*}.
+;;
+;; The input stream to parse is specified as a @dfn{port}, which is
+;; usually the last (and optional) argument. It defaults to the current
+;; input port if omitted.
+;;
+;; If the parser encounters an error, it will throw an exception to the
+;; key @code{parser-error}. The arguments will be of the form
+;; @code{(@var{port} @var{message} @var{specialising-msg}*)}.
+;;
+;; The first argument is a port, which typically points to the offending
+;; character or its neighborhood. You can then use @code{port-column}
+;; and @code{port-line} to query the current position. @var{message} is
+;; the description of the error. Other arguments supply more details
+;; about the problem.
+;;
+;;; Code:
+
+(define-module (sxml ssax input-parse)
+ #\use-module (ice-9 rdelim)
+ #\export (peek-next-char
+ assert-curr-char
+ skip-until
+ skip-while
+ next-token
+ next-token-of
+ read-text-line
+ read-string
+ find-string-from-port?))
+
+(define ascii->char integer->char)
+(define char->ascii char->integer)
+(define char-newline #\newline)
+(define char-return #\return)
+(define inc 1+)
+(define dec 1-)
+
+;; rewrite oleg's define-opt into define* style
+(define-macro (define-opt bindings body . body-rest)
+ (let* ((rev-bindings (reverse bindings))
+ (opt-bindings
+ (and (pair? rev-bindings) (pair? (car rev-bindings))
+ (eq? 'optional (caar rev-bindings))
+ (cdar rev-bindings))))
+ (if opt-bindings
+ `(define* ,(append (reverse (cons #\optional (cdr rev-bindings)))
+ opt-bindings)
+ ,body ,@body-rest)
+ `(define* ,bindings ,body ,@body-rest))))
+
+(define (parser-error port message . rest)
+ (apply throw 'parser-error port message rest))
+
+(include-from-path "sxml/upstream/input-parse.scm")
+
+;; This version for guile is quite speedy, due to read-delimited (which
+;; is implemented in C).
+(define-opt (next-token prefix-skipped-chars break-chars
+ (optional (comment "") (port (current-input-port))) )
+ (let ((delims (list->string (delete '*eof* break-chars))))
+ (if (eof-object? (if (null? prefix-skipped-chars)
+ (peek-char port)
+ (skip-while prefix-skipped-chars port)))
+ (if (memq '*eof* break-chars)
+ ""
+ (parser-error port "EOF while reading a token " comment))
+ (let ((token (read-delimited delims port 'peek)))
+ (if (and (eof-object? (peek-char port))
+ (not (memq '*eof* break-chars)))
+ (parser-error port "EOF while reading a token " comment)
+ token)))))
+
+(define-opt (read-text-line (optional (port (current-input-port))) )
+ (read-line port))
+
+;; 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 <wingo at pobox dot com>
+;; This function is from SLIB's strsrch.scm, and is in the public domain.
+(define (find-string-from-port? str <input-port> . max-no-char)
+ "Looks for @var{str} in @var{<input-port>}, optionally within the
+first @var{max-no-char} characters."
+ (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 <input-port>)))
+ (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 <input-port>)
+ (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)))
+;;;; (sxml transform) -- pre- and post-order sxml transformation
+;;;;
+;;;; Copyright (C) 2009 Free Software Foundation, Inc.
+;;;; Modified 2004 by Andy Wingo <wingo at pobox dot com>.
+;;;; Written 2003 by Oleg Kiselyov <oleg at pobox dot com> as SXML-tree-trans.scm.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+;;
+;;@heading SXML expression tree transformers
+;
+;@subheading Pre-Post-order traversal of a tree and creation of a new tree
+;@smallexample
+;pre-post-order:: <tree> x <bindings> -> <new-tree>
+;@end smallexample
+; where
+;@smallexample
+; <bindings> ::= (<binding> ...)
+; <binding> ::= (<trigger-symbol> *preorder* . <handler>) |
+; (<trigger-symbol> *macro* . <handler>) |
+; (<trigger-symbol> <new-bindings> . <handler>) |
+; (<trigger-symbol> . <handler>)
+; <trigger-symbol> ::= XMLname | *text* | *default*
+; <handler> :: <trigger-symbol> x [<tree>] -> <new-tree>
+;@end smallexample
+;
+; The pre-post-order function visits the nodes and nodelists
+; pre-post-order (depth-first). For each @code{<Node>} of the form
+; @code{(@var{name} <Node> ...)}, it looks up an association with the
+; given @var{name} among its @var{<bindings>}. If failed,
+; @code{pre-post-order} tries to locate a @code{*default*} binding. It's
+; an error if the latter attempt fails as well. Having found a binding,
+; the @code{pre-post-order} function first checks to see if the binding
+; is of the form
+;@smallexample
+; (<trigger-symbol> *preorder* . <handler>)
+;@end smallexample
+;
+; If it is, the handler is 'applied' to the current node. Otherwise, the
+; pre-post-order function first calls itself recursively for each child
+; of the current node, with @var{<new-bindings>} prepended to the
+; @var{<bindings>} in effect. The result of these calls is passed to the
+; @var{<handler>} (along with the head of the current @var{<Node>}). To
+; be more precise, the handler is _applied_ to the head of the current
+; node and its processed children. The result of the handler, which
+; should also be a @code{<tree>}, replaces the current @var{<Node>}. If
+; the current @var{<Node>} is a text string or other atom, a special
+; binding with a symbol @code{*text*} is looked up.
+;
+; A binding can also be of a form
+;@smallexample
+; (<trigger-symbol> *macro* . <handler>)
+;@end smallexample
+; This is equivalent to @code{*preorder*} described above. However, the
+; result is re-processed again, with the current stylesheet.
+;;
+;;; Code:
+
+(define-module (sxml transform)
+ #\export (SRV:send-reply
+ foldts
+ post-order
+ pre-post-order
+ replace-range))
+
+;; Upstream version:
+; $Id: SXML-tree-trans.scm,v 1.8 2003/04/24 19:39:53 oleg Exp oleg $
+
+; Like let* but allowing for multiple-value bindings
+(define-macro (let*-values bindings . body)
+ (if (null? bindings) (cons 'begin body)
+ (apply
+ (lambda (vars initializer)
+ (let ((cont
+ (cons 'let*-values
+ (cons (cdr bindings) body))))
+ (cond
+ ((not (pair? vars)) ; regular let case, a single var
+ `(let ((,vars ,initializer)) ,cont))
+ ((null? (cdr vars)) ; single var, see the prev case
+ `(let ((,(car vars) ,initializer)) ,cont))
+ (else ; the most generic case
+ `(call-with-values (lambda () ,initializer)
+ (lambda ,vars ,cont))))))
+ (car bindings))))
+
+(define (SRV:send-reply . fragments)
+ "Output the @var{fragments} to the current output port.
+
+The fragments are a list of strings, characters, numbers, thunks,
+@code{#f}, @code{#t} -- and other fragments. The function traverses the
+tree depth-first, writes out strings and characters, executes thunks,
+and ignores @code{#f} and @code{'()}. The function returns @code{#t} if
+anything was written at all; otherwise the result is @code{#f} If
+@code{#t} occurs among the fragments, it is not written out but causes
+the result of @code{SRV:send-reply} to be @code{#t}."
+ (let loop ((fragments fragments) (result #f))
+ (cond
+ ((null? fragments) result)
+ ((not (car fragments)) (loop (cdr fragments) result))
+ ((null? (car fragments)) (loop (cdr fragments) result))
+ ((eq? #t (car fragments)) (loop (cdr fragments) #t))
+ ((pair? (car fragments))
+ (loop (cdr fragments) (loop (car fragments) result)))
+ ((procedure? (car fragments))
+ ((car fragments))
+ (loop (cdr fragments) #t))
+ (else
+ (display (car fragments))
+ (loop (cdr fragments) #t)))))
+
+
+
+;------------------------------------------------------------------------
+; Traversal of an SXML tree or a grove:
+; a <Node> or a <Nodelist>
+;
+; A <Node> and a <Nodelist> are mutually-recursive datatypes that
+; underlie the SXML tree:
+; <Node> ::= (name . <Nodelist>) | "text string"
+; An (ordered) set of nodes is just a list of the constituent nodes:
+; <Nodelist> ::= (<Node> ...)
+; Nodelists, and Nodes other than text strings are both lists. A
+; <Nodelist> however is either an empty list, or a list whose head is
+; not a symbol (an atom in general). A symbol at the head of a node is
+; either an XML name (in which case it's a tag of an XML element), or
+; an administrative name such as '@'.
+; See SXPath.scm and SSAX.scm for more information on SXML.
+
+
+;; see the commentary for docs
+(define (pre-post-order tree bindings)
+ (let* ((default-binding (assq '*default* bindings))
+ (text-binding (or (assq '*text* bindings) default-binding))
+ (text-handler ; Cache default and text bindings
+ (and text-binding
+ (if (procedure? (cdr text-binding))
+ (cdr text-binding) (cddr text-binding)))))
+ (let loop ((tree tree))
+ (cond
+ ((null? tree) '())
+ ((not (pair? tree))
+ (let ((trigger '*text*))
+ (if text-handler (text-handler trigger tree)
+ (error "Unknown binding for " trigger " and no default"))))
+ ((not (symbol? (car tree))) (map loop tree)) ; tree is a nodelist
+ (else ; tree is an SXML node
+ (let* ((trigger (car tree))
+ (binding (or (assq trigger bindings) default-binding)))
+ (cond
+ ((not binding)
+ (error "Unknown binding for " trigger " and no default"))
+ ((not (pair? (cdr binding))) ; must be a procedure: handler
+ (apply (cdr binding) trigger (map loop (cdr tree))))
+ ((eq? '*preorder* (cadr binding))
+ (apply (cddr binding) tree))
+ ((eq? '*macro* (cadr binding))
+ (loop (apply (cddr binding) tree)))
+ (else ; (cadr binding) is a local binding
+ (apply (cddr binding) trigger
+ (pre-post-order (cdr tree) (append (cadr binding) bindings)))
+ ))))))))
+
+; post-order is a strict subset of pre-post-order without *preorder*
+; (let alone *macro*) traversals.
+; Now pre-post-order is actually faster than the old post-order.
+; The function post-order is deprecated and is aliased below for
+; backward compatibility.
+(define post-order pre-post-order)
+
+;------------------------------------------------------------------------
+; Extended tree fold
+; tree = atom | (node-name tree ...)
+;
+; foldts fdown fup fhere seed (Leaf str) = fhere seed str
+; foldts fdown fup fhere seed (Nd kids) =
+; fup seed $ foldl (foldts fdown fup fhere) (fdown seed) kids
+
+; procedure fhere: seed -> atom -> seed
+; procedure fdown: seed -> node -> seed
+; procedure fup: parent-seed -> last-kid-seed -> node -> seed
+; foldts returns the final seed
+
+(define (foldts fdown fup fhere seed tree)
+ (cond
+ ((null? tree) seed)
+ ((not (pair? tree)) ; An atom
+ (fhere seed tree))
+ (else
+ (let loop ((kid-seed (fdown seed tree)) (kids (cdr tree)))
+ (if (null? kids)
+ (fup seed kid-seed tree)
+ (loop (foldts fdown fup fhere kid-seed (car kids))
+ (cdr kids)))))))
+
+;------------------------------------------------------------------------
+; Traverse a forest depth-first and cut/replace ranges of nodes.
+;
+; The nodes that define a range don't have to have the same immediate
+; parent, don't have to be on the same level, and the end node of a
+; range doesn't even have to exist. A replace-range procedure removes
+; nodes from the beginning node of the range up to (but not including)
+; the end node of the range. In addition, the beginning node of the
+; range can be replaced by a node or a list of nodes. The range of
+; nodes is cut while depth-first traversing the forest. If all
+; branches of the node are cut a node is cut as well. The procedure
+; can cut several non-overlapping ranges from a forest.
+
+; replace-range:: BEG-PRED x END-PRED x FOREST -> FOREST
+; where
+; type FOREST = (NODE ...)
+; type NODE = Atom | (Name . FOREST) | FOREST
+;
+; The range of nodes is specified by two predicates, beg-pred and end-pred.
+; beg-pred:: NODE -> #f | FOREST
+; end-pred:: NODE -> #f | FOREST
+; The beg-pred predicate decides on the beginning of the range. The node
+; for which the predicate yields non-#f marks the beginning of the range
+; The non-#f value of the predicate replaces the node. The value can be a
+; list of nodes. The replace-range procedure then traverses the tree and skips
+; all the nodes, until the end-pred yields non-#f. The value of the end-pred
+; replaces the end-range node. The new end node and its brothers will be
+; re-scanned.
+; The predicates are evaluated pre-order. We do not descend into a node that
+; is marked as the beginning of the range.
+
+(define (replace-range beg-pred end-pred forest)
+
+ ; loop forest keep? new-forest
+ ; forest is the forest to traverse
+ ; new-forest accumulates the nodes we will keep, in the reverse
+ ; order
+ ; If keep? is #t, keep the curr node if atomic. If the node is not atomic,
+ ; traverse its children and keep those that are not in the skip range.
+ ; If keep? is #f, skip the current node if atomic. Otherwise,
+ ; traverse its children. If all children are skipped, skip the node
+ ; as well.
+
+ (define (loop forest keep? new-forest)
+ (if (null? forest) (values (reverse new-forest) keep?)
+ (let ((node (car forest)))
+ (if keep?
+ (cond ; accumulate mode
+ ((beg-pred node) => ; see if the node starts the skip range
+ (lambda (repl-branches) ; if so, skip/replace the node
+ (loop (cdr forest) #f
+ (append (reverse repl-branches) new-forest))))
+ ((not (pair? node)) ; it's an atom, keep it
+ (loop (cdr forest) keep? (cons node new-forest)))
+ (else
+ (let*-values
+ (((node?) (symbol? (car node))) ; or is it a nodelist?
+ ((new-kids keep?) ; traverse its children
+ (loop (if node? (cdr node) node) #t '())))
+ (loop (cdr forest) keep?
+ (cons
+ (if node? (cons (car node) new-kids) new-kids)
+ new-forest)))))
+ ; skip mode
+ (cond
+ ((end-pred node) => ; end the skip range
+ (lambda (repl-branches) ; repl-branches will be re-scanned
+ (loop (append repl-branches (cdr forest)) #t
+ new-forest)))
+ ((not (pair? node)) ; it's an atom, skip it
+ (loop (cdr forest) keep? new-forest))
+ (else
+ (let*-values
+ (((node?) (symbol? (car node))) ; or is it a nodelist?
+ ((new-kids keep?) ; traverse its children
+ (loop (if node? (cdr node) node) #f '())))
+ (loop (cdr forest) keep?
+ (if (or keep? (pair? new-kids))
+ (cons
+ (if node? (cons (car node) new-kids) new-kids)
+ new-forest)
+ new-forest) ; if all kids are skipped
+ )))))))) ; skip the node too
+
+ (let*-values (((new-forest keep?) (loop forest #t '())))
+ new-forest))
+
+;;; arch-tag: 6c814f4b-38f7-42c1-b8ef-ce3447edefc7
+;;; transform.scm ends here
+; Functional XML parsing framework: SAX/DOM and SXML parsers
+; with support for XML Namespaces and validation
+;
+; This is a package of low-to-high level lexing and parsing procedures
+; that can be combined to yield a SAX, a DOM, a validating parsers, or
+; a parser intended for a particular document type. The procedures in
+; the package can be used separately to tokenize or parse various
+; pieces of XML documents. The package supports XML Namespaces,
+; internal and external parsed entities, user-controlled handling of
+; whitespace, and validation. This module therefore is intended to be
+; a framework, a set of "Lego blocks" you can use to build a parser
+; following any discipline and performing validation to any degree. As
+; an example of the parser construction, this file includes a
+; semi-validating SXML parser.
+
+; The present XML framework has a "sequential" feel of SAX yet a
+; "functional style" of DOM. Like a SAX parser, the framework scans
+; the document only once and permits incremental processing. An
+; application that handles document elements in order can run as
+; efficiently as possible. _Unlike_ a SAX parser, the framework does
+; not require an application register stateful callbacks and surrender
+; control to the parser. Rather, it is the application that can drive
+; the framework -- calling its functions to get the current lexical or
+; syntax element. These functions do not maintain or mutate any state
+; save the input port. Therefore, the framework permits parsing of XML
+; in a pure functional style, with the input port being a monad (or a
+; linear, read-once parameter).
+
+; Besides the PORT, there is another monad -- SEED. Most of the
+; middle- and high-level parsers are single-threaded through the
+; seed. The functions of this framework do not process or affect the
+; SEED in any way: they simply pass it around as an instance of an
+; opaque datatype. User functions, on the other hand, can use the
+; seed to maintain user's state, to accumulate parsing results, etc. A
+; user can freely mix his own functions with those of the
+; framework. On the other hand, the user may wish to instantiate a
+; high-level parser: ssax:make-elem-parser or ssax:make-parser. In
+; the latter case, the user must provide functions of specific
+; signatures, which are called at predictable moments during the
+; parsing: to handle character data, element data, or processing
+; instructions (PI). The functions are always given the SEED, among
+; other parameters, and must return the new SEED.
+
+; From a functional point of view, XML parsing is a combined
+; pre-post-order traversal of a "tree" that is the XML document
+; itself. This down-and-up traversal tells the user about an element
+; when its start tag is encountered. The user is notified about the
+; element once more, after all element's children have been
+; handled. The process of XML parsing therefore is a fold over the
+; raw XML document. Unlike a fold over trees defined in [1], the
+; parser is necessarily single-threaded -- obviously as elements
+; in a text XML document are laid down sequentially. The parser
+; therefore is a tree fold that has been transformed to accept an
+; accumulating parameter [1,2].
+
+; Formally, the denotational semantics of the parser can be expressed
+; as
+; parser:: (Start-tag -> Seed -> Seed) ->
+; (Start-tag -> Seed -> Seed -> Seed) ->
+; (Char-Data -> Seed -> Seed) ->
+; XML-text-fragment -> Seed -> Seed
+; parser fdown fup fchar "<elem attrs> content </elem>" seed
+; = fup "<elem attrs>" seed
+; (parser fdown fup fchar "content" (fdown "<elem attrs>" seed))
+;
+; parser fdown fup fchar "char-data content" seed
+; = parser fdown fup fchar "content" (fchar "char-data" seed)
+;
+; parser fdown fup fchar "elem-content content" seed
+; = parser fdown fup fchar "content" (
+; parser fdown fup fchar "elem-content" seed)
+
+; Compare the last two equations with the left fold
+; fold-left kons elem:list seed = fold-left kons list (kons elem seed)
+
+; The real parser created my ssax:make-parser is slightly more complicated,
+; to account for processing instructions, entity references, namespaces,
+; processing of document type declaration, etc.
+
+
+; The XML standard document referred to in this module is
+; http://www.w3.org/TR/1998/REC-xml-19980210.html
+;
+; The present file also defines a procedure that parses the text of an
+; XML document or of a separate element into SXML, an
+; S-expression-based model of an XML Information Set. SXML is also an
+; Abstract Syntax Tree of an XML document. SXML is similar
+; but not identical to DOM; SXML is particularly suitable for
+; Scheme-based XML/HTML authoring, SXPath queries, and tree
+; transformations. See SXML.html for more details.
+; SXML is a term implementation of evaluation of the XML document [3].
+; The other implementation is context-passing.
+
+; The present frameworks fully supports the XML Namespaces Recommendation:
+; http://www.w3.org/TR/REC-xml-names/
+; Other links:
+; [1] Jeremy Gibbons, Geraint Jones, "The Under-appreciated Unfold,"
+; Proc. ICFP'98, 1998, pp. 273-279.
+; [2] Richard S. Bird, The promotion and accumulation strategies in
+; transformational programming, ACM Trans. Progr. Lang. Systems,
+; 6(4):487-504, October 1984.
+; [3] Ralf Hinze, "Deriving Backtracking Monad Transformers,"
+; Functional Pearl. Proc ICFP'00, pp. 186-197.
+
+; IMPORT
+; parser-error ssax:warn, see Handling of errors, below
+; functions declared in files util.scm, input-parse.scm and look-for-str.scm
+; char-encoding.scm for various platform-specific character-encoding functions.
+; From SRFI-13: string-concatenate/shared and string-concatenate-reverse/shared
+; If a particular implementation lacks SRFI-13 support, please
+; include the file srfi-13-local.scm
+
+; Handling of errors
+; This package relies on a function parser-error, which must be defined
+; by a user of the package. The function has the following signature:
+; parser-error PORT MESSAGE SPECIALISING-MSG*
+; Many procedures of this package call 'parser-error' whenever a
+; parsing, well-formedness or validation error is encountered. The
+; first argument is a port, which typically points to the offending
+; character or its neighborhood. Most of the Scheme systems let the
+; user query a PORT for the current position. The MESSAGE argument
+; indicates a failed XML production or a failed XML constraint. The
+; latter is referred to by its anchor name in the XML Recommendation
+; or XML Namespaces Recommendation. The parsing library (e.g.,
+; next-token, assert-curr-char) invoke 'parser-error' as well, in
+; exactly the same way. See input-parse.scm for more details.
+; See
+; http://pair.com/lisovsky/download/parse-error.scm
+; for an excellent example of such a redefined parser-error function.
+;
+; In addition, the present code invokes a function ssax:warn
+; ssax:warn PORT MESSAGE SPECIALISING-MSG*
+; to notify the user about warnings that are NOT errors but still
+; may alert the user.
+;
+; Again, parser-error and ssax:warn are supposed to be defined by the
+; user. However, if a run-test macro below is set to include
+; self-tests, this present code does provide the definitions for these
+; functions to allow tests to run.
+
+; Misc notes
+; It seems it is highly desirable to separate tests out in a dedicated
+; file.
+;
+; Jim Bender wrote on Mon, 9 Sep 2002 20:03:42 EDT on the SSAX-SXML
+; mailing list (message A fine-grained "lego")
+; The task was to record precise source location information, as PLT
+; does with its current XML parser. That parser records the start and
+; end location (filepos, line#, column#) for pi, elements, attributes,
+; chuncks of "pcdata".
+; As suggested above, though, in some cases I needed to be able force
+; open an interface that did not yet exist. For instance, I added an
+; "end-char-data-hook", which would be called at the end of char-data
+; fragment. This returns a function of type (seed -> seed) which is
+; invoked on the current seed only if read-char-data has indeed reached
+; the end of a block of char data (after reading a new token.
+; But the deepest interface that I needed to expose was that of reading
+; attributes. In the official distribution, this is not even a separate
+; function. Instead, it is embedded within SSAX:read-attributes. This
+; required some small re-structuring as well.
+; This definitely will not be to everyone's taste (nor needed by most).
+; Certainly, the existing make-parser interface addresses most custom
+; needs. And likely 80-90 lines of a "link specification" to create a
+; parser from many tiny little lego blocks may please only a few, while
+; appalling others.
+; The code is available at http://celtic.benderweb.net/ssax-lego.plt or
+; http://celtic.benderweb.net/ssax-lego.tar.gz
+; In the examples directory, I provide:
+; - a unit version of the make-parser interface,
+; - a simple SXML parser using that interface,
+; - an SXML parser which directly uses the "new lego",
+; - a pseudo-SXML parser, which records source location information
+; - and lastly a parser which returns the structures used in PLT's xml
+; collection, with source location information
+
+; $Id: SSAX.scm,v 5.1 2004/07/07 16:02:30 sperber Exp $
+;^^^^^^^^^
+
+
+ ; See the Makefile in the ../tests directory
+ ; (in particular, the rule vSSAX) for an example of how
+ ; to run this code on various Scheme systems.
+ ; See SSAX examples for many samples of using this code,
+ ; again, on a variety of Scheme systems.
+ ; See http://ssax.sf.net/
+
+
+; The following macro runs built-in test cases -- or does not run,
+; depending on which of the two cases below you commented out
+; Case 1: no tests:
+;(define-macro run-test (lambda body '(begin #f)))
+;(define-syntax run-test (syntax-rules () ((run-test . args) (begin #f))))
+
+; Case 2: with tests.
+; The following macro could've been defined just as
+; (define-macro run-test (lambda body `(begin (display "\n-->Test\n") ,@body)))
+;
+; Instead, it's more involved, to make up for case-insensitivity of
+; symbols on some Scheme systems. In Gambit, symbols are case
+; sensitive: (eq? 'A 'a) is #f and (eq? 'Aa (string->symbol "Aa")) is
+; #t. On some systems, symbols are case-insensitive and just the
+; opposite is true. Therefore, we introduce a notation '"ASymbol" (a
+; quoted string) that stands for a case-_sensitive_ ASymbol -- on any
+; R5RS Scheme system. This notation is valid only within the body of
+; run-test.
+; The notation is implemented by scanning the run-test's
+; body and replacing every occurrence of (quote "str") with the result
+; of (string->symbol "str"). We can do such a replacement at macro-expand
+; time (rather than at run time).
+
+; Here's the previous version of run-test, implemented as a low-level
+; macro.
+; (define-macro run-test
+; (lambda body
+; (define (re-write body)
+; (cond
+; ((vector? body)
+; (list->vector (re-write (vector->list body))))
+; ((not (pair? body)) body)
+; ((and (eq? 'quote (car body)) (pair? (cdr body))
+; (string? (cadr body)))
+; (string->symbol (cadr body)))
+; (else (cons (re-write (car body)) (re-write (cdr body))))))
+; (cons 'begin (re-write body))))
+;
+; For portability, it is re-written as syntax-rules. The syntax-rules
+; version is less powerful: for example, it can't handle
+; (case x (('"Foo") (do-on-Foo))) whereas the low-level macro
+; could correctly place a case-sensitive symbol at the right place.
+; We also do not scan vectors (because we don't use them here).
+; Twice-deep quasiquotes aren't handled either.
+; Still, the syntax-rules version satisfies our immediate needs.
+; Incidentally, I originally didn't believe that the macro below
+; was at all possible.
+;
+; The macro is written in a continuation-passing style. A continuation
+; typically has the following structure: (k-head ! . args)
+; When the continuation is invoked, we expand into
+; (k-head <computed-result> . arg). That is, the dedicated symbol !
+; is the placeholder for the result.
+;
+; It seems that the most modular way to write the run-test macro would
+; be the following
+;
+; (define-syntax run-test
+; (syntax-rules ()
+; ((run-test . ?body)
+; (letrec-syntax
+; ((scan-exp ; (scan-exp body k)
+; (syntax-rules (quote quasiquote !)
+; ((scan-exp (quote (hd . tl)) k)
+; (scan-lit-lst (hd . tl) (do-wrap ! quasiquote k)))
+; ((scan-exp (quote x) (k-head ! . args))
+; (k-head
+; (if (string? (quote x)) (string->symbol (quote x)) (quote x))
+; . args))
+; ((scan-exp (hd . tl) k)
+; (scan-exp hd (do-tl ! scan-exp tl k)))
+; ((scan-exp x (k-head ! . args))
+; (k-head x . args))))
+; (do-tl
+; (syntax-rules (!)
+; ((do-tl processed-hd fn () (k-head ! . args))
+; (k-head (processed-hd) . args))
+; ((do-tl processed-hd fn old-tl k)
+; (fn old-tl (do-cons ! processed-hd k)))))
+; ...
+; (do-finish
+; (syntax-rules ()
+; ((do-finish (new-body)) new-body)
+; ((do-finish new-body) (begin . new-body))))
+; ...
+; (scan-exp ?body (do-finish !))
+; ))))
+;
+; Alas, that doesn't work on all systems. We hit yet another dark
+; corner of the R5RS macros. The reason is that run-test is used in
+; the code below to introduce definitions. For example:
+; (run-test
+; (define (ssax:warn port msg . other-msg)
+; (apply cerr (cons* nl "Warning: " msg other-msg)))
+; )
+; This code expands to
+; (begin
+; (define (ssax:warn port msg . other-msg) ...))
+; so the definition gets spliced in into the top level. Right?
+; Well, On Petite Chez Scheme it is so. However, many other systems
+; don't like this approach. The reason is that the invocation of
+; (run-test (define (ssax:warn port msg . other-msg) ...))
+; first expands into
+; (letrec-syntax (...)
+; (scan-exp ((define (ssax:warn port msg . other-msg) ...)) ...))
+; because of the presence of (letrec-syntax ...), the begin form that
+; is generated eventually is no longer at the top level! The begin
+; form in Scheme is an overloading of two distinct forms: top-level
+; begin and the other begin. The forms have different rules: for example,
+; (begin (define x 1)) is OK for a top-level begin but not OK for
+; the other begin. Some Scheme systems see the that the macro
+; (run-test ...) expands into (letrec-syntax ...) and decide right there
+; that any further (begin ...) forms are NOT top-level begin forms.
+; The only way out is to make sure all our macros are top-level.
+; The best approach <sigh> seems to be to make run-test one huge
+; top-level macro.
+
+
+(define-syntax run-test
+ (syntax-rules (define)
+ ((run-test "scan-exp" (define vars body))
+ (define vars (run-test "scan-exp" body)))
+ ((run-test "scan-exp" ?body)
+ (letrec-syntax
+ ((scan-exp ; (scan-exp body k)
+ (syntax-rules (quote quasiquote !)
+ ((scan-exp '() (k-head ! . args))
+ (k-head '() . args))
+ ((scan-exp (quote (hd . tl)) k)
+ (scan-lit-lst (hd . tl) (do-wrap ! quasiquote k)))
+ ((scan-exp (quasiquote (hd . tl)) k)
+ (scan-lit-lst (hd . tl) (do-wrap ! quasiquote k)))
+ ((scan-exp (quote x) (k-head ! . args))
+ (k-head
+ (if (string? (quote x)) (string->symbol (quote x)) (quote x))
+ . args))
+ ((scan-exp (hd . tl) k)
+ (scan-exp hd (do-tl ! scan-exp tl k)))
+ ((scan-exp x (k-head ! . args))
+ (k-head x . args))))
+ (do-tl
+ (syntax-rules (!)
+ ((do-tl processed-hd fn () (k-head ! . args))
+ (k-head (processed-hd) . args))
+ ((do-tl processed-hd fn old-tl k)
+ (fn old-tl (do-cons ! processed-hd k)))))
+ (do-cons
+ (syntax-rules (!)
+ ((do-cons processed-tl processed-hd (k-head ! . args))
+ (k-head (processed-hd . processed-tl) . args))))
+ (do-wrap
+ (syntax-rules (!)
+ ((do-wrap val fn (k-head ! . args))
+ (k-head (fn val) . args))))
+ (do-finish
+ (syntax-rules ()
+ ((do-finish new-body) new-body)))
+
+ (scan-lit-lst ; scan literal list
+ (syntax-rules (quote unquote unquote-splicing !)
+ ((scan-lit-lst '() (k-head ! . args))
+ (k-head '() . args))
+ ((scan-lit-lst (quote (hd . tl)) k)
+ (do-tl quote scan-lit-lst ((hd . tl)) k))
+ ((scan-lit-lst (unquote x) k)
+ (scan-exp x (do-wrap ! unquote k)))
+ ((scan-lit-lst (unquote-splicing x) k)
+ (scan-exp x (do-wrap ! unquote-splicing k)))
+ ((scan-lit-lst (quote x) (k-head ! . args))
+ (k-head
+ ,(if (string? (quote x)) (string->symbol (quote x)) (quote x))
+ . args))
+ ((scan-lit-lst (hd . tl) k)
+ (scan-lit-lst hd (do-tl ! scan-lit-lst tl k)))
+ ((scan-lit-lst x (k-head ! . args))
+ (k-head x . args))))
+ )
+ (scan-exp ?body (do-finish !))))
+ ((run-test body ...)
+ (begin
+ (run-test "scan-exp" body) ...))
+))
+
+;========================================================================
+; Data Types
+
+; TAG-KIND
+; a symbol 'START, 'END, 'PI, 'DECL, 'COMMENT, 'CDSECT
+; or 'ENTITY-REF that identifies a markup token
+
+; UNRES-NAME
+; a name (called GI in the XML Recommendation) as given in an xml
+; document for a markup token: start-tag, PI target, attribute name.
+; If a GI is an NCName, UNRES-NAME is this NCName converted into
+; a Scheme symbol. If a GI is a QName, UNRES-NAME is a pair of
+; symbols: (PREFIX . LOCALPART)
+
+; RES-NAME
+; An expanded name, a resolved version of an UNRES-NAME.
+; For an element or an attribute name with a non-empty namespace URI,
+; RES-NAME is a pair of symbols, (URI-SYMB . LOCALPART).
+; Otherwise, it's a single symbol.
+
+; ELEM-CONTENT-MODEL
+; A symbol:
+; ANY - anything goes, expect an END tag.
+; EMPTY-TAG - no content, and no END-tag is coming
+; EMPTY - no content, expect the END-tag as the next token
+; PCDATA - expect character data only, and no children elements
+; MIXED
+; ELEM-CONTENT
+
+; URI-SYMB
+; A symbol representing a namespace URI -- or other symbol chosen
+; by the user to represent URI. In the former case,
+; URI-SYMB is created by %-quoting of bad URI characters and
+; converting the resulting string into a symbol.
+
+; NAMESPACES
+; A list representing namespaces in effect. An element of the list
+; has one of the following forms:
+; (PREFIX URI-SYMB . URI-SYMB) or
+; (PREFIX USER-PREFIX . URI-SYMB)
+; USER-PREFIX is a symbol chosen by the user
+; to represent the URI.
+; (#f USER-PREFIX . URI-SYMB)
+; Specification of the user-chosen prefix and a URI-SYMBOL.
+; (*DEFAULT* USER-PREFIX . URI-SYMB)
+; Declaration of the default namespace
+; (*DEFAULT* #f . #f)
+; Un-declaration of the default namespace. This notation
+; represents overriding of the previous declaration
+; A NAMESPACES list may contain several elements for the same PREFIX.
+; The one closest to the beginning of the list takes effect.
+
+; ATTLIST
+; An ordered collection of (NAME . VALUE) pairs, where NAME is
+; a RES-NAME or an UNRES-NAME. The collection is an ADT
+
+; STR-HANDLER
+; A procedure of three arguments: STRING1 STRING2 SEED
+; returning a new SEED
+; The procedure is supposed to handle a chunk of character data
+; STRING1 followed by a chunk of character data STRING2.
+; STRING2 is a short string, often "\n" and even ""
+
+; ENTITIES
+; An assoc list of pairs:
+; (named-entity-name . named-entity-body)
+; where named-entity-name is a symbol under which the entity was
+; declared, named-entity-body is either a string, or
+; (for an external entity) a thunk that will return an
+; input port (from which the entity can be read).
+; named-entity-body may also be #f. This is an indication that a
+; named-entity-name is currently being expanded. A reference to
+; this named-entity-name will be an error: violation of the
+; WFC nonrecursion.
+;
+; As an extension to the original SSAX, Guile allows a
+; named-entity-name of *DEFAULT* to indicate a fallback procedure,
+; called as (FALLBACK PORT NAME). The procedure should return a
+; string.
+
+; XML-TOKEN -- a record
+
+; In Gambit, you can use the following declaration:
+; (define-structure xml-token kind head)
+; The following declaration is "standard" as it follows SRFI-9:
+;;(define-record-type xml-token (make-xml-token kind head) xml-token?
+;; (kind xml-token-kind)
+;; (head xml-token-head) )
+; No field mutators are declared as SSAX is a pure functional parser
+;
+; But to make the code more portable, we define xml-token simply as
+; a pair. It suffices for us. Furthermore, xml-token-kind and xml-token-head
+; can be defined as simple procedures. However, they are declared as
+; macros below for efficiency.
+
+(define (make-xml-token kind head) (cons kind head))
+(define xml-token? pair?)
+(define-syntax xml-token-kind
+ (syntax-rules () ((xml-token-kind token) (car token))))
+(define-syntax xml-token-head
+ (syntax-rules () ((xml-token-head token) (cdr token))))
+
+; (define-macro xml-token-kind (lambda (token) `(car ,token)))
+; (define-macro xml-token-head (lambda (token) `(cdr ,token)))
+
+; This record represents a markup, which is, according to the XML
+; Recommendation, "takes the form of start-tags, end-tags, empty-element tags,
+; entity references, character references, comments, CDATA section delimiters,
+; document type declarations, and processing instructions."
+;
+; kind -- a TAG-KIND
+; head -- an UNRES-NAME. For xml-tokens of kinds 'COMMENT and
+; 'CDSECT, the head is #f
+;
+; For example,
+; <P> => kind='START, head='P
+; </P> => kind='END, head='P
+; <BR/> => kind='EMPTY-EL, head='BR
+; <!DOCTYPE OMF ...> => kind='DECL, head='DOCTYPE
+; <?xml version="1.0"?> => kind='PI, head='xml
+; &my-ent; => kind = 'ENTITY-REF, head='my-ent
+;
+; Character references are not represented by xml-tokens as these references
+; are transparently resolved into the corresponding characters.
+;
+
+
+
+; XML-DECL -- a record
+
+; The following is Gambit-specific, see below for a portable declaration
+;(define-structure xml-decl elems entities notations)
+
+; The record represents a datatype of an XML document: the list of
+; declared elements and their attributes, declared notations, list of
+; replacement strings or loading procedures for parsed general
+; entities, etc. Normally an xml-decl record is created from a DTD or
+; an XML Schema, although it can be created and filled in in many other
+; ways (e.g., loaded from a file).
+;
+; elems: an (assoc) list of decl-elem or #f. The latter instructs
+; the parser to do no validation of elements and attributes.
+;
+; decl-elem: declaration of one element:
+; (elem-name elem-content decl-attrs)
+; elem-name is an UNRES-NAME for the element.
+; elem-content is an ELEM-CONTENT-MODEL.
+; decl-attrs is an ATTLIST, of (ATTR-NAME . VALUE) associations
+; !!!This element can declare a user procedure to handle parsing of an
+; element (e.g., to do a custom validation, or to build a hash of
+; IDs as they're encountered).
+;
+; decl-attr: an element of an ATTLIST, declaration of one attribute
+; (attr-name content-type use-type default-value)
+; attr-name is an UNRES-NAME for the declared attribute
+; content-type is a symbol: CDATA, NMTOKEN, NMTOKENS, ...
+; or a list of strings for the enumerated type.
+; use-type is a symbol: REQUIRED, IMPLIED, FIXED
+; default-value is a string for the default value, or #f if not given.
+;
+;
+
+; see a function make-empty-xml-decl to make a XML declaration entry
+; suitable for a non-validating parsing.
+
+
+;-------------------------
+; Utilities
+
+; ssax:warn PORT MESSAGE SPECIALISING-MSG*
+; to notify the user about warnings that are NOT errors but still
+; may alert the user.
+; Result is unspecified.
+; We need to define the function to allow the self-tests to run.
+; Normally the definition of ssax:warn is to be provided by the user.
+(run-test
+ (define (ssax:warn port msg . other-msg)
+ (apply cerr (cons* nl "Warning: " msg other-msg)))
+)
+
+
+; parser-error PORT MESSAGE SPECIALISING-MSG*
+; to let the user know of a syntax error or a violation of a
+; well-formedness or validation constraint.
+; Result is unspecified.
+; We need to define the function to allow the self-tests to run.
+; Normally the definition of parser-error is to be provided by the user.
+(run-test
+ (define (parser-error port msg . specializing-msgs)
+ (apply error (cons msg specializing-msgs)))
+)
+
+; The following is a function that is often used in validation tests,
+; to make sure that the computed result matches the expected one.
+; This function is a standard equal? predicate with one exception.
+; On Scheme systems where (string->symbol "A") and a symbol A
+; are the same, equal_? is precisely equal?
+; On other Scheme systems, we compare symbols disregarding their case.
+; Since this function is used only in tests, we don't have to
+; strive to make it efficient.
+(run-test
+ (define (equal_? e1 e2)
+ (if (eq? 'A (string->symbol "A")) (equal? e1 e2)
+ (cond
+ ((symbol? e1)
+ (and (symbol? e2)
+ (string-ci=? (symbol->string e1) (symbol->string e2))))
+ ((pair? e1)
+ (and (pair? e2)
+ (equal_? (car e1) (car e2)) (equal_? (cdr e1) (cdr e2))))
+ ((vector? e1)
+ (and (vector? e2) (equal_? (vector->list e1) (vector->list e2))))
+ (else
+ (equal? e1 e2)))))
+)
+
+; The following function, which is often used in validation tests,
+; lets us conveniently enter newline, CR and tab characters in a character
+; string.
+; unesc-string: ESC-STRING -> STRING
+; where ESC-STRING is a character string that may contain
+; %n -- for #\newline
+; %r -- for #\return
+; %t -- for #\tab
+; %% -- for #\%
+;
+; The result of unesc-string is a character string with all %-combinations
+; above replaced with their character equivalents
+
+(run-test
+ (define (unesc-string str)
+ (call-with-input-string str
+ (lambda (port)
+ (let loop ((frags '()))
+ (let* ((token (next-token '() '(#\% *eof*) "unesc-string" port))
+ (cterm (read-char port))
+ (frags (cons token frags)))
+ (if (eof-object? cterm) (string-concatenate-reverse/shared frags)
+ (let ((cchar (read-char port))) ; char after #\%
+ (if (eof-object? cchar)
+ (error "unexpected EOF after reading % in unesc-string:" str)
+ (loop
+ (cons
+ (case cchar
+ ((#\n) (string #\newline))
+ ((#\r) (string char-return))
+ ((#\t) (string char-tab))
+ ((#\%) "%")
+ (else (error "bad %-char in unesc-string:" cchar)))
+ frags))))))))))
+)
+
+
+; Test if a string is made of only whitespace
+; An empty string is considered made of whitespace as well
+(define (string-whitespace? str)
+ (let ((len (string-length str)))
+ (cond
+ ((zero? len) #t)
+ ((= 1 len) (char-whitespace? (string-ref str 0)))
+ ((= 2 len) (and (char-whitespace? (string-ref str 0))
+ (char-whitespace? (string-ref str 1))))
+ (else
+ (let loop ((i 0))
+ (or (>= i len)
+ (and (char-whitespace? (string-ref str i))
+ (loop (inc i)))))))))
+
+; Find val in alist
+; Return (values found-el remaining-alist) or
+; (values #f alist)
+
+(define (assq-values val alist)
+ (let loop ((alist alist) (scanned '()))
+ (cond
+ ((null? alist) (values #f scanned))
+ ((equal? val (caar alist))
+ (values (car alist) (append scanned (cdr alist))))
+ (else
+ (loop (cdr alist) (cons (car alist) scanned))))))
+
+; From SRFI-1
+(define (fold-right kons knil lis1)
+ (let recur ((lis lis1))
+ (if (null? lis) knil
+ (let ((head (car lis)))
+ (kons head (recur (cdr lis)))))))
+
+; Left fold combinator for a single list
+(define (fold kons knil lis1)
+ (let lp ((lis lis1) (ans knil))
+ (if (null? lis) ans
+ (lp (cdr lis) (kons (car lis) ans)))))
+
+
+
+;========================================================================
+; Lower-level parsers and scanners
+;
+; They deal with primitive lexical units (Names, whitespaces, tags)
+; and with pieces of more generic productions. Most of these parsers
+; must be called in appropriate context. For example, ssax:complete-start-tag
+; must be called only when the start-tag has been detected and its GI
+; has been read.
+
+;------------------------------------------------------------------------
+; Low-level parsing code
+
+; Skip the S (whitespace) production as defined by
+; [3] S ::= (#x20 | #x9 | #xD | #xA)
+; The procedure returns the first not-whitespace character it
+; encounters while scanning the PORT. This character is left
+; on the input stream.
+
+(define ssax:S-chars (map ascii->char '(32 10 9 13)))
+
+(define (ssax:skip-S port)
+ (skip-while ssax:S-chars port))
+
+
+; Read a Name lexem and return it as string
+; [4] NameChar ::= Letter | Digit | '.' | '-' | '_' | ':'
+; | CombiningChar | Extender
+; [5] Name ::= (Letter | '_' | ':') (NameChar)*
+;
+; This code supports the XML Namespace Recommendation REC-xml-names,
+; which modifies the above productions as follows:
+;
+; [4] NCNameChar ::= Letter | Digit | '.' | '-' | '_'
+; | CombiningChar | Extender
+; [5] NCName ::= (Letter | '_') (NCNameChar)*
+; As the Rec-xml-names says,
+; "An XML document conforms to this specification if all other tokens
+; [other than element types and attribute names] in the document which
+; are required, for XML conformance, to match the XML production for
+; Name, match this specification's production for NCName."
+; Element types and attribute names must match the production QName,
+; defined below.
+
+; Check to see if a-char may start a NCName
+(define (ssax:ncname-starting-char? a-char)
+ (and (char? a-char)
+ (or
+ (char-alphabetic? a-char)
+ (char=? #\_ a-char))))
+
+
+; Read a NCName starting from the current position in the PORT and
+; return it as a symbol.
+(define (ssax:read-NCName port)
+ (let ((first-char (peek-char port)))
+ (or (ssax:ncname-starting-char? first-char)
+ (parser-error port "XMLNS [4] for '" first-char "'")))
+ (string->symbol
+ (next-token-of
+ (lambda (c)
+ (cond
+ ((eof-object? c) #f)
+ ((char-alphabetic? c) c)
+ ((string-index "0123456789.-_" c) c)
+ (else #f)))
+ port)))
+
+; Read a (namespace-) Qualified Name, QName, from the current
+; position in the PORT.
+; From REC-xml-names:
+; [6] QName ::= (Prefix ':')? LocalPart
+; [7] Prefix ::= NCName
+; [8] LocalPart ::= NCName
+; Return: an UNRES-NAME
+(define (ssax:read-QName port)
+ (let ((prefix-or-localpart (ssax:read-NCName port)))
+ (case (peek-char port)
+ ((#\:) ; prefix was given after all
+ (read-char port) ; consume the colon
+ (cons prefix-or-localpart (ssax:read-NCName port)))
+ (else prefix-or-localpart) ; Prefix was omitted
+ )))
+
+; The prefix of the pre-defined XML namespace
+(define ssax:Prefix-XML (string->symbol "xml"))
+
+(run-test
+ (assert (eq? '_
+ (call-with-input-string "_" ssax:read-NCName)))
+ (assert (eq? '_
+ (call-with-input-string "_" ssax:read-QName)))
+ (assert (eq? (string->symbol "_abc_")
+ (call-with-input-string "_abc_;" ssax:read-NCName)))
+ (assert (eq? (string->symbol "_abc_")
+ (call-with-input-string "_abc_;" ssax:read-QName)))
+ (assert (eq? (string->symbol "_a.b")
+ (call-with-input-string "_a.b " ssax:read-QName)))
+ (assert (equal? (cons (string->symbol "_a.b") (string->symbol "d.1-ef-"))
+ (call-with-input-string "_a.b:d.1-ef-;" ssax:read-QName)))
+ (assert (equal? (cons (string->symbol "a") (string->symbol "b"))
+ (call-with-input-string "a:b:c" ssax:read-QName)))
+
+ (assert (failed? (call-with-input-string ":abc" ssax:read-NCName)))
+ (assert (failed? (call-with-input-string "1:bc" ssax:read-NCName)))
+)
+
+; Compare one RES-NAME or an UNRES-NAME with the other.
+; Return a symbol '<, '>, or '= depending on the result of
+; the comparison.
+; Names without PREFIX are always smaller than those with the PREFIX.
+(define name-compare
+ (letrec ((symbol-compare
+ (lambda (symb1 symb2)
+ (cond
+ ((eq? symb1 symb2) '=)
+ ((string<? (symbol->string symb1) (symbol->string symb2))
+ '<)
+ (else '>)))))
+ (lambda (name1 name2)
+ (cond
+ ((symbol? name1) (if (symbol? name2) (symbol-compare name1 name2)
+ '<))
+ ((symbol? name2) '>)
+ ((eq? name2 ssax:largest-unres-name) '<)
+ ((eq? name1 ssax:largest-unres-name) '>)
+ ((eq? (car name1) (car name2)) ; prefixes the same
+ (symbol-compare (cdr name1) (cdr name2)))
+ (else (symbol-compare (car name1) (car name2)))))))
+
+; An UNRES-NAME that is postulated to be larger than anything that can occur in
+; a well-formed XML document.
+; name-compare enforces this postulate.
+(define ssax:largest-unres-name (cons
+ (string->symbol "#LARGEST-SYMBOL")
+ (string->symbol "#LARGEST-SYMBOL")))
+
+(run-test
+ (assert (eq? '= (name-compare 'ABC 'ABC)))
+ (assert (eq? '< (name-compare 'ABC 'ABCD)))
+ (assert (eq? '> (name-compare 'XB 'ABCD)))
+ (assert (eq? '> (name-compare '(HTML . PRE) 'PRE)))
+ (assert (eq? '< (name-compare 'HTML '(HTML . PRE))))
+ (assert (eq? '= (name-compare '(HTML . PRE) '(HTML . PRE))))
+ (assert (eq? '< (name-compare '(HTML . PRE) '(XML . PRE))))
+ (assert (eq? '> (name-compare '(HTML . PRE) '(HTML . P))))
+ (assert (eq? '< (name-compare '(HTML . PRE) ssax:largest-unres-name)))
+ (assert (eq? '< (name-compare '(ZZZZ . ZZZ) ssax:largest-unres-name)))
+ (assert (eq? '> (name-compare ssax:largest-unres-name '(ZZZZ . ZZZ) )))
+)
+
+
+
+; procedure: ssax:read-markup-token PORT
+; This procedure starts parsing of a markup token. The current position
+; in the stream must be #\<. This procedure scans enough of the input stream
+; to figure out what kind of a markup token it is seeing. The procedure returns
+; an xml-token structure describing the token. Note, generally reading
+; of the current markup is not finished! In particular, no attributes of
+; the start-tag token are scanned.
+;
+; Here's a detailed break out of the return values and the position in the PORT
+; when that particular value is returned:
+; PI-token: only PI-target is read.
+; To finish the Processing Instruction and disregard it,
+; call ssax:skip-pi. ssax:read-attributes may be useful
+; as well (for PIs whose content is attribute-value
+; pairs)
+; END-token: The end tag is read completely; the current position
+; is right after the terminating #\> character.
+; COMMENT is read and skipped completely. The current position
+; is right after "-->" that terminates the comment.
+; CDSECT The current position is right after "<!CDATA["
+; Use ssax:read-cdata-body to read the rest.
+; DECL We have read the keyword (the one that follows "<!")
+; identifying this declaration markup. The current
+; position is after the keyword (usually a
+; whitespace character)
+;
+; START-token We have read the keyword (GI) of this start tag.
+; No attributes are scanned yet. We don't know if this
+; tag has an empty content either.
+; Use ssax:complete-start-tag to finish parsing of
+; the token.
+
+(define ssax:read-markup-token ; procedure ssax:read-markup-token port
+ (let ()
+ ; we have read "<!-". Skip through the rest of the comment
+ ; Return the 'COMMENT token as an indication we saw a comment
+ ; and skipped it.
+ (define (skip-comment port)
+ (assert-curr-char '(#\-) "XML [15], second dash" port)
+ (if (not (find-string-from-port? "-->" port))
+ (parser-error port "XML [15], no -->"))
+ (make-xml-token 'COMMENT #f))
+
+ ; we have read "<![" that must begin a CDATA section
+ (define (read-cdata port)
+ (assert (string=? "CDATA[" (read-string 6 port)))
+ (make-xml-token 'CDSECT #f))
+
+ (lambda (port)
+ (assert-curr-char '(#\<) "start of the token" port)
+ (case (peek-char port)
+ ((#\/) (read-char port)
+ (begin0 (make-xml-token 'END (ssax:read-QName port))
+ (ssax:skip-S port)
+ (assert-curr-char '(#\>) "XML [42]" port)))
+ ((#\?) (read-char port) (make-xml-token 'PI (ssax:read-NCName port)))
+ ((#\!)
+ (case (peek-next-char port)
+ ((#\-) (read-char port) (skip-comment port))
+ ((#\[) (read-char port) (read-cdata port))
+ (else (make-xml-token 'DECL (ssax:read-NCName port)))))
+ (else (make-xml-token 'START (ssax:read-QName port)))))
+))
+
+
+; The current position is inside a PI. Skip till the rest of the PI
+(define (ssax:skip-pi port)
+ (if (not (find-string-from-port? "?>" port))
+ (parser-error port "Failed to find ?> terminating the PI")))
+
+
+; The current position is right after reading the PITarget. We read the
+; body of PI and return is as a string. The port will point to the
+; character right after '?>' combination that terminates PI.
+; [16] PI ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'
+
+(define (ssax:read-pi-body-as-string port)
+ (ssax:skip-S port) ; skip WS after the PI target name
+ (string-concatenate/shared
+ (let loop ()
+ (let ((pi-fragment
+ (next-token '() '(#\?) "reading PI content" port)))
+ (if (eqv? #\> (peek-next-char port))
+ (begin
+ (read-char port)
+ (cons pi-fragment '()))
+ (cons* pi-fragment "?" (loop)))))))
+
+(run-test
+ (assert (equal? "p1 content "
+ (call-with-input-string "<?pi1 p1 content ?>"
+ (lambda (port)
+ (ssax:read-markup-token port)
+ (ssax:read-pi-body-as-string port)))))
+ (assert (equal? "pi2? content? ?"
+ (call-with-input-string "<?pi2 pi2? content? ??>"
+ (lambda (port)
+ (ssax:read-markup-token port)
+ (ssax:read-pi-body-as-string port)))))
+)
+
+;(define (ssax:read-pi-body-as-name-values port)
+
+; The current pos in the port is inside an internal DTD subset
+; (e.g., after reading #\[ that begins an internal DTD subset)
+; Skip until the "]>" combination that terminates this DTD
+(define (ssax:skip-internal-dtd port)
+ (if (not (find-string-from-port? "]>" port))
+ (parser-error port
+ "Failed to find ]> terminating the internal DTD subset")))
+
+
+; procedure+: ssax:read-cdata-body PORT STR-HANDLER SEED
+;
+; This procedure must be called after we have read a string "<![CDATA["
+; that begins a CDATA section. The current position must be the first
+; position of the CDATA body. This function reads _lines_ of the CDATA
+; body and passes them to a STR-HANDLER, a character data consumer.
+;
+; The str-handler is a STR-HANDLER, a procedure STRING1 STRING2 SEED.
+; The first STRING1 argument to STR-HANDLER never contains a newline.
+; The second STRING2 argument often will. On the first invocation of
+; the STR-HANDLER, the seed is the one passed to ssax:read-cdata-body
+; as the third argument. The result of this first invocation will be
+; passed as the seed argument to the second invocation of the line
+; consumer, and so on. The result of the last invocation of the
+; STR-HANDLER is returned by the ssax:read-cdata-body. Note a
+; similarity to the fundamental 'fold' iterator.
+;
+; Within a CDATA section all characters are taken at their face value,
+; with only three exceptions:
+; CR, LF, and CRLF are treated as line delimiters, and passed
+; as a single #\newline to the STR-HANDLER
+; "]]>" combination is the end of the CDATA section.
+; &gt; is treated as an embedded #\> character
+; Note, &lt; and &amp; are not specially recognized (and are not expanded)!
+
+(define ssax:read-cdata-body
+ (let ((cdata-delimiters (list char-return #\newline #\] #\&)))
+
+ (lambda (port str-handler seed)
+ (let loop ((seed seed))
+ (let ((fragment (next-token '() cdata-delimiters
+ "reading CDATA" port)))
+ ; that is, we're reading the char after the 'fragment'
+ (case (read-char port)
+ ((#\newline) (loop (str-handler fragment nl seed)))
+ ((#\])
+ (if (not (eqv? (peek-char port) #\]))
+ (loop (str-handler fragment "]" seed))
+ (let check-after-second-braket
+ ((seed (if (string-null? fragment) seed
+ (str-handler fragment "" seed))))
+ (case (peek-next-char port) ; after the second bracket
+ ((#\>) (read-char port) seed) ; we have read "]]>"
+ ((#\]) (check-after-second-braket
+ (str-handler "]" "" seed)))
+ (else (loop (str-handler "]]" "" seed)))))))
+ ((#\&) ; Note that #\& within CDATA may stand for itself
+ (let ((ent-ref ; it does not have to start an entity ref
+ (next-token-of (lambda (c)
+ (and (not (eof-object? c)) (char-alphabetic? c) c)) port)))
+ (cond ; "&gt;" is to be replaced with #\>
+ ((and (string=? "gt" ent-ref) (eqv? (peek-char port) #\;))
+ (read-char port)
+ (loop (str-handler fragment ">" seed)))
+ (else
+ (loop
+ (str-handler ent-ref ""
+ (str-handler fragment "&" seed)))))))
+ (else ; Must be CR: if the next char is #\newline, skip it
+ (if (eqv? (peek-char port) #\newline) (read-char port))
+ (loop (str-handler fragment nl seed)))
+ ))))))
+
+; a few lines of validation code
+(run-test (letrec
+ ((consumer (lambda (fragment foll-fragment seed)
+ (cons* (if (equal? foll-fragment (string #\newline))
+ " NL" foll-fragment) fragment seed)))
+ (test (lambda (str expected-result)
+ (newline) (display "body: ") (write str)
+ (newline) (display "Result: ")
+ (let ((result
+ (reverse
+ (call-with-input-string (unesc-string str)
+ (lambda (port) (ssax:read-cdata-body port consumer '()))
+ ))))
+ (write result)
+ (assert (equal? result expected-result)))))
+ )
+ (test "]]>" '())
+ (test "abcd]]>" '("abcd" ""))
+ (test "abcd]]]>" '("abcd" "" "]" ""))
+ (test "abcd]]]]>" '("abcd" "" "]" "" "]" ""))
+ (test "abcd]]]]]>" '("abcd" "" "]" "" "]" "" "]" ""))
+ (test "abcd]]]a]]>" '("abcd" "" "]" "" "]]" "" "a" ""))
+ (test "abc%r%ndef%n]]>" '("abc" " NL" "def" " NL"))
+ (test "%r%n%r%n]]>" '("" " NL" "" " NL"))
+ (test "%r%n%r%na]]>" '("" " NL" "" " NL" "a" ""))
+ (test "%r%r%r%na]]>" '("" " NL" "" " NL" "" " NL" "a" ""))
+ (test "abc&!!!]]>" '("abc" "&" "" "" "!!!" ""))
+ (test "abc]]&gt;&gt&amp;]]]&gt;and]]>"
+ '("abc" "" "]]" "" "" ">" "" "&" "gt" "" "" "&" "amp" "" ";" "" "]" ""
+ "]]" "" "" ">" "and" ""))
+))
+
+
+; procedure+: ssax:read-char-ref PORT
+;
+; [66] CharRef ::= '&#' [0-9]+ ';'
+; | '&#x' [0-9a-fA-F]+ ';'
+;
+; This procedure must be called after we we have read "&#"
+; that introduces a char reference.
+; The procedure reads this reference and returns the corresponding char
+; The current position in PORT will be after ";" that terminates
+; the char reference
+; Faults detected:
+; WFC: XML-Spec.html#wf-Legalchar
+;
+; According to Section "4.1 Character and Entity References"
+; of the XML Recommendation:
+; "[Definition: A character reference refers to a specific character
+; in the ISO/IEC 10646 character set, for example one not directly
+; accessible from available input devices.]"
+; Therefore, we use a ucscode->string function to convert a character
+; code into the character -- *regardless* of the current character
+; encoding of the input stream.
+
+(define (ssax:read-char-ref port)
+ (let* ((base
+ (cond ((eqv? (peek-char port) #\x) (read-char port) 16)
+ (else 10)))
+ (name (next-token '() '(#\;) "XML [66]" port))
+ (char-code (string->number name base)))
+ (read-char port) ; read the terminating #\; char
+ (if (integer? char-code) (ucscode->string char-code)
+ (parser-error port "[wf-Legalchar] broken for '" name "'"))))
+
+
+; procedure+: ssax:handle-parsed-entity PORT NAME ENTITIES
+; CONTENT-HANDLER STR-HANDLER SEED
+;
+; Expand and handle a parsed-entity reference
+; port - a PORT
+; name - the name of the parsed entity to expand, a symbol
+; entities - see ENTITIES
+; content-handler -- procedure PORT ENTITIES SEED
+; that is supposed to return a SEED
+; str-handler - a STR-HANDLER. It is called if the entity in question
+; turns out to be a pre-declared entity
+;
+; The result is the one returned by CONTENT-HANDLER or STR-HANDLER
+; Faults detected:
+; WFC: XML-Spec.html#wf-entdeclared
+; WFC: XML-Spec.html#norecursion
+
+(define ssax:predefined-parsed-entities
+ `(
+ (,(string->symbol "amp") . "&")
+ (,(string->symbol "lt") . "<")
+ (,(string->symbol "gt") . ">")
+ (,(string->symbol "apos") . "'")
+ (,(string->symbol "quot") . "\"")))
+
+(define (ssax:handle-parsed-entity port name entities
+ content-handler str-handler seed)
+ (cond ; First we check the list of the declared entities
+ ((assq name entities) =>
+ (lambda (decl-entity)
+ (let ((ent-body (cdr decl-entity)) ; mark the list to prevent recursion
+ (new-entities (cons (cons name #f) entities)))
+ (cond
+ ((string? ent-body)
+ (call-with-input-string ent-body
+ (lambda (port) (content-handler port new-entities seed))))
+ ((procedure? ent-body)
+ (let ((port (ent-body)))
+ (begin0
+ (content-handler port new-entities seed)
+ (close-input-port port))))
+ (else
+ (parser-error port "[norecursion] broken for " name))))))
+ ((assq name ssax:predefined-parsed-entities)
+ => (lambda (decl-entity)
+ (str-handler (cdr decl-entity) "" seed)))
+ ((assq '*DEFAULT* entities) =>
+ (lambda (decl-entity)
+ (let ((fallback (cdr decl-entity))
+ (new-entities (cons (cons name #f) entities)))
+ (cond
+ ((procedure? fallback)
+ (call-with-input-string (fallback port name)
+ (lambda (port) (content-handler port new-entities seed))))
+ (else
+ (parser-error port "[norecursion] broken for " name))))))
+ (else (parser-error port "[wf-entdeclared] broken for " name))))
+
+
+
+; The ATTLIST Abstract Data Type
+; Currently is implemented as an assoc list sorted in the ascending
+; order of NAMES.
+
+(define (make-empty-attlist) '())
+
+; Add a name-value pair to the existing attlist preserving the order
+; Return the new list, in the sorted ascending order.
+; Return #f if a pair with the same name already exists in the attlist
+
+(define (attlist-add attlist name-value)
+ (if (null? attlist) (cons name-value attlist)
+ (case (name-compare (car name-value) (caar attlist))
+ ((=) #f)
+ ((<) (cons name-value attlist))
+ (else (cons (car attlist) (attlist-add (cdr attlist) name-value)))
+ )))
+
+(define attlist-null? null?)
+
+; Given an non-null attlist, return a pair of values: the top and the rest
+(define (attlist-remove-top attlist)
+ (values (car attlist) (cdr attlist)))
+
+(define (attlist->alist attlist) attlist)
+(define attlist-fold fold)
+
+; procedure+: ssax:read-attributes PORT ENTITIES
+;
+; This procedure reads and parses a production Attribute*
+; [41] Attribute ::= Name Eq AttValue
+; [10] AttValue ::= '"' ([^<&"] | Reference)* '"'
+; | "'" ([^<&'] | Reference)* "'"
+; [25] Eq ::= S? '=' S?
+;
+;
+; The procedure returns an ATTLIST, of Name (as UNRES-NAME), Value (as string)
+; pairs. The current character on the PORT is a non-whitespace character
+; that is not an ncname-starting character.
+;
+; Note the following rules to keep in mind when reading an 'AttValue'
+; "Before the value of an attribute is passed to the application
+; or checked for validity, the XML processor must normalize it as follows:
+; - a character reference is processed by appending the referenced
+; character to the attribute value
+; - an entity reference is processed by recursively processing the
+; replacement text of the entity [see ENTITIES]
+; [named entities amp lt gt quot apos are assumed pre-declared]
+; - a whitespace character (#x20, #xD, #xA, #x9) is processed by appending #x20
+; to the normalized value, except that only a single #x20 is appended for a
+; "#xD#xA" sequence that is part of an external parsed entity or the
+; literal entity value of an internal parsed entity
+; - other characters are processed by appending them to the normalized value "
+;
+;
+; Faults detected:
+; WFC: XML-Spec.html#CleanAttrVals
+; WFC: XML-Spec.html#uniqattspec
+
+(define ssax:read-attributes ; ssax:read-attributes port entities
+ (let ((value-delimeters (append ssax:S-chars '(#\< #\&))))
+ ; Read the AttValue from the PORT up to the delimiter
+ ; (which can be a single or double-quote character,
+ ; or even a symbol *eof*)
+ ; 'prev-fragments' is the list of string fragments, accumulated
+ ; so far, in reverse order.
+ ; Return the list of fragments with newly read fragments
+ ; prepended.
+ (define (read-attrib-value delimiter port entities prev-fragments)
+ (let* ((new-fragments
+ (cons (next-token '() (cons delimiter value-delimeters)
+ "XML [10]" port)
+ prev-fragments))
+ (cterm (read-char port)))
+ (cond
+ ((or (eof-object? cterm) (eqv? cterm delimiter))
+ new-fragments)
+ ((eqv? cterm char-return) ; treat a CR and CRLF as a LF
+ (if (eqv? (peek-char port) #\newline) (read-char port))
+ (read-attrib-value delimiter port entities
+ (cons " " new-fragments)))
+ ((memv cterm ssax:S-chars)
+ (read-attrib-value delimiter port entities
+ (cons " " new-fragments)))
+ ((eqv? cterm #\&)
+ (cond
+ ((eqv? (peek-char port) #\#)
+ (read-char port)
+ (read-attrib-value delimiter port entities
+ (cons (ssax:read-char-ref port) new-fragments)))
+ (else
+ (read-attrib-value delimiter port entities
+ (read-named-entity port entities new-fragments)))))
+ (else (parser-error port "[CleanAttrVals] broken")))))
+
+ ; we have read "&" that introduces a named entity reference.
+ ; read this reference and return the result of
+ ; normalizing of the corresponding string
+ ; (that is, read-attrib-value is applied to the replacement
+ ; text of the entity)
+ ; The current position will be after ";" that terminates
+ ; the entity reference
+ (define (read-named-entity port entities fragments)
+ (let ((name (ssax:read-NCName port)))
+ (assert-curr-char '(#\;) "XML [68]" port)
+ (ssax:handle-parsed-entity port name entities
+ (lambda (port entities fragments)
+ (read-attrib-value '*eof* port entities fragments))
+ (lambda (str1 str2 fragments)
+ (if (equal? "" str2) (cons str1 fragments)
+ (cons* str2 str1 fragments)))
+ fragments)))
+
+ (lambda (port entities)
+ (let loop ((attr-list (make-empty-attlist)))
+ (if (not (ssax:ncname-starting-char? (ssax:skip-S port))) attr-list
+ (let ((name (ssax:read-QName port)))
+ (ssax:skip-S port)
+ (assert-curr-char '(#\=) "XML [25]" port)
+ (ssax:skip-S port)
+ (let ((delimiter
+ (assert-curr-char '(#\' #\" ) "XML [10]" port)))
+ (loop
+ (or (attlist-add attr-list
+ (cons name
+ (string-concatenate-reverse/shared
+ (read-attrib-value delimiter port entities
+ '()))))
+ (parser-error port "[uniqattspec] broken for " name))))))))
+))
+
+; a few lines of validation code
+(run-test (letrec
+ ((test (lambda (str decl-entities expected-res)
+ (newline) (display "input: ") (write str)
+ (newline) (display "Result: ")
+ (let ((result
+ (call-with-input-string (unesc-string str)
+ (lambda (port)
+ (ssax:read-attributes port decl-entities)))))
+ (write result) (newline)
+ (assert (equal? result expected-res))))))
+ (test "" '() '())
+ (test "href='http://a%tb%r%n%r%n%nc'" '()
+ `((,(string->symbol "href") . "http://a b c")))
+ (test "href='http://a%tb%r%r%n%rc'" '()
+ `((,(string->symbol "href") . "http://a b c")))
+ (test "_1 ='12&amp;' _2= \"%r%n%t12&#10;3\">" '()
+ `((_1 . "12&") (_2 . ,(unesc-string " 12%n3"))))
+ (test "%tAbc='&lt;&amp;&gt;&#x0A;'%nNext='12&ent;34' />"
+ '((ent . "&lt;xx&gt;"))
+ `((,(string->symbol "Abc") . ,(unesc-string "<&>%n"))
+ (,(string->symbol "Next") . "12<xx>34")))
+ (test "%tAbc='&lt;&amp;&gt;&#x0d;'%nNext='12&ent;34' />"
+ '((ent . "&lt;xx&gt;"))
+ `((,(string->symbol "Abc") . ,(unesc-string "<&>%r"))
+ (,(string->symbol "Next") . "12<xx>34")))
+ (test "%tAbc='&lt;&amp;&gt;&#x0A;'%nNext='12&en;34' />"
+ `((en . ,(lambda () (open-input-string "&quot;xx&apos;"))))
+ `((,(string->symbol "Abc") . ,(unesc-string "<&>%n"))
+ (,(string->symbol "Next") . "12\"xx'34")))
+ (test "%tAbc='&lt;&amp;&gt;&#x0A;'%nNext='12&ent;34' />"
+ '((ent . "&lt;&ent1;T;&gt;") (ent1 . "&amp;"))
+ `((,(string->symbol "Abc") . ,(unesc-string "<&>%n"))
+ (,(string->symbol "Next") . "12<&T;>34")))
+ (test "%tAbc='&lt;&amp;&gt;&#x0A;'%nNext='12&ent;34' />"
+ `((*DEFAULT* . ,(lambda (port name)
+ (case name
+ ((ent) "&lt;&ent1;T;&gt;")
+ ((ent1) "&amp;")
+ (else (error "unrecognized" name))))))
+ `((,(string->symbol "Abc") . ,(unesc-string "<&>%n"))
+ (,(string->symbol "Next") . "12<&T;>34")))
+ (assert (failed?
+ (test "%tAbc='&lt;&amp;&gt;&#x0A;'%nNext='12&ent;34' />"
+ '((ent . "<&ent1;T;&gt;") (ent1 . "&amp;")) '())))
+ (assert (failed?
+ (test "%tAbc='&lt;&amp;&gt;&#x0A;'%nNext='12&ent;34' />"
+ '((ent . "&lt;&ent;T;&gt;") (ent1 . "&amp;")) '())))
+ (assert (failed?
+ (test "%tAbc='&lt;&amp;&gt;&#x0A;'%nNext='12&ent;34' />"
+ '((ent . "&lt;&ent1;T;&gt;") (ent1 . "&ent;")) '())))
+ (test "html:href='http://a%tb%r%n%r%n%nc'" '()
+ `(((,(string->symbol "html") . ,(string->symbol "href"))
+ . "http://a b c")))
+ (test "html:href='ref1' html:src='ref2'" '()
+ `(((,(string->symbol "html") . ,(string->symbol "href"))
+ . "ref1")
+ ((,(string->symbol "html") . ,(string->symbol "src"))
+ . "ref2")))
+ (test "html:href='ref1' xml:html='ref2'" '()
+ `(((,(string->symbol "html") . ,(string->symbol "href"))
+ . "ref1")
+ ((,ssax:Prefix-XML . ,(string->symbol "html"))
+ . "ref2")))
+ (assert (failed? (test "html:href='ref1' html:href='ref2'" '() '())))
+ (assert (failed? (test "html:href='<' html:href='ref2'" '() '())))
+ (assert (failed? (test "html:href='ref1' html:href='&ref2;'" '() '())))
+))
+
+; ssax:resolve-name PORT UNRES-NAME NAMESPACES apply-default-ns?
+;
+; Convert an UNRES-NAME to a RES-NAME given the appropriate NAMESPACES
+; declarations.
+; the last parameter apply-default-ns? determines if the default
+; namespace applies (for instance, it does not for attribute names)
+;
+; Per REC-xml-names/#nsc-NSDeclared, "xml" prefix is considered pre-declared
+; and bound to the namespace name "http://www.w3.org/XML/1998/namespace".
+;
+; This procedure tests for the namespace constraints:
+; http://www.w3.org/TR/REC-xml-names/#nsc-NSDeclared
+
+(define (ssax:resolve-name port unres-name namespaces apply-default-ns?)
+ (cond
+ ((pair? unres-name) ; it's a QNAME
+ (cons
+ (cond
+ ((assq (car unres-name) namespaces) => cadr)
+ ((eq? (car unres-name) ssax:Prefix-XML) ssax:Prefix-XML)
+ (else
+ (parser-error port "[nsc-NSDeclared] broken; prefix " (car unres-name))))
+ (cdr unres-name)))
+ (apply-default-ns? ; Do apply the default namespace, if any
+ (let ((default-ns (assq '*DEFAULT* namespaces)))
+ (if (and default-ns (cadr default-ns))
+ (cons (cadr default-ns) unres-name)
+ unres-name))) ; no default namespace declared
+ (else unres-name))) ; no prefix, don't apply the default-ns
+
+
+(run-test
+ (let* ((namespaces
+ '((HTML UHTML . URN-HTML)
+ (HTML UHTML-1 . URN-HTML)
+ (A UHTML . URN-HTML)))
+ (namespaces-def
+ (cons
+ '(*DEFAULT* DEF . URN-DEF) namespaces))
+ (namespaces-undef
+ (cons
+ '(*DEFAULT* #f . #f) namespaces-def))
+ (port (current-input-port)))
+
+ (assert (equal? 'ABC
+ (ssax:resolve-name port 'ABC namespaces #t)))
+ (assert (equal? '(DEF . ABC)
+ (ssax:resolve-name port 'ABC namespaces-def #t)))
+ (assert (equal? 'ABC
+ (ssax:resolve-name port 'ABC namespaces-def #f)))
+ (assert (equal? 'ABC
+ (ssax:resolve-name port 'ABC namespaces-undef #t)))
+ (assert (equal? '(UHTML . ABC)
+ (ssax:resolve-name port '(HTML . ABC) namespaces-def #t)))
+ (assert (equal? '(UHTML . ABC)
+ (ssax:resolve-name port '(HTML . ABC) namespaces-def #f)))
+ (assert (equal? `(,ssax:Prefix-XML . space)
+ (ssax:resolve-name port
+ `(,(string->symbol "xml") . space) namespaces-def #f)))
+ (assert (failed?
+ (ssax:resolve-name port '(XXX . ABC) namespaces-def #f)))
+))
+
+
+; procedure+: ssax:uri-string->symbol URI-STR
+; Convert a URI-STR to an appropriate symbol
+(define (ssax:uri-string->symbol uri-str)
+ (string->symbol uri-str))
+
+; procedure+: ssax:complete-start-tag TAG PORT ELEMS ENTITIES NAMESPACES
+;
+; This procedure is to complete parsing of a start-tag markup. The
+; procedure must be called after the start tag token has been
+; read. TAG is an UNRES-NAME. ELEMS is an instance of xml-decl::elems;
+; it can be #f to tell the function to do _no_ validation of elements
+; and their attributes.
+;
+; This procedure returns several values:
+; ELEM-GI: a RES-NAME.
+; ATTRIBUTES: element's attributes, an ATTLIST of (RES-NAME . STRING)
+; pairs. The list does NOT include xmlns attributes.
+; NAMESPACES: the input list of namespaces amended with namespace
+; (re-)declarations contained within the start-tag under parsing
+; ELEM-CONTENT-MODEL
+
+; On exit, the current position in PORT will be the first character after
+; #\> that terminates the start-tag markup.
+;
+; Faults detected:
+; VC: XML-Spec.html#enum
+; VC: XML-Spec.html#RequiredAttr
+; VC: XML-Spec.html#FixedAttr
+; VC: XML-Spec.html#ValueType
+; WFC: XML-Spec.html#uniqattspec (after namespaces prefixes are resolved)
+; VC: XML-Spec.html#elementvalid
+; WFC: REC-xml-names/#dt-NSName
+
+; Note, although XML Recommendation does not explicitly say it,
+; xmlns and xmlns: attributes don't have to be declared (although they
+; can be declared, to specify their default value)
+
+; Procedure: ssax:complete-start-tag tag-head port elems entities namespaces
+(define ssax:complete-start-tag
+
+ (let ((xmlns (string->symbol "xmlns"))
+ (largest-dummy-decl-attr (list ssax:largest-unres-name #f #f #f)))
+
+ ; Scan through the attlist and validate it, against decl-attrs
+ ; Return an assoc list with added fixed or implied attrs.
+ ; Note that both attlist and decl-attrs are ATTLISTs, and therefore,
+ ; sorted
+ (define (validate-attrs port attlist decl-attrs)
+
+ ; Check to see decl-attr is not of use type REQUIRED. Add
+ ; the association with the default value, if any declared
+ (define (add-default-decl decl-attr result)
+ (let*-values
+ (((attr-name content-type use-type default-value)
+ (apply values decl-attr)))
+ (and (eq? use-type 'REQUIRED)
+ (parser-error port "[RequiredAttr] broken for" attr-name))
+ (if default-value
+ (cons (cons attr-name default-value) result)
+ result)))
+
+ (let loop ((attlist attlist) (decl-attrs decl-attrs) (result '()))
+ (if (attlist-null? attlist)
+ (attlist-fold add-default-decl result decl-attrs)
+ (let*-values
+ (((attr attr-others)
+ (attlist-remove-top attlist))
+ ((decl-attr other-decls)
+ (if (attlist-null? decl-attrs)
+ (values largest-dummy-decl-attr decl-attrs)
+ (attlist-remove-top decl-attrs)))
+ )
+ (case (name-compare (car attr) (car decl-attr))
+ ((<)
+ (if (or (eq? xmlns (car attr))
+ (and (pair? (car attr)) (eq? xmlns (caar attr))))
+ (loop attr-others decl-attrs (cons attr result))
+ (parser-error port "[ValueType] broken for " attr)))
+ ((>)
+ (loop attlist other-decls
+ (add-default-decl decl-attr result)))
+ (else ; matched occurrence of an attr with its declaration
+ (let*-values
+ (((attr-name content-type use-type default-value)
+ (apply values decl-attr)))
+ ; Run some tests on the content of the attribute
+ (cond
+ ((eq? use-type 'FIXED)
+ (or (equal? (cdr attr) default-value)
+ (parser-error port "[FixedAttr] broken for " attr-name)))
+ ((eq? content-type 'CDATA) #t) ; everything goes
+ ((pair? content-type)
+ (or (member (cdr attr) content-type)
+ (parser-error port "[enum] broken for " attr-name "="
+ (cdr attr))))
+ (else
+ (ssax:warn port "declared content type " content-type
+ " not verified yet")))
+ (loop attr-others other-decls (cons attr result)))))
+ ))))
+
+
+ ; Add a new namespace declaration to namespaces.
+ ; First we convert the uri-str to a uri-symbol and search namespaces for
+ ; an association (_ user-prefix . uri-symbol).
+ ; If found, we return the argument namespaces with an association
+ ; (prefix user-prefix . uri-symbol) prepended.
+ ; Otherwise, we prepend (prefix uri-symbol . uri-symbol)
+ (define (add-ns port prefix uri-str namespaces)
+ (and (equal? "" uri-str)
+ (parser-error port "[dt-NSName] broken for " prefix))
+ (let ((uri-symbol (ssax:uri-string->symbol uri-str)))
+ (let loop ((nss namespaces))
+ (cond
+ ((null? nss)
+ (cons (cons* prefix uri-symbol uri-symbol) namespaces))
+ ((eq? uri-symbol (cddar nss))
+ (cons (cons* prefix (cadar nss) uri-symbol) namespaces))
+ (else (loop (cdr nss)))))))
+
+ ; partition attrs into proper attrs and new namespace declarations
+ ; return two values: proper attrs and the updated namespace declarations
+ (define (adjust-namespace-decl port attrs namespaces)
+ (let loop ((attrs attrs) (proper-attrs '()) (namespaces namespaces))
+ (cond
+ ((null? attrs) (values proper-attrs namespaces))
+ ((eq? xmlns (caar attrs)) ; re-decl of the default namespace
+ (loop (cdr attrs) proper-attrs
+ (if (equal? "" (cdar attrs)) ; un-decl of the default ns
+ (cons (cons* '*DEFAULT* #f #f) namespaces)
+ (add-ns port '*DEFAULT* (cdar attrs) namespaces))))
+ ((and (pair? (caar attrs)) (eq? xmlns (caaar attrs)))
+ (loop (cdr attrs) proper-attrs
+ (add-ns port (cdaar attrs) (cdar attrs) namespaces)))
+ (else
+ (loop (cdr attrs) (cons (car attrs) proper-attrs) namespaces)))))
+
+ ; The body of the function
+ (lambda (tag-head port elems entities namespaces)
+ (let*-values
+ (((attlist) (ssax:read-attributes port entities))
+ ((empty-el-tag?)
+ (begin
+ (ssax:skip-S port)
+ (and
+ (eqv? #\/
+ (assert-curr-char '(#\> #\/) "XML [40], XML [44], no '>'" port))
+ (assert-curr-char '(#\>) "XML [44], no '>'" port))))
+ ((elem-content decl-attrs) ; see xml-decl for their type
+ (if elems ; elements declared: validate!
+ (cond
+ ((assoc tag-head elems) =>
+ (lambda (decl-elem) ; of type xml-decl::decl-elem
+ (values
+ (if empty-el-tag? 'EMPTY-TAG (cadr decl-elem))
+ (caddr decl-elem))))
+ (else
+ (parser-error port "[elementvalid] broken, no decl for " tag-head)))
+ (values ; non-validating parsing
+ (if empty-el-tag? 'EMPTY-TAG 'ANY)
+ #f) ; no attributes declared
+ ))
+ ((merged-attrs) (if decl-attrs (validate-attrs port attlist decl-attrs)
+ (attlist->alist attlist)))
+ ((proper-attrs namespaces)
+ (adjust-namespace-decl port merged-attrs namespaces))
+ )
+ ;(cerr "proper attrs: " proper-attrs nl)
+ ; build the return value
+ (values
+ (ssax:resolve-name port tag-head namespaces #t)
+ (fold-right
+ (lambda (name-value attlist)
+ (or
+ (attlist-add attlist
+ (cons (ssax:resolve-name port (car name-value) namespaces #f)
+ (cdr name-value)))
+ (parser-error port "[uniqattspec] after NS expansion broken for "
+ name-value)))
+ (make-empty-attlist)
+ proper-attrs)
+ namespaces
+ elem-content)))))
+
+(run-test
+ (let* ((urn-a (string->symbol "urn:a"))
+ (urn-b (string->symbol "urn:b"))
+ (urn-html (string->symbol "http://w3c.org/html"))
+ (namespaces
+ `((#f '"UHTML" . ,urn-html)
+ ('"A" '"UA" . ,urn-a)))
+ (test
+ (lambda (tag-head-name elems str)
+ (call-with-input-string str
+ (lambda (port)
+ (call-with-values
+ (lambda ()
+ (ssax:complete-start-tag
+ (call-with-input-string tag-head-name
+ (lambda (port) (ssax:read-QName port)))
+ port
+ elems '() namespaces))
+ list))))))
+
+ ; First test with no validation of elements
+ ;(test "TAG1" #f "")
+ (assert (equal? `('"TAG1" () ,namespaces ANY)
+ (test "TAG1" #f ">")))
+ (assert (equal? `('"TAG1" () ,namespaces EMPTY-TAG)
+ (test "TAG1" #f "/>")))
+ (assert (equal? `('"TAG1" (('"HREF" . "a")) ,namespaces EMPTY-TAG)
+ (test "TAG1" #f "HREF='a'/>")))
+ (assert (equal? `(('"UA" . '"TAG1") (('"HREF" . "a"))
+ ,(cons `(*DEFAULT* '"UA" . ,urn-a) namespaces) ANY)
+ (test "TAG1" #f "HREF='a' xmlns='urn:a'>")))
+ (assert (equal? `('"TAG1" (('"HREF" . "a"))
+ ,(cons '(*DEFAULT* #f . #f) namespaces) ANY)
+ (test "TAG1" #f "HREF='a' xmlns=''>")))
+ (assert (failed? (test "UA:TAG1" #f "HREF='a' xmlns=''/>")))
+ (assert (equal? `(('"UA" . '"TAG1") ((('"UA" . '"HREF") . "a"))
+ ,(cons '(*DEFAULT* #f . #f) namespaces) ANY)
+ (test "A:TAG1" #f "A:HREF='a' xmlns=''>")))
+ (assert (equal? `(('"UA" . '"TAG1") ((('"UA" . '"HREF") . "a"))
+ ,(cons `(*DEFAULT* ,urn-b . ,urn-b) namespaces) ANY)
+ (test "A:TAG1" #f "A:HREF='a' xmlns='urn:b'>")))
+ (assert (failed? (test "B:TAG1" #f "A:HREF='a' xmlns:b=''/>")))
+ (assert (equal? `((,urn-b . '"TAG1") ((('"UA" . '"HREF") . "a"))
+ ,(cons `('"B" ,urn-b . ,urn-b) namespaces) ANY)
+ (test "B:TAG1" #f "A:HREF='a' xmlns:B='urn:b'>")))
+ (assert (equal? `((,urn-b . '"TAG1") ((('"UA" . '"HREF") . "a")
+ ((,urn-b . '"SRC") . "b"))
+ ,(cons `('"B" ,urn-b . ,urn-b) namespaces) ANY)
+ (test "B:TAG1" #f
+ "B:SRC='b' A:HREF='a' xmlns:B='urn:b'>")))
+ (assert (equal? `((,urn-b . '"TAG1") ((('"UA" . '"HREF") . "a")
+ ((,urn-b . '"HREF") . "b"))
+ ,(cons `('"B" ,urn-b . ,urn-b) namespaces) ANY)
+ (test "B:TAG1" #f
+ "B:HREF=\"b\" A:HREF='a' xmlns:B='urn:b'>")))
+ ; must be an error! Duplicate attr
+ (assert (failed? (test "B:TAG1" #f
+ "HREF=\"b\" HREF='a' xmlns:B='urn:a'/>")))
+ ; must be an error! Duplicate attr after ns expansion
+ (assert (failed? (test "B:TAG1" #f
+ "B:HREF=\"b\" A:HREF='a' xmlns:B='urn:a'/>")))
+ (assert (equal? `(('"UA" . '"TAG1") (('"HREF" . "a")
+ (('"UA" . '"HREF") . "b"))
+ ,(cons `(*DEFAULT* '"UA" . ,urn-a) namespaces) ANY)
+ (test "TAG1" #f
+ "A:HREF=\"b\" HREF='a' xmlns='urn:a'>")))
+ (assert (equal? `('"TAG1" ((('"UHTML" . '"HREF") . "a")
+ ((,urn-b . '"HREF") . "b"))
+ ,(append `(
+ ('"HTML" '"UHTML" . ,urn-html)
+ ('"B" ,urn-b . ,urn-b))
+ namespaces) ANY)
+ (test "TAG1" #f
+ "B:HREF=\"b\" xmlns:B='urn:b' xmlns:HTML='http://w3c.org/html' HTML:HREF='a' >")))
+
+ ; Now test the validating parsing
+ ; No decl for tag1
+ (assert (failed? (test "TAG1" '((TAG2 ANY ()))
+ "B:HREF='b' xmlns:B='urn:b'>")))
+ ; No decl for HREF elem
+;; (cond-expand
+;; ((not (or scm mit-scheme)) ; Regretfully, SCM treats '() as #f
+;; (assert (failed?
+;; (test "TAG1" '(('"TAG1" ANY ()))
+;; "B:HREF='b' xmlns:B='urn:b'>"))))
+;; (else #t))
+ ; No decl for HREF elem
+ (assert (failed?
+ (test "TAG1" '(('"TAG1" ANY (('"HREF1" CDATA IMPLIED #f))))
+ "B:HREF='b' xmlns:B='urn:b'>")))
+ (assert (equal? `('"TAG1" (('"HREF" . "b")) ,namespaces EMPTY-TAG)
+ (test "TAG1" '(('"TAG1" PCDATA (('"HREF" CDATA REQUIRED #f))))
+ "HREF='b'/>")))
+ (assert (equal? `('"TAG1" (('"HREF" . "b")) ,namespaces PCDATA)
+ (test "TAG1" '(('"TAG1" PCDATA (('"HREF" CDATA REQUIRED #f))))
+ "HREF='b'>")))
+ ; Req'd attribute not given error
+ (assert (failed?
+ (test "TAG1" '(('"TAG1" PCDATA (('"HREF" CDATA REQUIRED #f))))
+ ">")))
+ ; Wrong content-type of the attribute
+ (assert (failed?
+ (test "TAG1" '(('"TAG1" PCDATA (('"HREF" ("c") REQUIRED #f))))
+ "HREF='b'>")))
+ (assert (equal? `('"TAG1" (('"HREF" . "b")) ,namespaces PCDATA)
+ (test "TAG1" '(('"TAG1" PCDATA (('"HREF" ("c" "b") IMPLIED #f))))
+ "HREF='b'>")))
+ (assert (equal? `('"TAG1" (('"HREF" . "b")) ,namespaces PCDATA)
+ (test "TAG1" '(('"TAG1" PCDATA (('"HREF" CDATA IMPLIED "c"))))
+ "HREF='b'>")))
+ ; Bad fixed attribute
+ (assert (failed?
+ (test "TAG1" '(('"TAG1" PCDATA (('"HREF" CDATA FIXED "c"))))
+ "HREF='b'>")))
+ (assert (equal? `('"TAG1" (('"HREF" . "b")) ,namespaces PCDATA)
+ (test "TAG1" '(('"TAG1" PCDATA (('"HREF" CDATA FIXED "b"))))
+ "HREF='b'>")))
+ (assert (equal? `('"TAG1" (('"HREF" . "b")) ,namespaces PCDATA)
+ (test "TAG1" '(('"TAG1" PCDATA (('"HREF" CDATA FIXED "b")))) ">")))
+ (assert (equal? `('"TAG1" (('"HREF" . "b")) ,namespaces PCDATA)
+ (test "TAG1" '(('"TAG1" PCDATA (('"HREF" CDATA IMPLIED "b")))) ">")))
+ (assert (equal? `('"TAG1" () ,namespaces PCDATA)
+ (test "TAG1" '(('"TAG1" PCDATA (('"HREF" CDATA IMPLIED #f)))) ">")))
+ ; Undeclared attr
+ (assert (failed?
+ (test "TAG1"
+ '(('"TAG1" PCDATA ((('"A" . '"HREF") CDATA IMPLIED "c"))))
+ "HREF='b'>")))
+ (assert (equal? `('"TAG1" (('"HREF" . "b") (('"UA" . '"HREF") . "c"))
+ ,namespaces PCDATA)
+ (test "TAG1" '(('"TAG1" PCDATA (('"HREF" CDATA REQUIRED #f)
+ (('"A" . '"HREF") CDATA IMPLIED "c"))))
+ "HREF='b'>")))
+ (assert (equal? `(('"UA" . '"TAG1")
+ (('"HREF" . "b") (('"UA" . '"HREF") . "c"))
+ ,namespaces PCDATA)
+ (test "A:TAG1" '((('"A" . '"TAG1") PCDATA
+ (('"HREF" NMTOKEN REQUIRED #f)
+ (('"A" . '"HREF") CDATA IMPLIED "c"))))
+ "HREF='b'>")))
+ (assert (equal? `((,urn-b . '"TAG1") (('"HREF" . "b"))
+ ,(cons `('"B" ,urn-b . ,urn-b) namespaces) PCDATA)
+ (test "B:TAG1" '((('"B" . '"TAG1") PCDATA (('"HREF" CDATA REQUIRED #f)
+ (('"xmlns" . '"B") CDATA IMPLIED "urn:b"))))
+ "HREF='b'>")))
+ (assert (equal? `((,urn-b . '"TAG1") (((,urn-b . '"HREF") . "b"))
+ ,(cons `('"B" ,urn-b . ,urn-b) namespaces) PCDATA)
+ (test "B:TAG1" '((('"B" . '"TAG1") PCDATA
+ ((('"B" . '"HREF") CDATA REQUIRED #f)
+ (('"xmlns" . '"B") CDATA IMPLIED "urn:b"))))
+ "B:HREF='b'>")))
+ (assert (equal? `((,urn-b . '"TAG1") (('"HREF" . "b"))
+ ,(cons `(*DEFAULT* ,urn-b . ,urn-b) namespaces) PCDATA)
+ (test "TAG1" '(('"TAG1" PCDATA (('"HREF" CDATA REQUIRED #f)
+ ('"xmlns" CDATA IMPLIED "urn:b"))))
+ "HREF='b'>")))
+ ; xmlns not declared
+ (assert (equal? `((,urn-b . '"TAG1") (('"HREF" . "b"))
+ ,(cons `(*DEFAULT* ,urn-b . ,urn-b) namespaces) PCDATA)
+ (test "TAG1" '(('"TAG1" PCDATA (('"HREF" CDATA REQUIRED #f)
+ )))
+ "HREF='b' xmlns='urn:b'>")))
+ ; xmlns:B not declared
+ (assert (equal? `((,urn-b . '"TAG1") (((,urn-b . '"HREF") . "b"))
+ ,(cons `('"B" ,urn-b . ,urn-b) namespaces) PCDATA)
+ (test "B:TAG1" '((('"B" . '"TAG1") PCDATA
+ ((('"B" . '"HREF") CDATA REQUIRED #f)
+ )))
+ "B:HREF='b' xmlns:B='urn:b'>")))
+))
+
+; procedure+: ssax:read-external-id PORT
+;
+; This procedure parses an ExternalID production:
+; [75] ExternalID ::= 'SYSTEM' S SystemLiteral
+; | 'PUBLIC' S PubidLiteral S SystemLiteral
+; [11] SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'")
+; [12] PubidLiteral ::= '"' PubidChar* '"' | "'" (PubidChar - "'")* "'"
+; [13] PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9]
+; | [-'()+,./:=?;!*#@$_%]
+;
+; This procedure is supposed to be called when an ExternalID is expected;
+; that is, the current character must be either #\S or #\P that start
+; correspondingly a SYSTEM or PUBLIC token. This procedure returns the
+; SystemLiteral as a string. A PubidLiteral is disregarded if present.
+
+(define (ssax:read-external-id port)
+ (let ((discriminator (ssax:read-NCName port)))
+ (assert-curr-char ssax:S-chars "space after SYSTEM or PUBLIC" port)
+ (ssax:skip-S port)
+ (let ((delimiter
+ (assert-curr-char '(#\' #\" ) "XML [11], XML [12]" port)))
+ (cond
+ ((eq? discriminator (string->symbol "SYSTEM"))
+ (begin0
+ (next-token '() (list delimiter) "XML [11]" port)
+ (read-char port) ; reading the closing delim
+ ))
+ ((eq? discriminator (string->symbol "PUBLIC"))
+ (skip-until (list delimiter) port)
+ (assert-curr-char ssax:S-chars "space after PubidLiteral" port)
+ (ssax:skip-S port)
+ (let* ((delimiter
+ (assert-curr-char '(#\' #\" ) "XML [11]" port))
+ (systemid
+ (next-token '() (list delimiter) "XML [11]" port)))
+ (read-char port) ; reading the closing delim
+ systemid))
+ (else
+ (parser-error port "XML [75], " discriminator
+ " rather than SYSTEM or PUBLIC"))))))
+
+
+;-----------------------------------------------------------------------------
+; Higher-level parsers and scanners
+;
+; They parse productions corresponding to the whole (document) entity
+; or its higher-level pieces (prolog, root element, etc).
+
+
+; Scan the Misc production in the context
+; [1] document ::= prolog element Misc*
+; [22] prolog ::= XMLDecl? Misc* (doctypedec l Misc*)?
+; [27] Misc ::= Comment | PI | S
+;
+; The following function should be called in the prolog or epilog contexts.
+; In these contexts, whitespaces are completely ignored.
+; The return value from ssax:scan-Misc is either a PI-token,
+; a DECL-token, a START token, or EOF.
+; Comments are ignored and not reported.
+
+(define (ssax:scan-Misc port)
+ (let loop ((c (ssax:skip-S port)))
+ (cond
+ ((eof-object? c) c)
+ ((not (char=? c #\<))
+ (parser-error port "XML [22], char '" c "' unexpected"))
+ (else
+ (let ((token (ssax:read-markup-token port)))
+ (case (xml-token-kind token)
+ ((COMMENT) (loop (ssax:skip-S port)))
+ ((PI DECL START) token)
+ (else
+ (parser-error port "XML [22], unexpected token of kind "
+ (xml-token-kind token)
+ ))))))))
+
+; procedure+: ssax:read-char-data PORT EXPECT-EOF? STR-HANDLER SEED
+;
+; This procedure is to read the character content of an XML document
+; or an XML element.
+; [43] content ::=
+; (element | CharData | Reference | CDSect | PI
+; | Comment)*
+; To be more precise, the procedure reads CharData, expands CDSect
+; and character entities, and skips comments. The procedure stops
+; at a named reference, EOF, at the beginning of a PI or a start/end tag.
+;
+; port
+; a PORT to read
+; expect-eof?
+; a boolean indicating if EOF is normal, i.e., the character
+; data may be terminated by the EOF. EOF is normal
+; while processing a parsed entity.
+; str-handler
+; a STR-HANDLER
+; seed
+; an argument passed to the first invocation of STR-HANDLER.
+;
+; The procedure returns two results: SEED and TOKEN.
+; The SEED is the result of the last invocation of STR-HANDLER, or the
+; original seed if STR-HANDLER was never called.
+;
+; TOKEN can be either an eof-object (this can happen only if
+; expect-eof? was #t), or:
+; - an xml-token describing a START tag or an END-tag;
+; For a start token, the caller has to finish reading it.
+; - an xml-token describing the beginning of a PI. It's up to an
+; application to read or skip through the rest of this PI;
+; - an xml-token describing a named entity reference.
+;
+; CDATA sections and character references are expanded inline and
+; never returned. Comments are silently disregarded.
+;
+; As the XML Recommendation requires, all whitespace in character data
+; must be preserved. However, a CR character (#xD) must be disregarded
+; if it appears before a LF character (#xA), or replaced by a #xA character
+; otherwise. See Secs. 2.10 and 2.11 of the XML Recommendation. See also
+; the canonical XML Recommendation.
+
+ ; ssax:read-char-data port expect-eof? str-handler seed
+(define ssax:read-char-data
+ (let
+ ((terminators-usual (list #\< #\& char-return))
+ (terminators-usual-eof (list #\< '*eof* #\& char-return))
+
+ (handle-fragment
+ (lambda (fragment str-handler seed)
+ (if (string-null? fragment) seed
+ (str-handler fragment "" seed))))
+ )
+
+ (lambda (port expect-eof? str-handler seed)
+
+ ; Very often, the first character we encounter is #\<
+ ; Therefore, we handle this case in a special, fast path
+ (if (eqv? #\< (peek-char port))
+
+ ; The fast path
+ (let ((token (ssax:read-markup-token port)))
+ (case (xml-token-kind token)
+ ((START END) ; The most common case
+ (values seed token))
+ ((CDSECT)
+ (let ((seed (ssax:read-cdata-body port str-handler seed)))
+ (ssax:read-char-data port expect-eof? str-handler seed)))
+ ((COMMENT) (ssax:read-char-data port expect-eof?
+ str-handler seed))
+ (else
+ (values seed token))))
+
+
+ ; The slow path
+ (let ((char-data-terminators
+ (if expect-eof? terminators-usual-eof terminators-usual)))
+
+ (let loop ((seed seed))
+ (let* ((fragment
+ (next-token '() char-data-terminators
+ "reading char data" port))
+ (term-char (peek-char port)) ; one of char-data-terminators
+ )
+ (if (eof-object? term-char)
+ (values
+ (handle-fragment fragment str-handler seed)
+ term-char)
+ (case term-char
+ ((#\<)
+ (let ((token (ssax:read-markup-token port)))
+ (case (xml-token-kind token)
+ ((CDSECT)
+ (loop
+ (ssax:read-cdata-body port str-handler
+ (handle-fragment fragment str-handler seed))))
+ ((COMMENT)
+ (loop (handle-fragment fragment str-handler seed)))
+ (else
+ (values
+ (handle-fragment fragment str-handler seed)
+ token)))))
+ ((#\&)
+ (case (peek-next-char port)
+ ((#\#) (read-char port)
+ (loop (str-handler fragment
+ (ssax:read-char-ref port)
+ seed)))
+ (else
+ (let ((name (ssax:read-NCName port)))
+ (assert-curr-char '(#\;) "XML [68]" port)
+ (values
+ (handle-fragment fragment str-handler seed)
+ (make-xml-token 'ENTITY-REF name))))))
+ (else ; This must be a CR character
+ (if (eqv? (peek-next-char port) #\newline)
+ (read-char port))
+ (loop (str-handler fragment (string #\newline) seed))))
+ ))))))))
+
+
+; a few lines of validation code
+(run-test (letrec
+ ((a-tag (make-xml-token 'START (string->symbol "BR")))
+ (a-ref (make-xml-token 'ENTITY-REF (string->symbol "lt")))
+ (eof-object (lambda () eof-object)) ; a unique value
+ (str-handler (lambda (fragment foll-fragment seed)
+ (if (string-null? foll-fragment) (cons fragment seed)
+ (cons* foll-fragment fragment seed))))
+ (test (lambda (str expect-eof? expected-data expected-token)
+ (newline) (display "body: ") (write str)
+ (newline) (display "Result: ")
+ (let*-values
+ (((seed token)
+ (call-with-input-string (unesc-string str)
+ (lambda (port)
+ (ssax:read-char-data port expect-eof? str-handler '()))))
+ ((result) (reverse seed)))
+ (write result)
+ (display " ")
+ (display token)
+ (assert (equal? result (map unesc-string expected-data))
+ (if (eq? expected-token eof-object)
+ (eof-object? token)
+ (equal? token expected-token))))))
+ )
+ (test "" #t '() eof-object)
+ (assert (failed? (test "" #f '() eof-object)))
+ (test " " #t '(" ") eof-object)
+ (test "<BR/>" #f '() a-tag)
+ (test " <BR />" #f '(" ") a-tag)
+
+ (test " &lt;" #f '(" ") a-ref)
+ (test " a&lt;" #f '(" a") a-ref)
+ (test " a &lt;" #f '(" a ") a-ref)
+
+ (test " <!-- comment--> a a<BR/>" #f '(" " " a a") a-tag)
+ (test " <!-- comment-->%ra a<BR/>" #f '(" " "" "%n" "a a") a-tag)
+ (test " <!-- comment-->%r%na a<BR/>" #f '(" " "" "%n" "a a") a-tag)
+ (test " <!-- comment-->%r%na%t%r%r%na<BR/>" #f
+ '(" " "" "%n" "a%t" "%n" "" "%n" "a") a-tag)
+ (test "a<!-- comment--> a a<BR/>" #f '("a" " a a") a-tag)
+ (test "&#x21;<BR/>" #f '("" "!") a-tag)
+ (test "&#x21;%n<BR/>" #f '("" "!" "%n") a-tag)
+ (test "%t&#x21;%n<BR/>" #f '("%t" "!" "%n") a-tag)
+ (test "%t&#x21;%na a<BR/>" #f '("%t" "!" "%na a") a-tag)
+ (test "%t&#x21;%ra a<BR/>" #f '("%t" "!" "" "%n" "a a") a-tag)
+ (test "%t&#x21;%r%na a<BR/>" #f '("%t" "!" "" "%n" "a a") a-tag)
+
+ (test " %ta &#x21; b <BR/>" #f '(" %ta " "!" " b ") a-tag)
+ (test " %ta &#x20; b <BR/>" #f '(" %ta " " " " b ") a-tag)
+
+ (test "<![CDATA[<]]><BR/>" #f '("<") a-tag)
+ (test "<![CDATA[]]]><BR/>" #f '("]") a-tag)
+ (test "%t<![CDATA[<]]><BR/>" #f '("%t" "<") a-tag)
+ (test "%t<![CDATA[<]]>a b<BR/>" #f '("%t" "<" "a b") a-tag)
+ (test "%t<![CDATA[<]]> a b<BR/>" #f '("%t" "<" " a b") a-tag)
+
+ (test "%td <![CDATA[ <%r%r%n]]> a b<BR/>" #f
+ '("%td " " <" "%n" "" "%n" " a b") a-tag)
+))
+
+
+
+; procedure+: ssax:assert-token TOKEN KIND GI
+; Make sure that TOKEN is of anticipated KIND and has anticipated GI
+; Note GI argument may actually be a pair of two symbols, Namespace
+; URI or the prefix, and of the localname.
+; If the assertion fails, error-cont is evaluated by passing it
+; three arguments: token kind gi. The result of error-cont is returned.
+(define (ssax:assert-token token kind gi error-cont)
+ (or
+ (and (xml-token? token)
+ (eq? kind (xml-token-kind token))
+ (equal? gi (xml-token-head token)))
+ (error-cont token kind gi)))
+
+;========================================================================
+; Highest-level parsers: XML to SXML
+
+; These parsers are a set of syntactic forms to instantiate a SSAX parser.
+; A user can instantiate the parser to do the full validation, or
+; no validation, or any particular validation. The user specifies
+; which PI he wants to be notified about. The user tells what to do
+; with the parsed character and element data. The latter handlers
+; determine if the parsing follows a SAX or a DOM model.
+
+; syntax: ssax:make-pi-parser my-pi-handlers
+; Create a parser to parse and process one Processing Element (PI).
+
+; my-pi-handlers
+; An assoc list of pairs (PI-TAG . PI-HANDLER)
+; where PI-TAG is an NCName symbol, the PI target, and
+; PI-HANDLER is a procedure PORT PI-TAG SEED
+; where PORT points to the first symbol after the PI target.
+; The handler should read the rest of the PI up to and including
+; the combination '?>' that terminates the PI. The handler should
+; return a new seed.
+; One of the PI-TAGs may be the symbol *DEFAULT*. The corresponding
+; handler will handle PIs that no other handler will. If the
+; *DEFAULT* PI-TAG is not specified, ssax:make-pi-parser will assume
+; the default handler that skips the body of the PI
+;
+; The output of the ssax:make-pi-parser is a procedure
+; PORT PI-TAG SEED
+; that will parse the current PI according to the user-specified handlers.
+;
+; The previous version of ssax:make-pi-parser was a low-level macro:
+; (define-macro ssax:make-pi-parser
+; (lambda (my-pi-handlers)
+; `(lambda (port target seed)
+; (case target
+; ; Generate the body of the case statement
+; ,@(let loop ((pi-handlers my-pi-handlers) (default #f))
+; (cond
+; ((null? pi-handlers)
+; (if default `((else (,default port target seed)))
+; '((else
+; (ssax:warn port "Skipping PI: " target nl)
+; (ssax:skip-pi port)
+; seed))))
+; ((eq? '*DEFAULT* (caar pi-handlers))
+; (loop (cdr pi-handlers) (cdar pi-handlers)))
+; (else
+; (cons
+; `((,(caar pi-handlers)) (,(cdar pi-handlers) port target seed))
+; (loop (cdr pi-handlers) default)))))))))
+
+(define-syntax ssax:make-pi-parser
+ (syntax-rules ()
+ ((ssax:make-pi-parser orig-handlers)
+ (letrec-syntax
+ ; Generate the clauses of the case statement
+ ((loop
+ (syntax-rules (*DEFAULT*)
+ ((loop () #f accum port target seed) ; no default
+ (make-case
+ ((else
+ (ssax:warn port "Skipping PI: " target nl)
+ (ssax:skip-pi port)
+ seed)
+ . accum)
+ () target))
+ ((loop () default accum port target seed)
+ (make-case
+ ((else (default port target seed)) . accum)
+ () target))
+ ((loop ((*DEFAULT* . default) . handlers) old-def accum
+ port target seed)
+ (loop handlers default accum port target seed))
+ ((loop ((tag . handler) . handlers) default accum port target seed)
+ (loop handlers default
+ (((tag) (handler port target seed)) . accum)
+ port target seed))
+ ))
+ (make-case ; Reverse the clauses, make the 'case'
+ (syntax-rules ()
+ ((make-case () clauses target)
+ (case target . clauses))
+ ((make-case (clause . clauses) accum target)
+ (make-case clauses (clause . accum) target)))
+ ))
+ (lambda (port target seed)
+ (loop orig-handlers #f () port target seed))
+ ))))
+
+(run-test
+ (pp (ssax:make-pi-parser ()))
+ (pp (ssax:make-pi-parser ((xml . (lambda (port target seed) seed)))))
+ (pp (ssax:make-pi-parser ((xml . (lambda (port target seed) seed))
+ (html . list)
+ (*DEFAULT* . ssax:warn))))
+)
+
+; syntax: ssax:make-elem-parser my-new-level-seed my-finish-element
+; my-char-data-handler my-pi-handlers
+
+; Create a parser to parse and process one element, including its
+; character content or children elements. The parser is typically
+; applied to the root element of a document.
+
+; my-new-level-seed
+; procedure ELEM-GI ATTRIBUTES NAMESPACES EXPECTED-CONTENT SEED
+; where ELEM-GI is a RES-NAME of the element
+; about to be processed.
+; This procedure is to generate the seed to be passed
+; to handlers that process the content of the element.
+; This is the function identified as 'fdown' in the denotational
+; semantics of the XML parser given in the title comments to this
+; file.
+;
+; my-finish-element
+; procedure ELEM-GI ATTRIBUTES NAMESPACES PARENT-SEED SEED
+; This procedure is called when parsing of ELEM-GI is finished.
+; The SEED is the result from the last content parser (or
+; from my-new-level-seed if the element has the empty content).
+; PARENT-SEED is the same seed as was passed to my-new-level-seed.
+; The procedure is to generate a seed that will be the result
+; of the element parser.
+; This is the function identified as 'fup' in the denotational
+; semantics of the XML parser given in the title comments to this
+; file.
+;
+; my-char-data-handler
+; A STR-HANDLER
+;
+; my-pi-handlers
+; See ssax:make-pi-handler above
+;
+
+; The generated parser is a
+; procedure START-TAG-HEAD PORT ELEMS ENTITIES
+; NAMESPACES PRESERVE-WS? SEED
+; The procedure must be called after the start tag token has been
+; read. START-TAG-HEAD is an UNRES-NAME from the start-element tag.
+; ELEMS is an instance of xml-decl::elems.
+; See ssax:complete-start-tag::preserve-ws?
+
+; Faults detected:
+; VC: XML-Spec.html#elementvalid
+; WFC: XML-Spec.html#GIMatch
+
+
+(define-syntax ssax:make-elem-parser
+ (syntax-rules ()
+ ((ssax:make-elem-parser my-new-level-seed my-finish-element
+ my-char-data-handler my-pi-handlers)
+
+ (lambda (start-tag-head port elems entities namespaces
+ preserve-ws? seed)
+
+ (define xml-space-gi (cons ssax:Prefix-XML
+ (string->symbol "space")))
+
+ (let handle-start-tag ((start-tag-head start-tag-head)
+ (port port) (entities entities)
+ (namespaces namespaces)
+ (preserve-ws? preserve-ws?) (parent-seed seed))
+ (let*-values
+ (((elem-gi attributes namespaces expected-content)
+ (ssax:complete-start-tag start-tag-head port elems
+ entities namespaces))
+ ((seed)
+ (my-new-level-seed elem-gi attributes
+ namespaces expected-content parent-seed)))
+ (case expected-content
+ ((EMPTY-TAG)
+ (my-finish-element
+ elem-gi attributes namespaces parent-seed seed))
+ ((EMPTY) ; The end tag must immediately follow
+ (ssax:assert-token
+ (and (eqv? #\< (ssax:skip-S port)) (ssax:read-markup-token port))
+ 'END start-tag-head
+ (lambda (token exp-kind exp-head)
+ (parser-error port "[elementvalid] broken for " token
+ " while expecting "
+ exp-kind exp-head)))
+ (my-finish-element
+ elem-gi attributes namespaces parent-seed seed))
+ (else ; reading the content...
+ (let ((preserve-ws? ; inherit or set the preserve-ws? flag
+ (cond
+ ((assoc xml-space-gi attributes) =>
+ (lambda (name-value)
+ (equal? "preserve" (cdr name-value))))
+ (else preserve-ws?))))
+ (let loop ((port port) (entities entities)
+ (expect-eof? #f) (seed seed))
+ (let*-values
+ (((seed term-token)
+ (ssax:read-char-data port expect-eof?
+ my-char-data-handler seed)))
+ (if (eof-object? term-token)
+ seed
+ (case (xml-token-kind term-token)
+ ((END)
+ (ssax:assert-token term-token 'END start-tag-head
+ (lambda (token exp-kind exp-head)
+ (parser-error port "[GIMatch] broken for "
+ term-token " while expecting "
+ exp-kind exp-head)))
+ (my-finish-element
+ elem-gi attributes namespaces parent-seed seed))
+ ((PI)
+ (let ((seed
+ ((ssax:make-pi-parser my-pi-handlers)
+ port (xml-token-head term-token) seed)))
+ (loop port entities expect-eof? seed)))
+ ((ENTITY-REF)
+ (let ((seed
+ (ssax:handle-parsed-entity
+ port (xml-token-head term-token)
+ entities
+ (lambda (port entities seed)
+ (loop port entities #t seed))
+ my-char-data-handler
+ seed))) ; keep on reading the content after ent
+ (loop port entities expect-eof? seed)))
+ ((START) ; Start of a child element
+ (if (eq? expected-content 'PCDATA)
+ (parser-error port "[elementvalid] broken for "
+ elem-gi
+ " with char content only; unexpected token "
+ term-token))
+ ; Do other validation of the element content
+ (let ((seed
+ (handle-start-tag
+ (xml-token-head term-token)
+ port entities namespaces
+ preserve-ws? seed)))
+ (loop port entities expect-eof? seed)))
+ (else
+ (parser-error port "XML [43] broken for "
+ term-token))))))))
+ )))
+))))
+
+
+; syntax: ssax:make-parser user-handler-tag user-handler-proc ...
+;
+; Create an XML parser, an instance of the XML parsing framework.
+; This will be a SAX, a DOM, or a specialized parser depending
+; on the supplied user-handlers.
+
+; user-handler-tag is a symbol that identifies a procedural expression
+; that follows the tag. Given below are tags and signatures of the
+; corresponding procedures. Not all tags have to be specified. If some
+; are omitted, reasonable defaults will apply.
+;
+
+; tag: DOCTYPE
+; handler-procedure: PORT DOCNAME SYSTEMID INTERNAL-SUBSET? SEED
+; If internal-subset? is #t, the current position in the port
+; is right after we have read #\[ that begins the internal DTD subset.
+; We must finish reading of this subset before we return
+; (or must call skip-internal-subset if we aren't interested in reading it).
+; The port at exit must be at the first symbol after the whole
+; DOCTYPE declaration.
+; The handler-procedure must generate four values:
+; ELEMS ENTITIES NAMESPACES SEED
+; See xml-decl::elems for ELEMS. It may be #f to switch off the validation.
+; NAMESPACES will typically contain USER-PREFIXes for selected URI-SYMBs.
+; The default handler-procedure skips the internal subset,
+; if any, and returns (values #f '() '() seed)
+
+; tag: UNDECL-ROOT
+; handler-procedure: ELEM-GI SEED
+; where ELEM-GI is an UNRES-NAME of the root element. This procedure
+; is called when an XML document under parsing contains _no_ DOCTYPE
+; declaration.
+; The handler-procedure, as a DOCTYPE handler procedure above,
+; must generate four values:
+; ELEMS ENTITIES NAMESPACES SEED
+; The default handler-procedure returns (values #f '() '() seed)
+
+; tag: DECL-ROOT
+; handler-procedure: ELEM-GI SEED
+; where ELEM-GI is an UNRES-NAME of the root element. This procedure
+; is called when an XML document under parsing does contains the DOCTYPE
+; declaration.
+; The handler-procedure must generate a new SEED (and verify
+; that the name of the root element matches the doctype, if the handler
+; so wishes).
+; The default handler-procedure is the identity function.
+
+; tag: NEW-LEVEL-SEED
+; handler-procedure: see ssax:make-elem-parser, my-new-level-seed
+
+; tag: FINISH-ELEMENT
+; handler-procedure: see ssax:make-elem-parser, my-finish-element
+
+; tag: CHAR-DATA-HANDLER
+; handler-procedure: see ssax:make-elem-parser, my-char-data-handler
+
+; tag: PI
+; handler-procedure: see ssax:make-pi-parser
+; The default value is '()
+
+; The generated parser is a
+; procedure PORT SEED
+
+; This procedure parses the document prolog and then exits to
+; an element parser (created by ssax:make-elem-parser) to handle
+; the rest.
+;
+; [1] document ::= prolog element Misc*
+; [22] prolog ::= XMLDecl? Misc* (doctypedec | Misc*)?
+; [27] Misc ::= Comment | PI | S
+;
+; [28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S?
+; ('[' (markupdecl | PEReference | S)* ']' S?)? '>'
+; [29] markupdecl ::= elementdecl | AttlistDecl
+; | EntityDecl
+; | NotationDecl | PI
+; | Comment
+;
+
+
+; This is ssax:make-parser with all the (specialization) handlers given
+; as positional arguments. It is called by ssax:make-parser, see below
+(define-syntax ssax:make-parser/positional-args
+ (syntax-rules ()
+ ((ssax:make-parser/positional-args
+ *handler-DOCTYPE
+ *handler-UNDECL-ROOT
+ *handler-DECL-ROOT
+ *handler-NEW-LEVEL-SEED
+ *handler-FINISH-ELEMENT
+ *handler-CHAR-DATA-HANDLER
+ *handler-PI)
+ (lambda (port seed)
+
+ ; We must've just scanned the DOCTYPE token
+ ; Handle the doctype declaration and exit to
+ ; scan-for-significant-prolog-token-2, and eventually, to the
+ ; element parser.
+ (define (handle-decl port token-head seed)
+ (or (eq? (string->symbol "DOCTYPE") token-head)
+ (parser-error port "XML [22], expected DOCTYPE declaration, found "
+ token-head))
+ (assert-curr-char ssax:S-chars "XML [28], space after DOCTYPE" port)
+ (ssax:skip-S port)
+ (let*-values
+ (((docname) (ssax:read-QName port))
+ ((systemid)
+ (and (ssax:ncname-starting-char? (ssax:skip-S port))
+ (ssax:read-external-id port)))
+ ((internal-subset?)
+ (begin (ssax:skip-S port)
+ (eqv? #\[ (assert-curr-char '(#\> #\[)
+ "XML [28], end-of-DOCTYPE" port))))
+ ((elems entities namespaces seed)
+ (*handler-DOCTYPE port docname systemid
+ internal-subset? seed))
+ )
+ (scan-for-significant-prolog-token-2 port elems entities namespaces
+ seed)))
+
+
+ ; Scan the leading PIs until we encounter either a doctype declaration
+ ; or a start token (of the root element)
+ ; In the latter two cases, we exit to the appropriate continuation
+ (define (scan-for-significant-prolog-token-1 port seed)
+ (let ((token (ssax:scan-Misc port)))
+ (if (eof-object? token)
+ (parser-error port "XML [22], unexpected EOF")
+ (case (xml-token-kind token)
+ ((PI)
+ (let ((seed
+ ((ssax:make-pi-parser *handler-PI)
+ port (xml-token-head token) seed)))
+ (scan-for-significant-prolog-token-1 port seed)))
+ ((DECL) (handle-decl port (xml-token-head token) seed))
+ ((START)
+ (let*-values
+ (((elems entities namespaces seed)
+ (*handler-UNDECL-ROOT (xml-token-head token) seed)))
+ (element-parser (xml-token-head token) port elems
+ entities namespaces #f seed)))
+ (else (parser-error port "XML [22], unexpected markup "
+ token))))))
+
+
+ ; Scan PIs after the doctype declaration, till we encounter
+ ; the start tag of the root element. After that we exit
+ ; to the element parser
+ (define (scan-for-significant-prolog-token-2 port elems entities
+ namespaces seed)
+ (let ((token (ssax:scan-Misc port)))
+ (if (eof-object? token)
+ (parser-error port "XML [22], unexpected EOF")
+ (case (xml-token-kind token)
+ ((PI)
+ (let ((seed
+ ((ssax:make-pi-parser *handler-PI)
+ port (xml-token-head token) seed)))
+ (scan-for-significant-prolog-token-2 port elems entities
+ namespaces seed)))
+ ((START)
+ (element-parser (xml-token-head token) port elems
+ entities namespaces #f
+ (*handler-DECL-ROOT (xml-token-head token) seed)))
+ (else (parser-error port "XML [22], unexpected markup "
+ token))))))
+
+
+ ; A procedure start-tag-head port elems entities namespaces
+ ; preserve-ws? seed
+ (define element-parser
+ (ssax:make-elem-parser *handler-NEW-LEVEL-SEED
+ *handler-FINISH-ELEMENT
+ *handler-CHAR-DATA-HANDLER
+ *handler-PI))
+
+ ; Get the ball rolling ...
+ (scan-for-significant-prolog-token-1 port seed)
+))))
+
+
+
+; The following meta-macro turns a regular macro (with positional
+; arguments) into a form with keyword (labeled) arguments. We later
+; use the meta-macro to convert ssax:make-parser/positional-args into
+; ssax:make-parser. The latter provides a prettier (with labeled
+; arguments and defaults) interface to
+; ssax:make-parser/positional-args
+;
+; ssax:define-labeled-arg-macro LABELED-ARG-MACRO-NAME
+; (POS-MACRO-NAME ARG-DESCRIPTOR ...)
+; expands into the definition of a macro
+; LABELED-ARG-MACRO-NAME KW-NAME KW-VALUE KW-NAME1 KW-VALUE1 ...
+; which, in turn, expands into
+; POS-MACRO-NAME ARG1 ARG2 ...
+; where each ARG1 etc. comes either from KW-VALUE or from
+; the deafult part of ARG-DESCRIPTOR. ARG1 corresponds to the first
+; ARG-DESCRIPTOR, ARG2 corresponds to the second descriptor, etc.
+; Here ARG-DESCRIPTOR describes one argument of the positional macro.
+; It has the form
+; (ARG-NAME DEFAULT-VALUE)
+; or
+; (ARG-NAME)
+; In the latter form, the default value is not given, so that the invocation of
+; LABELED-ARG-MACRO-NAME must mention the corresponding parameter.
+; ARG-NAME can be anything: an identifier, a string, or even a number.
+
+
+(define-syntax ssax:define-labeled-arg-macro
+ (syntax-rules ()
+ ((ssax:define-labeled-arg-macro
+ labeled-arg-macro-name
+ (positional-macro-name
+ (arg-name . arg-def) ...))
+ (define-syntax labeled-arg-macro-name
+ (syntax-rules ()
+ ((labeled-arg-macro-name . kw-val-pairs)
+ (letrec-syntax
+ ((find
+ (syntax-rules (arg-name ...)
+ ((find k-args (arg-name . default) arg-name
+ val . others) ; found arg-name among kw-val-pairs
+ (next val . k-args)) ...
+ ((find k-args key arg-no-match-name val . others)
+ (find k-args key . others))
+ ((find k-args (arg-name default)) ; default must be here
+ (next default . k-args)) ...
+ ))
+ (next ; pack the continuation to find
+ (syntax-rules ()
+ ((next val vals key . keys)
+ (find ((val . vals) . keys) key . kw-val-pairs))
+ ((next val vals) ; processed all arg-descriptors
+ (rev-apply (val) vals))))
+ (rev-apply
+ (syntax-rules ()
+ ((rev-apply form (x . xs))
+ (rev-apply (x . form) xs))
+ ((rev-apply form ()) form))))
+ (next positional-macro-name ()
+ (arg-name . arg-def) ...))))))))
+
+
+; The definition of ssax:make-parser
+(ssax:define-labeled-arg-macro ssax:make-parser
+ (ssax:make-parser/positional-args
+ (DOCTYPE
+ (lambda (port docname systemid internal-subset? seed)
+ (when internal-subset?
+ (ssax:warn port "Internal DTD subset is not currently handled ")
+ (ssax:skip-internal-dtd port))
+ (ssax:warn port "DOCTYPE DECL " docname " "
+ systemid " found and skipped")
+ (values #f '() '() seed)
+ ))
+ (UNDECL-ROOT
+ (lambda (elem-gi seed) (values #f '() '() seed)))
+ (DECL-ROOT
+ (lambda (elem-gi seed) seed))
+ (NEW-LEVEL-SEED) ; required
+ (FINISH-ELEMENT) ; required
+ (CHAR-DATA-HANDLER) ; required
+ (PI ())
+ ))
+
+(run-test
+ (letrec ((simple-parser
+ (lambda (str doctype-fn)
+ (call-with-input-string str
+ (lambda (port)
+ ((ssax:make-parser
+ NEW-LEVEL-SEED
+ (lambda (elem-gi attributes namespaces
+ expected-content seed)
+ '())
+
+ FINISH-ELEMENT
+ (lambda (elem-gi attributes namespaces parent-seed seed)
+ (let
+ ((seed (if (null? namespaces) (reverse seed)
+ (cons (list '*NAMESPACES* namespaces)
+ (reverse seed)))))
+ (let ((seed (if (attlist-null? attributes) seed
+ (cons
+ (cons '@
+ (map (lambda (attr)
+ (list (car attr) (cdr attr)))
+ (attlist->alist attributes)))
+ seed))))
+ (cons (cons elem-gi seed) parent-seed))))
+
+ CHAR-DATA-HANDLER
+ (lambda (string1 string2 seed)
+ (if (string-null? string2) (cons string1 seed)
+ (cons* string2 string1 seed)))
+
+ DOCTYPE
+ (lambda (port docname systemid internal-subset? seed)
+ (when internal-subset?
+ (ssax:warn port
+ "Internal DTD subset is not currently handled ")
+ (ssax:skip-internal-dtd port))
+ (ssax:warn port "DOCTYPE DECL " docname " "
+ systemid " found and skipped")
+ (doctype-fn docname seed))
+
+ UNDECL-ROOT
+ (lambda (elem-gi seed)
+ (doctype-fn elem-gi seed))
+ )
+ port '())))))
+
+ (dummy-doctype-fn (lambda (elem-gi seed) (values #f '() '() seed)))
+ (test
+ (lambda (str doctype-fn expected)
+ (cout nl "Parsing: " str nl)
+ (let ((result (simple-parser (unesc-string str) doctype-fn)))
+ (write result)
+ (assert (equal? result expected)))))
+ )
+
+ (test "<BR/>" dummy-doctype-fn '(('"BR")))
+ (assert (failed? (test "<BR>" dummy-doctype-fn '())))
+ (test "<BR></BR>" dummy-doctype-fn '(('"BR")))
+ (assert (failed? (test "<BR></BB>" dummy-doctype-fn '())))
+
+ (test " <A HREF='URL'> link <I>itlink </I> &amp;amp;</A>"
+ dummy-doctype-fn
+ '(('"A" (@ ('"HREF" "URL")) " link " ('"I" "itlink ")
+ " " "&" "amp;")))
+
+ (test
+ " <A HREF='URL' xml:space='preserve'> link <I>itlink </I> &amp;amp;</A>" dummy-doctype-fn
+ '(('"A" (@ ('"HREF" "URL") (('"xml" . '"space") "preserve"))
+ " link " ('"I" "itlink ") " " "&" "amp;")))
+
+ (test " <A HREF='URL' xml:space='preserve'> link <I xml:space='default'>itlink </I> &amp;amp;</A>" dummy-doctype-fn
+ '(('"A" (@ ('"HREF" "URL") (('"xml" . '"space") "preserve"))
+ " link "
+ ('"I" (@ (('"xml" . '"space") "default")) "itlink ")
+ " " "&" "amp;")))
+ (test "<itemize><item>This is item 1 </item>%n<!-- Just:a comment --><item>Item 2</item>%n </itemize>" dummy-doctype-fn
+ `(('"itemize" ('"item" "This is item 1 ")
+ ,(unesc-string "%n") ('"item" "Item 2") ,(unesc-string "%n "))))
+ (test " <P><![CDATA[<BR>%n<![CDATA[<BR>]]&gt;]]></P>"
+ dummy-doctype-fn `(('"P" "<BR>" ,nl "<![CDATA[<BR>" "]]" "" ">")))
+
+ (test " <P><![CDATA[<BR>%r<![CDATA[<BR>]]&gt;]]></P>"
+ dummy-doctype-fn `(('"P" "<BR>" ,nl "<![CDATA[<BR>" "]]" "" ">")))
+
+ (test "<?xml version='1.0'?>%n%n<Reports TStamp='1'></Reports>"
+ dummy-doctype-fn '(('"Reports" (@ ('"TStamp" "1")))))
+ (test "%n<?PI xxx?><!-- Comment %n -%r-->%n<?PI1 zzz?><T/>"
+ dummy-doctype-fn '(('"T")))
+ (test "<!DOCTYPE T SYSTEM 'system1' ><!-- comment -->%n<T/>"
+ (lambda (elem-gi seed) (assert (equal? elem-gi ''"T"))
+ (values #f '() '() seed))
+ '(('"T")))
+ (test "<!DOCTYPE T PUBLIC '//EN/T' \"system1\" [ <!ELEMENT a 'aa'> ]>%n<?pi?><T/>"
+ (lambda (elem-gi seed) (assert (equal? elem-gi ''"T"))
+ (values #f '() '() seed))
+ '(('"T")))
+ (test "<BR/>"
+ (lambda (elem-gi seed)
+ (values '(('"BR" EMPTY ())) '() '() seed)) '(('"BR")))
+ (test "<BR></BR>"
+ (lambda (elem-gi seed)
+ (values '(('"BR" EMPTY ())) '() '() seed)) '(('"BR")))
+ (assert (failed? (test "<BR>aa</BR>"
+ (lambda (elem-gi seed)
+ (values '(('"BR" EMPTY ())) '() '() seed)) '())))
+ (test "<BR>aa</BR>"
+ (lambda (elem-gi seed)
+ (values '(('"BR" PCDATA ())) '() '() seed)) '(('"BR" "aa")))
+ (assert (failed? (test "<BR>a<I>a</I></BR>"
+ (lambda (elem-gi seed)
+ (values '(('"BR" PCDATA ())) '() '() seed)) '())))
+ (test "<BR>a<I>a</I></BR>"
+ (lambda (elem-gi seed)
+ (values '(('"BR" ANY ()) ('"I" PCDATA ())) '() '() seed))
+ '(('"BR" "a" ('"I" "a"))))
+
+
+ (test "<DIV>Example: \"&example;\"</DIV>"
+ (lambda (elem-gi seed)
+ (values #f '((example . "<P>An ampersand (&#38;) may be escaped numerically (&#38;#38;) or with a general entity (&amp;amp;).</P>")) '() seed))
+ '(('"DIV" "Example: \""
+ ('"P" "An ampersand (" "&" ") may be escaped numerically (" "&" "#38;) or with a general entity (" "&" "amp;).") "\"")))
+ (test "<DIV>Example: \"&example;\" <P/></DIV>"
+ (lambda (elem-gi seed)
+ (values #f '(('"quote" . "<I>example:</I> ex")
+ ('"example" . "<Q>&quote;!</Q>?")) '() seed))
+ '(('"DIV" "Example: \"" ('"Q" ('"I" "example:") " ex" "!") "?"
+ "\" " ('"P"))))
+ (assert (failed?
+ (test "<DIV>Example: \"&example;\" <P/></DIV>"
+ (lambda (elem-gi seed)
+ (values #f '(('"quote" . "<I>example:")
+ ('"example" . "<Q>&quote;</I>!</Q>?")) '() seed))
+ '())))
+
+ (test "<DIV A:B='A' B='B' xmlns:A='URI1' xmlns='URI1'><A:P xmlns=''><BR/></A:P></DIV>"
+ (lambda (elem-gi seed)
+ (values #f '() '() seed))
+ '((('"URI1" . '"DIV") (@ ('"B" "B") (('"URI1" . '"B") "A"))
+ (*NAMESPACES* (('"A" '"URI1" . '"URI1")
+ (*DEFAULT* '"URI1" . '"URI1")))
+ (('"URI1" . '"P")
+ (*NAMESPACES* ((*DEFAULT* #f . #f) ('"A" '"URI1" . '"URI1")
+ (*DEFAULT* '"URI1" . '"URI1")))
+ ('"BR"
+ (*NAMESPACES* ((*DEFAULT* #f . #f)
+ ('"A" '"URI1" . '"URI1")
+ (*DEFAULT* '"URI1" . '"URI1"))))))))
+ (test "<DIV A:B='A' B='B' xmlns:A='URI1' xmlns='URI1'><A:P xmlns=''><BR/></A:P></DIV>"
+ (lambda (elem-gi seed)
+ (values #f '() '((#f '"UA" . '"URI1")) seed))
+ '((('"UA" . '"DIV") (@ ('"B" "B") (('"UA" . '"B") "A"))
+ (*NAMESPACES* (('"A" '"UA" . '"URI1")
+ (*DEFAULT* '"UA" . '"URI1") (#f '"UA" . '"URI1")))
+ (('"UA" . '"P")
+ (*NAMESPACES* ((*DEFAULT* #f . #f) ('"A" '"UA" . '"URI1")
+ (*DEFAULT* '"UA" . '"URI1") (#f '"UA" . '"URI1")))
+ ('"BR"
+ (*NAMESPACES* ((*DEFAULT* #f . #f) ('"A" '"UA" . '"URI1")
+ (*DEFAULT* '"UA" . '"URI1")
+ (#f '"UA" . '"URI1"))))))))
+ ; uniqattr should fail
+ (assert (failed?
+ (test "<DIV A:B='A' B='B' xmlns:A='URI1' xmlns='URI1'><A:P xmlns=''><BR/></A:P></DIV>"
+ (lambda (elem-gi seed)
+ (values
+ `(('"DIV" ANY (('"B" CDATA IMPLIED #f)
+ (('"A" . '"B") CDATA IMPLIED #f)
+ (('"C" . '"B") CDATA IMPLIED "xx")
+ (('"xmlns" . '"C") CDATA IMPLIED "URI1")
+ ))
+ (('"A" . '"P") ANY ()) ('"BR" '"EMPTY" ()))
+ '() '((#f '"UA" . '"URI1")) seed))
+ '())))
+ ; prefix C undeclared
+ (assert (failed?
+ (test "<DIV A:B='A' B='B' xmlns:A='URI1' xmlns='URI1'><A:P xmlns=''><BR/></A:P></DIV>"
+ (lambda (elem-gi seed)
+ (values
+ '(('"DIV" ANY (('"B" CDATA IMPLIED #f)
+ ('"xmlns" CDATA IMPLIED "URI1")
+ (('"A" . '"B") CDATA IMPLIED #f)
+ (('"C" . '"B") CDATA IMPLIED "xx")
+ ))
+ (('"A" . '"P") ANY ()) ('"BR" EMPTY ()))
+ '() '((#f '"UA" . '"URI1")) seed))
+ '())))
+
+ ; contradiction to xmlns declaration
+ (assert (failed?
+ (test "<DIV A:B='A' B='B' xmlns:A='URI1' xmlns='URI1'><A:P xmlns=''><BR/></A:P></DIV>"
+ (lambda (elem-gi seed)
+ (values
+ '(('"DIV" ANY (('"B" CDATA IMPLIED #f)
+ ('"xmlns" CDATA FIXED "URI2")
+ (('"A" . '"B") CDATA IMPLIED #f)
+ ))
+ (('"A" . '"P") ANY ()) ('"BR" EMPTY ()))
+ '() '((#f '"UA" . '"URI1")) seed))
+ '())))
+
+ (test "<DIV A:B='A' B='B' xmlns:A='URI1' xmlns='URI1'><A:P xmlns=''><BR/></A:P></DIV>"
+ (lambda (elem-gi seed)
+ (values
+ '(('"DIV" ANY (('"B" CDATA IMPLIED #f)
+ ('"xmlns" CDATA FIXED "URI1")
+ (('"A" . '"B") CDATA IMPLIED #f)
+ ))
+ (('"A" . '"P") ANY ()) ('"BR" EMPTY ()))
+ '() '((#f '"UA" . '"URI1")) seed))
+ '((('"UA" . '"DIV") (@ ('"B" "B") (('"UA" . '"B") "A"))
+ (*NAMESPACES* ((*DEFAULT* '"UA" . '"URI1")
+ ('"A" '"UA" . '"URI1") (#f '"UA" . '"URI1")))
+ (('"UA" . '"P")
+ (*NAMESPACES* ((*DEFAULT* #f . #f)
+ (*DEFAULT* '"UA" . '"URI1")
+ ('"A" '"UA" . '"URI1") (#f '"UA" . '"URI1")))
+ ('"BR"
+ (*NAMESPACES* ((*DEFAULT* #f . #f) (*DEFAULT* '"UA" . '"URI1")
+ ('"A" '"UA" . '"URI1") (#f '"UA" . '"URI1"))))))))
+
+ (test "<DIV A:B='A' B='B' xmlns:A='URI1' xmlns='URI1'><A:P xmlns=''><BR/></A:P></DIV>"
+ (lambda (elem-gi seed)
+ (values
+ '(('"DIV" ANY (('"B" CDATA IMPLIED #f)
+ (('"A" . '"B") CDATA IMPLIED #f)
+ (('"C" . '"B") CDATA IMPLIED "xx")
+ (('"xmlns" . '"C") CDATA IMPLIED "URI2")
+ ))
+ (('"A" . '"P") ANY ()) ('"BR" EMPTY ()))
+ '() '((#f '"UA" . '"URI1")) seed))
+ '((('"UA" . '"DIV") (@ ('"B" "B") (('"UA" . '"B") "A")
+ (('"URI2" . '"B") "xx"))
+ (*NAMESPACES* ((*DEFAULT* '"UA" . '"URI1")
+ ('"A" '"UA" . '"URI1")
+ ('"C" '"URI2" . '"URI2")
+ (#f '"UA" . '"URI1")))
+ (('"UA" . '"P")
+ (*NAMESPACES* ((*DEFAULT* #f . #f) (*DEFAULT* '"UA" . '"URI1")
+ ('"A" '"UA" . '"URI1")
+ ('"C" '"URI2" . '"URI2") (#f '"UA" . '"URI1")))
+ ('"BR"
+ (*NAMESPACES* ((*DEFAULT* #f . #f)
+ (*DEFAULT* '"UA" . '"URI1")
+ ('"A" '"UA" . '"URI1")
+ ('"C" '"URI2" . '"URI2")
+ (#f '"UA" . '"URI1"))))))))
+))
+
+
+
+;========================================================================
+; Highest-level parsers: XML to SXML
+;
+
+; First, a few utility procedures that turned out useful
+
+; ssax:reverse-collect-str LIST-OF-FRAGS -> LIST-OF-FRAGS
+; given the list of fragments (some of which are text strings)
+; reverse the list and concatenate adjacent text strings.
+; We can prove from the general case below that if LIST-OF-FRAGS
+; has zero or one element, the result of the procedure is equal?
+; to its argument. This fact justifies the shortcut evaluation below.
+(define (ssax:reverse-collect-str fragments)
+ (cond
+ ((null? fragments) '()) ; a shortcut
+ ((null? (cdr fragments)) fragments) ; see the comment above
+ (else
+ (let loop ((fragments fragments) (result '()) (strs '()))
+ (cond
+ ((null? fragments)
+ (if (null? strs) result
+ (cons (string-concatenate/shared strs) result)))
+ ((string? (car fragments))
+ (loop (cdr fragments) result (cons (car fragments) strs)))
+ (else
+ (loop (cdr fragments)
+ (cons
+ (car fragments)
+ (if (null? strs) result
+ (cons (string-concatenate/shared strs) result)))
+ '())))))))
+
+
+; ssax:reverse-collect-str-drop-ws LIST-OF-FRAGS -> LIST-OF-FRAGS
+; given the list of fragments (some of which are text strings)
+; reverse the list and concatenate adjacent text strings.
+; We also drop "unsignificant" whitespace, that is, whitespace
+; in front, behind and between elements. The whitespace that
+; is included in character data is not affected.
+; We use this procedure to "intelligently" drop "insignificant"
+; whitespace in the parsed SXML. If the strict compliance with
+; the XML Recommendation regarding the whitespace is desired, please
+; use the ssax:reverse-collect-str procedure instead.
+
+(define (ssax:reverse-collect-str-drop-ws fragments)
+ (cond
+ ((null? fragments) '()) ; a shortcut
+ ((null? (cdr fragments)) ; another shortcut
+ (if (and (string? (car fragments)) (string-whitespace? (car fragments)))
+ '() fragments)) ; remove trailing ws
+ (else
+ (let loop ((fragments fragments) (result '()) (strs '())
+ (all-whitespace? #t))
+ (cond
+ ((null? fragments)
+ (if all-whitespace? result ; remove leading ws
+ (cons (string-concatenate/shared strs) result)))
+ ((string? (car fragments))
+ (loop (cdr fragments) result (cons (car fragments) strs)
+ (and all-whitespace?
+ (string-whitespace? (car fragments)))))
+ (else
+ (loop (cdr fragments)
+ (cons
+ (car fragments)
+ (if all-whitespace? result
+ (cons (string-concatenate/shared strs) result)))
+ '() #t)))))))
+
+
+; procedure: ssax:xml->sxml PORT NAMESPACE-PREFIX-ASSIG
+;
+; This is an instance of a SSAX parser above that returns an SXML
+; representation of the XML document to be read from PORT.
+; NAMESPACE-PREFIX-ASSIG is a list of (USER-PREFIX . URI-STRING)
+; that assigns USER-PREFIXes to certain namespaces identified by
+; particular URI-STRINGs. It may be an empty list.
+; The procedure returns an SXML tree. The port points out to the
+; first character after the root element.
+
+(define (ssax:xml->sxml port namespace-prefix-assig)
+ (letrec
+ ((namespaces
+ (map (lambda (el)
+ (cons* #f (car el) (ssax:uri-string->symbol (cdr el))))
+ namespace-prefix-assig))
+
+ (RES-NAME->SXML
+ (lambda (res-name)
+ (string->symbol
+ (string-append
+ (symbol->string (car res-name))
+ ":"
+ (symbol->string (cdr res-name))))))
+
+ )
+ (let ((result
+ (reverse
+ ((ssax:make-parser
+ NEW-LEVEL-SEED
+ (lambda (elem-gi attributes namespaces
+ expected-content seed)
+ '())
+
+ FINISH-ELEMENT
+ (lambda (elem-gi attributes namespaces parent-seed seed)
+ (let ((seed (ssax:reverse-collect-str seed))
+ (attrs
+ (attlist-fold
+ (lambda (attr accum)
+ (cons (list
+ (if (symbol? (car attr)) (car attr)
+ (RES-NAME->SXML (car attr)))
+ (cdr attr)) accum))
+ '() attributes)))
+ (cons
+ (cons
+ (if (symbol? elem-gi) elem-gi
+ (RES-NAME->SXML elem-gi))
+ (if (null? attrs) seed
+ (cons (cons '@ attrs) seed)))
+ parent-seed)))
+
+ CHAR-DATA-HANDLER
+ (lambda (string1 string2 seed)
+ (if (string-null? string2) (cons string1 seed)
+ (cons* string2 string1 seed)))
+
+ DOCTYPE
+ (lambda (port docname systemid internal-subset? seed)
+ (when internal-subset?
+ (ssax:warn port
+ "Internal DTD subset is not currently handled ")
+ (ssax:skip-internal-dtd port))
+ (ssax:warn port "DOCTYPE DECL " docname " "
+ systemid " found and skipped")
+ (values #f '() namespaces seed))
+
+ UNDECL-ROOT
+ (lambda (elem-gi seed)
+ (values #f '() namespaces seed))
+
+ PI
+ ((*DEFAULT* .
+ (lambda (port pi-tag seed)
+ (cons
+ (list '*PI* pi-tag (ssax:read-pi-body-as-string port))
+ seed))))
+ )
+ port '()))))
+ (cons '*TOP*
+ (if (null? namespace-prefix-assig) result
+ (cons
+ (list '@ (cons '*NAMESPACES*
+ (map (lambda (ns) (list (car ns) (cdr ns)))
+ namespace-prefix-assig)))
+ result)))
+)))
+
+; For backwards compatibility
+(define SSAX:XML->SXML ssax:xml->sxml)
+
+
+; a few lines of validation code
+(run-test (letrec
+ ((test (lambda (str namespace-assig expected-res)
+ (newline) (display "input: ")
+ (write (unesc-string str)) (newline) (display "Result: ")
+ (let ((result
+ (call-with-input-string (unesc-string str)
+ (lambda (port)
+ (ssax:xml->sxml port namespace-assig)))))
+ (pp result)
+ (assert (equal_? result expected-res))))))
+
+ (test " <BR/>" '() '(*TOP* (BR)))
+ (test "<BR></BR>" '() '(*TOP* (BR)))
+ (test " <BR CLEAR='ALL'%nCLASS='Class1'/>" '()
+ '(*TOP* (BR (@ (CLEAR "ALL") (CLASS "Class1")))))
+ (test " <A HREF='URL'> link <I>itlink </I> &amp;amp;</A>" '()
+ '(*TOP* (A (@ (HREF "URL")) " link " (I "itlink ") " &amp;")))
+ (test " <A HREF='URL' xml:space='preserve'> link <I>itlink </I> &amp;amp;</A>" '()
+ '(*TOP* (A (@ (xml:space "preserve") (HREF "URL"))
+ " link " (I "itlink ") " &amp;")))
+ (test " <A HREF='URL' xml:space='preserve'> link <I xml:space='default'>itlink </I> &amp;amp;</A>" '()
+ '(*TOP* (A (@ (xml:space "preserve") (HREF "URL"))
+ " link " (I (@ (xml:space "default"))
+ "itlink ") " &amp;")))
+ (test " <P><?pi1 p1 content ?>?<?pi2 pi2? content? ??></P>" '()
+ '(*TOP* (P (*PI* pi1 "p1 content ") "?"
+ (*PI* pi2 "pi2? content? ?"))))
+ (test " <P>some text <![CDATA[<]]>1%n&quot;<B>strong</B>&quot;%r</P>"
+ '()
+ `(*TOP* (P ,(unesc-string "some text <1%n\"")
+ (B "strong") ,(unesc-string "\"%n"))))
+ (test " <P><![CDATA[<BR>%n<![CDATA[<BR>]]&gt;]]></P>" '()
+ `(*TOP* (P ,(unesc-string "<BR>%n<![CDATA[<BR>]]>"))))
+; (test "<T1><T2>it&apos;s%r%nand that%n</T2>%r%n%r%n%n</T1>" '()
+; '(*TOP* (T1 (T2 "it's%nand that%n") "%n%n%n")))
+ (test "<T1><T2>it&apos;s%r%nand that%n</T2>%r%n%r%n%n</T1>" '()
+ `(*TOP* (T1 (T2 ,(unesc-string "it's%nand that%n")) ,(unesc-string "%n%n%n"))))
+ (test "<T1><T2>it&apos;s%rand that%n</T2>%r%n%r%n%n</T1>" '()
+ `(*TOP* (T1 (T2 ,(unesc-string "it's%nand that%n")) ,(unesc-string "%n%n%n"))))
+ (test "<!DOCTYPE T SYSTEM 'system1' ><!-- comment -->%n<T/>" '()
+ '(*TOP* (T)))
+ (test "<?xml version='1.0'?>%n<WEIGHT unit=\"pound\">%n<NET certified='certified'> 67 </NET>%n<GROSS> 95 </GROSS>%n</WEIGHT>" '()
+ `(*TOP* (*PI* xml "version='1.0'") (WEIGHT (@ (unit "pound"))
+ ,nl (NET (@ (certified "certified")) " 67 ") ,nl
+ (GROSS " 95 ") ,nl)
+ ))
+; (test "<?xml version='1.0'?>%n<WEIGHT unit=\"pound\">%n<NET certified='certified'> 67 </NET>%n<GROSS> 95 </GROSS>%n</WEIGHT>" '()
+; '(*TOP* (*PI* xml "version='1.0'") (WEIGHT (@ (unit "pound"))
+; "%n" (NET (@ (certified "certified")) " 67 ")
+; "%n" (GROSS " 95 ") "%n")
+; ))
+ (test "<DIV A:B='A' B='B' xmlns:A='URI1' xmlns='URI1'><A:P xmlns=''><BR/></A:P></DIV>" '()
+ '(*TOP* (URI1:DIV (@ (URI1:B "A") (B "B")) (URI1:P (BR)))))
+ (test "<DIV A:B='A' B='B' xmlns:A='URI1' xmlns='URI1'><A:P xmlns=''><BR/></A:P></DIV>" '((UA . "URI1"))
+ '(*TOP* (@ (*NAMESPACES* (UA "URI1")))
+ (UA:DIV (@ (UA:B "A") (B "B")) (UA:P (BR)))))
+
+ ; A few tests from XML Namespaces Recommendation
+ (test (string-append
+ "<x xmlns:edi='http://ecommerce.org/schema'>"
+ "<!-- the 'taxClass' attribute's ns http://ecommerce.org/schema -->"
+ "<lineItem edi:taxClass='exempt'>Baby food</lineItem>" nl
+ "</x>") '()
+ `(*TOP*
+ (x (lineItem
+ (@ (http://ecommerce.org/schema:taxClass "exempt"))
+ "Baby food") ,nl)))
+ (test (string-append
+ "<x xmlns:edi='http://ecommerce.org/schema'>"
+ "<!-- the 'taxClass' attribute's ns http://ecommerce.org/schema -->"
+ "<lineItem edi:taxClass='exempt'>Baby food</lineItem>"
+ "</x>") '((EDI . "http://ecommerce.org/schema"))
+ '(*TOP*
+ (@ (*NAMESPACES* (EDI "http://ecommerce.org/schema")))
+ (x (lineItem
+ (@ (EDI:taxClass "exempt"))
+ "Baby food"))))
+
+ (test (string-append
+ "<bk:book xmlns:bk='urn:loc.gov:books' "
+ "xmlns:isbn='urn:ISBN:0-395-36341-6'>"
+ "<bk:title>Cheaper by the Dozen</bk:title>"
+ "<isbn:number>1568491379</isbn:number></bk:book>")
+ '()
+ '(*TOP* (urn:loc.gov:books:book
+ (urn:loc.gov:books:title "Cheaper by the Dozen")
+ (urn:ISBN:0-395-36341-6:number "1568491379"))))
+
+ (test (string-append
+ "<!-- initially, the default namespace is 'books' -->"
+ "<book xmlns='urn:loc.gov:books' "
+ "xmlns:isbn='urn:ISBN:0-395-36341-6'>"
+ "<title>Cheaper by the Dozen</title>"
+ "<isbn:number>1568491379</isbn:number>"
+ "<notes>"
+ "<!-- make HTML the default namespace for some commentary -->"
+ "<p xmlns='urn:w3-org-ns:HTML'>"
+ "This is a <i>funny</i> book!"
+ "</p>"
+ "</notes>"
+ "</book>") '()
+ '(*TOP* (urn:loc.gov:books:book
+ (urn:loc.gov:books:title "Cheaper by the Dozen")
+ (urn:ISBN:0-395-36341-6:number "1568491379")
+ (urn:loc.gov:books:notes
+ (urn:w3-org-ns:HTML:p
+ "This is a " (urn:w3-org-ns:HTML:i "funny")
+ " book!")))))
+
+ (test (string-append
+ "<Beers>"
+ "<!-- the default namespace is now that of HTML -->"
+ "<table xmlns='http://www.w3.org/TR/REC-html40'>"
+ "<th><td>Name</td><td>Origin</td><td>Description</td></th>"
+ "<tr>"
+ "<!-- no default namespace inside table cells -->"
+ "<td><brandName xmlns=\"\">Huntsman</brandName></td>"
+ "<td><origin xmlns=''>Bath, UK</origin></td>"
+ "<td>"
+ "<details xmlns=''><class>Bitter</class><hop>Fuggles</hop>"
+ "<pro>Wonderful hop, light alcohol, good summer beer</pro>"
+ "<con>Fragile; excessive variance pub to pub</con>"
+ "</details>"
+ "</td>"
+ "</tr>"
+ "</table>"
+ "</Beers>")
+ '((html . "http://www.w3.org/TR/REC-html40"))
+ '(*TOP*
+ (@ (*NAMESPACES* (html "http://www.w3.org/TR/REC-html40")))
+ (Beers (html:table
+ (html:th (html:td "Name")
+ (html:td "Origin")
+ (html:td "Description"))
+ (html:tr (html:td (brandName "Huntsman"))
+ (html:td (origin "Bath, UK"))
+ (html:td
+ (details
+ (class "Bitter")
+ (hop "Fuggles")
+ (pro "Wonderful hop, light alcohol, good summer beer")
+ (con "Fragile; excessive variance pub to pub"))))))))
+
+ (test (string-append
+ "<!-- 1 --><RESERVATION xmlns:HTML='http://www.w3.org/TR/REC-html40'>"
+ "<!-- 2 --><NAME HTML:CLASS=\"largeSansSerif\">Layman, A</NAME>"
+ "<!-- 3 --><SEAT CLASS='Y' HTML:CLASS=\"largeMonotype\">33B</SEAT>"
+ "<!-- 4 --><HTML:A HREF='/cgi-bin/ResStatus'>Check Status</HTML:A>"
+ "<!-- 5 --><DEPARTURE>1997-05-24T07:55:00+1</DEPARTURE></RESERVATION>")
+ '((HTML . "http://www.w3.org/TR/REC-html40"))
+ '(*TOP*
+ (@ (*NAMESPACES* (HTML "http://www.w3.org/TR/REC-html40")))
+ (RESERVATION
+ (NAME (@ (HTML:CLASS "largeSansSerif")) "Layman, A")
+ (SEAT (@ (HTML:CLASS "largeMonotype") (CLASS "Y")) "33B")
+ (HTML:A (@ (HREF "/cgi-bin/ResStatus")) "Check Status")
+ (DEPARTURE "1997-05-24T07:55:00+1"))))
+ ; Part of RDF from the XML Infoset
+ (test (string-concatenate/shared '(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes'?>"
+ "<!-- this can be decoded as US-ASCII or iso-8859-1 as well,"
+ " since it contains no characters outside the US-ASCII repertoire -->"
+ "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'"
+ " xmlns:rdfs='http://www.w3.org/2000/01/rdf-schema#'"
+ " xmlns='http://www.w3.org/2001/02/infoset#'>"
+ "<rdfs:Class ID='Boolean'/>"
+ "<Boolean ID='Boolean.true'/>"
+ "<Boolean ID='Boolean.false'/>"
+ "<!--Info item classes-->"
+ "<rdfs:Class ID='InfoItem'/>"
+ "<rdfs:Class ID='Document' rdfs:subClassOf='#InfoItem'/>"
+ "<rdfs:Class ID='Element' rdfs:subClassOf='#InfoItem'/>"
+ "<rdfs:Class ID='Attribute' rdfs:subClassOf='#InfoItem'/>"
+ "<rdfs:Class ID='InfoItemSet'
+ rdfs:subClassOf='http://www.w3.org/1999/02/22-rdf-syntax-ns#Bag'/>"
+ "<rdfs:Class ID='AttributeSet' rdfs:subClassOf='#InfoItemSet'/>"
+ "<!--Info item properties-->"
+ "<rdfs:Property ID='allDeclarationsProcessed'>"
+ "<rdfs:domain resource='#Document'/>"
+ "<rdfs:range resource='#Boolean'/></rdfs:Property>"
+ "<rdfs:Property ID='attributes'>"
+ "<rdfs:domain resource='#Element'/>"
+ "<rdfs:range resource='#AttributeSet'/>"
+ "</rdfs:Property>"
+ "</rdf:RDF>"))
+ '((RDF . "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
+ (RDFS . "http://www.w3.org/2000/01/rdf-schema#")
+ (ISET . "http://www.w3.org/2001/02/infoset#"))
+ '(*TOP* (@ (*NAMESPACES*
+ (RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
+ (RDFS "http://www.w3.org/2000/01/rdf-schema#")
+ (ISET "http://www.w3.org/2001/02/infoset#")))
+ (*PI* xml "version='1.0' encoding='utf-8' standalone='yes'")
+ (RDF:RDF
+ (RDFS:Class (@ (ID "Boolean")))
+ (ISET:Boolean (@ (ID "Boolean.true")))
+ (ISET:Boolean (@ (ID "Boolean.false")))
+ (RDFS:Class (@ (ID "InfoItem")))
+ (RDFS:Class (@ (RDFS:subClassOf "#InfoItem") (ID "Document")))
+ (RDFS:Class (@ (RDFS:subClassOf "#InfoItem") (ID "Element")))
+ (RDFS:Class (@ (RDFS:subClassOf "#InfoItem") (ID "Attribute")))
+ (RDFS:Class
+ (@ (RDFS:subClassOf
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#Bag")
+ (ID "InfoItemSet")))
+ (RDFS:Class
+ (@ (RDFS:subClassOf "#InfoItemSet") (ID "AttributeSet")))
+ (RDFS:Property
+ (@ (ID "allDeclarationsProcessed"))
+ (RDFS:domain (@ (resource "#Document")))
+ (RDFS:range (@ (resource "#Boolean"))))
+ (RDFS:Property
+ (@ (ID "attributes"))
+ (RDFS:domain (@ (resource "#Element")))
+ (RDFS:range (@ (resource "#AttributeSet")))))))
+
+ ; Part of RDF from RSS of the Daemon News Mall
+ (test (string-concatenate/shared (list-intersperse '(
+ "<?xml version='1.0'?><rdf:RDF "
+ "xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' "
+ "xmlns='http://my.netscape.com/rdf/simple/0.9/'>"
+ "<channel>"
+ "<title>Daemon News Mall</title>"
+ "<link>http://mall.daemonnews.org/</link>"
+ "<description>Central source for all your BSD needs</description>"
+ "</channel>"
+ "<item>"
+ "<title>Daemon News Jan/Feb Issue NOW Available! Subscribe $24.95</title>"
+ "<link>http://mall.daemonnews.org/?page=shop/flypage&amp;product_id=880</link>"
+ "</item>"
+ "<item>"
+ "<title>The Design and Implementation of the 4.4BSD Operating System $54.95</title>"
+ "<link>http://mall.daemonnews.org/?page=shop/flypage&amp;product_id=912&amp;category_id=1761</link>"
+ "</item>"
+ "</rdf:RDF>")
+ (string #\newline)
+ ))
+ '((RDF . "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
+ (RSS . "http://my.netscape.com/rdf/simple/0.9/")
+ (ISET . "http://www.w3.org/2001/02/infoset#"))
+ `(*TOP* (@ (*NAMESPACES*
+ (RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
+ (RSS "http://my.netscape.com/rdf/simple/0.9/")
+ (ISET "http://www.w3.org/2001/02/infoset#")))
+ (*PI* xml "version='1.0'")
+ (RDF:RDF ,nl
+ (RSS:channel ,nl
+ (RSS:title "Daemon News Mall") ,nl
+ (RSS:link "http://mall.daemonnews.org/") ,nl
+ (RSS:description "Central source for all your BSD needs") ,nl) ,nl
+ (RSS:item ,nl
+ (RSS:title
+ "Daemon News Jan/Feb Issue NOW Available! Subscribe $24.95") ,nl
+ (RSS:link
+ "http://mall.daemonnews.org/?page=shop/flypage&product_id=880") ,nl) ,nl
+ (RSS:item ,nl
+ (RSS:title
+ "The Design and Implementation of the 4.4BSD Operating System $54.95") ,nl
+ (RSS:link
+ "http://mall.daemonnews.org/?page=shop/flypage&product_id=912&category_id=1761") ,nl) ,nl)))
+
+ (test (string-concatenate/shared
+ '("<Forecasts TStamp='958082142'>"
+ "<TAF TStamp='958066200' LatLon='36.583, -121.850' BId='724915'"
+ " SName='KMRY, MONTEREY PENINSULA'>"
+ "<VALID TRange='958068000, 958154400'>111730Z 111818</VALID>"
+ "<PERIOD TRange='958068000, 958078800'>"
+ "<PREVAILING>31010KT P6SM FEW030</PREVAILING>"
+ "</PERIOD>"
+ "<PERIOD TRange='958078800, 958104000' Title='FM2100'>"
+ "<PREVAILING>29016KT P6SM FEW040</PREVAILING>"
+ "</PERIOD>"
+ "<PERIOD TRange='958104000, 958154400' Title='FM0400'>"
+ "<PREVAILING>29010KT P6SM SCT200</PREVAILING>"
+ "<VAR Title='BECMG 0708' TRange='958114800, 958118400'>VRB05KT</VAR>"
+ "</PERIOD></TAF>"
+ "</Forecasts>"))
+ '()
+ '(*TOP* (Forecasts
+ (@ (TStamp "958082142"))
+ (TAF (@ (TStamp "958066200")
+ (SName "KMRY, MONTEREY PENINSULA")
+ (LatLon "36.583, -121.850")
+ (BId "724915"))
+ (VALID (@ (TRange "958068000, 958154400")) "111730Z 111818")
+ (PERIOD (@ (TRange "958068000, 958078800"))
+ (PREVAILING "31010KT P6SM FEW030"))
+ (PERIOD (@ (Title "FM2100") (TRange "958078800, 958104000"))
+ (PREVAILING "29016KT P6SM FEW040"))
+ (PERIOD (@ (Title "FM0400") (TRange "958104000, 958154400"))
+ (PREVAILING "29010KT P6SM SCT200")
+ (VAR (@ (Title "BECMG 0708")
+ (TRange "958114800, 958118400"))
+ "VRB05KT"))))))
+))
+
+(run-test
+ (newline)
+ (display "All tests passed")
+ (newline)
+)
+; XML/HTML processing in Scheme
+; SXML expression tree transformers
+;
+; IMPORT
+; A prelude appropriate for your Scheme system
+; (myenv-bigloo.scm, myenv-mit.scm, etc.)
+;
+; EXPORT
+; (provide SRV:send-reply
+; post-order pre-post-order replace-range)
+;
+; See vSXML-tree-trans.scm for the validation code, which also
+; serves as usage examples.
+;
+; $Id: SXML-tree-trans.scm,v 1.6 2003/04/25 19:16:15 oleg Exp $
+
+
+; Output the 'fragments'
+; The fragments are a list of strings, characters,
+; numbers, thunks, #f, #t -- and other fragments.
+; The function traverses the tree depth-first, writes out
+; strings and characters, executes thunks, and ignores
+; #f and '().
+; The function returns #t if anything was written at all;
+; otherwise the result is #f
+; If #t occurs among the fragments, it is not written out
+; but causes the result of SRV:send-reply to be #t
+
+(define (SRV:send-reply . fragments)
+ (let loop ((fragments fragments) (result #f))
+ (cond
+ ((null? fragments) result)
+ ((not (car fragments)) (loop (cdr fragments) result))
+ ((null? (car fragments)) (loop (cdr fragments) result))
+ ((eq? #t (car fragments)) (loop (cdr fragments) #t))
+ ((pair? (car fragments))
+ (loop (cdr fragments) (loop (car fragments) result)))
+ ((procedure? (car fragments))
+ ((car fragments))
+ (loop (cdr fragments) #t))
+ (else
+ (display (car fragments))
+ (loop (cdr fragments) #t)))))
+
+
+
+;------------------------------------------------------------------------
+; Traversal of an SXML tree or a grove:
+; a <Node> or a <Nodelist>
+;
+; A <Node> and a <Nodelist> are mutually-recursive datatypes that
+; underlie the SXML tree:
+; <Node> ::= (name . <Nodelist>) | "text string"
+; An (ordered) set of nodes is just a list of the constituent nodes:
+; <Nodelist> ::= (<Node> ...)
+; Nodelists, and Nodes other than text strings are both lists. A
+; <Nodelist> however is either an empty list, or a list whose head is
+; not a symbol (an atom in general). A symbol at the head of a node is
+; either an XML name (in which case it's a tag of an XML element), or
+; an administrative name such as '@'.
+; See SXPath.scm and SSAX.scm for more information on SXML.
+
+
+; Pre-Post-order traversal of a tree and creation of a new tree:
+; pre-post-order:: <tree> x <bindings> -> <new-tree>
+; where
+; <bindings> ::= (<binding> ...)
+; <binding> ::= (<trigger-symbol> *preorder* . <handler>) |
+; (<trigger-symbol> *macro* . <handler>) |
+; (<trigger-symbol> <new-bindings> . <handler>) |
+; (<trigger-symbol> . <handler>)
+; <trigger-symbol> ::= XMLname | *text* | *default*
+; <handler> :: <trigger-symbol> x [<tree>] -> <new-tree>
+;
+; The pre-post-order function visits the nodes and nodelists
+; pre-post-order (depth-first). For each <Node> of the form (name
+; <Node> ...) it looks up an association with the given 'name' among
+; its <bindings>. If failed, pre-post-order tries to locate a
+; *default* binding. It's an error if the latter attempt fails as
+; well. Having found a binding, the pre-post-order function first
+; checks to see if the binding is of the form
+; (<trigger-symbol> *preorder* . <handler>)
+; If it is, the handler is 'applied' to the current node. Otherwise,
+; the pre-post-order function first calls itself recursively for each
+; child of the current node, with <new-bindings> prepended to the
+; <bindings> in effect. The result of these calls is passed to the
+; <handler> (along with the head of the current <Node>). To be more
+; precise, the handler is _applied_ to the head of the current node
+; and its processed children. The result of the handler, which should
+; also be a <tree>, replaces the current <Node>. If the current <Node>
+; is a text string or other atom, a special binding with a symbol
+; *text* is looked up.
+;
+; A binding can also be of a form
+; (<trigger-symbol> *macro* . <handler>)
+; This is equivalent to *preorder* described above. However, the result
+; is re-processed again, with the current stylesheet.
+
+(define (pre-post-order tree bindings)
+ (let* ((default-binding (assq '*default* bindings))
+ (text-binding (or (assq '*text* bindings) default-binding))
+ (text-handler ; Cache default and text bindings
+ (and text-binding
+ (if (procedure? (cdr text-binding))
+ (cdr text-binding) (cddr text-binding)))))
+ (let loop ((tree tree))
+ (cond
+ ((null? tree) '())
+ ((not (pair? tree))
+ (let ((trigger '*text*))
+ (if text-handler (text-handler trigger tree)
+ (error "Unknown binding for " trigger " and no default"))))
+ ((not (symbol? (car tree))) (map loop tree)) ; tree is a nodelist
+ (else ; tree is an SXML node
+ (let* ((trigger (car tree))
+ (binding (or (assq trigger bindings) default-binding)))
+ (cond
+ ((not binding)
+ (error "Unknown binding for " trigger " and no default"))
+ ((not (pair? (cdr binding))) ; must be a procedure: handler
+ (apply (cdr binding) trigger (map loop (cdr tree))))
+ ((eq? '*preorder* (cadr binding))
+ (apply (cddr binding) tree))
+ ((eq? '*macro* (cadr binding))
+ (loop (apply (cddr binding) tree)))
+ (else ; (cadr binding) is a local binding
+ (apply (cddr binding) trigger
+ (pre-post-order (cdr tree) (append (cadr binding) bindings)))
+ ))))))))
+
+; post-order is a strict subset of pre-post-order without *preorder*
+; (let alone *macro*) traversals.
+; Now pre-post-order is actually faster than the old post-order.
+; The function post-order is deprecated and is aliased below for
+; backward compatibility.
+(define post-order pre-post-order)
+
+;------------------------------------------------------------------------
+; Extended tree fold
+; tree = atom | (node-name tree ...)
+;
+; foldts fdown fup fhere seed (Leaf str) = fhere seed str
+; foldts fdown fup fhere seed (Nd kids) =
+; fup seed $ foldl (foldts fdown fup fhere) (fdown seed) kids
+
+; procedure fhere: seed -> atom -> seed
+; procedure fdown: seed -> node -> seed
+; procedure fup: parent-seed -> last-kid-seed -> node -> seed
+; foldts returns the final seed
+
+(define (foldts fdown fup fhere seed tree)
+ (cond
+ ((null? tree) seed)
+ ((not (pair? tree)) ; An atom
+ (fhere seed tree))
+ (else
+ (let loop ((kid-seed (fdown seed tree)) (kids (cdr tree)))
+ (if (null? kids)
+ (fup seed kid-seed tree)
+ (loop (foldts fdown fup fhere kid-seed (car kids))
+ (cdr kids)))))))
+
+;------------------------------------------------------------------------
+; Traverse a forest depth-first and cut/replace ranges of nodes.
+;
+; The nodes that define a range don't have to have the same immediate
+; parent, don't have to be on the same level, and the end node of a
+; range doesn't even have to exist. A replace-range procedure removes
+; nodes from the beginning node of the range up to (but not including)
+; the end node of the range. In addition, the beginning node of the
+; range can be replaced by a node or a list of nodes. The range of
+; nodes is cut while depth-first traversing the forest. If all
+; branches of the node are cut a node is cut as well. The procedure
+; can cut several non-overlapping ranges from a forest.
+
+; replace-range:: BEG-PRED x END-PRED x FOREST -> FOREST
+; where
+; type FOREST = (NODE ...)
+; type NODE = Atom | (Name . FOREST) | FOREST
+;
+; The range of nodes is specified by two predicates, beg-pred and end-pred.
+; beg-pred:: NODE -> #f | FOREST
+; end-pred:: NODE -> #f | FOREST
+; The beg-pred predicate decides on the beginning of the range. The node
+; for which the predicate yields non-#f marks the beginning of the range
+; The non-#f value of the predicate replaces the node. The value can be a
+; list of nodes. The replace-range procedure then traverses the tree and skips
+; all the nodes, until the end-pred yields non-#f. The value of the end-pred
+; replaces the end-range node. The new end node and its brothers will be
+; re-scanned.
+; The predicates are evaluated pre-order. We do not descend into a node that
+; is marked as the beginning of the range.
+
+(define (replace-range beg-pred end-pred forest)
+
+ ; loop forest keep? new-forest
+ ; forest is the forest to traverse
+ ; new-forest accumulates the nodes we will keep, in the reverse
+ ; order
+ ; If keep? is #t, keep the curr node if atomic. If the node is not atomic,
+ ; traverse its children and keep those that are not in the skip range.
+ ; If keep? is #f, skip the current node if atomic. Otherwise,
+ ; traverse its children. If all children are skipped, skip the node
+ ; as well.
+
+ (define (loop forest keep? new-forest)
+ (if (null? forest) (values (reverse new-forest) keep?)
+ (let ((node (car forest)))
+ (if keep?
+ (cond ; accumulate mode
+ ((beg-pred node) => ; see if the node starts the skip range
+ (lambda (repl-branches) ; if so, skip/replace the node
+ (loop (cdr forest) #f
+ (append (reverse repl-branches) new-forest))))
+ ((not (pair? node)) ; it's an atom, keep it
+ (loop (cdr forest) keep? (cons node new-forest)))
+ (else
+ (let*-values
+ (((node?) (symbol? (car node))) ; or is it a nodelist?
+ ((new-kids keep?) ; traverse its children
+ (loop (if node? (cdr node) node) #t '())))
+ (loop (cdr forest) keep?
+ (cons
+ (if node? (cons (car node) new-kids) new-kids)
+ new-forest)))))
+ ; skip mode
+ (cond
+ ((end-pred node) => ; end the skip range
+ (lambda (repl-branches) ; repl-branches will be re-scanned
+ (loop (append repl-branches (cdr forest)) #t
+ new-forest)))
+ ((not (pair? node)) ; it's an atom, skip it
+ (loop (cdr forest) keep? new-forest))
+ (else
+ (let*-values
+ (((node?) (symbol? (car node))) ; or is it a nodelist?
+ ((new-kids keep?) ; traverse its children
+ (loop (if node? (cdr node) node) #f '())))
+ (loop (cdr forest) keep?
+ (if (or keep? (pair? new-kids))
+ (cons
+ (if node? (cons (car node) new-kids) new-kids)
+ new-forest)
+ new-forest) ; if all kids are skipped
+ )))))))) ; skip the node too
+
+ (let*-values (((new-forest keep?) (loop forest #t '())))
+ new-forest))
+
+; XML processing in Scheme
+; SXPath -- SXML Query Language
+;
+; SXPath is a query language for SXML, an instance of XML Information
+; set (Infoset) in the form of s-expressions. See SSAX.scm for the
+; definition of SXML and more details. SXPath is also a translation into
+; Scheme of an XML Path Language, XPath:
+; http://www.w3.org/TR/xpath
+; XPath and SXPath describe means of selecting a set of Infoset's items
+; or their properties.
+;
+; To facilitate queries, XPath maps the XML Infoset into an explicit
+; tree, and introduces important notions of a location path and a
+; current, context node. A location path denotes a selection of a set of
+; nodes relative to a context node. Any XPath tree has a distinguished,
+; root node -- which serves as the context node for absolute location
+; paths. Location path is recursively defined as a location step joined
+; with a location path. A location step is a simple query of the
+; database relative to a context node. A step may include expressions
+; that further filter the selected set. Each node in the resulting set
+; is used as a context node for the adjoining location path. The result
+; of the step is a union of the sets returned by the latter location
+; paths.
+;
+; The SXML representation of the XML Infoset (see SSAX.scm) is rather
+; suitable for querying as it is. Bowing to the XPath specification,
+; we will refer to SXML information items as 'Nodes':
+; <Node> ::= <Element> | <attributes-coll> | <attrib>
+; | "text string" | <PI>
+; This production can also be described as
+; <Node> ::= (name . <Nodeset>) | "text string"
+; An (ordered) set of nodes is just a list of the constituent nodes:
+; <Nodeset> ::= (<Node> ...)
+; Nodesets, and Nodes other than text strings are both lists. A
+; <Nodeset> however is either an empty list, or a list whose head is not
+; a symbol. A symbol at the head of a node is either an XML name (in
+; which case it's a tag of an XML element), or an administrative name
+; such as '@'. This uniform list representation makes processing rather
+; simple and elegant, while avoiding confusion. The multi-branch tree
+; structure formed by the mutually-recursive datatypes <Node> and
+; <Nodeset> lends itself well to processing by functional languages.
+;
+; A location path is in fact a composite query over an XPath tree or
+; its branch. A singe step is a combination of a projection, selection
+; or a transitive closure. Multiple steps are combined via join and
+; union operations. This insight allows us to _elegantly_ implement
+; XPath as a sequence of projection and filtering primitives --
+; converters -- joined by _combinators_. Each converter takes a node
+; and returns a nodeset which is the result of the corresponding query
+; relative to that node. A converter can also be called on a set of
+; nodes. In that case it returns a union of the corresponding queries over
+; each node in the set. The union is easily implemented as a list
+; append operation as all nodes in a SXML tree are considered
+; distinct, by XPath conventions. We also preserve the order of the
+; members in the union. Query combinators are high-order functions:
+; they take converter(s) (which is a Node|Nodeset -> Nodeset function)
+; and compose or otherwise combine them. We will be concerned with
+; only relative location paths [XPath]: an absolute location path is a
+; relative path applied to the root node.
+;
+; Similarly to XPath, SXPath defines full and abbreviated notations
+; for location paths. In both cases, the abbreviated notation can be
+; mechanically expanded into the full form by simple rewriting
+; rules. In case of SXPath the corresponding rules are given as
+; comments to a sxpath function, below. The regression test suite at
+; the end of this file shows a representative sample of SXPaths in
+; both notations, juxtaposed with the corresponding XPath
+; expressions. Most of the samples are borrowed literally from the
+; XPath specification, while the others are adjusted for our running
+; example, tree1.
+;
+; To do:
+; Rename filter to node-filter or ns-filter
+; Use ;=== for chapters, ;--- for sections, and ;^^^ for end sections
+;
+; $Id: SXPath-old.scm,v 1.4 2004/07/07 16:02:31 sperber Exp $
+
+
+ ; See http://pobox.com/~oleg/ftp/Scheme/myenv.scm
+ ; See http://pobox.com/~oleg/ftp/Scheme/myenv-scm.scm
+ ; See http://pobox.com/~oleg/ftp/Scheme/myenv-bigloo.scm
+;(module SXPath
+; (include "myenv-bigloo.scm")) ; For use with Bigloo 2.2b
+;(load "myenv-scm.scm") ; For use with SCM v5d2
+;(include "myenv.scm") ; For use with Gambit-C 3.0
+
+
+
+(define (nodeset? x)
+ (or (and (pair? x) (not (symbol? (car x)))) (null? x)))
+
+;-------------------------
+; Basic converters and applicators
+; A converter is a function
+; type Converter = Node|Nodeset -> Nodeset
+; A converter can also play a role of a predicate: in that case, if a
+; converter, applied to a node or a nodeset, yields a non-empty
+; nodeset, the converter-predicate is deemed satisfied. Throughout
+; this file a nil nodeset is equivalent to #f in denoting a failure.
+
+; The following function implements a 'Node test' as defined in
+; Sec. 2.3 of XPath document. A node test is one of the components of a
+; location step. It is also a converter-predicate in SXPath.
+;
+; The function node-typeof? takes a type criterion and returns a function,
+; which, when applied to a node, will tell if the node satisfies
+; the test.
+; node-typeof? :: Crit -> Node -> Boolean
+;
+; The criterion 'crit' is a symbol, one of the following:
+; id - tests if the Node has the right name (id)
+; @ - tests if the Node is an <attributes-coll>
+; * - tests if the Node is an <Element>
+; *text* - tests if the Node is a text node
+; *PI* - tests if the Node is a PI node
+; *any* - #t for any type of Node
+
+(define (node-typeof? crit)
+ (lambda (node)
+ (case crit
+ ((*) (and (pair? node) (not (memq (car node) '(@ *PI*)))))
+ ((*any*) #t)
+ ((*text*) (string? node))
+ (else
+ (and (pair? node) (eq? crit (car node))))
+)))
+
+
+; Curried equivalence converter-predicates
+(define (node-eq? other)
+ (lambda (node)
+ (eq? other node)))
+
+(define (node-equal? other)
+ (lambda (node)
+ (equal? other node)))
+
+; node-pos:: N -> Nodeset -> Nodeset, or
+; node-pos:: N -> Converter
+; Select the N'th element of a Nodeset and return as a singular Nodeset;
+; Return an empty nodeset if the Nth element does not exist.
+; ((node-pos 1) Nodeset) selects the node at the head of the Nodeset,
+; if exists; ((node-pos 2) Nodeset) selects the Node after that, if
+; exists.
+; N can also be a negative number: in that case the node is picked from
+; the tail of the list.
+; ((node-pos -1) Nodeset) selects the last node of a non-empty nodeset;
+; ((node-pos -2) Nodeset) selects the last but one node, if exists.
+
+(define (node-pos n)
+ (lambda (nodeset)
+ (cond
+ ((not (nodeset? nodeset)) '())
+ ((null? nodeset) nodeset)
+ ((eqv? n 1) (list (car nodeset)))
+ ((negative? n) ((node-pos (+ n 1 (length nodeset))) nodeset))
+ (else
+ (assert (positive? n))
+ ((node-pos (dec n)) (cdr nodeset))))))
+
+; filter:: Converter -> Converter
+; A filter applicator, which introduces a filtering context. The argument
+; converter is considered a predicate, with either #f or nil result meaning
+; failure.
+(define (filter pred?)
+ (lambda (lst) ; a nodeset or a node (will be converted to a singleton nset)
+ (let loop ((lst (if (nodeset? lst) lst (list lst))) (res '()))
+ (if (null? lst)
+ (reverse res)
+ (let ((pred-result (pred? (car lst))))
+ (loop (cdr lst)
+ (if (and pred-result (not (null? pred-result)))
+ (cons (car lst) res)
+ res)))))))
+
+; take-until:: Converter -> Converter, or
+; take-until:: Pred -> Node|Nodeset -> Nodeset
+; Given a converter-predicate and a nodeset, apply the predicate to
+; each element of the nodeset, until the predicate yields anything but #f or
+; nil. Return the elements of the input nodeset that have been processed
+; till that moment (that is, which fail the predicate).
+; take-until is a variation of the filter above: take-until passes
+; elements of an ordered input set till (but not including) the first
+; element that satisfies the predicate.
+; The nodeset returned by ((take-until (not pred)) nset) is a subset --
+; to be more precise, a prefix -- of the nodeset returned by
+; ((filter pred) nset)
+
+(define (take-until pred?)
+ (lambda (lst) ; a nodeset or a node (will be converted to a singleton nset)
+ (let loop ((lst (if (nodeset? lst) lst (list lst))))
+ (if (null? lst) lst
+ (let ((pred-result (pred? (car lst))))
+ (if (and pred-result (not (null? pred-result)))
+ '()
+ (cons (car lst) (loop (cdr lst)))))
+ ))))
+
+
+; take-after:: Converter -> Converter, or
+; take-after:: Pred -> Node|Nodeset -> Nodeset
+; Given a converter-predicate and a nodeset, apply the predicate to
+; each element of the nodeset, until the predicate yields anything but #f or
+; nil. Return the elements of the input nodeset that have not been processed:
+; that is, return the elements of the input nodeset that follow the first
+; element that satisfied the predicate.
+; take-after along with take-until partition an input nodeset into three
+; parts: the first element that satisfies a predicate, all preceding
+; elements and all following elements.
+
+(define (take-after pred?)
+ (lambda (lst) ; a nodeset or a node (will be converted to a singleton nset)
+ (let loop ((lst (if (nodeset? lst) lst (list lst))))
+ (if (null? lst) lst
+ (let ((pred-result (pred? (car lst))))
+ (if (and pred-result (not (null? pred-result)))
+ (cdr lst)
+ (loop (cdr lst))))
+ ))))
+
+; Apply proc to each element of lst and return the list of results.
+; if proc returns a nodeset, splice it into the result
+;
+; From another point of view, map-union is a function Converter->Converter,
+; which places an argument-converter in a joining context.
+
+(define (map-union proc lst)
+ (if (null? lst) lst
+ (let ((proc-res (proc (car lst))))
+ ((if (nodeset? proc-res) append cons)
+ proc-res (map-union proc (cdr lst))))))
+
+; node-reverse :: Converter, or
+; node-reverse:: Node|Nodeset -> Nodeset
+; Reverses the order of nodes in the nodeset
+; This basic converter is needed to implement a reverse document order
+; (see the XPath Recommendation).
+(define node-reverse
+ (lambda (node-or-nodeset)
+ (if (not (nodeset? node-or-nodeset)) (list node-or-nodeset)
+ (reverse node-or-nodeset))))
+
+; node-trace:: String -> Converter
+; (node-trace title) is an identity converter. In addition it prints out
+; a node or nodeset it is applied to, prefixed with the 'title'.
+; This converter is very useful for debugging.
+
+(define (node-trace title)
+ (lambda (node-or-nodeset)
+ (cout nl "-->")
+ (display title)
+ (display " :")
+ (pretty-print node-or-nodeset)
+ node-or-nodeset))
+
+
+;-------------------------
+; Converter combinators
+;
+; Combinators are higher-order functions that transmogrify a converter
+; or glue a sequence of converters into a single, non-trivial
+; converter. The goal is to arrive at converters that correspond to
+; XPath location paths.
+;
+; From a different point of view, a combinator is a fixed, named
+; _pattern_ of applying converters. Given below is a complete set of
+; such patterns that together implement XPath location path
+; specification. As it turns out, all these combinators can be built
+; from a small number of basic blocks: regular functional composition,
+; map-union and filter applicators, and the nodeset union.
+
+
+
+; select-kids:: Pred -> Node -> Nodeset
+; Given a Node, return an (ordered) subset its children that satisfy
+; the Pred (a converter, actually)
+; select-kids:: Pred -> Nodeset -> Nodeset
+; The same as above, but select among children of all the nodes in
+; the Nodeset
+;
+; More succinctly, the signature of this function is
+; select-kids:: Converter -> Converter
+
+(define (select-kids test-pred?)
+ (lambda (node) ; node or node-set
+ (cond
+ ((null? node) node)
+ ((not (pair? node)) '()) ; No children
+ ((symbol? (car node))
+ ((filter test-pred?) (cdr node))) ; it's a single node
+ (else (map-union (select-kids test-pred?) node)))))
+
+
+; node-self:: Pred -> Node -> Nodeset, or
+; node-self:: Converter -> Converter
+; Similar to select-kids but apply to the Node itself rather
+; than to its children. The resulting Nodeset will contain either one
+; component, or will be empty (if the Node failed the Pred).
+(define node-self filter)
+
+
+; node-join:: [LocPath] -> Node|Nodeset -> Nodeset, or
+; node-join:: [Converter] -> Converter
+; join the sequence of location steps or paths as described
+; in the title comments above.
+(define (node-join . selectors)
+ (lambda (nodeset) ; Nodeset or node
+ (let loop ((nodeset nodeset) (selectors selectors))
+ (if (null? selectors) nodeset
+ (loop
+ (if (nodeset? nodeset)
+ (map-union (car selectors) nodeset)
+ ((car selectors) nodeset))
+ (cdr selectors))))))
+
+
+; node-reduce:: [LocPath] -> Node|Nodeset -> Nodeset, or
+; node-reduce:: [Converter] -> Converter
+; A regular functional composition of converters.
+; From a different point of view,
+; ((apply node-reduce converters) nodeset)
+; is equivalent to
+; (foldl apply nodeset converters)
+; i.e., folding, or reducing, a list of converters with the nodeset
+; as a seed.
+(define (node-reduce . converters)
+ (lambda (nodeset) ; Nodeset or node
+ (let loop ((nodeset nodeset) (converters converters))
+ (if (null? converters) nodeset
+ (loop ((car converters) nodeset) (cdr converters))))))
+
+
+; node-or:: [Converter] -> Converter
+; This combinator applies all converters to a given node and
+; produces the union of their results.
+; This combinator corresponds to a union, '|' operation for XPath
+; location paths.
+; (define (node-or . converters)
+; (lambda (node-or-nodeset)
+; (if (null? converters) node-or-nodeset
+; (append
+; ((car converters) node-or-nodeset)
+; ((apply node-or (cdr converters)) node-or-nodeset)))))
+; More optimal implementation follows
+(define (node-or . converters)
+ (lambda (node-or-nodeset)
+ (let loop ((result '()) (converters converters))
+ (if (null? converters) result
+ (loop (append result (or ((car converters) node-or-nodeset) '()))
+ (cdr converters))))))
+
+
+; node-closure:: Converter -> Converter
+; Select all _descendants_ of a node that satisfy a converter-predicate.
+; This combinator is similar to select-kids but applies to
+; grand... children as well.
+; This combinator implements the "descendant::" XPath axis
+; Conceptually, this combinator can be expressed as
+; (define (node-closure f)
+; (node-or
+; (select-kids f)
+; (node-reduce (select-kids (node-typeof? '*)) (node-closure f))))
+; This definition, as written, looks somewhat like a fixpoint, and it
+; will run forever. It is obvious however that sooner or later
+; (select-kids (node-typeof? '*)) will return an empty nodeset. At
+; this point further iterations will no longer affect the result and
+; can be stopped.
+
+(define (node-closure test-pred?)
+ (lambda (node) ; Nodeset or node
+ (let loop ((parent node) (result '()))
+ (if (null? parent) result
+ (loop ((select-kids (node-typeof? '*)) parent)
+ (append result
+ ((select-kids test-pred?) parent)))
+ ))))
+
+; node-parent:: RootNode -> Converter
+; (node-parent rootnode) yields a converter that returns a parent of a
+; node it is applied to. If applied to a nodeset, it returns the list
+; of parents of nodes in the nodeset. The rootnode does not have
+; to be the root node of the whole SXML tree -- it may be a root node
+; of a branch of interest.
+; Given the notation of Philip Wadler's paper on semantics of XSLT,
+; parent(x) = { y | y=subnode*(root), x=subnode(y) }
+; Therefore, node-parent is not the fundamental converter: it can be
+; expressed through the existing ones. Yet node-parent is a rather
+; convenient converter. It corresponds to a parent:: axis of SXPath.
+; Note that the parent:: axis can be used with an attribute node as well!
+
+(define (node-parent rootnode)
+ (lambda (node) ; Nodeset or node
+ (if (nodeset? node) (map-union (node-parent rootnode) node)
+ (let ((pred
+ (node-or
+ (node-reduce
+ (node-self (node-typeof? '*))
+ (select-kids (node-eq? node)))
+ (node-join
+ (select-kids (node-typeof? '@))
+ (select-kids (node-eq? node))))))
+ ((node-or
+ (node-self pred)
+ (node-closure pred))
+ rootnode)))))
+
+;-------------------------
+; Evaluate an abbreviated SXPath
+; sxpath:: AbbrPath -> Converter, or
+; sxpath:: AbbrPath -> Node|Nodeset -> Nodeset
+; AbbrPath is a list. It is translated to the full SXPath according
+; to the following rewriting rules
+; (sxpath '()) -> (node-join)
+; (sxpath '(path-component ...)) ->
+; (node-join (sxpath1 path-component) (sxpath '(...)))
+; (sxpath1 '//) -> (node-or
+; (node-self (node-typeof? '*any*))
+; (node-closure (node-typeof? '*any*)))
+; (sxpath1 '(equal? x)) -> (select-kids (node-equal? x))
+; (sxpath1 '(eq? x)) -> (select-kids (node-eq? x))
+; (sxpath1 ?symbol) -> (select-kids (node-typeof? ?symbol)
+; (sxpath1 procedure) -> procedure
+; (sxpath1 '(?symbol ...)) -> (sxpath1 '((?symbol) ...))
+; (sxpath1 '(path reducer ...)) ->
+; (node-reduce (sxpath path) (sxpathr reducer) ...)
+; (sxpathr number) -> (node-pos number)
+; (sxpathr path-filter) -> (filter (sxpath path-filter))
+
+(define (sxpath path)
+ (lambda (nodeset)
+ (let loop ((nodeset nodeset) (path path))
+ (cond
+ ((null? path) nodeset)
+ ((nodeset? nodeset)
+ (map-union (sxpath path) nodeset))
+ ((procedure? (car path))
+ (loop ((car path) nodeset) (cdr path)))
+ ((eq? '// (car path))
+ (loop
+ ((if (nodeset? nodeset) append cons) nodeset
+ ((node-closure (node-typeof? '*any*)) nodeset))
+ (cdr path)))
+ ((symbol? (car path))
+ (loop ((select-kids (node-typeof? (car path))) nodeset)
+ (cdr path)))
+ ((and (pair? (car path)) (eq? 'equal? (caar path)))
+ (loop ((select-kids (apply node-equal? (cdar path))) nodeset)
+ (cdr path)))
+ ((and (pair? (car path)) (eq? 'eq? (caar path)))
+ (loop ((select-kids (apply node-eq? (cdar path))) nodeset)
+ (cdr path)))
+ ((pair? (car path))
+ (let reducer ((nodeset
+ (if (symbol? (caar path))
+ ((select-kids (node-typeof? (caar path))) nodeset)
+ (loop nodeset (caar path))))
+ (reducing-path (cdar path)))
+ (cond
+ ((null? reducing-path) (loop nodeset (cdr path)))
+ ((number? (car reducing-path))
+ (reducer ((node-pos (car reducing-path)) nodeset)
+ (cdr reducing-path)))
+ (else
+ (reducer ((filter (sxpath (car reducing-path))) nodeset)
+ (cdr reducing-path))))))
+ (else
+ (error "Invalid path step: " (car path)))
+))))
+
+;------------------------------------------------------------------------
+; Sample XPath/SXPath expressions: regression test suite for the
+; implementation above.
+
+; A running example
+
+(define tree1
+ '(html
+ (head (title "Slides"))
+ (body
+ (p (@ (align "center"))
+ (table (@ (style "font-size: x-large"))
+ (tr
+ (td (@ (align "right")) "Talks ")
+ (td (@ (align "center")) " = ")
+ (td " slides + transition"))
+ (tr (td)
+ (td (@ (align "center")) " = ")
+ (td " data + control"))
+ (tr (td)
+ (td (@ (align "center")) " = ")
+ (td " programs"))))
+ (ul
+ (li (a (@ (href "slides/slide0001.gif")) "Introduction"))
+ (li (a (@ (href "slides/slide0010.gif")) "Summary")))
+ )))
+
+
+; Example from a posting "Re: DrScheme and XML",
+; Shriram Krishnamurthi, comp.lang.scheme, Nov. 26. 1999.
+; http://www.deja.com/getdoc.xp?AN=553507805
+(define tree3
+ '(poem (@ (title "The Lovesong of J. Alfred Prufrock")
+ (poet "T. S. Eliot"))
+ (stanza
+ (line "Let us go then, you and I,")
+ (line "When the evening is spread out against the sky")
+ (line "Like a patient etherized upon a table:"))
+ (stanza
+ (line "In the room the women come and go")
+ (line "Talking of Michaelangelo."))))
+
+; Validation Test harness
+
+(define-syntax run-test
+ (syntax-rules (define)
+ ((run-test "scan-exp" (define vars body))
+ (define vars (run-test "scan-exp" body)))
+ ((run-test "scan-exp" ?body)
+ (letrec-syntax
+ ((scan-exp ; (scan-exp body k)
+ (syntax-rules (quote quasiquote !)
+ ((scan-exp '() (k-head ! . args))
+ (k-head '() . args))
+ ((scan-exp (quote (hd . tl)) k)
+ (scan-lit-lst (hd . tl) (do-wrap ! quasiquote k)))
+ ((scan-exp (quasiquote (hd . tl)) k)
+ (scan-lit-lst (hd . tl) (do-wrap ! quasiquote k)))
+ ((scan-exp (quote x) (k-head ! . args))
+ (k-head
+ (if (string? (quote x)) (string->symbol (quote x)) (quote x))
+ . args))
+ ((scan-exp (hd . tl) k)
+ (scan-exp hd (do-tl ! scan-exp tl k)))
+ ((scan-exp x (k-head ! . args))
+ (k-head x . args))))
+ (do-tl
+ (syntax-rules (!)
+ ((do-tl processed-hd fn () (k-head ! . args))
+ (k-head (processed-hd) . args))
+ ((do-tl processed-hd fn old-tl k)
+ (fn old-tl (do-cons ! processed-hd k)))))
+ (do-cons
+ (syntax-rules (!)
+ ((do-cons processed-tl processed-hd (k-head ! . args))
+ (k-head (processed-hd . processed-tl) . args))))
+ (do-wrap
+ (syntax-rules (!)
+ ((do-wrap val fn (k-head ! . args))
+ (k-head (fn val) . args))))
+ (do-finish
+ (syntax-rules ()
+ ((do-finish new-body) new-body)))
+
+ (scan-lit-lst ; scan literal list
+ (syntax-rules (quote unquote unquote-splicing !)
+ ((scan-lit-lst '() (k-head ! . args))
+ (k-head '() . args))
+ ((scan-lit-lst (quote (hd . tl)) k)
+ (do-tl quote scan-lit-lst ((hd . tl)) k))
+ ((scan-lit-lst (unquote x) k)
+ (scan-exp x (do-wrap ! unquote k)))
+ ((scan-lit-lst (unquote-splicing x) k)
+ (scan-exp x (do-wrap ! unquote-splicing k)))
+ ((scan-lit-lst (quote x) (k-head ! . args))
+ (k-head
+ ,(if (string? (quote x)) (string->symbol (quote x)) (quote x))
+ . args))
+ ((scan-lit-lst (hd . tl) k)
+ (scan-lit-lst hd (do-tl ! scan-lit-lst tl k)))
+ ((scan-lit-lst x (k-head ! . args))
+ (k-head x . args))))
+ )
+ (scan-exp ?body (do-finish !))))
+ ((run-test body ...)
+ (begin
+ (run-test "scan-exp" body) ...))
+))
+
+; Overwrite the above macro to switch the tests off
+; (define-macro (run-test selector node expected-result) #f)
+
+; Location path, full form: child::para
+; Location path, abbreviated form: para
+; selects the para element children of the context node
+
+(let ((tree
+ '(elem (@) (para (@) "para") (br (@)) "cdata" (para (@) "second par"))
+ )
+ (expected '((para (@) "para") (para (@) "second par")))
+ )
+ (run-test (select-kids (node-typeof? 'para)) tree expected)
+ (run-test (sxpath '(para)) tree expected)
+)
+
+; Location path, full form: child::*
+; Location path, abbreviated form: *
+; selects all element children of the context node
+
+(let ((tree
+ '(elem (@) (para (@) "para") (br (@)) "cdata" (para "second par"))
+ )
+ (expected
+ '((para (@) "para") (br (@)) (para "second par")))
+ )
+ (run-test (select-kids (node-typeof? '*)) tree expected)
+ (run-test (sxpath '(*)) tree expected)
+)
+
+
+
+; Location path, full form: child::text()
+; Location path, abbreviated form: text()
+; selects all text node children of the context node
+(let ((tree
+ '(elem (@) (para (@) "para") (br (@)) "cdata" (para "second par"))
+ )
+ (expected
+ '("cdata"))
+ )
+ (run-test (select-kids (node-typeof? '*text*)) tree expected)
+ (run-test (sxpath '(*text*)) tree expected)
+)
+
+
+; Location path, full form: child::node()
+; Location path, abbreviated form: node()
+; selects all the children of the context node, whatever their node type
+(let* ((tree
+ '(elem (@) (para (@) "para") (br (@)) "cdata" (para "second par"))
+ )
+ (expected (cdr tree))
+ )
+ (run-test (select-kids (node-typeof? '*any*)) tree expected)
+ (run-test (sxpath '(*any*)) tree expected)
+)
+
+; Location path, full form: child::*/child::para
+; Location path, abbreviated form: */para
+; selects all para grandchildren of the context node
+
+(let ((tree
+ '(elem (@) (para (@) "para") (br (@)) "cdata" (para "second par")
+ (div (@ (name "aa")) (para "third para")))
+ )
+ (expected
+ '((para "third para")))
+ )
+ (run-test
+ (node-join (select-kids (node-typeof? '*))
+ (select-kids (node-typeof? 'para)))
+ tree expected)
+ (run-test (sxpath '(* para)) tree expected)
+)
+
+
+; Location path, full form: attribute::name
+; Location path, abbreviated form: @name
+; selects the 'name' attribute of the context node
+
+(let ((tree
+ '(elem (@ (name "elem") (id "idz"))
+ (para (@) "para") (br (@)) "cdata" (para (@) "second par")
+ (div (@ (name "aa")) (para (@) "third para")))
+ )
+ (expected
+ '((name "elem")))
+ )
+ (run-test
+ (node-join (select-kids (node-typeof? '@))
+ (select-kids (node-typeof? 'name)))
+ tree expected)
+ (run-test (sxpath '(@ name)) tree expected)
+)
+
+; Location path, full form: attribute::*
+; Location path, abbreviated form: @*
+; selects all the attributes of the context node
+(let ((tree
+ '(elem (@ (name "elem") (id "idz"))
+ (para (@) "para") (br (@)) "cdata" (para "second par")
+ (div (@ (name "aa")) (para (@) "third para")))
+ )
+ (expected
+ '((name "elem") (id "idz")))
+ )
+ (run-test
+ (node-join (select-kids (node-typeof? '@))
+ (select-kids (node-typeof? '*)))
+ tree expected)
+ (run-test (sxpath '(@ *)) tree expected)
+)
+
+
+; Location path, full form: descendant::para
+; Location path, abbreviated form: .//para
+; selects the para element descendants of the context node
+
+(let ((tree
+ '(elem (@ (name "elem") (id "idz"))
+ (para (@) "para") (br (@)) "cdata" (para "second par")
+ (div (@ (name "aa")) (para (@) "third para")))
+ )
+ (expected
+ '((para (@) "para") (para "second par") (para (@) "third para")))
+ )
+ (run-test
+ (node-closure (node-typeof? 'para))
+ tree expected)
+ (run-test (sxpath '(// para)) tree expected)
+)
+
+; Location path, full form: self::para
+; Location path, abbreviated form: _none_
+; selects the context node if it is a para element; otherwise selects nothing
+
+(let ((tree
+ '(elem (@ (name "elem") (id "idz"))
+ (para (@) "para") (br (@)) "cdata" (para "second par")
+ (div (@ (name "aa")) (para (@) "third para")))
+ )
+ )
+ (run-test (node-self (node-typeof? 'para)) tree '())
+ (run-test (node-self (node-typeof? 'elem)) tree (list tree))
+)
+
+; Location path, full form: descendant-or-self::node()
+; Location path, abbreviated form: //
+; selects the context node, all the children (including attribute nodes)
+; of the context node, and all the children of all the (element)
+; descendants of the context node.
+; This is _almost_ a powerset of the context node.
+(let* ((tree
+ '(para (@ (name "elem") (id "idz"))
+ (para (@) "para") (br (@)) "cdata" (para "second par")
+ (div (@ (name "aa")) (para (@) "third para")))
+ )
+ (expected
+ (cons tree
+ (append (cdr tree)
+ '((@) "para" (@) "second par"
+ (@ (name "aa")) (para (@) "third para")
+ (@) "third para"))))
+ )
+ (run-test
+ (node-or
+ (node-self (node-typeof? '*any*))
+ (node-closure (node-typeof? '*any*)))
+ tree expected)
+ (run-test (sxpath '(//)) tree expected)
+)
+
+; Location path, full form: ancestor::div
+; Location path, abbreviated form: _none_
+; selects all div ancestors of the context node
+; This Location expression is equivalent to the following:
+; /descendant-or-self::div[descendant::node() = curr_node]
+; This shows that the ancestor:: axis is actually redundant. Still,
+; it can be emulated as the following SXPath expression demonstrates.
+
+; The insight behind "ancestor::div" -- selecting all "div" ancestors
+; of the current node -- is
+; S[ancestor::div] context_node =
+; { y | y=subnode*(root), context_node=subnode(subnode*(y)),
+; isElement(y), name(y) = "div" }
+; We observe that
+; { y | y=subnode*(root), pred(y) }
+; can be expressed in SXPath as
+; ((node-or (node-self pred) (node-closure pred)) root-node)
+; The composite predicate 'isElement(y) & name(y) = "div"' corresponds to
+; (node-self (node-typeof? 'div)) in SXPath. Finally, filter
+; context_node=subnode(subnode*(y)) is tantamount to
+; (node-closure (node-eq? context-node)), whereas node-reduce denotes the
+; the composition of converters-predicates in the filtering context.
+
+(let*
+ ((root
+ '(div (@ (name "elem") (id "idz"))
+ (para (@) "para") (br (@)) "cdata" (para (@) "second par")
+ (div (@ (name "aa")) (para (@) "third para"))))
+ (context-node ; /descendant::any()[child::text() == "third para"]
+ (car
+ ((node-closure
+ (select-kids
+ (node-equal? "third para")))
+ root)))
+ (pred
+ (node-reduce (node-self (node-typeof? 'div))
+ (node-closure (node-eq? context-node))
+ ))
+ )
+ (run-test
+ (node-or
+ (node-self pred)
+ (node-closure pred))
+ root
+ (cons root
+ '((div (@ (name "aa")) (para (@) "third para")))))
+)
+
+
+
+; Location path, full form: child::div/descendant::para
+; Location path, abbreviated form: div//para
+; selects the para element descendants of the div element
+; children of the context node
+
+(let ((tree
+ '(elem (@ (name "elem") (id "idz"))
+ (para (@) "para") (br (@)) "cdata" (para "second par")
+ (div (@ (name "aa")) (para (@) "third para")
+ (div (para "fourth para"))))
+ )
+ (expected
+ '((para (@) "third para") (para "fourth para")))
+ )
+ (run-test
+ (node-join
+ (select-kids (node-typeof? 'div))
+ (node-closure (node-typeof? 'para)))
+ tree expected)
+ (run-test (sxpath '(div // para)) tree expected)
+)
+
+
+; Location path, full form: /descendant::olist/child::item
+; Location path, abbreviated form: //olist/item
+; selects all the item elements that have an olist parent (which is not root)
+; and that are in the same document as the context node
+; See the following test.
+
+; Location path, full form: /descendant::td/attribute::align
+; Location path, abbreviated form: //td/@align
+; Selects 'align' attributes of all 'td' elements in tree1
+(let ((tree tree1)
+ (expected
+ '((align "right") (align "center") (align "center") (align "center"))
+ ))
+ (run-test
+ (node-join
+ (node-closure (node-typeof? 'td))
+ (select-kids (node-typeof? '@))
+ (select-kids (node-typeof? 'align)))
+ tree expected)
+ (run-test (sxpath '(// td @ align)) tree expected)
+)
+
+
+; Location path, full form: /descendant::td[attribute::align]
+; Location path, abbreviated form: //td[@align]
+; Selects all td elements that have an attribute 'align' in tree1
+(let ((tree tree1)
+ (expected
+ '((td (@ (align "right")) "Talks ") (td (@ (align "center")) " = ")
+ (td (@ (align "center")) " = ") (td (@ (align "center")) " = "))
+ ))
+ (run-test
+ (node-reduce
+ (node-closure (node-typeof? 'td))
+ (filter
+ (node-join
+ (select-kids (node-typeof? '@))
+ (select-kids (node-typeof? 'align)))))
+ tree expected)
+ (run-test (sxpath `(// td ,(node-self (sxpath '(@ align))))) tree expected)
+ (run-test (sxpath '(// (td (@ align)))) tree expected)
+ (run-test (sxpath '(// ((td) (@ align)))) tree expected)
+ ; note! (sxpath ...) is a converter. Therefore, it can be used
+ ; as any other converter, for example, in the full-form SXPath.
+ ; Thus we can mix the full and abbreviated form SXPath's freely.
+ (run-test
+ (node-reduce
+ (node-closure (node-typeof? 'td))
+ (filter
+ (sxpath '(@ align))))
+ tree expected)
+)
+
+
+; Location path, full form: /descendant::td[attribute::align = "right"]
+; Location path, abbreviated form: //td[@align = "right"]
+; Selects all td elements that have an attribute align = "right" in tree1
+(let ((tree tree1)
+ (expected
+ '((td (@ (align "right")) "Talks "))
+ ))
+ (run-test
+ (node-reduce
+ (node-closure (node-typeof? 'td))
+ (filter
+ (node-join
+ (select-kids (node-typeof? '@))
+ (select-kids (node-equal? '(align "right"))))))
+ tree expected)
+ (run-test (sxpath '(// (td (@ (equal? (align "right")))))) tree expected)
+)
+
+; Location path, full form: child::para[position()=1]
+; Location path, abbreviated form: para[1]
+; selects the first para child of the context node
+(let ((tree
+ '(elem (@ (name "elem") (id "idz"))
+ (para (@) "para") (br (@)) "cdata" (para "second par")
+ (div (@ (name "aa")) (para (@) "third para")))
+ )
+ (expected
+ '((para (@) "para"))
+ ))
+ (run-test
+ (node-reduce
+ (select-kids (node-typeof? 'para))
+ (node-pos 1))
+ tree expected)
+ (run-test (sxpath '((para 1))) tree expected)
+)
+
+; Location path, full form: child::para[position()=last()]
+; Location path, abbreviated form: para[last()]
+; selects the last para child of the context node
+(let ((tree
+ '(elem (@ (name "elem") (id "idz"))
+ (para (@) "para") (br (@)) "cdata" (para "second par")
+ (div (@ (name "aa")) (para (@) "third para")))
+ )
+ (expected
+ '((para "second par"))
+ ))
+ (run-test
+ (node-reduce
+ (select-kids (node-typeof? 'para))
+ (node-pos -1))
+ tree expected)
+ (run-test (sxpath '((para -1))) tree expected)
+)
+
+; Illustrating the following Note of Sec 2.5 of XPath:
+; "NOTE: The location path //para[1] does not mean the same as the
+; location path /descendant::para[1]. The latter selects the first
+; descendant para element; the former selects all descendant para
+; elements that are the first para children of their parents."
+
+(let ((tree
+ '(elem (@ (name "elem") (id "idz"))
+ (para (@) "para") (br (@)) "cdata" (para "second par")
+ (div (@ (name "aa")) (para (@) "third para")))
+ )
+ )
+ (run-test
+ (node-reduce ; /descendant::para[1] in SXPath
+ (node-closure (node-typeof? 'para))
+ (node-pos 1))
+ tree '((para (@) "para")))
+ (run-test (sxpath '(// (para 1))) tree
+ '((para (@) "para") (para (@) "third para")))
+)
+
+; Location path, full form: parent::node()
+; Location path, abbreviated form: ..
+; selects the parent of the context node. The context node may be
+; an attribute node!
+; For the last test:
+; Location path, full form: parent::*/attribute::name
+; Location path, abbreviated form: ../@name
+; Selects the name attribute of the parent of the context node
+
+(let* ((tree
+ '(elem (@ (name "elem") (id "idz"))
+ (para (@) "para") (br (@)) "cdata" (para "second par")
+ (div (@ (name "aa")) (para (@) "third para")))
+ )
+ (para1 ; the first para node
+ (car ((sxpath '(para)) tree)))
+ (para3 ; the third para node
+ (car ((sxpath '(div para)) tree)))
+ (div ; div node
+ (car ((sxpath '(// div)) tree)))
+ )
+ (run-test
+ (node-parent tree)
+ para1 (list tree))
+ (run-test
+ (node-parent tree)
+ para3 (list div))
+ (run-test ; checking the parent of an attribute node
+ (node-parent tree)
+ ((sxpath '(@ name)) div) (list div))
+ (run-test
+ (node-join
+ (node-parent tree)
+ (select-kids (node-typeof? '@))
+ (select-kids (node-typeof? 'name)))
+ para3 '((name "aa")))
+ (run-test
+ (sxpath `(,(node-parent tree) @ name))
+ para3 '((name "aa")))
+)
+
+; Location path, full form: following-sibling::chapter[position()=1]
+; Location path, abbreviated form: none
+; selects the next chapter sibling of the context node
+; The path is equivalent to
+; let cnode = context-node
+; in
+; parent::* / child::chapter [take-after node_eq(self::*,cnode)]
+; [position()=1]
+(let* ((tree
+ '(document
+ (preface "preface")
+ (chapter (@ (id "one")) "Chap 1 text")
+ (chapter (@ (id "two")) "Chap 2 text")
+ (chapter (@ (id "three")) "Chap 3 text")
+ (chapter (@ (id "four")) "Chap 4 text")
+ (epilogue "Epilogue text")
+ (appendix (@ (id "A")) "App A text")
+ (References "References"))
+ )
+ (a-node ; to be used as a context node
+ (car ((sxpath '(// (chapter (@ (equal? (id "two")))))) tree)))
+ (expected
+ '((chapter (@ (id "three")) "Chap 3 text")))
+ )
+ (run-test
+ (node-reduce
+ (node-join
+ (node-parent tree)
+ (select-kids (node-typeof? 'chapter)))
+ (take-after (node-eq? a-node))
+ (node-pos 1)
+ )
+ a-node expected)
+)
+
+; preceding-sibling::chapter[position()=1]
+; selects the previous chapter sibling of the context node
+; The path is equivalent to
+; let cnode = context-node
+; in
+; parent::* / child::chapter [take-until node_eq(self::*,cnode)]
+; [position()=-1]
+(let* ((tree
+ '(document
+ (preface "preface")
+ (chapter (@ (id "one")) "Chap 1 text")
+ (chapter (@ (id "two")) "Chap 2 text")
+ (chapter (@ (id "three")) "Chap 3 text")
+ (chapter (@ (id "four")) "Chap 4 text")
+ (epilogue "Epilogue text")
+ (appendix (@ (id "A")) "App A text")
+ (References "References"))
+ )
+ (a-node ; to be used as a context node
+ (car ((sxpath '(// (chapter (@ (equal? (id "three")))))) tree)))
+ (expected
+ '((chapter (@ (id "two")) "Chap 2 text")))
+ )
+ (run-test
+ (node-reduce
+ (node-join
+ (node-parent tree)
+ (select-kids (node-typeof? 'chapter)))
+ (take-until (node-eq? a-node))
+ (node-pos -1)
+ )
+ a-node expected)
+)
+
+
+; /descendant::figure[position()=42]
+; selects the forty-second figure element in the document
+; See the next example, which is more general.
+
+; Location path, full form:
+; child::table/child::tr[position()=2]/child::td[position()=3]
+; Location path, abbreviated form: table/tr[2]/td[3]
+; selects the third td of the second tr of the table
+(let ((tree ((node-closure (node-typeof? 'p)) tree1))
+ (expected
+ '((td " data + control"))
+ ))
+ (run-test
+ (node-join
+ (select-kids (node-typeof? 'table))
+ (node-reduce (select-kids (node-typeof? 'tr))
+ (node-pos 2))
+ (node-reduce (select-kids (node-typeof? 'td))
+ (node-pos 3)))
+ tree expected)
+ (run-test (sxpath '(table (tr 2) (td 3))) tree expected)
+)
+
+
+; Location path, full form:
+; child::para[attribute::type='warning'][position()=5]
+; Location path, abbreviated form: para[@type='warning'][5]
+; selects the fifth para child of the context node that has a type
+; attribute with value warning
+(let ((tree
+ '(chapter
+ (para "para1")
+ (para (@ (type "warning")) "para 2")
+ (para (@ (type "warning")) "para 3")
+ (para (@ (type "warning")) "para 4")
+ (para (@ (type "warning")) "para 5")
+ (para (@ (type "warning")) "para 6"))
+ )
+ (expected
+ '((para (@ (type "warning")) "para 6"))
+ ))
+ (run-test
+ (node-reduce
+ (select-kids (node-typeof? 'para))
+ (filter
+ (node-join
+ (select-kids (node-typeof? '@))
+ (select-kids (node-equal? '(type "warning")))))
+ (node-pos 5))
+ tree expected)
+ (run-test (sxpath '( (((para (@ (equal? (type "warning"))))) 5 ) ))
+ tree expected)
+ (run-test (sxpath '( (para (@ (equal? (type "warning"))) 5 ) ))
+ tree expected)
+)
+
+
+; Location path, full form:
+; child::para[position()=5][attribute::type='warning']
+; Location path, abbreviated form: para[5][@type='warning']
+; selects the fifth para child of the context node if that child has a 'type'
+; attribute with value warning
+(let ((tree
+ '(chapter
+ (para "para1")
+ (para (@ (type "warning")) "para 2")
+ (para (@ (type "warning")) "para 3")
+ (para (@ (type "warning")) "para 4")
+ (para (@ (type "warning")) "para 5")
+ (para (@ (type "warning")) "para 6"))
+ )
+ (expected
+ '((para (@ (type "warning")) "para 5"))
+ ))
+ (run-test
+ (node-reduce
+ (select-kids (node-typeof? 'para))
+ (node-pos 5)
+ (filter
+ (node-join
+ (select-kids (node-typeof? '@))
+ (select-kids (node-equal? '(type "warning"))))))
+ tree expected)
+ (run-test (sxpath '( (( (para 5)) (@ (equal? (type "warning"))))))
+ tree expected)
+ (run-test (sxpath '( (para 5 (@ (equal? (type "warning")))) ))
+ tree expected)
+)
+
+; Location path, full form:
+; child::*[self::chapter or self::appendix]
+; Location path, semi-abbreviated form: *[self::chapter or self::appendix]
+; selects the chapter and appendix children of the context node
+(let ((tree
+ '(document
+ (preface "preface")
+ (chapter (@ (id "one")) "Chap 1 text")
+ (chapter (@ (id "two")) "Chap 2 text")
+ (chapter (@ (id "three")) "Chap 3 text")
+ (epilogue "Epilogue text")
+ (appendix (@ (id "A")) "App A text")
+ (References "References"))
+ )
+ (expected
+ '((chapter (@ (id "one")) "Chap 1 text")
+ (chapter (@ (id "two")) "Chap 2 text")
+ (chapter (@ (id "three")) "Chap 3 text")
+ (appendix (@ (id "A")) "App A text"))
+ ))
+ (run-test
+ (node-join
+ (select-kids (node-typeof? '*))
+ (filter
+ (node-or
+ (node-self (node-typeof? 'chapter))
+ (node-self (node-typeof? 'appendix)))))
+ tree expected)
+ (run-test (sxpath `(* ,(node-or (node-self (node-typeof? 'chapter))
+ (node-self (node-typeof? 'appendix)))))
+ tree expected)
+)
+
+
+; Location path, full form: child::chapter[child::title='Introduction']
+; Location path, abbreviated form: chapter[title = 'Introduction']
+; selects the chapter children of the context node that have one or more
+; title children with string-value equal to Introduction
+; See a similar example: //td[@align = "right"] above.
+
+; Location path, full form: child::chapter[child::title]
+; Location path, abbreviated form: chapter[title]
+; selects the chapter children of the context node that have one or
+; more title children
+; See a similar example //td[@align] above.
+
+(cerr nl "Example with tree3: extracting the first lines of every stanza" nl)
+(let ((tree tree3)
+ (expected
+ '("Let us go then, you and I," "In the room the women come and go")
+ ))
+ (run-test
+ (node-join
+ (node-closure (node-typeof? 'stanza))
+ (node-reduce
+ (select-kids (node-typeof? 'line)) (node-pos 1))
+ (select-kids (node-typeof? '*text*)))
+ tree expected)
+ (run-test (sxpath '(// stanza (line 1) *text*)) tree expected)
+)
+
+;
+; syntax: assert ?expr ?expr ... [report: ?r-exp ?r-exp ...]
+;
+; If (and ?expr ?expr ...) evaluates to anything but #f, the result
+; is the value of that expression.
+; If (and ?expr ?expr ...) evaluates to #f, 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 ?r-exp that
+; follow the identifier 'report:'
+; Typically, ?r-exp is either a variable or a string constant.
+; If the user specified no ?r-exp, the values of variables that are
+; referenced in ?expr will be printed upon the assertion failure.
+
+(define-syntax assert
+ (syntax-rules (report\:)
+ ((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 ...))))
+
+(define-syntax assure
+ (syntax-rules ()
+ ((assure exp error-msg)
+ (assert exp report\: error-msg))));****************************************************************************
+; Simple Parsing of input
+;
+; The following simple functions surprisingly often suffice to parse
+; an input stream. They either skip, or build and return tokens,
+; according to inclusion or delimiting semantics. The list of
+; characters to expect, include, or to break at may vary from one
+; invocation of a function to another. This allows the functions to
+; easily parse even context-sensitive languages.
+;
+; EOF is generally frowned on, and thrown up upon if encountered.
+; Exceptions are mentioned specifically. The list of expected characters
+; (characters to skip until, or break-characters) may include an EOF
+; "character", which is to be coded as symbol *eof*
+;
+; The input stream to parse is specified as a PORT, which is usually
+; the last (and optional) argument. It defaults to the current input
+; port if omitted.
+;
+; IMPORT
+; This package relies on a function parser-error, which must be defined
+; by a user of the package. The function has the following signature:
+; parser-error PORT MESSAGE SPECIALISING-MSG*
+; Many procedures of this package call parser-error to report a parsing
+; error. The first argument is a port, which typically points to the
+; offending character or its neighborhood. Most of the Scheme systems
+; let the user query a PORT for the current position. MESSAGE is the
+; description of the error. Other arguments supply more details about
+; the problem.
+; myenv.scm, myenv-bigloo.scm or a similar prelude is assumed.
+; From SRFI-13, string-concatenate-reverse
+; If a particular implementation lacks SRFI-13 support, please
+; include the file srfi-13-local.scm
+;
+; $Id: input-parse.scm,v 1.7 2004/07/07 16:02:31 sperber Exp $
+
+;------------------------------------------------------------------------
+
+; -- procedure+: peek-next-char [PORT]
+; advances to the next character in the PORT and peeks at it.
+; This function is useful when parsing LR(1)-type languages
+; (one-char-read-ahead).
+; The optional argument PORT defaults to the current input port.
+
+(define-opt (peek-next-char (optional (port (current-input-port))))
+ (read-char port)
+ (peek-char port))
+
+
+;------------------------------------------------------------------------
+
+; -- procedure+: assert-curr-char CHAR-LIST STRING [PORT]
+; Reads a character from the PORT and looks it up
+; in the CHAR-LIST of expected characters
+; If the read character was found among expected, it is returned
+; Otherwise, the procedure writes a nasty message using STRING
+; as a comment, and quits.
+; The optional argument PORT defaults to the current input port.
+;
+(define-opt (assert-curr-char expected-chars comment
+ (optional (port (current-input-port))))
+ (let ((c (read-char port)))
+ (if (memv c expected-chars) c
+ (parser-error port "Wrong character " c
+ " (0x" (if (eof-object? c) "*eof*"
+ (number->string (char->integer c) 16)) ") "
+ comment ". " expected-chars " expected"))))
+
+
+; -- procedure+: skip-until CHAR-LIST [PORT]
+; Reads and skips characters from the PORT until one of the break
+; characters is encountered. This break character is returned.
+; The break characters are specified as the CHAR-LIST. This list
+; may include EOF, which is to be coded as a symbol *eof*
+;
+; -- procedure+: skip-until NUMBER [PORT]
+; Skips the specified NUMBER of characters from the PORT and returns #f
+;
+; The optional argument PORT defaults to the current input port.
+
+
+(define-opt (skip-until arg (optional (port (current-input-port))) )
+ (cond
+ ((number? arg) ; skip 'arg' characters
+ (do ((i arg (dec i)))
+ ((not (positive? i)) #f)
+ (if (eof-object? (read-char port))
+ (parser-error port "Unexpected EOF while skipping "
+ arg " characters"))))
+ (else ; skip until break-chars (=arg)
+ (let loop ((c (read-char port)))
+ (cond
+ ((memv c arg) c)
+ ((eof-object? c)
+ (if (memq '*eof* arg) c
+ (parser-error port "Unexpected EOF while skipping until " arg)))
+ (else (loop (read-char port))))))))
+
+
+; -- procedure+: skip-while CHAR-LIST [PORT]
+; Reads characters from the PORT and disregards them,
+; as long as they are mentioned in the CHAR-LIST.
+; The first character (which may be EOF) peeked from the stream
+; that is NOT a member of the CHAR-LIST is returned. This character
+; is left on the stream.
+; The optional argument PORT defaults to the current input port.
+
+(define-opt (skip-while skip-chars (optional (port (current-input-port))) )
+ (do ((c (peek-char port) (peek-char port)))
+ ((not (memv c skip-chars)) c)
+ (read-char port)))
+
+; whitespace const
+
+;------------------------------------------------------------------------
+; Stream tokenizers
+
+
+; -- procedure+:
+; next-token PREFIX-CHAR-LIST BREAK-CHAR-LIST [COMMENT-STRING] [PORT]
+; skips any number of the prefix characters (members of the
+; PREFIX-CHAR-LIST), if any, and reads the sequence of characters
+; up to (but not including) a break character, one of the
+; BREAK-CHAR-LIST.
+; The string of characters thus read is returned.
+; The break character is left on the input stream
+; The list of break characters may include EOF, which is to be coded as
+; a symbol *eof*. Otherwise, EOF is fatal, generating an error message
+; including a specified COMMENT-STRING (if any)
+;
+; The optional argument PORT defaults to the current input port.
+;
+; Note: since we can't tell offhand how large the token being read is
+; going to be, we make a guess, pre-allocate a string, and grow it by
+; quanta if necessary. The quantum is always the length of the string
+; before it was extended the last time. Thus the algorithm does
+; a Fibonacci-type extension, which has been proven optimal.
+; Note, explicit port specification in read-char, peek-char helps.
+
+; Procedure: input-parse:init-buffer
+; returns an initial buffer for next-token* procedures.
+; The input-parse:init-buffer may allocate a new buffer per each invocation:
+; (define (input-parse:init-buffer) (make-string 32))
+; Size 32 turns out to be fairly good, on average.
+; That policy is good only when a Scheme system is multi-threaded with
+; preemptive scheduling, or when a Scheme system supports shared substrings.
+; In all the other cases, it's better for input-parse:init-buffer to
+; return the same static buffer. next-token* functions return a copy
+; (a substring) of accumulated data, so the same buffer can be reused.
+; We shouldn't worry about an incoming token being too large:
+; next-token will use another chunk automatically. Still,
+; the best size for the static buffer is to allow most of the tokens to fit in.
+; Using a static buffer _dramatically_ reduces the amount of produced garbage
+; (e.g., during XML parsing).
+
+(define input-parse:init-buffer
+ (let ((buffer (make-string 512)))
+ (lambda () buffer)))
+
+
+ ; See a better version below
+(define-opt (next-token-old prefix-skipped-chars break-chars
+ (optional (comment "") (port (current-input-port))) )
+ (let* ((buffer (input-parse:init-buffer))
+ (curr-buf-len (string-length buffer))
+ (quantum curr-buf-len))
+ (let loop ((i 0) (c (skip-while prefix-skipped-chars port)))
+ (cond
+ ((memv c break-chars) (substring buffer 0 i))
+ ((eof-object? c)
+ (if (memq '*eof* break-chars)
+ (substring buffer 0 i) ; was EOF expected?
+ (parser-error port "EOF while reading a token " comment)))
+ (else
+ (if (>= i curr-buf-len) ; make space for i-th char in buffer
+ (begin ; -> grow the buffer by the quantum
+ (set! buffer (string-append buffer (make-string quantum)))
+ (set! quantum curr-buf-len)
+ (set! curr-buf-len (string-length buffer))))
+ (string-set! buffer i c)
+ (read-char port) ; move to the next char
+ (loop (inc i) (peek-char port))
+ )))))
+
+
+; A better version of next-token, which accumulates the characters
+; in chunks, and later on reverse-concatenates them, using
+; SRFI-13 if available.
+; The overhead of copying characters is only 100% (or even smaller: bulk
+; string copying might be well-optimised), compared to the (hypothetical)
+; circumstance if we had known the size of the token beforehand.
+; For small tokens, the code performs just as above. For large
+; tokens, we expect an improvement. Note, the code also has no
+; assignments.
+; See next-token-comp.scm
+
+(define-opt (next-token prefix-skipped-chars break-chars
+ (optional (comment "") (port (current-input-port))) )
+ (let outer ((buffer (input-parse:init-buffer)) (filled-buffer-l '())
+ (c (skip-while prefix-skipped-chars port)))
+ (let ((curr-buf-len (string-length buffer)))
+ (let loop ((i 0) (c c))
+ (cond
+ ((memv c break-chars)
+ (if (null? filled-buffer-l) (substring buffer 0 i)
+ (string-concatenate-reverse filled-buffer-l buffer i)))
+ ((eof-object? c)
+ (if (memq '*eof* break-chars) ; was EOF expected?
+ (if (null? filled-buffer-l) (substring buffer 0 i)
+ (string-concatenate-reverse filled-buffer-l buffer i))
+ (parser-error port "EOF while reading a token " comment)))
+ ((>= i curr-buf-len)
+ (outer (make-string curr-buf-len)
+ (cons buffer filled-buffer-l) c))
+ (else
+ (string-set! buffer i c)
+ (read-char port) ; move to the next char
+ (loop (inc i) (peek-char port))))))))
+
+; -- procedure+: next-token-of INC-CHARSET [PORT]
+; Reads characters from the PORT that belong to the list of characters
+; INC-CHARSET. The reading stops at the first character which is not
+; a member of the set. This character is left on the stream.
+; All the read characters are returned in a string.
+;
+; -- procedure+: next-token-of PRED [PORT]
+; Reads characters from the PORT for which PRED (a procedure of one
+; argument) returns non-#f. The reading stops at the first character
+; for which PRED returns #f. That character is left on the stream.
+; All the results of evaluating of PRED up to #f are returned in a
+; string.
+;
+; PRED is a procedure that takes one argument (a character
+; or the EOF object) and returns a character or #f. The returned
+; character does not have to be the same as the input argument
+; to the PRED. For example,
+; (next-token-of (lambda (c)
+; (cond ((eof-object? c) #f)
+; ((char-alphabetic? c) (char-downcase c))
+; (else #f))))
+; will try to read an alphabetic token from the current
+; input port, and return it in lower case.
+;
+; The optional argument PORT defaults to the current input port.
+;
+; This procedure is similar to next-token but only it implements
+; an inclusion rather than delimiting semantics.
+
+(define-opt (next-token-of incl-list/pred
+ (optional (port (current-input-port))) )
+ (let* ((buffer (input-parse:init-buffer))
+ (curr-buf-len (string-length buffer)))
+ (if (procedure? incl-list/pred)
+ (let outer ((buffer buffer) (filled-buffer-l '()))
+ (let loop ((i 0))
+ (if (>= i curr-buf-len) ; make sure we have space
+ (outer (make-string curr-buf-len) (cons buffer filled-buffer-l))
+ (let ((c (incl-list/pred (peek-char port))))
+ (if c
+ (begin
+ (string-set! buffer i c)
+ (read-char port) ; move to the next char
+ (loop (inc i)))
+ ; incl-list/pred decided it had had enough
+ (if (null? filled-buffer-l) (substring buffer 0 i)
+ (string-concatenate-reverse filled-buffer-l buffer i)))))))
+
+ ; incl-list/pred is a list of allowed characters
+ (let outer ((buffer buffer) (filled-buffer-l '()))
+ (let loop ((i 0))
+ (if (>= i curr-buf-len) ; make sure we have space
+ (outer (make-string curr-buf-len) (cons buffer filled-buffer-l))
+ (let ((c (peek-char port)))
+ (cond
+ ((not (memv c incl-list/pred))
+ (if (null? filled-buffer-l) (substring buffer 0 i)
+ (string-concatenate-reverse filled-buffer-l buffer i)))
+ (else
+ (string-set! buffer i c)
+ (read-char port) ; move to the next char
+ (loop (inc i))))))))
+ )))
+
+
+; -- procedure+: read-text-line [PORT]
+; Reads one line of text from the PORT, and returns it as a string.
+; A line is a (possibly empty) sequence of characters terminated
+; by CR, CRLF or LF (or even the end of file).
+; The terminating character (or CRLF combination) is removed from
+; the input stream. The terminating character(s) is not a part
+; of the return string either.
+; If EOF is encountered before any character is read, the return
+; value is EOF.
+;
+; The optional argument PORT defaults to the current input port.
+
+(define *read-line-breaks* (list char-newline char-return '*eof*))
+
+(define-opt (read-text-line (optional (port (current-input-port))) )
+ (if (eof-object? (peek-char port)) (peek-char port)
+ (let* ((line
+ (next-token '() *read-line-breaks*
+ "reading a line" port))
+ (c (read-char port))) ; must be either \n or \r or EOF
+ (and (eqv? c char-return) (eqv? (peek-char port) #\newline)
+ (read-char port)) ; skip \n that follows \r
+ line)))
+
+
+; -- procedure+: read-string N [PORT]
+; Reads N characters from the PORT, and returns them in a string.
+; If EOF is encountered before N characters are read, a shorter string
+; will be returned.
+; If N is not positive, an empty string will be returned.
+; The optional argument PORT defaults to the current input port.
+
+(define-opt (read-string n (optional (port (current-input-port))) )
+ (if (not (positive? n)) ""
+ (let ((buffer (make-string n)))
+ (let loop ((i 0) (c (read-char port)))
+ (if (eof-object? c) (substring buffer 0 i)
+ (let ((i1 (inc i)))
+ (string-set! buffer i c)
+ (if (= i1 n) buffer
+ (loop i1 (read-char port)))))))))
+
+;;;; (sxml xpath) -- SXPath
+;;;;
+;;;; Copyright (C) 2009 Free Software Foundation, Inc.
+;;;; Modified 2004 by Andy Wingo <wingo at pobox dot com>.
+;;;; Written 2001 by Oleg Kiselyov <oleg at pobox dot com> SXPath.scm.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary:
+;;
+;;@heading SXPath: SXML Query Language
+;;
+;; SXPath is a query language for SXML, an instance of XML Information
+;; set (Infoset) in the form of s-expressions. See @code{(sxml ssax)}
+;; for the definition of SXML and more details. SXPath is also a
+;; translation into Scheme of an XML Path Language,
+;; @uref{http://www.w3.org/TR/xpath,XPath}. XPath and SXPath describe
+;; means of selecting a set of Infoset's items or their properties.
+;;
+;; To facilitate queries, XPath maps the XML Infoset into an explicit
+;; tree, and introduces important notions of a location path and a
+;; current, context node. A location path denotes a selection of a set of
+;; nodes relative to a context node. Any XPath tree has a distinguished,
+;; root node -- which serves as the context node for absolute location
+;; paths. Location path is recursively defined as a location step joined
+;; with a location path. A location step is a simple query of the
+;; database relative to a context node. A step may include expressions
+;; that further filter the selected set. Each node in the resulting set
+;; is used as a context node for the adjoining location path. The result
+;; of the step is a union of the sets returned by the latter location
+;; paths.
+;;
+;; The SXML representation of the XML Infoset (see SSAX.scm) is rather
+;; suitable for querying as it is. Bowing to the XPath specification,
+;; we will refer to SXML information items as 'Nodes':
+;;@example
+;; <Node> ::= <Element> | <attributes-coll> | <attrib>
+;; | "text string" | <PI>
+;;@end example
+;; This production can also be described as
+;;@example
+;; <Node> ::= (name . <Nodeset>) | "text string"
+;;@end example
+;; An (ordered) set of nodes is just a list of the constituent nodes:
+;;@example
+;; <Nodeset> ::= (<Node> ...)
+;;@end example
+;; Nodesets, and Nodes other than text strings are both lists. A
+;; <Nodeset> however is either an empty list, or a list whose head is not
+;; a symbol. A symbol at the head of a node is either an XML name (in
+;; which case it's a tag of an XML element), or an administrative name
+;; such as '@@'. This uniform list representation makes processing rather
+;; simple and elegant, while avoiding confusion. The multi-branch tree
+;; structure formed by the mutually-recursive datatypes <Node> and
+;; <Nodeset> lends itself well to processing by functional languages.
+;;
+;; A location path is in fact a composite query over an XPath tree or
+;; its branch. A singe step is a combination of a projection, selection
+;; or a transitive closure. Multiple steps are combined via join and
+;; union operations. This insight allows us to @emph{elegantly}
+;; implement XPath as a sequence of projection and filtering primitives
+;; -- converters -- joined by @dfn{combinators}. Each converter takes a
+;; node and returns a nodeset which is the result of the corresponding
+;; query relative to that node. A converter can also be called on a set
+;; of nodes. In that case it returns a union of the corresponding
+;; queries over each node in the set. The union is easily implemented as
+;; a list append operation as all nodes in a SXML tree are considered
+;; distinct, by XPath conventions. We also preserve the order of the
+;; members in the union. Query combinators are high-order functions:
+;; they take converter(s) (which is a Node|Nodeset -> Nodeset function)
+;; and compose or otherwise combine them. We will be concerned with only
+;; relative location paths [XPath]: an absolute location path is a
+;; relative path applied to the root node.
+;;
+;; Similarly to XPath, SXPath defines full and abbreviated notations
+;; for location paths. In both cases, the abbreviated notation can be
+;; mechanically expanded into the full form by simple rewriting
+;; rules. In case of SXPath the corresponding rules are given as
+;; comments to a sxpath function, below. The regression test suite at
+;; the end of this file shows a representative sample of SXPaths in
+;; both notations, juxtaposed with the corresponding XPath
+;; expressions. Most of the samples are borrowed literally from the
+;; XPath specification, while the others are adjusted for our running
+;; example, tree1.
+;;
+;;; Code:
+
+(define-module (sxml xpath)
+ #\use-module (ice-9 pretty-print)
+ #\export (nodeset? node-typeof? node-eq? node-equal? node-pos
+ filter take-until take-after map-union node-reverse
+ node-trace select-kids node-self node-join node-reduce
+ node-or node-closure node-parent
+ sxpath))
+
+;; Upstream version:
+; $Id: SXPath.scm,v 3.5 2001/01/12 23:20:35 oleg Exp oleg $
+
+(define (nodeset? x)
+ (or (and (pair? x) (not (symbol? (car x)))) (null? x)))
+
+;-------------------------
+; Basic converters and applicators
+; A converter is a function
+; type Converter = Node|Nodeset -> Nodeset
+; A converter can also play a role of a predicate: in that case, if a
+; converter, applied to a node or a nodeset, yields a non-empty
+; nodeset, the converter-predicate is deemed satisfied. Throughout
+; this file a nil nodeset is equivalent to #f in denoting a failure.
+
+; The following function implements a 'Node test' as defined in
+; Sec. 2.3 of XPath document. A node test is one of the components of a
+; location step. It is also a converter-predicate in SXPath.
+;
+; The function node-typeof? takes a type criterion and returns a function,
+; which, when applied to a node, will tell if the node satisfies
+; the test.
+; node-typeof? :: Crit -> Node -> Boolean
+;
+; The criterion 'crit' is a symbol, one of the following:
+; id - tests if the Node has the right name (id)
+; @ - tests if the Node is an <attributes-coll>
+; * - tests if the Node is an <Element>
+; *text* - tests if the Node is a text node
+; *PI* - tests if the Node is a PI node
+; *any* - #t for any type of Node
+
+(define (node-typeof? crit)
+ (lambda (node)
+ (case crit
+ ((*) (and (pair? node) (not (memq (car node) '(@ *PI*)))))
+ ((*any*) #t)
+ ((*text*) (string? node))
+ (else
+ (and (pair? node) (eq? crit (car node))))
+)))
+
+
+; Curried equivalence converter-predicates
+(define (node-eq? other)
+ (lambda (node)
+ (eq? other node)))
+
+(define (node-equal? other)
+ (lambda (node)
+ (equal? other node)))
+
+; node-pos:: N -> Nodeset -> Nodeset, or
+; node-pos:: N -> Converter
+; Select the N'th element of a Nodeset and return as a singular Nodeset;
+; Return an empty nodeset if the Nth element does not exist.
+; ((node-pos 1) Nodeset) selects the node at the head of the Nodeset,
+; if exists; ((node-pos 2) Nodeset) selects the Node after that, if
+; exists.
+; N can also be a negative number: in that case the node is picked from
+; the tail of the list.
+; ((node-pos -1) Nodeset) selects the last node of a non-empty nodeset;
+; ((node-pos -2) Nodeset) selects the last but one node, if exists.
+
+(define (node-pos n)
+ (lambda (nodeset)
+ (cond
+ ((not (nodeset? nodeset)) '())
+ ((null? nodeset) nodeset)
+ ((eqv? n 1) (list (car nodeset)))
+ ((negative? n) ((node-pos (+ n 1 (length nodeset))) nodeset))
+ (else
+ (or (positive? n) (error "yikes!"))
+ ((node-pos (1- n)) (cdr nodeset))))))
+
+; filter:: Converter -> Converter
+; A filter applicator, which introduces a filtering context. The argument
+; converter is considered a predicate, with either #f or nil result meaning
+; failure.
+(define (filter pred?)
+ (lambda (lst) ; a nodeset or a node (will be converted to a singleton nset)
+ (let loop ((lst (if (nodeset? lst) lst (list lst))) (res '()))
+ (if (null? lst)
+ (reverse res)
+ (let ((pred-result (pred? (car lst))))
+ (loop (cdr lst)
+ (if (and pred-result (not (null? pred-result)))
+ (cons (car lst) res)
+ res)))))))
+
+; take-until:: Converter -> Converter, or
+; take-until:: Pred -> Node|Nodeset -> Nodeset
+; Given a converter-predicate and a nodeset, apply the predicate to
+; each element of the nodeset, until the predicate yields anything but #f or
+; nil. Return the elements of the input nodeset that have been processed
+; till that moment (that is, which fail the predicate).
+; take-until is a variation of the filter above: take-until passes
+; elements of an ordered input set till (but not including) the first
+; element that satisfies the predicate.
+; The nodeset returned by ((take-until (not pred)) nset) is a subset --
+; to be more precise, a prefix -- of the nodeset returned by
+; ((filter pred) nset)
+
+(define (take-until pred?)
+ (lambda (lst) ; a nodeset or a node (will be converted to a singleton nset)
+ (let loop ((lst (if (nodeset? lst) lst (list lst))))
+ (if (null? lst) lst
+ (let ((pred-result (pred? (car lst))))
+ (if (and pred-result (not (null? pred-result)))
+ '()
+ (cons (car lst) (loop (cdr lst)))))
+ ))))
+
+
+; take-after:: Converter -> Converter, or
+; take-after:: Pred -> Node|Nodeset -> Nodeset
+; Given a converter-predicate and a nodeset, apply the predicate to
+; each element of the nodeset, until the predicate yields anything but #f or
+; nil. Return the elements of the input nodeset that have not been processed:
+; that is, return the elements of the input nodeset that follow the first
+; element that satisfied the predicate.
+; take-after along with take-until partition an input nodeset into three
+; parts: the first element that satisfies a predicate, all preceding
+; elements and all following elements.
+
+(define (take-after pred?)
+ (lambda (lst) ; a nodeset or a node (will be converted to a singleton nset)
+ (let loop ((lst (if (nodeset? lst) lst (list lst))))
+ (if (null? lst) lst
+ (let ((pred-result (pred? (car lst))))
+ (if (and pred-result (not (null? pred-result)))
+ (cdr lst)
+ (loop (cdr lst))))
+ ))))
+
+; Apply proc to each element of lst and return the list of results.
+; if proc returns a nodeset, splice it into the result
+;
+; From another point of view, map-union is a function Converter->Converter,
+; which places an argument-converter in a joining context.
+
+(define (map-union proc lst)
+ (if (null? lst) lst
+ (let ((proc-res (proc (car lst))))
+ ((if (nodeset? proc-res) append cons)
+ proc-res (map-union proc (cdr lst))))))
+
+; node-reverse :: Converter, or
+; node-reverse:: Node|Nodeset -> Nodeset
+; Reverses the order of nodes in the nodeset
+; This basic converter is needed to implement a reverse document order
+; (see the XPath Recommendation).
+(define node-reverse
+ (lambda (node-or-nodeset)
+ (if (not (nodeset? node-or-nodeset)) (list node-or-nodeset)
+ (reverse node-or-nodeset))))
+
+; node-trace:: String -> Converter
+; (node-trace title) is an identity converter. In addition it prints out
+; a node or nodeset it is applied to, prefixed with the 'title'.
+; This converter is very useful for debugging.
+
+(define (node-trace title)
+ (lambda (node-or-nodeset)
+ (display "\n-->")
+ (display title)
+ (display " :")
+ (pretty-print node-or-nodeset)
+ node-or-nodeset))
+
+
+;-------------------------
+; Converter combinators
+;
+; Combinators are higher-order functions that transmogrify a converter
+; or glue a sequence of converters into a single, non-trivial
+; converter. The goal is to arrive at converters that correspond to
+; XPath location paths.
+;
+; From a different point of view, a combinator is a fixed, named
+; _pattern_ of applying converters. Given below is a complete set of
+; such patterns that together implement XPath location path
+; specification. As it turns out, all these combinators can be built
+; from a small number of basic blocks: regular functional composition,
+; map-union and filter applicators, and the nodeset union.
+
+
+
+; select-kids:: Pred -> Node -> Nodeset
+; Given a Node, return an (ordered) subset its children that satisfy
+; the Pred (a converter, actually)
+; select-kids:: Pred -> Nodeset -> Nodeset
+; The same as above, but select among children of all the nodes in
+; the Nodeset
+;
+; More succinctly, the signature of this function is
+; select-kids:: Converter -> Converter
+
+(define (select-kids test-pred?)
+ (lambda (node) ; node or node-set
+ (cond
+ ((null? node) node)
+ ((not (pair? node)) '()) ; No children
+ ((symbol? (car node))
+ ((filter test-pred?) (cdr node))) ; it's a single node
+ (else (map-union (select-kids test-pred?) node)))))
+
+
+; node-self:: Pred -> Node -> Nodeset, or
+; node-self:: Converter -> Converter
+; Similar to select-kids but apply to the Node itself rather
+; than to its children. The resulting Nodeset will contain either one
+; component, or will be empty (if the Node failed the Pred).
+(define node-self filter)
+
+
+; node-join:: [LocPath] -> Node|Nodeset -> Nodeset, or
+; node-join:: [Converter] -> Converter
+; join the sequence of location steps or paths as described
+; in the title comments above.
+(define (node-join . selectors)
+ (lambda (nodeset) ; Nodeset or node
+ (let loop ((nodeset nodeset) (selectors selectors))
+ (if (null? selectors) nodeset
+ (loop
+ (if (nodeset? nodeset)
+ (map-union (car selectors) nodeset)
+ ((car selectors) nodeset))
+ (cdr selectors))))))
+
+
+; node-reduce:: [LocPath] -> Node|Nodeset -> Nodeset, or
+; node-reduce:: [Converter] -> Converter
+; A regular functional composition of converters.
+; From a different point of view,
+; ((apply node-reduce converters) nodeset)
+; is equivalent to
+; (foldl apply nodeset converters)
+; i.e., folding, or reducing, a list of converters with the nodeset
+; as a seed.
+(define (node-reduce . converters)
+ (lambda (nodeset) ; Nodeset or node
+ (let loop ((nodeset nodeset) (converters converters))
+ (if (null? converters) nodeset
+ (loop ((car converters) nodeset) (cdr converters))))))
+
+
+; node-or:: [Converter] -> Converter
+; This combinator applies all converters to a given node and
+; produces the union of their results.
+; This combinator corresponds to a union, '|' operation for XPath
+; location paths.
+; (define (node-or . converters)
+; (lambda (node-or-nodeset)
+; (if (null? converters) node-or-nodeset
+; (append
+; ((car converters) node-or-nodeset)
+; ((apply node-or (cdr converters)) node-or-nodeset)))))
+; More optimal implementation follows
+(define (node-or . converters)
+ (lambda (node-or-nodeset)
+ (let loop ((result '()) (converters converters))
+ (if (null? converters) result
+ (loop (append result (or ((car converters) node-or-nodeset) '()))
+ (cdr converters))))))
+
+
+; node-closure:: Converter -> Converter
+; Select all _descendants_ of a node that satisfy a converter-predicate.
+; This combinator is similar to select-kids but applies to
+; grand... children as well.
+; This combinator implements the "descendant::" XPath axis
+; Conceptually, this combinator can be expressed as
+; (define (node-closure f)
+; (node-or
+; (select-kids f)
+; (node-reduce (select-kids (node-typeof? '*)) (node-closure f))))
+; This definition, as written, looks somewhat like a fixpoint, and it
+; will run forever. It is obvious however that sooner or later
+; (select-kids (node-typeof? '*)) will return an empty nodeset. At
+; this point further iterations will no longer affect the result and
+; can be stopped.
+
+(define (node-closure test-pred?)
+ (lambda (node) ; Nodeset or node
+ (let loop ((parent node) (result '()))
+ (if (null? parent) result
+ (loop ((select-kids (node-typeof? '*)) parent)
+ (append result
+ ((select-kids test-pred?) parent)))
+ ))))
+
+; node-parent:: RootNode -> Converter
+; (node-parent rootnode) yields a converter that returns a parent of a
+; node it is applied to. If applied to a nodeset, it returns the list
+; of parents of nodes in the nodeset. The rootnode does not have
+; to be the root node of the whole SXML tree -- it may be a root node
+; of a branch of interest.
+; Given the notation of Philip Wadler's paper on semantics of XSLT,
+; parent(x) = { y | y=subnode*(root), x=subnode(y) }
+; Therefore, node-parent is not the fundamental converter: it can be
+; expressed through the existing ones. Yet node-parent is a rather
+; convenient converter. It corresponds to a parent:: axis of SXPath.
+; Note that the parent:: axis can be used with an attribute node as well!
+
+(define (node-parent rootnode)
+ (lambda (node) ; Nodeset or node
+ (if (nodeset? node) (map-union (node-parent rootnode) node)
+ (let ((pred
+ (node-or
+ (node-reduce
+ (node-self (node-typeof? '*))
+ (select-kids (node-eq? node)))
+ (node-join
+ (select-kids (node-typeof? '@))
+ (select-kids (node-eq? node))))))
+ ((node-or
+ (node-self pred)
+ (node-closure pred))
+ rootnode)))))
+
+;-------------------------
+; Evaluate an abbreviated SXPath
+; sxpath:: AbbrPath -> Converter, or
+; sxpath:: AbbrPath -> Node|Nodeset -> Nodeset
+; AbbrPath is a list. It is translated to the full SXPath according
+; to the following rewriting rules
+; (sxpath '()) -> (node-join)
+; (sxpath '(path-component ...)) ->
+; (node-join (sxpath1 path-component) (sxpath '(...)))
+; (sxpath1 '//) -> (node-or
+; (node-self (node-typeof? '*any*))
+; (node-closure (node-typeof? '*any*)))
+; (sxpath1 '(equal? x)) -> (select-kids (node-equal? x))
+; (sxpath1 '(eq? x)) -> (select-kids (node-eq? x))
+; (sxpath1 ?symbol) -> (select-kids (node-typeof? ?symbol)
+; (sxpath1 procedure) -> procedure
+; (sxpath1 '(?symbol ...)) -> (sxpath1 '((?symbol) ...))
+; (sxpath1 '(path reducer ...)) ->
+; (node-reduce (sxpath path) (sxpathr reducer) ...)
+; (sxpathr number) -> (node-pos number)
+; (sxpathr path-filter) -> (filter (sxpath path-filter))
+
+(define (sxpath path)
+ (lambda (nodeset)
+ (let loop ((nodeset nodeset) (path path))
+ (cond
+ ((null? path) nodeset)
+ ((nodeset? nodeset)
+ (map-union (sxpath path) nodeset))
+ ((procedure? (car path))
+ (loop ((car path) nodeset) (cdr path)))
+ ((eq? '// (car path))
+ (loop
+ ((if (nodeset? nodeset) append cons) nodeset
+ ((node-closure (node-typeof? '*any*)) nodeset))
+ (cdr path)))
+ ((symbol? (car path))
+ (loop ((select-kids (node-typeof? (car path))) nodeset)
+ (cdr path)))
+ ((and (pair? (car path)) (eq? 'equal? (caar path)))
+ (loop ((select-kids (apply node-equal? (cdar path))) nodeset)
+ (cdr path)))
+ ((and (pair? (car path)) (eq? 'eq? (caar path)))
+ (loop ((select-kids (apply node-eq? (cdar path))) nodeset)
+ (cdr path)))
+ ((pair? (car path))
+ (let reducer ((nodeset
+ (if (symbol? (caar path))
+ ((select-kids (node-typeof? (caar path))) nodeset)
+ (loop nodeset (caar path))))
+ (reducing-path (cdar path)))
+ (cond
+ ((null? reducing-path) (loop nodeset (cdr path)))
+ ((number? (car reducing-path))
+ (reducer ((node-pos (car reducing-path)) nodeset)
+ (cdr reducing-path)))
+ (else
+ (reducer ((filter (sxpath (car reducing-path))) nodeset)
+ (cdr reducing-path))))))
+ (else
+ (error "Invalid path step: " (car path)))))))
+
+;;; arch-tag: c4e57abf-6b61-4612-a6aa-d1536d440774
+;;; xpath.scm ends here
+;;; ck, to facilitate applicative-order macro programming
+
+;;; Copyright (C) 2012 Free Software Foundation, Inc
+;;; Copyright (C) 2009, 2011 Oleg Kiselyov
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;
+;;;
+;;; Originally written by Oleg Kiselyov and later contributed to Guile.
+;;;
+;;; Based on the CK machine introduced in:
+;;;
+;;; Matthias Felleisen and Daniel P. Friedman: Control operators, the
+;;; SECD machine, and the lambda-calculus. In Martin Wirsing, editor,
+;;; Formal Description of Programming Concepts III, pages
+;;; 193-217. Elsevier, Amsterdam, 1986.
+;;;
+;;; See http://okmij.org/ftp/Scheme/macros.html#ck-macros for details.
+;;;
+
+(define-module (system base ck)
+ #\export (ck))
+
+(define-syntax ck
+ (syntax-rules (quote)
+ ((ck () 'v) v) ; yield the value on empty stack
+
+ ((ck (((op ...) ea ...) . s) 'v) ; re-focus on the other argument, ea
+ (ck-arg s (op ... 'v) ea ...))
+
+ ((ck s (op ea ...)) ; Focus: handling an application;
+ (ck-arg s (op) ea ...)))) ; check if args are values
+
+(define-syntax ck-arg
+ (syntax-rules (quote)
+ ((ck-arg s (op va ...)) ; all arguments are evaluated,
+ (op s va ...)) ; do the redex
+
+ ((ck-arg s (op ...) 'v ea1 ...) ; optimization when the first ea
+ (ck-arg s (op ... 'v) ea1 ...)) ; was already a value
+
+ ((ck-arg s (op ...) ea ea1 ...) ; focus on ea, to evaluate it
+ (ck (((op ...) ea1 ...) . s) ea))))
+;;; High-level compiler interface
+
+;; Copyright (C) 2001, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
+
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code:
+
+(define-module (system base compile)
+ #\use-module (system base syntax)
+ #\use-module (system base language)
+ #\use-module (system base message)
+ #\use-module (system vm vm) ;; FIXME: there's a reason for this, can't remember why tho
+ #\use-module (ice-9 regex)
+ #\use-module (ice-9 optargs)
+ #\use-module (ice-9 receive)
+ #\export (compiled-file-name
+ compile-file
+ compile-and-load
+ read-and-compile
+ compile
+ decompile))
+
+
+;;;
+;;; Compiler
+;;;
+
+(define (call-once thunk)
+ (let ((entered #f))
+ (dynamic-wind
+ (lambda ()
+ (if entered
+ (error "thunk may only be entered once: ~a" thunk))
+ (set! entered #t))
+ thunk
+ (lambda () #t))))
+
+;; emacs: (put 'call-with-output-file/atomic 'scheme-indent-function 1)
+(define* (call-with-output-file/atomic filename proc #\optional reference)
+ (let* ((template (string-append filename ".XXXXXX"))
+ (tmp (mkstemp! template "wb")))
+ (call-once
+ (lambda ()
+ (with-throw-handler #t
+ (lambda ()
+ (proc tmp)
+ ;; Chmodding by name instead of by port allows this chmod to
+ ;; work on systems without fchmod, like MinGW.
+ (let ((perms (or (false-if-exception (stat:perms (stat reference)))
+ (lognot (umask)))))
+ (chmod template (logand #o0666 perms)))
+ (close-port tmp)
+ (rename-file template filename))
+ (lambda args
+ (close-port tmp)
+ (delete-file template)))))))
+
+(define (ensure-language x)
+ (if (language? x)
+ x
+ (lookup-language x)))
+
+;; Throws an exception if `dir' is not writable. The mkdir occurs
+;; before the check, so that we avoid races (possibly due to parallel
+;; compilation).
+;;
+(define (ensure-directory dir)
+ (catch 'system-error
+ (lambda ()
+ (mkdir dir))
+ (lambda (k subr fmt args rest)
+ (let ((errno (and (pair? rest) (car rest))))
+ (cond
+ ((eqv? errno EEXIST)
+ ;; Assume it's a writable directory, to avoid TOCTOU errors,
+ ;; as well as UID/EUID mismatches that occur with access(2).
+ #t)
+ ((eqv? errno ENOENT)
+ (ensure-directory (dirname dir))
+ (ensure-directory dir))
+ (else
+ (throw k subr fmt args rest)))))))
+
+;;; This function is among the trickiest I've ever written. I tried many
+;;; variants. In the end, simple is best, of course.
+;;;
+;;; After turning this around a number of times, it seems that the
+;;; desired behavior is that .go files should exist in a path, for
+;;; searching. That is orthogonal to this function. For writing .go
+;;; files, either you know where they should go, in which case you tell
+;;; compile-file explicitly, as in the srcdir != builddir case; or you
+;;; don't know, in which case this function is called, and we just put
+;;; them in your own ccache dir in ~/.cache/guile/ccache.
+;;;
+;;; See also boot-9.scm:load.
+(define (compiled-file-name file)
+ ;; FIXME: would probably be better just to append SHA1(canon-path)
+ ;; to the %compile-fallback-path, to avoid deep directory stats.
+ (define (canonical->suffix canon)
+ (cond
+ ((string-prefix? "/" canon) canon)
+ ((and (> (string-length canon) 2)
+ (eqv? (string-ref canon 1) #\:))
+ ;; Paths like C:... transform to /C...
+ (string-append "/" (substring canon 0 1) (substring canon 2)))
+ (else canon)))
+ (define (compiled-extension)
+ (cond ((or (null? %load-compiled-extensions)
+ (string-null? (car %load-compiled-extensions)))
+ (warn "invalid %load-compiled-extensions"
+ %load-compiled-extensions)
+ ".go")
+ (else (car %load-compiled-extensions))))
+ (and %compile-fallback-path
+ (let ((f (string-append
+ %compile-fallback-path
+ (canonical->suffix (canonicalize-path file))
+ (compiled-extension))))
+ (and (false-if-exception (ensure-directory (dirname f)))
+ f))))
+
+(define* (compile-file file #\key
+ (output-file #f)
+ (from (current-language))
+ (to 'objcode)
+ (env (default-environment from))
+ (opts '())
+ (canonicalization 'relative))
+ (with-fluids ((%file-port-name-canonicalization canonicalization))
+ (let* ((comp (or output-file (compiled-file-name file)
+ (error "failed to create path for auto-compiled file"
+ file)))
+ (in (open-input-file file))
+ (enc (file-encoding in)))
+ ;; Choose the input encoding deterministically.
+ (set-port-encoding! in (or enc "UTF-8"))
+
+ (ensure-directory (dirname comp))
+ (call-with-output-file/atomic comp
+ (lambda (port)
+ ((language-printer (ensure-language to))
+ (read-and-compile in #\env env #\from from #\to to #\opts opts)
+ port))
+ file)
+ comp)))
+
+(define* (compile-and-load file #\key (from (current-language)) (to 'value)
+ (env (current-module)) (opts '())
+ (canonicalization 'relative))
+ (with-fluids ((%file-port-name-canonicalization canonicalization))
+ (read-and-compile (open-input-file file)
+ #\from from #\to to #\opts opts
+ #\env env)))
+
+
+;;;
+;;; Compiler interface
+;;;
+
+(define (compile-passes from to opts)
+ (map cdr
+ (or (lookup-compilation-order from to)
+ (error "no way to compile" from "to" to))))
+
+(define (compile-fold passes exp env opts)
+ (let lp ((passes passes) (x exp) (e env) (cenv env) (first? #t))
+ (if (null? passes)
+ (values x e cenv)
+ (receive (x e new-cenv) ((car passes) x e opts)
+ (lp (cdr passes) x e (if first? new-cenv cenv) #f)))))
+
+(define (find-language-joint from to)
+ (let lp ((in (reverse (or (lookup-compilation-order from to)
+ (error "no way to compile" from "to" to))))
+ (lang to))
+ (cond ((null? in) to)
+ ((language-joiner lang) lang)
+ (else
+ (lp (cdr in) (caar in))))))
+
+(define (default-language-joiner lang)
+ (lambda (exps env)
+ (if (and (pair? exps) (null? (cdr exps)))
+ (car exps)
+ (error
+ "Multiple expressions read and compiled, but language has no joiner"
+ lang))))
+
+(define (read-and-parse lang port cenv)
+ (let ((exp ((language-reader lang) port cenv)))
+ (cond
+ ((eof-object? exp) exp)
+ ((language-parser lang) => (lambda (parse) (parse exp)))
+ (else exp))))
+
+(define* (read-and-compile port #\key
+ (from (current-language))
+ (to 'objcode)
+ (env (default-environment from))
+ (opts '()))
+ (let ((from (ensure-language from))
+ (to (ensure-language to)))
+ (let ((joint (find-language-joint from to)))
+ (parameterize ((current-language from))
+ (let lp ((exps '()) (env #f) (cenv env))
+ (let ((x (read-and-parse (current-language) port cenv)))
+ (cond
+ ((eof-object? x)
+ (close-port port)
+ (compile ((or (language-joiner joint)
+ (default-language-joiner joint))
+ (reverse exps)
+ env)
+ #\from joint #\to to
+ ;; env can be false if no expressions were read.
+ #\env (or env (default-environment joint))
+ #\opts opts))
+ (else
+ ;; compile-fold instead of compile so we get the env too
+ (receive (jexp jenv jcenv)
+ (compile-fold (compile-passes (current-language) joint opts)
+ x cenv opts)
+ (lp (cons jexp exps) jenv jcenv))))))))))
+
+(define* (compile x #\key
+ (from (current-language))
+ (to 'value)
+ (env (default-environment from))
+ (opts '()))
+
+ (let ((warnings (memq #\warnings opts)))
+ (if (pair? warnings)
+ (let ((warnings (cadr warnings)))
+ ;; Sanity-check the requested warnings.
+ (for-each (lambda (w)
+ (or (lookup-warning-type w)
+ (warning 'unsupported-warning #f w)))
+ warnings))))
+
+ (receive (exp env cenv)
+ (compile-fold (compile-passes from to opts) x env opts)
+ exp))
+
+
+;;;
+;;; Decompiler interface
+;;;
+
+(define (decompile-passes from to opts)
+ (map cdr
+ (or (lookup-decompilation-order from to)
+ (error "no way to decompile" from "to" to))))
+
+(define (decompile-fold passes exp env opts)
+ (if (null? passes)
+ (values exp env)
+ (receive (exp env) ((car passes) exp env opts)
+ (decompile-fold (cdr passes) exp env opts))))
+
+(define* (decompile x #\key
+ (env #f)
+ (from 'value)
+ (to 'assembly)
+ (opts '()))
+ (decompile-fold (decompile-passes from to opts)
+ x
+ env
+ opts))
+;;; -*- mode: scheme; coding: utf-8; -*-
+;;;
+;;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 <http://www.gnu.org/licenses/>.
+
+(define-module (system base lalr)
+
+ ;; XXX: In theory this import is not needed but the evaluator (not the
+ ;; compiler) complains about `lexical-token' being unbound when expanding
+ ;; `(define-record-type lexical-token ...)' if we omit it.
+ #\use-module (srfi srfi-9)
+
+ #\export (lalr-parser print-states
+
+ make-lexical-token lexical-token?
+ lexical-token-category
+ lexical-token-source
+ lexical-token-value
+
+ make-source-location source-location?
+ source-location-input
+ source-location-line
+ source-location-column
+ source-location-offset
+ source-location-length
+ source-location->source-properties
+
+ ;; `lalr-parser' is a defmacro, which produces code that refers to
+ ;; these drivers.
+ lr-driver glr-driver))
+
+;; The LALR parser generator was written by Dominique Boucher. It's available
+;; from http://code.google.com/p/lalr-scm/ and released under the LGPLv3+.
+(include-from-path "system/base/lalr.upstream.scm")
+
+(define (source-location->source-properties loc)
+ `((filename . ,(source-location-input loc))
+ (line . ,(source-location-line loc))
+ (column . ,(source-location-column loc))))
+;;;
+;;;; An Efficient and Portable LALR(1) Parser Generator for Scheme
+;;;
+;; Copyright 2014 Jan Nieuwenhuizen <janneke@gnu.org>
+;; Copyright 1993, 2010 Dominique Boucher
+;;
+;; 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 General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+(define *lalr-scm-version* "2.5.0")
+
+
+(cond-expand
+
+ ;; -- Gambit-C
+ (gambit
+
+ (define-macro (def-macro form . body)
+ `(define-macro ,form (let () ,@body)))
+
+ (def-macro (BITS-PER-WORD) 28)
+ (def-macro (logical-or x . y) `(bitwise-ior ,x ,@y))
+ (def-macro (lalr-error msg obj) `(error ,msg ,obj))
+
+ (define pprint pretty-print)
+ (define lalr-keyword? keyword?)
+ (define (note-source-location lvalue tok) lvalue))
+
+ ;; --
+ (bigloo
+ (define-macro (def-macro form . body)
+ `(define-macro ,form (let () ,@body)))
+
+ (define pprint (lambda (obj) (write obj) (newline)))
+ (define lalr-keyword? keyword?)
+ (def-macro (BITS-PER-WORD) 29)
+ (def-macro (logical-or x . y) `(bit-or ,x ,@y))
+ (def-macro (lalr-error msg obj) `(error "lalr-parser" ,msg ,obj))
+ (define (note-source-location lvalue tok) lvalue))
+
+ ;; -- Chicken
+ (chicken
+
+ (define-macro (def-macro form . body)
+ `(define-macro ,form (let () ,@body)))
+
+ (define pprint pretty-print)
+ (define lalr-keyword? symbol?)
+ (def-macro (BITS-PER-WORD) 30)
+ (def-macro (logical-or x . y) `(bitwise-ior ,x ,@y))
+ (def-macro (lalr-error msg obj) `(error ,msg ,obj))
+ (define (note-source-location lvalue tok) lvalue))
+
+ ;; -- STKlos
+ (stklos
+ (require "pp")
+
+ (define (pprint form) (pp form \:port (current-output-port)))
+
+ (define lalr-keyword? keyword?)
+ (define-macro (BITS-PER-WORD) 30)
+ (define-macro (logical-or x . y) `(bit-or ,x ,@y))
+ (define-macro (lalr-error msg obj) `(error 'lalr-parser ,msg ,obj))
+ (define (note-source-location lvalue tok) lvalue))
+
+ ;; -- Guile
+ (guile
+ (use-modules (ice-9 pretty-print))
+ (use-modules (srfi srfi-9))
+
+ (define pprint pretty-print)
+ (define lalr-keyword? symbol?)
+ (define-macro (BITS-PER-WORD) 30)
+ (define-macro (logical-or x . y) `(logior ,x ,@y))
+ (define-macro (lalr-error msg obj) `(error ,msg ,obj))
+ (define (note-source-location lvalue tok)
+ (if (and (supports-source-properties? lvalue)
+ (not (source-property lvalue 'loc))
+ (lexical-token? tok))
+ (set-source-property! lvalue 'loc (lexical-token-source tok)))
+ lvalue))
+
+
+ ;; -- Kawa
+ (kawa
+ (require 'pretty-print)
+ (define (BITS-PER-WORD) 30)
+ (define logical-or logior)
+ (define (lalr-keyword? obj) (keyword? obj))
+ (define (pprint obj) (pretty-print obj))
+ (define (lalr-error msg obj) (error msg obj))
+ (define (note-source-location lvalue tok) lvalue))
+
+ ;; -- SISC
+ (sisc
+ (import logicops)
+ (import record)
+
+ (define pprint pretty-print)
+ (define lalr-keyword? symbol?)
+ (define-macro BITS-PER-WORD (lambda () 32))
+ (define-macro logical-or (lambda (x . y) `(logor ,x ,@y)))
+ (define-macro (lalr-error msg obj) `(error "~a ~S:" ,msg ,obj))
+ (define (note-source-location lvalue tok) lvalue))
+
+ (else
+ (error "Unsupported Scheme system")))
+
+
+(define-record-type lexical-token
+ (make-lexical-token category source value)
+ lexical-token?
+ (category lexical-token-category)
+ (source lexical-token-source)
+ (value lexical-token-value))
+
+
+(define-record-type source-location
+ (make-source-location input line column offset length)
+ source-location?
+ (input source-location-input)
+ (line source-location-line)
+ (column source-location-column)
+ (offset source-location-offset)
+ (length source-location-length))
+
+
+
+ ;; - Macros pour la gestion des vecteurs de bits
+
+(define-macro (lalr-parser . arguments)
+ (define (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))))
+
+ (define (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
+
+ (define (new-core) (make-vector 4 0))
+ (define (set-core-number! c n) (vector-set! c 0 n))
+ (define (set-core-acc-sym! c s) (vector-set! c 1 s))
+ (define (set-core-nitems! c n) (vector-set! c 2 n))
+ (define (set-core-items! c i) (vector-set! c 3 i))
+ (define (core-number c) (vector-ref c 0))
+ (define (core-acc-sym c) (vector-ref c 1))
+ (define (core-nitems c) (vector-ref c 2))
+ (define (core-items c) (vector-ref c 3))
+
+ (define (new-shift) (make-vector 3 0))
+ (define (set-shift-number! c x) (vector-set! c 0 x))
+ (define (set-shift-nshifts! c x) (vector-set! c 1 x))
+ (define (set-shift-shifts! c x) (vector-set! c 2 x))
+ (define (shift-number s) (vector-ref s 0))
+ (define (shift-nshifts s) (vector-ref s 1))
+ (define (shift-shifts s) (vector-ref s 2))
+
+ (define (new-red) (make-vector 3 0))
+ (define (set-red-number! c x) (vector-set! c 0 x))
+ (define (set-red-nreds! c x) (vector-set! c 1 x))
+ (define (set-red-rules! c x) (vector-set! c 2 x))
+ (define (red-number c) (vector-ref c 0))
+ (define (red-nreds c) (vector-ref c 1))
+ (define (red-rules c) (vector-ref c 2))
+
+
+ (define (new-set nelem)
+ (make-vector nelem 0))
+
+
+ (define (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 driver-name 'lr-driver)
+
+ (define (glr-driver?)
+ (eq? driver-name 'glr-driver))
+ (define (lr-driver?)
+ (eq? driver-name 'lr-driver))
+
+ (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))))))))))))
+
+
+ (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)))))))))
+
+
+
+ (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))))))
+
+
+
+
+ (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))))))
+
+
+ (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))))))
+
+
+ (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)) (newline)
+ 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 : ")
+ (display (list stateno ruleno gotono)) (newline))
+ (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 expected-conflicts 0)
+
+ (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 'none))))
+
+ (define conflict-messages '())
+
+ (define (add-conflict-message . l)
+ (set! conflict-messages (cons l conflict-messages)))
+
+ (define (log-conflicts)
+ (if (> (length conflict-messages) expected-conflicts)
+ (for-each
+ (lambda (message)
+ (for-each display message)
+ (newline))
+ conflict-messages)))
+
+ ;; --- Add an action to the action table
+ (define (add-action state symbol new-action)
+ (let* ((state-actions (vector-ref action-table state))
+ (actions (assv symbol state-actions)))
+ (if (pair? actions)
+ (let ((current-action (cadr actions)))
+ (if (not (= new-action current-action))
+ ;; -- there is a conflict
+ (begin
+ (if (and (<= current-action 0) (<= new-action 0))
+ ;; --- reduce/reduce conflict
+ (begin
+ (add-conflict-message
+ "%% Reduce/Reduce conflict (reduce " (- new-action) ", reduce " (- current-action)
+ ") on '" (get-symbol (+ symbol nvars)) "' in state " state)
+ (if (glr-driver?)
+ (set-cdr! (cdr actions) (cons new-action (cddr actions)))
+ (set-car! (cdr actions) (max current-action new-action))))
+ ;; --- shift/reduce conflict
+ ;; can we resolve the conflict using precedences?
+ (case (resolve-conflict symbol (- current-action))
+ ;; -- shift
+ ((shift) (if (glr-driver?)
+ (set-cdr! (cdr actions) (cons new-action (cddr actions)))
+ (set-car! (cdr actions) new-action)))
+ ;; -- reduce
+ ((reduce) #f) ; well, nothing to do...
+ ;; -- signal a conflict!
+ (else (add-conflict-message
+ "%% Shift/Reduce conflict (shift " new-action ", reduce " (- current-action)
+ ") on '" (get-symbol (+ symbol nvars)) "' in state " state)
+ (if (glr-driver?)
+ (set-cdr! (cdr actions) (cons new-action (cddr actions)))
+ (set-car! (cdr actions) new-action))))))))
+
+ (vector-set! action-table state (cons (list symbol new-action) state-actions)))
+ ))
+
+ (define (add-action-for-all-terminals state action)
+ (do ((i 1 (+ i 1)))
+ ((= i nterms))
+ (add-action state i action)))
+
+ (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))
+ (if (glr-driver?)
+ (add-action-for-all-terminals i (- (car (red-rules red))))
+ (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)
+ (log-conflicts))
+
+ (define (compact-action-table terms)
+ (define (most-common-action acts)
+ (let ((accums '()))
+ (let loop ((l acts))
+ (if (pair? l)
+ (let* ((x (cadar 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 (and (= (length x) 2)
+ (eq? (cadr 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 (cons 'error (reverse rev-terms))))
+ (terms/prec (cons '(eoi none 0) (cons '(error 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_))
+ (if (and (pair? (cdr first))
+ (null? (cddr first))
+ (member (cadr first) terms))
+ (if (null? rest)
+ (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)))))))
+
+ (define (check-error-production rhs)
+ (let loop ((rhs rhs))
+ (if (pair? rhs)
+ (begin
+ (if (and (eq? (car rhs) 'error)
+ (or (null? (cdr rhs))
+ (not (member (cadr rhs) terms))
+ (not (null? (cddr rhs)))))
+ (lalr-error "Invalid 'error' production. A single terminal symbol must follow the 'error' token._" rhs))
+ (loop (cdr rhs))))))
+
+
+ (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))))
+ ;; -- check for undefined tokens
+ (for-each (lambda (x)
+ (if (not (or (member x terms) (member x nonterms)))
+ (lalr-error "Invalid terminal or nonterminal_" x)))
+ rhs)
+ ;; -- check 'error' productions
+ (check-error-production rhs)
+
+ (if (and (pair? rest)
+ (eq? (car rest) '_)
+ (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
+ (display (get-symbol v))
+ (display " ")
+ (loop (+ i 1)))
+ (begin
+ (display " (rule ")
+ (display (- v))
+ (display ")")
+ (newline))))))))))
+
+ (define (get-symbol n)
+ (if (>= n nvars)
+ (vector-ref the-terminals (- n nvars))
+ (vector-ref the-nonterminals n)))
+
+
+ (define (print-states)
+ (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 (cadar l)))
+ (display " ")
+ (cond
+ ((eq? sym 'default)
+ (display "default action"))
+ (else
+ (if (number? sym)
+ (display (get-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 ,(if (eq? driver-name 'lr-driver)
+ '(___stack ___sp ___goto-table ___push yypushback)
+ '(___sp ___goto-table ___push))
+ ,(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))
+ (ns (number->string (+ (- n i) 1))))
+ (cons
+ `(tok ,(if (eq? driver-name 'lr-driver)
+ `(vector-ref ___stack (- ___sp ,(- (* i 2) 1)))
+ `(list-ref ___sp ,(+ (* (- i 1) 2) 1))))
+ (cons
+ `(,(string->symbol (string-append "$" ns))
+ (if (lexical-token? tok) (lexical-token-value tok) tok))
+ (cons
+ `(,(string->symbol (string-append "@" ns))
+ (if (lexical-token? tok) (lexical-token-source tok) tok))
+ (loop (+ i 1) rest)))))
+ '()))
+ '()))
+ ,(if (= nt 0)
+ '$1
+ `(___push ,n ,nt ,(cdr p) ,@(if (eq? driver-name 'lr-driver) '() '(___sp))
+ ,(if (eq? driver-name 'lr-driver)
+ `(vector-ref ___stack (- ___sp ,(length rhs)))
+ `(list-ref ___sp ,(length rhs))))))))))
+
+ gram/actions))))
+
+
+
+ ;; Options
+
+ (define *valid-options*
+ (list
+ (cons 'out-table_
+ (lambda (option)
+ (and (list? option)
+ (= (length option) 2)
+ (string? (cadr option)))))
+ (cons 'output_
+ (lambda (option)
+ (and (list? option)
+ (= (length option) 3)
+ (symbol? (cadr option))
+ (string? (caddr option)))))
+ (cons 'expect_
+ (lambda (option)
+ (and (list? option)
+ (= (length option) 2)
+ (integer? (cadr option))
+ (>= (cadr option) 0))))
+
+ (cons 'driver_
+ (lambda (option)
+ (and (list? option)
+ (= (length option) 2)
+ (symbol? (cadr option))
+ (memq (cadr option) '(lr glr)))))))
+
+
+ (define (validate-options options)
+ (for-each
+ (lambda (option)
+ (let ((p (assoc (car option) *valid-options*)))
+ (if (or (not p)
+ (not ((cdr p) option)))
+ (lalr-error "Invalid option_" option))))
+ options))
+
+
+ (define (output-parser! options code)
+ (let ((option (assq 'output_ options)))
+ (if option
+ (let ((parser-name (cadr option))
+ (file-name (caddr option)))
+ (with-output-to-file file-name
+ (lambda ()
+ (pprint `(define ,parser-name ,code))
+ (newline)))))))
+
+
+ (define (output-table! options)
+ (let ((option (assq 'out-table_ options)))
+ (if option
+ (let ((file-name (cadr option)))
+ (with-output-to-file file-name print-states)))))
+
+
+ (define (set-expected-conflicts! options)
+ (let ((option (assq 'expect_ options)))
+ (set! expected-conflicts (if option (cadr option) 0))))
+
+ (define (set-driver-name! options)
+ (let ((option (assq 'driver_ options)))
+ (if option
+ (let ((driver-type (cadr option)))
+ (set! driver-name (if (eq? driver-type 'glr) 'glr-driver 'lr-driver))))))
+
+
+ ;; -- arguments
+
+ (define (extract-arguments lst proc)
+ (let loop ((options '())
+ (tokens '())
+ (rules '())
+ (lst lst))
+ (if (pair? lst)
+ (let ((p (car lst)))
+ (cond
+ ((and (pair? p)
+ (lalr-keyword? (car p))
+ (assq (car p) *valid-options*))
+ (loop (cons p options) tokens rules (cdr lst)))
+ (else
+ (proc options p (cdr lst)))))
+ (lalr-error "Malformed lalr-parser form" lst))))
+
+
+ (define (build-driver options tokens rules)
+ (validate-options options)
+ (set-expected-conflicts! options)
+ (set-driver-name! options)
+ (let* ((gram/actions (gen-tables! tokens rules))
+ (code `(,driver-name ',action-table ,(build-goto-table) ,(build-reduction-table gram/actions))))
+
+ (output-table! options)
+ (output-parser! options code)
+ code))
+
+ (extract-arguments arguments build-driver))
+
+
+
+;;;
+;;;; --
+;;;; Implementation of the lr-driver
+;;;
+
+
+(cond-expand
+ (gambit
+ (declare
+ (standard-bindings)
+ (fixnum)
+ (block)
+ (not safe)))
+ (chicken
+ (declare
+ (uses extras)
+ (usual-integrations)
+ (fixnum)
+ (not safe)))
+ (else))
+
+
+;;;
+;;;; Source location utilities
+;;;
+
+
+;; This function assumes that src-location-1 and src-location-2 are source-locations
+;; Returns #f if they are not locations for the same input
+(define (combine-locations src-location-1 src-location-2)
+ (let ((offset-1 (source-location-offset src-location-1))
+ (offset-2 (source-location-offset src-location-2))
+ (length-1 (source-location-length src-location-1))
+ (length-2 (source-location-length src-location-2)))
+
+ (cond ((not (equal? (source-location-input src-location-1)
+ (source-location-input src-location-2)))
+ #f)
+ ((or (not (number? offset-1)) (not (number? offset-2))
+ (not (number? length-1)) (not (number? length-2))
+ (< offset-1 0) (< offset-2 0)
+ (< length-1 0) (< length-2 0))
+ (make-source-location (source-location-input src-location-1)
+ (source-location-line src-location-1)
+ (source-location-column src-location-1)
+ -1 -1))
+ ((<= offset-1 offset-2)
+ (make-source-location (source-location-input src-location-1)
+ (source-location-line src-location-1)
+ (source-location-column src-location-1)
+ offset-1
+ (- (+ offset-2 length-2) offset-1)))
+ (else
+ (make-source-location (source-location-input src-location-1)
+ (source-location-line src-location-1)
+ (source-location-column src-location-1)
+ offset-2
+ (- (+ offset-1 length-1) offset-2))))))
+
+
+;;;
+;;;; LR-driver
+;;;
+
+
+(define *max-stack-size* 500)
+
+(define (lr-driver action-table goto-table reduction-table)
+ (define ___atable action-table)
+ (define ___gtable goto-table)
+ (define ___rtable reduction-table)
+
+ (define ___lexerp #f)
+ (define ___errorp #f)
+
+ (define ___stack #f)
+ (define ___sp 0)
+
+ (define ___curr-input #f)
+ (define ___reuse-input #f)
+
+ (define ___input #f)
+ (define (___consume)
+ (set! ___input (if ___reuse-input ___curr-input (___lexerp)))
+ (set! ___reuse-input #f)
+ (set! ___curr-input ___input))
+
+ (define (___pushback)
+ (set! ___reuse-input #t))
+
+ (define (___initstack)
+ (set! ___stack (make-vector *max-stack-size* 0))
+ (set! ___sp 0))
+
+ (define (___growstack)
+ (let ((new-stack (make-vector (* 2 (vector-length ___stack)) 0)))
+ (let loop ((i (- (vector-length ___stack) 1)))
+ (if (>= i 0)
+ (begin
+ (vector-set! new-stack i (vector-ref ___stack i))
+ (loop (- i 1)))))
+ (set! ___stack new-stack)))
+
+ (define (___checkstack)
+ (if (>= ___sp (vector-length ___stack))
+ (___growstack)))
+
+ (define (___push delta new-category lvalue tok)
+ (set! ___sp (- ___sp (* delta 2)))
+ (let* ((state (vector-ref ___stack ___sp))
+ (new-state (cdr (assoc new-category (vector-ref ___gtable state)))))
+ (set! ___sp (+ ___sp 2))
+ (___checkstack)
+ (vector-set! ___stack ___sp new-state)
+ (vector-set! ___stack (- ___sp 1) (note-source-location lvalue tok))))
+
+ (define (___reduce st)
+ ((vector-ref ___rtable st) ___stack ___sp ___gtable ___push ___pushback))
+
+ (define (___shift token attribute)
+ (set! ___sp (+ ___sp 2))
+ (___checkstack)
+ (vector-set! ___stack (- ___sp 1) attribute)
+ (vector-set! ___stack ___sp token))
+
+ (define (___action x l)
+ (let ((y (assoc x l)))
+ (if y (cadr y) (cadar l))))
+
+ (define (___recover tok)
+ (let find-state ((sp ___sp))
+ (if (< sp 0)
+ (set! ___sp sp)
+ (let* ((state (vector-ref ___stack sp))
+ (act (assoc 'error (vector-ref ___atable state))))
+ (if act
+ (begin
+ (set! ___sp sp)
+ (___sync (cadr act) tok))
+ (find-state (- sp 2)))))))
+
+ (define (___sync state tok)
+ (let ((sync-set (map car (cdr (vector-ref ___atable state)))))
+ (set! ___sp (+ ___sp 4))
+ (___checkstack)
+ (vector-set! ___stack (- ___sp 3) #f)
+ (vector-set! ___stack (- ___sp 2) state)
+ (let skip ()
+ (let ((i (___category ___input)))
+ (if (eq? i '*eoi*)
+ (set! ___sp -1)
+ (if (memq i sync-set)
+ (let ((act (assoc i (vector-ref ___atable state))))
+ (vector-set! ___stack (- ___sp 1) #f)
+ (vector-set! ___stack ___sp (cadr act)))
+ (begin
+ (___consume)
+ (skip))))))))
+
+ (define (___category tok)
+ (if (lexical-token? tok)
+ (lexical-token-category tok)
+ tok))
+
+ (define (___run)
+ (let loop ()
+ (if ___input
+ (let* ((state (vector-ref ___stack ___sp))
+ (i (___category ___input))
+ (act (___action i (vector-ref ___atable state))))
+
+ (cond ((not (symbol? i))
+ (___errorp "Syntax error_ invalid token_ " ___input)
+ #f)
+
+ ;; Input succesfully parsed
+ ((eq? act 'accept)
+ (vector-ref ___stack 1))
+
+ ;; Syntax error in input
+ ((eq? act '*error*)
+ (if (eq? i '*eoi*)
+ (begin
+ (___errorp "Syntax error_ unexpected end of input")
+ #f)
+ (begin
+ (___errorp "Syntax error_ unexpected token _ " ___input)
+ (___recover i)
+ (if (>= ___sp 0)
+ (set! ___input #f)
+ (begin
+ (set! ___sp 0)
+ (set! ___input '*eoi*)))
+ (loop))))
+
+ ;; Shift current token on top of the stack
+ ((>= act 0)
+ (___shift act ___input)
+ (set! ___input (if (eq? i '*eoi*) '*eoi* #f))
+ (loop))
+
+ ;; Reduce by rule (- act)
+ (else
+ (___reduce (- act))
+ (loop))))
+
+ ;; no lookahead, so check if there is a default action
+ ;; that does not require the lookahead
+ (let* ((state (vector-ref ___stack ___sp))
+ (acts (vector-ref ___atable state))
+ (defact (if (pair? acts) (cadar acts) #f)))
+ (if (and (= 1 (length acts)) (< defact 0))
+ (___reduce (- defact))
+ (___consume))
+ (loop)))))
+
+
+ (lambda (lexerp errorp)
+ (set! ___errorp errorp)
+ (set! ___lexerp lexerp)
+ (___initstack)
+ (___run)))
+
+
+;;;
+;;;; Simple-minded GLR-driver
+;;;
+
+
+(define (glr-driver action-table goto-table reduction-table)
+ (define ___atable action-table)
+ (define ___gtable goto-table)
+ (define ___rtable reduction-table)
+
+ (define ___lexerp #f)
+ (define ___errorp #f)
+
+ ;; -- Input handling
+
+ (define *input* #f)
+ (define (initialize-lexer lexer)
+ (set! ___lexerp lexer)
+ (set! *input* #f))
+ (define (consume)
+ (set! *input* (___lexerp)))
+
+ (define (token-category tok)
+ (if (lexical-token? tok)
+ (lexical-token-category tok)
+ tok))
+
+ (define (token-attribute tok)
+ (if (lexical-token? tok)
+ (lexical-token-value tok)
+ tok))
+
+ ;; -- Processes (stacks) handling
+
+ (define *processes* '())
+
+ (define (initialize-processes)
+ (set! *processes* '()))
+ (define (add-process process)
+ (set! *processes* (cons process *processes*)))
+ (define (get-processes)
+ (reverse *processes*))
+
+ (define (for-all-processes proc)
+ (let ((processes (get-processes)))
+ (initialize-processes)
+ (for-each proc processes)))
+
+ ;; -- parses
+ (define *parses* '())
+ (define (get-parses)
+ *parses*)
+ (define (initialize-parses)
+ (set! *parses* '()))
+ (define (add-parse parse)
+ (set! *parses* (cons parse *parses*)))
+
+
+ (define (push delta new-category lvalue stack tok)
+ (let* ((stack (drop stack (* delta 2)))
+ (state (car stack))
+ (new-state (cdr (assv new-category (vector-ref ___gtable state)))))
+ (cons new-state (cons (note-source-location lvalue tok) stack))))
+
+ (define (reduce state stack)
+ ((vector-ref ___rtable state) stack ___gtable push))
+
+ (define (shift state symbol stack)
+ (cons state (cons symbol stack)))
+
+ (define (get-actions token action-list)
+ (let ((pair (assoc token action-list)))
+ (if pair
+ (cdr pair)
+ (cdar action-list)))) ;; get the default action
+
+
+ (define (run)
+ (let loop-tokens ()
+ (consume)
+ (let ((symbol (token-category *input*)))
+ (for-all-processes
+ (lambda (process)
+ (let loop ((stacks (list process)) (active-stacks '()))
+ (cond ((pair? stacks)
+ (let* ((stack (car stacks))
+ (state (car stack)))
+ (let actions-loop ((actions (get-actions symbol (vector-ref ___atable state)))
+ (active-stacks active-stacks))
+ (if (pair? actions)
+ (let ((action (car actions))
+ (other-actions (cdr actions)))
+ (cond ((eq? action '*error*)
+ (actions-loop other-actions active-stacks))
+ ((eq? action 'accept)
+ (add-parse (car (take-right stack 2)))
+ (actions-loop other-actions active-stacks))
+ ((>= action 0)
+ (let ((new-stack (shift action *input* stack)))
+ (add-process new-stack))
+ (actions-loop other-actions active-stacks))
+ (else
+ (let ((new-stack (reduce (- action) stack)))
+ (actions-loop other-actions (cons new-stack active-stacks))))))
+ (loop (cdr stacks) active-stacks)))))
+ ((pair? active-stacks)
+ (loop (reverse active-stacks) '())))))))
+ (if (pair? (get-processes))
+ (loop-tokens))))
+
+
+ (lambda (lexerp errorp)
+ (set! ___errorp errorp)
+ (initialize-lexer lexerp)
+ (initialize-processes)
+ (initialize-parses)
+ (add-process '(0))
+ (run)
+ (get-parses)))
+
+
+(define (drop l n)
+ (cond ((and (> n 0) (pair? l))
+ (drop (cdr l) (- n 1)))
+ (else
+ l)))
+
+(define (take-right l n)
+ (drop l (- (length l) n)));;; Multi-language support
+
+;; Copyright (C) 2001, 2009, 2010, 2011, 2013 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Code_
+
+(define-module (system base language)
+ #\use-module (system base syntax)
+ #\export (define-language language? lookup-language make-language
+ language-name language-title language-reader
+ language-printer language-parser
+ language-compilers language-decompilers language-evaluator
+ language-joiner language-for-humans?
+ language-make-default-environment
+
+ lookup-compilation-order lookup-decompilation-order
+ invalidate-compilation-cache! default-environment
+
+ *current-language*)
+
+ #\re-export (current-language))
+
+
+;;;
+;;; Language class
+;;;
+
+(define-record/keywords <language>
+ name
+ title
+ reader
+ printer
+ (parser #f)
+ (compilers '())
+ (decompilers '())
+ (evaluator #f)
+ (joiner #f)
+ (for-humans? #t)
+ (make-default-environment make-fresh-user-module))
+
+(define-macro (define-language name . spec)
+ `(begin
+ (invalidate-compilation-cache!)
+ (define ,name (make-language #\name ',name ,@spec))))
+
+(define (lookup-language name)
+ (let ((m (resolve-module `(language ,name spec))))
+ (if (module-bound? m name)
+ (module-ref m name)
+ (error "no such language" name))))
+
+(define *compilation-cache* '())
+(define *decompilation-cache* '())
+
+(define (invalidate-compilation-cache!)
+ (set! *decompilation-cache* '())
+ (set! *compilation-cache* '()))
+
+(define (compute-translation-order from to language-translators)
+ (cond
+ ((not (language? to))
+ (compute-translation-order from (lookup-language to) language-translators))
+ (else
+ (let lp ((from from) (seen '()))
+ (cond
+ ((not (language? from))
+ (lp (lookup-language from) seen))
+ ((eq? from to) (reverse! seen))
+ ((memq from seen) #f)
+ (else (or-map (lambda (pair)
+ (lp (car pair) (acons from (cdr pair) seen)))
+ (language-translators from))))))))
+
+(define (lookup-compilation-order from to)
+ (let ((key (cons from to)))
+ (or (assoc-ref *compilation-cache* key)
+ (let ((order (compute-translation-order from to language-compilers)))
+ (set! *compilation-cache*
+ (acons key order *compilation-cache*))
+ order))))
+
+(define (lookup-decompilation-order from to)
+ (let ((key (cons from to)))
+ (or (assoc-ref *decompilation-cache* key)
+ ;; trickery!
+ (let ((order (and=>
+ (compute-translation-order to from language-decompilers)
+ reverse!)))
+ (set! *decompilation-cache* (acons key order *decompilation-cache*))
+ order))))
+
+(define (default-environment lang)
+ "Return the default compilation environment for source language LANG."
+ ((language-make-default-environment
+ (if (language? lang) lang (lookup-language lang)))))
+
+
+
+;;;
+;;; Current language
+;;;
+
+;; Deprecated; use current-language instead.
+(define *current-language* (parameter-fluid current-language))
+;;; User interface messages
+
+;; Copyright (C) 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary_
+;;;
+;;; This module provide a simple interface to send messages to the user.
+;;; TODO_ Internationalize messages.
+;;;
+;;; Code_
+
+(define-module (system base message)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-9)
+ #\use-module (ice-9 match)
+ #\export (*current-warning-port*
+ *current-warning-prefix*
+ warning
+
+ warning-type? warning-type-name warning-type-description
+ warning-type-printer lookup-warning-type
+
+ %warning-types))
+
+
+;;;
+;;; Source location
+;;;
+
+(define (location-string loc)
+ (if (pair? loc)
+ (format #f "~a_~a_~a"
+ (or (assoc-ref loc 'filename) "<stdin>")
+ (1+ (assoc-ref loc 'line))
+ (assoc-ref loc 'column))
+ "<unknown-location>"))
+
+
+;;;
+;;; Warnings
+;;;
+
+;; This name existed before %current-warning-port was introduced, but
+;; otherwise it is a deprecated binding.
+(define *current-warning-port*
+ ;; Can't play the identifier-syntax deprecation game in Guile 2.0, as
+ ;; other modules might depend on this being a normal binding and not a
+ ;; syntax binding.
+ (parameter-fluid current-warning-port))
+
+(define *current-warning-prefix*
+ ;; Prefix string when emitting a warning.
+ (make-fluid ";;; "))
+
+
+(define-record-type <warning-type>
+ (make-warning-type name description printer)
+ warning-type?
+ (name warning-type-name)
+ (description warning-type-description)
+ (printer warning-type-printer))
+
+(define %warning-types
+ ;; List of known warning types.
+ (map (lambda (args)
+ (apply make-warning-type args))
+
+ (let-syntax ((emit
+ (lambda (s)
+ (syntax-case s ()
+ ((_ port fmt args ...)
+ (string? (syntax->datum #'fmt))
+ (with-syntax ((fmt
+ (string-append "~a"
+ (syntax->datum
+ #'fmt))))
+ #'(format port fmt
+ (fluid-ref *current-warning-prefix*)
+ args ...)))))))
+ `((unsupported-warning ;; a "meta warning"
+ "warn about unknown warning types"
+ ,(lambda (port unused name)
+ (emit port "warning_ unknown warning type `~A'~%"
+ name)))
+
+ (unused-variable
+ "report unused variables"
+ ,(lambda (port loc name)
+ (emit port "~A_ warning_ unused variable `~A'~%"
+ loc name)))
+
+ (unused-toplevel
+ "report unused local top-level variables"
+ ,(lambda (port loc name)
+ (emit port "~A_ warning_ possibly unused local top-level variable `~A'~%"
+ loc name)))
+
+ (unbound-variable
+ "report possibly unbound variables"
+ ,(lambda (port loc name)
+ (emit port "~A_ warning_ possibly unbound variable `~A'~%"
+ loc name)))
+
+ (arity-mismatch
+ "report procedure arity mismatches (wrong number of arguments)"
+ ,(lambda (port loc name certain?)
+ (if certain?
+ (emit port
+ "~A_ warning_ wrong number of arguments to `~A'~%"
+ loc name)
+ (emit port
+ "~A_ warning_ possibly wrong number of arguments to `~A'~%"
+ loc name))))
+
+ (duplicate-case-datum
+ "report a duplicate datum in a case expression"
+ ,(lambda (port loc datum clause case-expr)
+ (emit port
+ "~A_ warning_ duplicate datum ~S in clause ~S of case expression ~S~%"
+ loc datum clause case-expr)))
+
+ (bad-case-datum
+ "report a case datum that cannot be meaningfully compared using `eqv?'"
+ ,(lambda (port loc datum clause case-expr)
+ (emit port
+ "~A_ warning_ datum ~S cannot be meaningfully compared using `eqv?' in clause ~S of case expression ~S~%"
+ loc datum clause case-expr)))
+
+ (format
+ "report wrong number of arguments to `format'"
+ ,(lambda (port loc . rest)
+ (define (escape-newlines str)
+ (list->string
+ (string-fold-right (lambda (c r)
+ (if (eq? c #\newline)
+ (append '(#\\ #\n) r)
+ (cons c r)))
+ '()
+ str)))
+
+ (define (range min max)
+ (cond ((eq? min 'any)
+ (if (eq? max 'any)
+ "any number" ;; can't happen
+ (emit #f "up to ~a" max)))
+ ((eq? max 'any)
+ (emit #f "at least ~a" min))
+ ((= min max) (number->string min))
+ (else
+ (emit #f "~a to ~a" min max))))
+
+ (match rest
+ (('simple-format fmt opt)
+ (emit port
+ "~A_ warning_ ~S_ unsupported format option ~~~A, use (ice-9 format) instead~%"
+ loc (escape-newlines fmt) opt))
+ (('wrong-format-arg-count fmt min max actual)
+ (emit port
+ "~A_ warning_ ~S_ wrong number of `format' arguments_ expected ~A, got ~A~%"
+ loc (escape-newlines fmt)
+ (range min max) actual))
+ (('syntax-error 'unterminated-iteration fmt)
+ (emit port "~A_ warning_ ~S_ unterminated iteration~%"
+ loc (escape-newlines fmt)))
+ (('syntax-error 'unterminated-conditional fmt)
+ (emit port "~A_ warning_ ~S_ unterminated conditional~%"
+ loc (escape-newlines fmt)))
+ (('syntax-error 'unexpected-semicolon fmt)
+ (emit port "~A_ warning_ ~S_ unexpected `~~;'~%"
+ loc (escape-newlines fmt)))
+ (('syntax-error 'unexpected-conditional-termination fmt)
+ (emit port "~A_ warning_ ~S_ unexpected `~~]'~%"
+ loc (escape-newlines fmt)))
+ (('wrong-port wrong-port)
+ (emit port
+ "~A_ warning_ ~S_ wrong port argument~%"
+ loc wrong-port))
+ (('wrong-format-string fmt)
+ (emit port
+ "~A_ warning_ ~S_ wrong format string~%"
+ loc fmt))
+ (('non-literal-format-string)
+ (emit port
+ "~A_ warning_ non-literal format string~%"
+ loc))
+ (('wrong-num-args count)
+ (emit port
+ "~A_ warning_ wrong number of arguments to `format'~%"
+ loc))
+ (else
+ (emit port "~A_ `format' warning~%" loc)))))))))
+
+(define (lookup-warning-type name)
+ "Return the warning type NAME or `#f' if not found."
+ (find (lambda (wt)
+ (eq? name (warning-type-name wt)))
+ %warning-types))
+
+(define (warning type location . args)
+ "Emit a warning of type TYPE for source location LOCATION (a source
+property alist) using the data in ARGS."
+ (let ((wt (lookup-warning-type type))
+ (port (current-warning-port)))
+ (if (warning-type? wt)
+ (apply (warning-type-printer wt)
+ port (location-string location)
+ args)
+ (format port "~A_ unknown warning type `~A'_ ~A~%"
+ (location-string location) type args))))
+
+;;; message.scm ends here
+;;; pmatch, a simple matcher
+
+;;; Copyright (C) 2009, 2010, 2012 Free Software Foundation, Inc
+;;; Copyright (C) 2005,2006,2007 Oleg Kiselyov
+;;; Copyright (C) 2007 Daniel P. Friedman
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Originally written by Oleg Kiselyov for LeanTAP in Kanren, which is
+;;; available under the MIT license.
+;;;
+;;; http_//kanren.cvs.sourceforge.net/viewvc/kanren/kanren/mini/leanTAP.scm?view=log
+;;;
+;;; This version taken from_
+;;; αKanren_ A Fresh Name in Nominal Logic Programming
+;;; by William E. Byrd and Daniel P. Friedman
+;;; Proceedings of the 2007 Workshop on Scheme and Functional Programming
+;;; Université Laval Technical Report DIUL-RT-0701
+
+;;; To be clear_ the original code is MIT-licensed, and the modifications
+;;; made to it by Guile are under Guile's license (currently LGPL v3+).
+
+;;; Code_
+
+(define-module (system base pmatch)
+ #\export-syntax (pmatch))
+
+(define-syntax-rule (pmatch e cs ...)
+ (let ((v e)) (pmatch1 v cs ...)))
+
+(define-syntax pmatch1
+ (syntax-rules (else guard)
+ ((_ v) (if #f #f))
+ ((_ v (else e0 e ...)) (let () e0 e ...))
+ ((_ v (pat (guard g ...) e0 e ...) cs ...)
+ (let ((fk (lambda () (pmatch1 v cs ...))))
+ (ppat v pat
+ (if (and g ...) (let () e0 e ...) (fk))
+ (fk))))
+ ((_ v (pat e0 e ...) cs ...)
+ (let ((fk (lambda () (pmatch1 v cs ...))))
+ (ppat v pat (let () e0 e ...) (fk))))))
+
+(define-syntax ppat
+ (syntax-rules (_ quote unquote)
+ ((_ v _ kt kf) kt)
+ ((_ v () kt kf) (if (null? v) kt kf))
+ ((_ v (quote lit) kt kf)
+ (if (equal? v (quote lit)) kt kf))
+ ((_ v (unquote var) kt kf) (let ((var v)) kt))
+ ((_ v (x . y) kt kf)
+ (if (pair? v)
+ (let ((vx (car v)) (vy (cdr v)))
+ (ppat vx x (ppat vy y kt kf) kf))
+ kf))
+ ((_ v lit kt kf) (if (eq? v (quote lit)) kt kf))))
+;;; Guile VM specific syntaxes and utilities
+
+;; Copyright (C) 2001, 2009, 2016 Free Software Foundation, Inc
+
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code_
+
+(define-module (system base syntax)
+ #\export (%compute-initargs)
+ #\export-syntax (define-type define-record define-record/keywords
+ record-case transform-record))
+
+(define (symbol-trim-both sym pred)
+ (string->symbol (string-trim-both (symbol->string sym) pred)))
+(define (trim-brackets sym)
+ (symbol-trim-both sym (list->char-set '(#\< #\>))))
+
+
+;;;
+;;; Type
+;;;
+
+(define-macro (define-type name . rest)
+ (let ((name (if (pair? name) (car name) name))
+ (opts (if (pair? name) (cdr name) '())))
+ (let ((printer (kw-arg-ref opts #\printer))
+ (common-slots (or (kw-arg-ref opts #\common-slots) '())))
+ `(begin ,@(map (lambda (def)
+ `(define-record ,(if printer
+ `(,(car def) ,printer)
+ (car def))
+ ,@common-slots
+ ,@(cdr def)))
+ rest)
+ ,@(map (lambda (common-slot i)
+ `(define ,(symbol-append (trim-brackets name)
+ '- common-slot)
+ (make-procedure-with-setter
+ (lambda (x) (struct-ref x ,i))
+ (lambda (x v) (struct-set! x ,i v)))))
+ common-slots (iota (length common-slots)))))))
+
+
+;;;
+;;; Record
+;;;
+
+(define-macro (define-record name-form . slots)
+ (let* ((name (if (pair? name-form) (car name-form) name-form))
+ (printer (and (pair? name-form) (cadr name-form)))
+ (slot-names (map (lambda (slot) (if (pair? slot) (car slot) slot))
+ slots))
+ (stem (trim-brackets name)))
+ `(begin
+ (define ,name (make-record-type ,(symbol->string name) ',slot-names
+ ,@(if printer (list printer) '())))
+ ,(let* ((reqs (let lp ((slots slots))
+ (if (or (null? slots) (not (symbol? (car slots))))
+ '()
+ (cons (car slots) (lp (cdr slots))))))
+ (opts (list-tail slots (length reqs)))
+ (tail (module-gensym "defrec")))
+ `(define (,(symbol-append 'make- stem) ,@reqs . ,tail)
+ (let ,(map (lambda (o)
+ `(,(car o) (cond ((null? ,tail) ,(cadr o))
+ (else (let ((_x (car ,tail)))
+ (set! ,tail (cdr ,tail))
+ _x)))))
+ opts)
+ (make-struct ,name 0 ,@slot-names))))
+ (define ,(symbol-append stem '?) (record-predicate ,name))
+ ,@(map (lambda (sname)
+ `(define ,(symbol-append stem '- sname)
+ (make-procedure-with-setter
+ (record-accessor ,name ',sname)
+ (record-modifier ,name ',sname))))
+ slot-names))))
+
+;; like the former, but accepting keyword arguments in addition to
+;; optional arguments
+(define-macro (define-record/keywords name-form . slots)
+ (let* ((name (if (pair? name-form) (car name-form) name-form))
+ (printer (and (pair? name-form) (cadr name-form)))
+ (slot-names (map (lambda (slot) (if (pair? slot) (car slot) slot))
+ slots))
+ (stem (trim-brackets name)))
+ `(begin
+ (define ,name (make-record-type ,(symbol->string name) ',slot-names
+ ,@(if printer (list printer) '())))
+ (define ,(symbol-append 'make- stem)
+ (let ((slots (list ,@(map (lambda (slot)
+ (if (pair? slot)
+ `(cons ',(car slot) ,(cadr slot))
+ `',slot))
+ slots)))
+ (constructor (record-constructor ,name)))
+ (lambda args
+ (apply constructor (%compute-initargs args slots)))))
+ (define ,(symbol-append stem '?) (record-predicate ,name))
+ ,@(map (lambda (sname)
+ `(define ,(symbol-append stem '- sname)
+ (make-procedure-with-setter
+ (record-accessor ,name ',sname)
+ (record-modifier ,name ',sname))))
+ slot-names))))
+
+(define (%compute-initargs args slots)
+ (define (finish out)
+ (map (lambda (slot)
+ (let ((name (if (pair? slot) (car slot) slot)))
+ (cond ((assq name out) => cdr)
+ ((pair? slot) (cdr slot))
+ (else (error "unbound slot" args slots name)))))
+ slots))
+ (let lp ((in args) (positional slots) (out '()))
+ (cond
+ ((null? in)
+ (finish out))
+ ((keyword? (car in))
+ (let ((sym (keyword->symbol (car in))))
+ (cond
+ ((and (not (memq sym slots))
+ (not (assq sym (filter pair? slots))))
+ (error "unknown slot" sym))
+ ((assq sym out) (error "slot already set" sym out))
+ (else (lp (cddr in) '() (acons sym (cadr in) out))))))
+ ((null? positional)
+ (error "too many initargs" args slots))
+ (else
+ (lp (cdr in) (cdr positional)
+ (let ((slot (car positional)))
+ (acons (if (pair? slot) (car slot) slot)
+ (car in)
+ out)))))))
+
+;; So, dear reader. It is pleasant indeed around this fire or at this
+;; cafe or in this room, is it not? I think so too.
+;;
+;; This macro used to generate code that looked like this_
+;;
+;; `(((record-predicate ,record-type) ,r)
+;; (let ,(map (lambda (slot)
+;; (if (pair? slot)
+;; `(,(car slot) ((record-accessor ,record-type ',(cadr slot)) ,r))
+;; `(,slot ((record-accessor ,record-type ',slot) ,r))))
+;; slots)
+;; ,@body)))))
+;;
+;; But this was a hot spot, so computing all those predicates and
+;; accessors all the time was getting expensive, so we did a terrible
+;; thing_ we decided that since above we're already defining accessors
+;; and predicates with computed names, we might as well just rely on that fact here.
+;;
+;; It's a bit nasty, I agree. But it is fast.
+;;
+;;scheme@(guile-user)> (with-statprof #\hz 1000 #\full-stacks? #t (resolve-module '(oop goops)))% cumulative self
+;; time seconds seconds name
+;; 8.82 0.03 0.01 glil->assembly
+;; 8.82 0.01 0.01 record-type-fields
+;; 5.88 0.01 0.01 %compute-initargs
+;; 5.88 0.01 0.01 list-index
+
+
+;;; So ugly... but I am too ignorant to know how to make it better.
+(define-syntax record-case
+ (lambda (x)
+ (syntax-case x ()
+ ((_ record clause ...)
+ (let ((r (syntax r))
+ (rtd (syntax rtd)))
+ (define (process-clause tag fields exprs)
+ (let ((infix (trim-brackets (syntax->datum tag))))
+ (with-syntax ((tag tag)
+ (((f . accessor) ...)
+ (let lp ((fields fields))
+ (syntax-case fields ()
+ (() (syntax ()))
+ (((v0 f0) f1 ...)
+ (acons (syntax v0)
+ (datum->syntax x
+ (symbol-append infix '- (syntax->datum
+ (syntax f0))))
+ (lp (syntax (f1 ...)))))
+ ((f0 f1 ...)
+ (acons (syntax f0)
+ (datum->syntax x
+ (symbol-append infix '- (syntax->datum
+ (syntax f0))))
+ (lp (syntax (f1 ...))))))))
+ ((e0 e1 ...)
+ (syntax-case exprs ()
+ (() (syntax (#t)))
+ ((e0 e1 ...) (syntax (e0 e1 ...))))))
+ (syntax
+ ((eq? rtd tag)
+ (let ((f (accessor r))
+ ...)
+ e0 e1 ...))))))
+ (with-syntax
+ ((r r)
+ (rtd rtd)
+ ((processed ...)
+ (let lp ((clauses (syntax (clause ...)))
+ (out '()))
+ (syntax-case clauses (else)
+ (()
+ (reverse! (cons (syntax
+ (else (error "unhandled record" r)))
+ out)))
+ (((else e0 e1 ...))
+ (reverse! (cons (syntax (else e0 e1 ...)) out)))
+ (((else e0 e1 ...) . rest)
+ (syntax-violation 'record-case
+ "bad else clause placement"
+ (syntax x)
+ (syntax (else e0 e1 ...))))
+ ((((<foo> f0 ...) e0 ...) . rest)
+ (lp (syntax rest)
+ (cons (process-clause (syntax <foo>)
+ (syntax (f0 ...))
+ (syntax (e0 ...)))
+ out)))))))
+ (syntax
+ (let* ((r record)
+ (rtd (struct-vtable r)))
+ (cond processed ...)))))))))
+
+
+;; Here we take the terrorism to another level. Nasty, but the client
+;; code looks good.
+
+(define-macro (transform-record type-and-common record . clauses)
+ (let ((r (module-gensym "rec"))
+ (rtd (module-gensym "rtd"))
+ (type-stem (trim-brackets (car type-and-common))))
+ (define (make-stem s)
+ (symbol-append type-stem '- s))
+ (define (further-predicates x record-stem slots)
+ (define (access slot)
+ `(,(symbol-append (make-stem record-stem) '- slot) ,x))
+ (let lp ((in slots) (out '()))
+ (cond ((null? in) out)
+ ((pair? (car in))
+ (let ((slot (caar in))
+ (arg (cadar in)))
+ (cond ((symbol? arg)
+ (lp (cdr in) out))
+ ((pair? arg)
+ (lp (cdr in)
+ (append (further-predicates (access slot)
+ (car arg)
+ (cdr arg))
+ out)))
+ (else (lp (cdr in) (cons `(eq? ,(access slot) ',arg)
+ out))))))
+ (else (lp (cdr in) out)))))
+ (define (let-clauses x record-stem slots)
+ (define (access slot)
+ `(,(symbol-append (make-stem record-stem) '- slot) ,x))
+ (let lp ((in slots) (out '()))
+ (cond ((null? in) out)
+ ((pair? (car in))
+ (let ((slot (caar in))
+ (arg (cadar in)))
+ (cond ((symbol? arg)
+ (lp (cdr in)
+ (cons `(,arg ,(access slot)) out)))
+ ((pair? arg)
+ (lp (cdr in)
+ (append (let-clauses (access slot)
+ (car arg)
+ (cdr arg))
+ out)))
+ (else
+ (lp (cdr in) out)))))
+ (else
+ (lp (cdr in)
+ (cons `(,(car in) ,(access (car in))) out))))))
+ (define (transform-expr x)
+ (cond ((not (pair? x)) x)
+ ((eq? (car x) '->)
+ (if (= (length x) 2)
+ (let ((form (cadr x)))
+ `(,(symbol-append 'make- (make-stem (car form)))
+ ,@(cdr type-and-common)
+ ,@(map (lambda (y)
+ (if (and (pair? y) (eq? (car y) 'unquote))
+ (transform-expr (cadr y))
+ y))
+ (cdr form))))
+ (error "bad -> form" x)))
+ (else (cons (car x) (map transform-expr (cdr x))))))
+ (define (process-clause clause)
+ (if (eq? (car clause) 'else)
+ clause
+ (let ((stem (caar clause))
+ (slots (cdar clause))
+ (body (cdr clause)))
+ (let ((record-type (symbol-append '< (make-stem stem) '>)))
+ `((and (eq? ,rtd ,record-type)
+ ,@(reverse (further-predicates r stem slots)))
+ (let ,(reverse (let-clauses r stem slots))
+ ,@(if (pair? body)
+ (map transform-expr body)
+ '((if #f #f)))))))))
+ `(let* ((,r ,record)
+ (,rtd (struct-vtable ,r))
+ ,@(map (lambda (slot)
+ `(,slot (,(make-stem slot) ,r)))
+ (cdr type-and-common)))
+ (cond ,@(let ((clauses (map process-clause clauses)))
+ (if (assq 'else clauses)
+ clauses
+ (append clauses `((else (error "unhandled record" ,r))))))))))
+;;; Compilation targets
+
+;; Copyright (C) 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Code_
+
+(define-module (system base target)
+ #\use-module (rnrs bytevectors)
+ #\use-module (ice-9 regex)
+ #\export (target-type with-target
+
+ target-cpu target-vendor target-os
+
+ target-endianness target-word-size))
+
+
+
+;;;
+;;; Target types
+;;;
+
+(define %native-word-size
+ ;; The native word size. Note_ don't use `word-size' from
+ ;; (system vm objcode) to avoid a circular dependency.
+ ((@ (system foreign) sizeof) '*))
+
+(define %target-type (make-fluid %host-type))
+(define %target-endianness (make-fluid (native-endianness)))
+(define %target-word-size (make-fluid %native-word-size))
+
+(define (validate-target target)
+ (if (or (not (string? target))
+ (let ((parts (string-split target #\-)))
+ (or (< (length parts) 3)
+ (or-map string-null? parts))))
+ (error "invalid target" target)))
+
+(define (with-target target thunk)
+ (validate-target target)
+ (let ((cpu (triplet-cpu target)))
+ (with-fluids ((%target-type target)
+ (%target-endianness (cpu-endianness cpu))
+ (%target-word-size (triplet-pointer-size target)))
+ (thunk))))
+
+(define (cpu-endianness cpu)
+ "Return the endianness for CPU."
+ (if (string=? cpu (triplet-cpu %host-type))
+ (native-endianness)
+ (cond ((string-match "^i[0-9]86$" cpu)
+ (endianness little))
+ ((member cpu '("x86_64" "ia64"
+ "powerpcle" "powerpc64le" "mipsel" "mips64el" "nios2" "sh3" "sh4" "alpha"))
+ (endianness little))
+ ((member cpu '("sparc" "sparc64" "powerpc" "powerpc64" "spu"
+ "mips" "mips64" "m68k" "s390x"))
+ (endianness big))
+ ((string-match "^arm.*el" cpu)
+ (endianness little))
+ ((string-match "^arm.*eb" cpu)
+ (endianness big))
+ ((string-prefix? "arm" cpu) ;ARMs are LE by default
+ (endianness little))
+ ((string-match "^aarch64.*be" cpu)
+ (endianness big))
+ ((string=? "aarch64" cpu)
+ (endianness little))
+ (else
+ (error "unknown CPU endianness" cpu)))))
+
+(define (triplet-pointer-size triplet)
+ "Return the size of pointers in bytes for TRIPLET."
+ (let ((cpu (triplet-cpu triplet)))
+ (cond ((and (string=? cpu (triplet-cpu %host-type))
+ (string=? (triplet-os triplet) (triplet-os %host-type)))
+ %native-word-size)
+
+ ((string-match "^i[0-9]86$" cpu) 4)
+
+ ;; Although GNU config.guess doesn't yet recognize them,
+ ;; Debian (ab)uses the OS part to denote the specific ABI
+ ;; being used_ <http_//wiki.debian.org/Multiarch/Tuples>.
+ ;; See <http_//www.linux-mips.org/wiki/WhatsWrongWithO32N32N64>
+ ;; for details on the MIPS ABIs.
+ ((string-match "^mips64.*-gnuabi64" triplet) 8) ; n64 ABI
+ ((string-match "^mips64" cpu) 4) ; n32 or o32
+
+ ((string-match "^x86_64-.*-gnux32" triplet) 4) ; x32
+
+ ((string-match "64$" cpu) 8)
+ ((string-match "64_?[lbe][lbe]$" cpu) 8)
+ ((member cpu '("sparc" "powerpc" "mips" "mipsel" "nios2" "m68k" "sh3" "sh4")) 4)
+ ((member cpu '("s390x" "alpha")) 8)
+ ((string-match "^arm.*" cpu) 4)
+ (else (error "unknown CPU word size" cpu)))))
+
+(define (triplet-cpu t)
+ (substring t 0 (string-index t #\-)))
+
+(define (triplet-vendor t)
+ (let ((start (1+ (string-index t #\-))))
+ (substring t start (string-index t #\- start))))
+
+(define (triplet-os t)
+ (let ((start (1+ (string-index t #\- (1+ (string-index t #\-))))))
+ (substring t start)))
+
+
+(define (target-type)
+ "Return the GNU configuration triplet of the target platform."
+ (fluid-ref %target-type))
+
+(define (target-cpu)
+ "Return the CPU name of the target platform."
+ (triplet-cpu (target-type)))
+
+(define (target-vendor)
+ "Return the vendor name of the target platform."
+ (triplet-vendor (target-type)))
+
+(define (target-os)
+ "Return the operating system name of the target platform."
+ (triplet-os (target-type)))
+
+(define (target-endianness)
+ "Return the endianness object of the target platform."
+ (fluid-ref %target-endianness))
+
+(define (target-word-size)
+ "Return the word size, in bytes, of the target platform."
+ (fluid-ref %target-word-size))
+;;; 'SCM' type tag decoding.
+;;; Copyright (C) 2014, 2015 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 <http_//www.gnu.org/licenses/>.
+
+(define-module (system base types)
+ #\use-module (rnrs bytevectors)
+ #\use-module (rnrs io ports)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-9 gnu)
+ #\use-module (srfi srfi-11)
+ #\use-module (srfi srfi-26)
+ #\use-module (srfi srfi-60)
+ #\use-module (ice-9 match)
+ #\use-module (ice-9 iconv)
+ #\use-module (ice-9 format)
+ #\use-module (ice-9 vlist)
+ #\use-module (system foreign)
+ #\export (%word-size
+
+ memory-backend
+ memory-backend?
+ %ffi-memory-backend
+ dereference-word
+ memory-port
+ type-number->name
+
+ inferior-object?
+ inferior-object-kind
+ inferior-object-sub-kind
+ inferior-object-address
+
+ inferior-fluid?
+ inferior-fluid-number
+
+ inferior-struct?
+ inferior-struct-name
+ inferior-struct-fields
+
+ scm->object))
+
+;;; Commentary_
+;;;
+;;; 'SCM' type tag decoding, primarily to support Guile debugging in GDB.
+;;;
+;;; Code_
+
+
+;;;
+;;; Memory back-ends.
+;;;
+
+(define %word-size
+ ;; The pointer size.
+ (sizeof '*))
+
+(define-record-type <memory-backend>
+ (memory-backend peek open type-name)
+ memory-backend?
+ (peek memory-backend-peek)
+ (open memory-backend-open)
+ (type-name memory-backend-type-name)) ; for SMOBs and ports
+
+(define %ffi-memory-backend
+ ;; The FFI back-end to access the current process's memory. The main
+ ;; purpose of this back-end is to allow testing.
+ (let ()
+ (define (dereference-word address)
+ (let* ((ptr (make-pointer address))
+ (bv (pointer->bytevector ptr %word-size)))
+ (bytevector-uint-ref bv 0 (native-endianness) %word-size)))
+
+ (define (open address size)
+ (define current-address address)
+
+ (define (read-memory! bv index count)
+ (let* ((ptr (make-pointer current-address))
+ (mem (pointer->bytevector ptr count)))
+ (bytevector-copy! mem 0 bv index count)
+ (set! current-address (+ current-address count))
+ count))
+
+ (if size
+ (let* ((ptr (make-pointer address))
+ (bv (pointer->bytevector ptr size)))
+ (open-bytevector-input-port bv))
+ (let ((port (make-custom-binary-input-port "ffi-memory"
+ read-memory!
+ #f #f #f)))
+ (setvbuf port _IONBF)
+ port)))
+
+ (memory-backend dereference-word open #f)))
+
+(define-inlinable (dereference-word backend address)
+ "Return the word at ADDRESS, using BACKEND."
+ (let ((peek (memory-backend-peek backend)))
+ (peek address)))
+
+(define-syntax memory-port
+ (syntax-rules ()
+ "Return an input port to the SIZE bytes at ADDRESS, using BACKEND. When
+SIZE is omitted, return an unbounded port to the memory at ADDRESS."
+ ((_ backend address)
+ (let ((open (memory-backend-open backend)))
+ (open address #f)))
+ ((_ backend address size)
+ (if (zero? size)
+ ;; GDB's 'open-memory' raises an error when size
+ ;; is zero, so we must handle that case specially.
+ (open-bytevector-input-port '#vu8())
+ (let ((open (memory-backend-open backend)))
+ (open address size))))))
+
+(define (get-word port)
+ "Read a word from PORT and return it as an integer."
+ (let ((bv (get-bytevector-n port %word-size)))
+ (bytevector-uint-ref bv 0 (native-endianness) %word-size)))
+
+(define-inlinable (type-number->name backend kind number)
+ "Return the name of the type NUMBER of KIND, where KIND is one of
+'smob or 'port, or #f if the information is unavailable."
+ (let ((proc (memory-backend-type-name backend)))
+ (and proc (proc kind number))))
+
+
+;;;
+;;; Matching bit patterns and cells.
+;;;
+
+(define-syntax match-cell-words
+ (syntax-rules (bytevector)
+ ((_ port ((bytevector name len) rest ...) body)
+ (let ((name (get-bytevector-n port len))
+ (remainder (modulo len %word-size)))
+ (unless (zero? remainder)
+ (get-bytevector-n port (- %word-size remainder)))
+ (match-cell-words port (rest ...) body)))
+ ((_ port (name rest ...) body)
+ (let ((name (get-word port)))
+ (match-cell-words port (rest ...) body)))
+ ((_ port () body)
+ body)))
+
+(define-syntax match-bit-pattern
+ (syntax-rules (& !! = _)
+ ((match-bit-pattern bits ((a !! b) & n = c) consequent alternate)
+ (let ((tag (logand bits n)))
+ (if (= tag c)
+ (let ((b tag)
+ (a (logand bits (bitwise-not n))))
+ consequent)
+ alternate)))
+ ((match-bit-pattern bits (x & n = c) consequent alternate)
+ (let ((tag (logand bits n)))
+ (if (= tag c)
+ (let ((x bits))
+ consequent)
+ alternate)))
+ ((match-bit-pattern bits (_ & n = c) consequent alternate)
+ (let ((tag (logand bits n)))
+ (if (= tag c)
+ consequent
+ alternate)))
+ ((match-bit-pattern bits ((a << n) !! c) consequent alternate)
+ (let ((tag (bitwise-and bits (- (expt 2 n) 1))))
+ (if (= tag c)
+ (let ((a (arithmetic-shift bits (- n))))
+ consequent)
+ alternate)))))
+
+(define-syntax match-cell-clauses
+ (syntax-rules ()
+ ((_ port tag (((tag-pattern thing ...) body) rest ...))
+ (match-bit-pattern tag tag-pattern
+ (match-cell-words port (thing ...) body)
+ (match-cell-clauses port tag (rest ...))))
+ ((_ port tag ())
+ (inferior-object 'unmatched-tag tag))))
+
+(define-syntax match-cell
+ (syntax-rules ()
+ "Match a cell---i.e., a non-immediate value other than a pair. The
+cell's contents are read from PORT."
+ ((_ port (pattern body ...) ...)
+ (let ((port* port)
+ (tag (get-word port)))
+ (match-cell-clauses port* tag
+ ((pattern (begin body ...))
+ ...))))))
+
+(define-syntax match-scm-clauses
+ (syntax-rules ()
+ ((_ bits
+ (bit-pattern body ...)
+ rest ...)
+ (match-bit-pattern bits bit-pattern
+ (begin body ...)
+ (match-scm-clauses bits rest ...)))
+ ((_ bits)
+ 'unmatched-scm)))
+
+(define-syntax match-scm
+ (syntax-rules ()
+ "Match BITS, an integer representation of an 'SCM' value, against
+CLAUSES. Each clause must have the form_
+
+ (PATTERN BODY ...)
+
+PATTERN is a bit pattern that may specify bitwise operations on BITS to
+determine if it matches. TEMPLATE specify the name of the variable to bind
+the matching bits, possibly with bitwise operations to extract it from BITS."
+ ((_ bits clauses ...)
+ (let ((bits* bits))
+ (match-scm-clauses bits* clauses ...)))))
+
+
+;;;
+;;; Tags---keep in sync with libguile/tags.h!
+;;;
+
+;; Immediate values.
+(define %tc2-int 2)
+(define %tc3-imm24 4)
+
+(define %tc3-cons 0)
+(define %tc3-int1 %tc2-int)
+(define %tc3-int2 (+ %tc2-int 4))
+
+(define %tc8-char (+ 8 %tc3-imm24))
+(define %tc8-flag (+ %tc3-imm24 0))
+
+;; Cell types.
+(define %tc3-struct 1)
+(define %tc7-symbol 5)
+(define %tc7-variable 7)
+(define %tc7-vector 13)
+(define %tc7-wvect 15)
+(define %tc7-string 21)
+(define %tc7-number 23)
+(define %tc7-hashtable 29)
+(define %tc7-pointer 31)
+(define %tc7-fluid 37)
+(define %tc7-stringbuf 39)
+(define %tc7-dynamic-state 45)
+(define %tc7-frame 47)
+(define %tc7-objcode 53)
+(define %tc7-vm 55)
+(define %tc7-vm-continuation 71)
+(define %tc7-bytevector 77)
+(define %tc7-program 79)
+(define %tc7-array 85)
+(define %tc7-bitvector 87)
+(define %tc7-port 125)
+(define %tc7-smob 127)
+
+(define %tc16-bignum (+ %tc7-number (* 1 256)))
+(define %tc16-real (+ %tc7-number (* 2 256)))
+(define %tc16-complex (+ %tc7-number (* 3 256)))
+(define %tc16-fraction (+ %tc7-number (* 4 256)))
+
+
+;; "Stringbufs".
+(define-record-type <stringbuf>
+ (stringbuf string)
+ stringbuf?
+ (string stringbuf-contents))
+
+(set-record-type-printer! <stringbuf>
+ (lambda (stringbuf port)
+ (display "#<stringbuf " port)
+ (write (stringbuf-contents stringbuf) port)
+ (display "#>" port)))
+
+;; Structs.
+(define-record-type <inferior-struct>
+ (inferior-struct name fields)
+ inferior-struct?
+ (name inferior-struct-name)
+ (fields inferior-struct-fields set-inferior-struct-fields!))
+
+(define print-inferior-struct
+ (let ((%printed-struct (make-parameter vlist-null)))
+ (lambda (struct port)
+ (if (vhash-assq struct (%printed-struct))
+ (format port "#-1#")
+ (begin
+ (format port "#<struct ~a"
+ (inferior-struct-name struct))
+ (parameterize ((%printed-struct
+ (vhash-consq struct #t (%printed-struct))))
+ (for-each (lambda (field)
+ (if (eq? field struct)
+ (display " #0#" port)
+ (format port " ~s" field)))
+ (inferior-struct-fields struct)))
+ (format port " ~x>" (object-address struct)))))))
+
+(set-record-type-printer! <inferior-struct> print-inferior-struct)
+
+;; Fluids.
+(define-record-type <inferior-fluid>
+ (inferior-fluid number value)
+ inferior-fluid?
+ (number inferior-fluid-number)
+ (value inferior-fluid-value))
+
+(set-record-type-printer! <inferior-fluid>
+ (lambda (fluid port)
+ (match fluid
+ (($ <inferior-fluid> number)
+ (format port "#<fluid ~a ~x>"
+ number
+ (object-address fluid))))))
+
+;; Object type to represent complex objects from the inferior process that
+;; cannot be really converted to usable Scheme objects in the current
+;; process.
+(define-record-type <inferior-object>
+ (%inferior-object kind sub-kind address)
+ inferior-object?
+ (kind inferior-object-kind)
+ (sub-kind inferior-object-sub-kind)
+ (address inferior-object-address))
+
+(define inferior-object
+ (case-lambda
+ "Return an object representing an inferior object at ADDRESS, of type
+KIND/SUB-KIND."
+ ((kind address)
+ (%inferior-object kind #f address))
+ ((kind sub-kind address)
+ (%inferior-object kind sub-kind address))))
+
+(set-record-type-printer! <inferior-object>
+ (lambda (io port)
+ (match io
+ (($ <inferior-object> kind sub-kind address)
+ (format port "#<~a ~_[~*~;~a ~]~x>"
+ kind sub-kind sub-kind
+ address)))))
+
+(define (inferior-smob backend type-number address)
+ "Return an object representing the SMOB at ADDRESS whose type is
+TYPE-NUMBER."
+ (inferior-object 'smob
+ (or (type-number->name backend 'smob type-number)
+ type-number)
+ address))
+
+(define (inferior-port backend type-number address)
+ "Return an object representing the port at ADDRESS whose type is
+TYPE-NUMBER."
+ (inferior-object 'port
+ (or (type-number->name backend 'port type-number)
+ type-number)
+ address))
+
+(define %visited-cells
+ ;; Vhash of mapping addresses of already visited cells to the
+ ;; corresponding inferior object. This is used to detect and represent
+ ;; cycles.
+ (make-parameter vlist-null))
+
+(define-syntax visited
+ (syntax-rules (->)
+ ((_ (address -> object) body ...)
+ (parameterize ((%visited-cells (vhash-consv address object
+ (%visited-cells))))
+ body ...))))
+
+(define (address->inferior-struct address vtable-data-address backend)
+ "Read the struct at ADDRESS using BACKEND. Return an 'inferior-struct'
+object representing it."
+ (define %vtable-layout-index 0)
+ (define %vtable-name-index 5)
+
+ (let* ((layout-address (+ vtable-data-address
+ (* %vtable-layout-index %word-size)))
+ (layout-bits (dereference-word backend layout-address))
+ (layout (scm->object layout-bits backend))
+ (name-address (+ vtable-data-address
+ (* %vtable-name-index %word-size)))
+ (name-bits (dereference-word backend name-address))
+ (name (scm->object name-bits backend)))
+ (if (symbol? layout)
+ (let* ((layout (symbol->string layout))
+ (len (/ (string-length layout) 2))
+ (slots (dereference-word backend (+ address %word-size)))
+ (port (memory-port backend slots (* len %word-size)))
+ (fields (get-bytevector-n port (* len %word-size)))
+ (result (inferior-struct name #f)))
+
+ ;; Keep track of RESULT so callees can refer to it if we are
+ ;; decoding a circular struct.
+ (visited (address -> result)
+ (let ((values (map (cut scm->object <> backend)
+ (bytevector->uint-list fields
+ (native-endianness)
+ %word-size))))
+ (set-inferior-struct-fields! result values)
+ result)))
+ (inferior-object 'invalid-struct address))))
+
+(define* (cell->object address #\optional (backend %ffi-memory-backend))
+ "Return an object representing the object at ADDRESS, reading from memory
+using BACKEND."
+ (or (and=> (vhash-assv address (%visited-cells)) cdr) ; circular object
+ (let ((port (memory-port backend address)))
+ (match-cell port
+ (((vtable-data-address & 7 = %tc3-struct))
+ (address->inferior-struct address
+ (- vtable-data-address %tc3-struct)
+ backend))
+ (((_ & #x7f = %tc7-symbol) buf hash props)
+ (match (cell->object buf backend)
+ (($ <stringbuf> string)
+ (string->symbol string))))
+ (((_ & #x7f = %tc7-variable) obj)
+ (inferior-object 'variable address))
+ (((_ & #x7f = %tc7-string) buf start len)
+ (match (cell->object buf backend)
+ (($ <stringbuf> string)
+ (substring string start (+ start len)))))
+ (((_ & #x047f = %tc7-stringbuf) len (bytevector buf len))
+ (stringbuf (bytevector->string buf "ISO-8859-1")))
+ (((_ & #x047f = (bitwise-ior #x400 %tc7-stringbuf))
+ len (bytevector buf (* 4 len)))
+ (stringbuf (bytevector->string buf (match (native-endianness)
+ ('little "UTF-32LE")
+ ('big "UTF-32BE")))))
+ (((_ & #x7f = %tc7-bytevector) len address)
+ (let ((bv-port (memory-port backend address len)))
+ (get-bytevector-n bv-port len)))
+ ((((len << 7) !! %tc7-vector) weakv-data)
+ (let* ((len (arithmetic-shift len -1))
+ (words (get-bytevector-n port (* len %word-size)))
+ (vector (make-vector len)))
+ (visited (address -> vector)
+ (fold (lambda (element index)
+ (vector-set! vector index element)
+ (+ 1 index))
+ 0
+ (map (cut scm->object <> backend)
+ (bytevector->uint-list words (native-endianness)
+ %word-size)))
+ vector)))
+ (((_ & #x7f = %tc7-wvect))
+ (inferior-object 'weak-vector address)) ; TODO_ show elements
+ ((((n << 8) !! %tc7-fluid) init-value)
+ (inferior-fluid n #f)) ; TODO_ show current value
+ (((_ & #x7f = %tc7-dynamic-state))
+ (inferior-object 'dynamic-state address))
+ ((((flags+type << 8) !! %tc7-port))
+ (inferior-port backend (logand flags+type #xff) address))
+ (((_ & #x7f = %tc7-program))
+ (inferior-object 'program address))
+ (((_ & #xffff = %tc16-bignum))
+ (inferior-object 'bignum address))
+ (((_ & #xffff = %tc16-real) pad)
+ (let* ((address (+ address (* 2 %word-size)))
+ (port (memory-port backend address (sizeof double)))
+ (words (get-bytevector-n port (sizeof double))))
+ (bytevector-ieee-double-ref words 0 (native-endianness))))
+ (((_ & #x7f = %tc7-number) mpi)
+ (inferior-object 'number address))
+ (((_ & #x7f = %tc7-hashtable) buckets meta-data unused)
+ (inferior-object 'hash-table address))
+ (((_ & #x7f = %tc7-pointer) address)
+ (make-pointer address))
+ (((_ & #x7f = %tc7-objcode))
+ (inferior-object 'objcode address))
+ (((_ & #x7f = %tc7-vm))
+ (inferior-object 'vm address))
+ (((_ & #x7f = %tc7-vm-continuation))
+ (inferior-object 'vm-continuation address))
+ (((_ & #x7f = %tc7-array))
+ (inferior-object 'array address))
+ (((_ & #x7f = %tc7-bitvector))
+ (inferior-object 'bitvector address))
+ ((((smob-type << 8) !! %tc7-smob) word1)
+ (inferior-smob backend smob-type address))))))
+
+
+(define* (scm->object bits #\optional (backend %ffi-memory-backend))
+ "Return the Scheme object corresponding to BITS, the bits of an 'SCM'
+object."
+ (match-scm bits
+ (((integer << 2) !! %tc2-int)
+ integer)
+ ((address & 6 = %tc3-cons)
+ (let* ((type (dereference-word backend address))
+ (pair? (not (bit-set? 0 type))))
+ (if pair?
+ (or (and=> (vhash-assv address (%visited-cells)) cdr)
+ (let ((car type)
+ (cdrloc (+ address %word-size))
+ (pair (cons *unspecified* *unspecified*)))
+ (visited (address -> pair)
+ (set-car! pair (scm->object car backend))
+ (set-cdr! pair
+ (scm->object (dereference-word backend cdrloc)
+ backend))
+ pair)))
+ (cell->object address backend))))
+ (((char << 8) !! %tc8-char)
+ (integer->char char))
+ (((flag << 8) !! %tc8-flag)
+ (case flag
+ ((0) #f)
+ ((1) #nil)
+ ((3) '())
+ ((4) #t)
+ ((8) (if #f #f))
+ ((9) (inferior-object 'undefined bits))
+ ((10) (eof-object))
+ ((11) (inferior-object 'unbound bits))))))
+
+;;; Local Variables_
+;;; eval_ (put 'match-scm 'scheme-indent-function 1)
+;;; eval_ (put 'match-cell 'scheme-indent-function 1)
+;;; eval_ (put 'visited 'scheme-indent-function 1)
+;;; End_
+
+;;; types.scm ends here
+;;;; Copyright (C) 2010, 2011, 2013 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (system foreign)
+ #\use-module (rnrs bytevectors)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-9 gnu)
+ #\export (void
+ float double
+ short
+ unsigned-short
+ int unsigned-int long unsigned-long size_t ssize_t ptrdiff_t
+ int8 uint8
+ uint16 int16
+ uint32 int32
+ uint64 int64
+
+ sizeof alignof
+
+ %null-pointer
+ null-pointer?
+ pointer?
+ make-pointer
+ pointer->scm
+ scm->pointer
+ pointer-address
+
+ pointer->bytevector
+ bytevector->pointer
+ set-pointer-finalizer!
+
+ dereference-pointer
+ string->pointer
+ pointer->string
+
+ pointer->procedure
+ ;; procedure->pointer (see below)
+ make-c-struct parse-c-struct
+
+ define-wrapped-pointer-type))
+
+(eval-when (expand load eval)
+ (load-extension (string-append "libguile-" (effective-version))
+ "scm_init_foreign"))
+
+
+;;;
+;;; Pointers.
+;;;
+
+(define (null-pointer? pointer)
+ "Return true if POINTER is the null pointer."
+ (= (pointer-address pointer) 0))
+
+(if (defined? 'procedure->pointer)
+ (export procedure->pointer))
+
+
+;;;
+;;; Structures.
+;;;
+
+(define bytevector-pointer-ref
+ (case (sizeof '*)
+ ((8) (lambda (bv offset)
+ (make-pointer (bytevector-u64-native-ref bv offset))))
+ ((4) (lambda (bv offset)
+ (make-pointer (bytevector-u32-native-ref bv offset))))
+ (else (error "what machine is this?"))))
+
+(define bytevector-pointer-set!
+ (case (sizeof '*)
+ ((8) (lambda (bv offset ptr)
+ (bytevector-u64-native-set! bv offset (pointer-address ptr))))
+ ((4) (lambda (bv offset ptr)
+ (bytevector-u32-native-set! bv offset (pointer-address ptr))))
+ (else (error "what machine is this?"))))
+
+(define *writers*
+ `((,float . ,bytevector-ieee-single-native-set!)
+ (,double . ,bytevector-ieee-double-native-set!)
+ (,int8 . ,bytevector-s8-set!)
+ (,uint8 . ,bytevector-u8-set!)
+ (,int16 . ,bytevector-s16-native-set!)
+ (,uint16 . ,bytevector-u16-native-set!)
+ (,int32 . ,bytevector-s32-native-set!)
+ (,uint32 . ,bytevector-u32-native-set!)
+ (,int64 . ,bytevector-s64-native-set!)
+ (,uint64 . ,bytevector-u64-native-set!)
+ (* . ,bytevector-pointer-set!)))
+
+(define *readers*
+ `((,float . ,bytevector-ieee-single-native-ref)
+ (,double . ,bytevector-ieee-double-native-ref)
+ (,int8 . ,bytevector-s8-ref)
+ (,uint8 . ,bytevector-u8-ref)
+ (,int16 . ,bytevector-s16-native-ref)
+ (,uint16 . ,bytevector-u16-native-ref)
+ (,int32 . ,bytevector-s32-native-ref)
+ (,uint32 . ,bytevector-u32-native-ref)
+ (,int64 . ,bytevector-s64-native-ref)
+ (,uint64 . ,bytevector-u64-native-ref)
+ (* . ,bytevector-pointer-ref)))
+
+
+(define (align off alignment)
+ (1+ (logior (1- off) (1- alignment))))
+
+(define (write-c-struct bv offset types vals)
+ (let lp ((offset offset) (types types) (vals vals))
+ (cond
+ ((not (pair? types))
+ (or (null? vals)
+ (error "too many values" vals)))
+ ((not (pair? vals))
+ (error "too few values" types))
+ (else
+ ;; alignof will error-check
+ (let* ((type (car types))
+ (offset (align offset (alignof type))))
+ (if (pair? type)
+ (write-c-struct bv offset (car types) (car vals))
+ ((assv-ref *writers* type) bv offset (car vals)))
+ (lp (+ offset (sizeof type)) (cdr types) (cdr vals)))))))
+
+(define (read-c-struct bv offset types)
+ (let lp ((offset offset) (types types) (vals '()))
+ (cond
+ ((not (pair? types))
+ (reverse vals))
+ (else
+ ;; alignof will error-check
+ (let* ((type (car types))
+ (offset (align offset (alignof type))))
+ (lp (+ offset (sizeof type)) (cdr types)
+ (cons (if (pair? type)
+ (read-c-struct bv offset (car types))
+ ((assv-ref *readers* type) bv offset))
+ vals)))))))
+
+(define (make-c-struct types vals)
+ (let ((bv (make-bytevector (sizeof types) 0)))
+ (write-c-struct bv 0 types vals)
+ (bytevector->pointer bv)))
+
+(define (parse-c-struct foreign types)
+ (let ((size (fold (lambda (type total)
+ (+ (sizeof type)
+ (align total (alignof type))))
+ 0
+ types)))
+ (read-c-struct (pointer->bytevector foreign size) 0 types)))
+
+
+;;;
+;;; Wrapped pointer types.
+;;;
+
+(define-syntax define-wrapped-pointer-type
+ (lambda (stx)
+ "Define helper procedures to wrap pointer objects into Scheme
+objects with a disjoint type. Specifically, this macro defines PRED, a
+predicate for the new Scheme type, WRAP, a procedure that takes a
+pointer object and returns an object that satisfies PRED, and UNWRAP
+which does the reverse. PRINT must name a user-defined object printer."
+ (syntax-case stx ()
+ ((_ type-name pred wrap unwrap print)
+ (with-syntax ((%wrap (datum->syntax #'wrap (gensym "wrap"))))
+ #'(begin
+ (define-record-type type-name
+ (%wrap pointer)
+ pred
+ (pointer unwrap))
+ (define wrap
+ ;; Use a weak hash table to preserve pointer identity, i.e.,
+ ;; PTR1 == PTR2 <-> (eq? (wrap PTR1) (wrap PTR2)).
+ (let ((ptr->obj (make-weak-value-hash-table 3000)))
+ (lambda (ptr)
+ ;; XXX_ We can't use `hash-create-handle!' +
+ ;; `set-cdr!' here because the former would create a
+ ;; weak-cdr pair but the latter wouldn't register a
+ ;; disappearing link (see `scm_hash_fn_set_x'.)
+ (or (hash-ref ptr->obj ptr)
+ (let ((o (%wrap ptr)))
+ (hash-set! ptr->obj ptr o)
+ o)))))
+ (set-record-type-printer! type-name print)))))))
+;;; Repl commands
+
+;; Copyright (C) 2001, 2009, 2010, 2011, 2013 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Code_
+
+(define-module (system repl command)
+ #\use-module (system base syntax)
+ #\use-module (system base pmatch)
+ #\use-module (system base compile)
+ #\use-module (system repl common)
+ #\use-module (system repl debug)
+ #\use-module (system vm objcode)
+ #\use-module (system vm program)
+ #\use-module (system vm trap-state)
+ #\use-module (system vm vm)
+ #\use-module ((system vm frame) #\select (frame-return-values))
+ #\autoload (system base language) (lookup-language language-reader)
+ #\autoload (system vm trace) (call-with-trace)
+ #\use-module (ice-9 format)
+ #\use-module (ice-9 session)
+ #\use-module (ice-9 documentation)
+ #\use-module (ice-9 and-let-star)
+ #\use-module (ice-9 rdelim)
+ #\use-module (ice-9 control)
+ #\use-module ((ice-9 pretty-print) #\select ((pretty-print . pp)))
+ #\use-module ((system vm inspect) #\select ((inspect . %inspect)))
+ #\use-module (statprof)
+ #\export (meta-command define-meta-command))
+
+
+;;;
+;;; Meta command interface
+;;;
+
+(define *command-table*
+ '((help (help h) (show) (apropos a) (describe d))
+ (module (module m) (import use) (load l) (reload re) (binding b) (in))
+ (language (language L))
+ (compile (compile c) (compile-file cc)
+ (expand exp) (optimize opt)
+ (disassemble x) (disassemble-file xx))
+ (profile (time t) (profile pr) (trace tr))
+ (debug (backtrace bt) (up) (down) (frame fr)
+ (procedure proc) (locals) (error-message error)
+ (break br bp) (break-at-source break-at bs)
+ (step s) (step-instruction si)
+ (next n) (next-instruction ni)
+ (finish)
+ (tracepoint tp)
+ (traps) (delete del) (disable) (enable)
+ (registers regs))
+ (inspect (inspect i) (pretty-print pp))
+ (system (gc) (statistics stat) (option o)
+ (quit q continue cont))))
+
+(define *show-table*
+ '((show (warranty w) (copying c) (version v))))
+
+(define (group-name g) (car g))
+(define (group-commands g) (cdr g))
+
+(define *command-infos* (make-hash-table))
+(define (command-name c) (car c))
+(define (command-abbrevs c) (cdr c))
+(define (command-info c) (hashq-ref *command-infos* (command-name c)))
+(define (command-procedure c) (command-info-procedure (command-info c)))
+(define (command-doc c) (procedure-documentation (command-procedure c)))
+
+(define (make-command-info proc arguments-reader)
+ (cons proc arguments-reader))
+
+(define (command-info-procedure info)
+ (car info))
+
+(define (command-info-arguments-reader info)
+ (cdr info))
+
+(define (command-usage c)
+ (let ((doc (command-doc c)))
+ (substring doc 0 (string-index doc #\newline))))
+
+(define (command-summary c)
+ (let* ((doc (command-doc c))
+ (start (1+ (string-index doc #\newline))))
+ (cond ((string-index doc #\newline start)
+ => (lambda (end) (substring doc start end)))
+ (else (substring doc start)))))
+
+(define (lookup-group name)
+ (assq name *command-table*))
+
+(define* (lookup-command key #\optional (table *command-table*))
+ (let loop ((groups table) (commands '()))
+ (cond ((and (null? groups) (null? commands)) #f)
+ ((null? commands)
+ (loop (cdr groups) (cdar groups)))
+ ((memq key (car commands)) (car commands))
+ (else (loop groups (cdr commands))))))
+
+(define* (display-group group #\optional (abbrev? #t))
+ (format #t "~_(~A~) Commands~_[~; [abbrev]~]_~2%" (group-name group) abbrev?)
+ (for-each (lambda (c)
+ (display-summary (command-usage c)
+ (if abbrev? (command-abbrevs c) '())
+ (command-summary c)))
+ (group-commands group))
+ (newline))
+
+(define (display-command command)
+ (display "Usage_ ")
+ (display (command-doc command))
+ (newline))
+
+(define (display-summary usage abbrevs summary)
+ (let* ((usage-len (string-length usage))
+ (abbrevs (if (pair? abbrevs)
+ (format #f "[,~A~{ ,~A~}]" (car abbrevs) (cdr abbrevs))
+ ""))
+ (abbrevs-len (string-length abbrevs)))
+ (format #t " ,~A~A~A - ~A\n"
+ usage
+ (cond
+ ((> abbrevs-len 32)
+ (error "abbrevs too long" abbrevs))
+ ((> (+ usage-len abbrevs-len) 32)
+ (format #f "~%~v_" (+ 2 (- 32 abbrevs-len))))
+ (else
+ (format #f "~v_" (- 32 abbrevs-len usage-len))))
+ abbrevs
+ summary)))
+
+(define (read-command repl)
+ (catch #t
+ (lambda () (read))
+ (lambda (key . args)
+ (pmatch args
+ ((,subr ,msg ,args . ,rest)
+ (format #t "Throw to key `~a' while reading command_\n" key)
+ (display-error #f (current-output-port) subr msg args rest))
+ (else
+ (format #t "Throw to key `~a' with args `~s' while reading command.\n"
+ key args)))
+ (force-output)
+ *unspecified*)))
+
+(define (read-command-arguments c repl)
+ ((command-info-arguments-reader (command-info c)) repl))
+
+(define (meta-command repl)
+ (let ((command (read-command repl)))
+ (cond
+ ((eq? command *unspecified*)) ; read error, already signalled; pass.
+ ((not (symbol? command))
+ (format #t "Meta-command not a symbol_ ~s~%" command))
+ ((lookup-command command)
+ => (lambda (c)
+ (and=> (read-command-arguments c repl)
+ (lambda (args) (apply (command-procedure c) repl args)))))
+ (else
+ (format #t "Unknown meta command_ ~A~%" command)))))
+
+(define (add-meta-command! name category proc argument-reader)
+ (hashq-set! *command-infos* name (make-command-info proc argument-reader))
+ (if category
+ (let ((entry (assq category *command-table*)))
+ (if entry
+ (set-cdr! entry (append (cdr entry) (list (list name))))
+ (set! *command-table*
+ (append *command-table*
+ (list (list category (list name)))))))))
+
+(define-syntax define-meta-command
+ (syntax-rules ()
+ ((_ ((name category) repl (expression0 ...) . datums) docstring b0 b1 ...)
+ (add-meta-command!
+ 'name
+ 'category
+ (lambda* (repl expression0 ... . datums)
+ docstring
+ b0 b1 ...)
+ (lambda (repl)
+ (define (handle-read-error form-name key args)
+ (pmatch args
+ ((,subr ,msg ,args . ,rest)
+ (format #t "Throw to key `~a' while reading ~@[argument `~A' of ~]command `~A'_\n"
+ key form-name 'name)
+ (display-error #f (current-output-port) subr msg args rest))
+ (else
+ (format #t "Throw to key `~a' with args `~s' while reading ~@[ argument `~A' of ~]command `~A'.\n"
+ key args form-name 'name)))
+ (abort))
+ (% (let* ((expression0
+ (catch #t
+ (lambda ()
+ (repl-reader
+ ""
+ (lambda* (#\optional (port (current-input-port)))
+ ((language-reader (repl-language repl))
+ port (current-module)))))
+ (lambda (k . args)
+ (handle-read-error 'expression0 k args))))
+ ...)
+ (append
+ (list expression0 ...)
+ (catch #t
+ (lambda ()
+ (let ((port (open-input-string (read-line))))
+ (let lp ((out '()))
+ (let ((x (read port)))
+ (if (eof-object? x)
+ (reverse out)
+ (lp (cons x out)))))))
+ (lambda (k . args)
+ (handle-read-error #f k args)))))
+ (lambda (k) #f))))) ; the abort handler
+
+ ((_ ((name category) repl . datums) docstring b0 b1 ...)
+ (define-meta-command ((name category) repl () . datums)
+ docstring b0 b1 ...))
+
+ ((_ (name repl (expression0 ...) . datums) docstring b0 b1 ...)
+ (define-meta-command ((name #f) repl (expression0 ...) . datums)
+ docstring b0 b1 ...))
+
+ ((_ (name repl . datums) docstring b0 b1 ...)
+ (define-meta-command ((name #f) repl () . datums)
+ docstring b0 b1 ...))))
+
+
+
+;;;
+;;; Help commands
+;;;
+
+(define-meta-command (help repl . args)
+ "help [all | GROUP | [-c] COMMAND]
+Show help.
+
+With one argument, tries to look up the argument as a group name, giving
+help on that group if successful. Otherwise tries to look up the
+argument as a command, giving help on the command.
+
+If there is a command whose name is also a group name, use the ,help
+-c COMMAND form to give help on the command instead of the group.
+
+Without any argument, a list of help commands and command groups
+are displayed."
+ (pmatch args
+ (()
+ (display-group (lookup-group 'help))
+ (display "Command Groups_\n\n")
+ (display-summary "help all" #f "List all commands")
+ (for-each (lambda (g)
+ (let* ((name (symbol->string (group-name g)))
+ (usage (string-append "help " name))
+ (header (string-append "List " name " commands")))
+ (display-summary usage #f header)))
+ (cdr *command-table*))
+ (newline)
+ (display
+ "Type `,help -c COMMAND' to show documentation of a particular command.")
+ (newline))
+ ((all)
+ (for-each display-group *command-table*))
+ ((,group) (guard (lookup-group group))
+ (display-group (lookup-group group)))
+ ((,command) (guard (lookup-command command))
+ (display-command (lookup-command command)))
+ ((-c ,command) (guard (lookup-command command))
+ (display-command (lookup-command command)))
+ ((,command)
+ (format #t "Unknown command or group_ ~A~%" command))
+ ((-c ,command)
+ (format #t "Unknown command_ ~A~%" command))
+ (else
+ (format #t "Bad arguments_ ~A~%" args))))
+
+(define-meta-command (show repl . args)
+ "show [TOPIC]
+Gives information about Guile.
+
+With one argument, tries to show a particular piece of information;
+
+currently supported topics are `warranty' (or `w'), `copying' (or `c'),
+and `version' (or `v').
+
+Without any argument, a list of topics is displayed."
+ (pmatch args
+ (()
+ (display-group (car *show-table*) #f)
+ (newline))
+ ((,topic) (guard (lookup-command topic *show-table*))
+ ((command-procedure (lookup-command topic *show-table*)) repl))
+ ((,command)
+ (format #t "Unknown topic_ ~A~%" command))
+ (else
+ (format #t "Bad arguments_ ~A~%" args))))
+
+;;; `warranty', `copying' and `version' are "hidden" meta-commands, only
+;;; accessible via `show'. They have an entry in *command-infos* but not
+;;; in *command-table*.
+
+(define-meta-command (warranty repl)
+ "show warranty
+Details on the lack of warranty."
+ (display *warranty*)
+ (newline))
+
+(define-meta-command (copying repl)
+ "show copying
+Show the LGPLv3."
+ (display *copying*)
+ (newline))
+
+(define-meta-command (version repl)
+ "show version
+Version information."
+ (display *version*)
+ (newline))
+
+(define-meta-command (apropos repl regexp)
+ "apropos REGEXP
+Find bindings/modules/packages."
+ (apropos (->string regexp)))
+
+(define-meta-command (describe repl (form))
+ "describe OBJ
+Show description/documentation."
+ (display
+ (object-documentation
+ (let ((input (repl-parse repl form)))
+ (if (symbol? input)
+ (module-ref (current-module) input)
+ (repl-eval repl input)))))
+ (newline))
+
+(define-meta-command (option repl . args)
+ "option [NAME] [EXP]
+List/show/set options."
+ (pmatch args
+ (()
+ (for-each (lambda (spec)
+ (format #t " ~A~24t~A\n" (car spec) (cadr spec)))
+ (repl-options repl)))
+ ((,name)
+ (display (repl-option-ref repl name))
+ (newline))
+ ((,name ,exp)
+ ;; Would be nice to evaluate in the current language, but the REPL
+ ;; option parser doesn't permit that, currently.
+ (repl-option-set! repl name (eval exp (current-module))))))
+
+(define-meta-command (quit repl)
+ "quit
+Quit this session."
+ (throw 'quit))
+
+
+;;;
+;;; Module commands
+;;;
+
+(define-meta-command (module repl . args)
+ "module [MODULE]
+Change modules / Show current module."
+ (pmatch args
+ (() (puts (module-name (current-module))))
+ ((,mod-name) (guard (list? mod-name))
+ (set-current-module (resolve-module mod-name)))
+ (,mod-name (set-current-module (resolve-module mod-name)))))
+
+(define-meta-command (import repl . args)
+ "import [MODULE ...]
+Import modules / List those imported."
+ (let ()
+ (define (use name)
+ (let ((mod (resolve-interface name)))
+ (if mod
+ (module-use! (current-module) mod)
+ (format #t "No such module_ ~A~%" name))))
+ (if (null? args)
+ (for-each puts (map module-name (module-uses (current-module))))
+ (for-each use args))))
+
+(define-meta-command (load repl file)
+ "load FILE
+Load a file in the current module."
+ (load (->string file)))
+
+(define-meta-command (reload repl . args)
+ "reload [MODULE]
+Reload the given module, or the current module if none was given."
+ (pmatch args
+ (() (reload-module (current-module)))
+ ((,mod-name) (guard (list? mod-name))
+ (reload-module (resolve-module mod-name)))
+ (,mod-name (reload-module (resolve-module mod-name)))))
+
+(define-meta-command (binding repl)
+ "binding
+List current bindings."
+ (module-for-each (lambda (k v) (format #t "~23A ~A\n" k v))
+ (current-module)))
+
+(define-meta-command (in repl module command-or-expression . args)
+ "in MODULE COMMAND-OR-EXPRESSION
+Evaluate an expression or command in the context of module."
+ (let ((m (resolve-module module #\ensure #f)))
+ (if m
+ (pmatch command-or-expression
+ (('unquote ,command) (guard (lookup-command command))
+ (save-module-excursion
+ (lambda ()
+ (set-current-module m)
+ (apply (command-procedure (lookup-command command)) repl args))))
+ (,expression
+ (guard (null? args))
+ (repl-print repl (eval expression m)))
+ (else
+ (format #t "Invalid arguments to `in'_ expected a single expression or a command.\n")))
+ (format #t "No such module_ ~s\n" module))))
+
+
+;;;
+;;; Language commands
+;;;
+
+(define-meta-command (language repl name)
+ "language LANGUAGE
+Change languages."
+ (let ((lang (lookup-language name))
+ (cur (repl-language repl)))
+ (format #t "Happy hacking with ~a! To switch back, type `,L ~a'.\n"
+ (language-title lang) (language-name cur))
+ (current-language lang)
+ (set! (repl-language repl) lang)))
+
+
+;;;
+;;; Compile commands
+;;;
+
+(define-meta-command (compile repl (form))
+ "compile EXP
+Generate compiled code."
+ (let ((x (repl-compile repl (repl-parse repl form))))
+ (cond ((objcode? x) (guile_disassemble x))
+ (else (repl-print repl x)))))
+
+(define-meta-command (compile-file repl file . opts)
+ "compile-file FILE
+Compile a file."
+ (compile-file (->string file) #\opts opts))
+
+(define-meta-command (expand repl (form))
+ "expand EXP
+Expand any macros in a form."
+ (let ((x (repl-expand repl (repl-parse repl form))))
+ (run-hook before-print-hook x)
+ (pp x)))
+
+(define-meta-command (optimize repl (form))
+ "optimize EXP
+Run the optimizer on a piece of code and print the result."
+ (let ((x (repl-optimize repl (repl-parse repl form))))
+ (run-hook before-print-hook x)
+ (pp x)))
+
+(define (guile_disassemble x)
+ ((@ (language assembly disassemble) disassemble) x))
+
+(define-meta-command (disassemble repl (form))
+ "disassemble EXP
+Disassemble a compiled procedure."
+ (let ((obj (repl-eval repl (repl-parse repl form))))
+ (if (or (program? obj) (objcode? obj))
+ (guile_disassemble obj)
+ (format #t "Argument to ,disassemble not a procedure or objcode_ ~a~%"
+ obj))))
+
+(define-meta-command (disassemble-file repl file)
+ "disassemble-file FILE
+Disassemble a file."
+ (guile_disassemble (load-objcode (->string file))))
+
+
+;;;
+;;; Profile commands
+;;;
+
+(define-meta-command (time repl (form))
+ "time EXP
+Time execution."
+ (let* ((gc-start (gc-run-time))
+ (real-start (get-internal-real-time))
+ (run-start (get-internal-run-time))
+ (result (repl-eval repl (repl-parse repl form)))
+ (run-end (get-internal-run-time))
+ (real-end (get-internal-real-time))
+ (gc-end (gc-run-time)))
+ (define (diff start end)
+ (/ (- end start) 1.0 internal-time-units-per-second))
+ (repl-print repl result)
+ (format #t ";; ~,6Fs real time, ~,6Fs run time. ~,6Fs spent in GC.\n"
+ (diff real-start real-end)
+ (diff run-start run-end)
+ (diff gc-start gc-end))
+ result))
+
+(define-meta-command (profile repl (form) . opts)
+ "profile EXP
+Profile execution."
+ ;; FIXME opts
+ (apply statprof
+ (repl-prepare-eval-thunk repl (repl-parse repl form))
+ opts))
+
+(define-meta-command (trace repl (form) . opts)
+ "trace EXP
+Trace execution."
+ ;; FIXME_ doc options, or somehow deal with them better
+ (apply call-with-trace
+ (repl-prepare-eval-thunk repl (repl-parse repl form))
+ (cons* #\width (terminal-width) opts)))
+
+
+;;;
+;;; Debug commands
+;;;
+
+(define-syntax define-stack-command
+ (lambda (x)
+ (syntax-case x ()
+ ((_ (name repl . args) docstring body body* ...)
+ #`(define-meta-command (name repl . args)
+ docstring
+ (let ((debug (repl-debug repl)))
+ (if debug
+ (letrec-syntax
+ ((#,(datum->syntax #'repl 'frames)
+ (identifier-syntax (debug-frames debug)))
+ (#,(datum->syntax #'repl 'message)
+ (identifier-syntax (debug-error-message debug)))
+ (#,(datum->syntax #'repl 'for-trap?)
+ (identifier-syntax (debug-for-trap? debug)))
+ (#,(datum->syntax #'repl 'index)
+ (identifier-syntax
+ (id (debug-index debug))
+ ((set! id exp) (set! (debug-index debug) exp))))
+ (#,(datum->syntax #'repl 'cur)
+ (identifier-syntax
+ (vector-ref #,(datum->syntax #'repl 'frames)
+ #,(datum->syntax #'repl 'index)))))
+ body body* ...)
+ (format #t "Nothing to debug.~%"))))))))
+
+(define-stack-command (backtrace repl #\optional count
+ #\key (width (terminal-width)) full?)
+ "backtrace [COUNT] [#:width W] [#full? F]
+Print a backtrace.
+
+Print a backtrace of all stack frames, or innermost COUNT frames.
+If COUNT is negative, the last COUNT frames will be shown."
+ (print-frames frames
+ #\count count
+ #\width width
+ #\full? full?
+ #\for-trap? for-trap?))
+
+(define-stack-command (up repl #\optional (count 1))
+ "up [COUNT]
+Select a calling stack frame.
+
+Select and print stack frames that called this one.
+An argument says how many frames up to go."
+ (cond
+ ((or (not (integer? count)) (<= count 0))
+ (format #t "Invalid argument to `up'_ expected a positive integer for COUNT.~%"))
+ ((>= (+ count index) (vector-length frames))
+ (cond
+ ((= index (1- (vector-length frames)))
+ (format #t "Already at outermost frame.\n"))
+ (else
+ (set! index (1- (vector-length frames)))
+ (print-frame cur #\index index
+ #\next-source? (and (zero? index) for-trap?)))))
+ (else
+ (set! index (+ count index))
+ (print-frame cur #\index index
+ #\next-source? (and (zero? index) for-trap?)))))
+
+(define-stack-command (down repl #\optional (count 1))
+ "down [COUNT]
+Select a called stack frame.
+
+Select and print stack frames called by this one.
+An argument says how many frames down to go."
+ (cond
+ ((or (not (integer? count)) (<= count 0))
+ (format #t "Invalid argument to `down'_ expected a positive integer for COUNT.~%"))
+ ((< (- index count) 0)
+ (cond
+ ((zero? index)
+ (format #t "Already at innermost frame.\n"))
+ (else
+ (set! index 0)
+ (print-frame cur #\index index #\next-source? for-trap?))))
+ (else
+ (set! index (- index count))
+ (print-frame cur #\index index
+ #\next-source? (and (zero? index) for-trap?)))))
+
+(define-stack-command (frame repl #\optional idx)
+ "frame [IDX]
+Show a frame.
+
+Show the selected frame.
+With an argument, select a frame by index, then show it."
+ (cond
+ (idx
+ (cond
+ ((or (not (integer? idx)) (< idx 0))
+ (format #t "Invalid argument to `frame'_ expected a non-negative integer for IDX.~%"))
+ ((< idx (vector-length frames))
+ (set! index idx)
+ (print-frame cur #\index index
+ #\next-source? (and (zero? index) for-trap?)))
+ (else
+ (format #t "No such frame.~%"))))
+ (else (print-frame cur #\index index
+ #\next-source? (and (zero? index) for-trap?)))))
+
+(define-stack-command (procedure repl)
+ "procedure
+Print the procedure for the selected frame."
+ (repl-print repl (frame-procedure cur)))
+
+(define-stack-command (locals repl #\key (width (terminal-width)))
+ "locals
+Show local variables.
+
+Show locally-bound variables in the selected frame."
+ (print-locals cur #\width width))
+
+(define-stack-command (error-message repl)
+ "error-message
+Show error message.
+
+Display the message associated with the error that started the current
+debugging REPL."
+ (format #t "~a~%" (if (string? message) message "No error message")))
+
+(define-meta-command (break repl (form))
+ "break PROCEDURE
+Break on calls to PROCEDURE.
+
+Starts a recursive prompt when PROCEDURE is called."
+ (let ((proc (repl-eval repl (repl-parse repl form))))
+ (if (not (procedure? proc))
+ (error "Not a procedure_ ~a" proc)
+ (let ((idx (add-trap-at-procedure-call! proc)))
+ (format #t "Trap ~a_ ~a.~%" idx (trap-name idx))))))
+
+(define-meta-command (break-at-source repl file line)
+ "break-at-source FILE LINE
+Break when control reaches the given source location.
+
+Starts a recursive prompt when control reaches line LINE of file FILE.
+Note that the given source location must be inside a procedure."
+ (let ((file (if (symbol? file) (symbol->string file) file)))
+ (let ((idx (add-trap-at-source-location! file line)))
+ (format #t "Trap ~a_ ~a.~%" idx (trap-name idx)))))
+
+(define (repl-pop-continuation-resumer repl msg)
+ ;; Capture the dynamic environment with this prompt thing. The
+ ;; result is a procedure that takes a frame.
+ (% (call-with-values
+ (lambda ()
+ (abort
+ (lambda (k)
+ ;; Call frame->stack-vector before reinstating the
+ ;; continuation, so that we catch the %stacks fluid at
+ ;; the time of capture.
+ (lambda (frame)
+ (k frame
+ (frame->stack-vector
+ (frame-previous frame)))))))
+ (lambda (from stack)
+ (format #t "~a~%" msg)
+ (let ((vals (frame-return-values from)))
+ (if (null? vals)
+ (format #t "No return values.~%")
+ (begin
+ (format #t "Return values_~%")
+ (for-each (lambda (x) (repl-print repl x)) vals))))
+ ((module-ref (resolve-interface '(system repl repl)) 'start-repl)
+ #\debug (make-debug stack 0 msg #t))))))
+
+(define-stack-command (finish repl)
+ "finish
+Run until the current frame finishes.
+
+Resume execution, breaking when the current frame finishes."
+ (let ((handler (repl-pop-continuation-resumer
+ repl (format #f "Return from ~a" cur))))
+ (add-ephemeral-trap-at-frame-finish! cur handler)
+ (throw 'quit)))
+
+(define (repl-next-resumer msg)
+ ;; Capture the dynamic environment with this prompt thing. The
+ ;; result is a procedure that takes a frame.
+ (% (let ((stack (abort
+ (lambda (k)
+ ;; Call frame->stack-vector before reinstating the
+ ;; continuation, so that we catch the %stacks fluid
+ ;; at the time of capture.
+ (lambda (frame)
+ (k (frame->stack-vector frame)))))))
+ (format #t "~a~%" msg)
+ ((module-ref (resolve-interface '(system repl repl)) 'start-repl)
+ #\debug (make-debug stack 0 msg #t)))))
+
+(define-stack-command (step repl)
+ "step
+Step until control reaches a different source location.
+
+Step until control reaches a different source location."
+ (let ((msg (format #f "Step into ~a" cur)))
+ (add-ephemeral-stepping-trap! cur (repl-next-resumer msg)
+ #\into? #t #\instruction? #f)
+ (throw 'quit)))
+
+(define-stack-command (step-instruction repl)
+ "step-instruction
+Step until control reaches a different instruction.
+
+Step until control reaches a different VM instruction."
+ (let ((msg (format #f "Step into ~a" cur)))
+ (add-ephemeral-stepping-trap! cur (repl-next-resumer msg)
+ #\into? #t #\instruction? #t)
+ (throw 'quit)))
+
+(define-stack-command (next repl)
+ "next
+Step until control reaches a different source location in the current frame.
+
+Step until control reaches a different source location in the current frame."
+ (let ((msg (format #f "Step into ~a" cur)))
+ (add-ephemeral-stepping-trap! cur (repl-next-resumer msg)
+ #\into? #f #\instruction? #f)
+ (throw 'quit)))
+
+(define-stack-command (next-instruction repl)
+ "next-instruction
+Step until control reaches a different instruction in the current frame.
+
+Step until control reaches a different VM instruction in the current frame."
+ (let ((msg (format #f "Step into ~a" cur)))
+ (add-ephemeral-stepping-trap! cur (repl-next-resumer msg)
+ #\into? #f #\instruction? #t)
+ (throw 'quit)))
+
+(define-meta-command (tracepoint repl (form))
+ "tracepoint PROCEDURE
+Add a tracepoint to PROCEDURE.
+
+A tracepoint will print out the procedure and its arguments, when it is
+called, and its return value(s) when it returns."
+ (let ((proc (repl-eval repl (repl-parse repl form))))
+ (if (not (procedure? proc))
+ (error "Not a procedure_ ~a" proc)
+ (let ((idx (add-trace-at-procedure-call! proc)))
+ (format #t "Trap ~a_ ~a.~%" idx (trap-name idx))))))
+
+(define-meta-command (traps repl)
+ "traps
+Show the set of currently attached traps.
+
+Show the set of currently attached traps (breakpoints and tracepoints)."
+ (let ((traps (list-traps)))
+ (if (null? traps)
+ (format #t "No traps set.~%")
+ (for-each (lambda (idx)
+ (format #t " ~a_ ~a~a~%"
+ idx (trap-name idx)
+ (if (trap-enabled? idx) "" " (disabled)")))
+ traps))))
+
+(define-meta-command (delete repl idx)
+ "delete IDX
+Delete a trap.
+
+Delete a trap."
+ (if (not (integer? idx))
+ (error "expected a trap index (a non-negative integer)" idx)
+ (delete-trap! idx)))
+
+(define-meta-command (disable repl idx)
+ "disable IDX
+Disable a trap.
+
+Disable a trap."
+ (if (not (integer? idx))
+ (error "expected a trap index (a non-negative integer)" idx)
+ (disable-trap! idx)))
+
+(define-meta-command (enable repl idx)
+ "enable IDX
+Enable a trap.
+
+Enable a trap."
+ (if (not (integer? idx))
+ (error "expected a trap index (a non-negative integer)" idx)
+ (enable-trap! idx)))
+
+(define-stack-command (registers repl)
+ "registers
+Print registers.
+
+Print the registers of the current frame."
+ (print-registers cur))
+
+(define-meta-command (width repl #\optional x)
+ "width [X]
+Set debug output width.
+
+Set the number of screen columns in the output from `backtrace' and
+`locals'."
+ (terminal-width x)
+ (format #t "Set screen width to ~a columns.~%" (terminal-width)))
+
+
+
+;;;
+;;; Inspection commands
+;;;
+
+(define-meta-command (inspect repl (form))
+ "inspect EXP
+Inspect the result(s) of evaluating EXP."
+ (call-with-values (repl-prepare-eval-thunk repl (repl-parse repl form))
+ (lambda args
+ (for-each %inspect args))))
+
+(define-meta-command (pretty-print repl (form))
+ "pretty-print EXP
+Pretty-print the result(s) of evaluating EXP."
+ (call-with-values (repl-prepare-eval-thunk repl (repl-parse repl form))
+ (lambda args
+ (for-each
+ (lambda (x)
+ (run-hook before-print-hook x)
+ (pp x))
+ args))))
+
+
+;;;
+;;; System commands
+;;;
+
+(define-meta-command (gc repl)
+ "gc
+Garbage collection."
+ (gc))
+
+(define-meta-command (statistics repl)
+ "statistics
+Display statistics."
+ (let ((this-tms (times))
+ (this-gcs (gc-stats))
+ (last-tms (repl-tm-stats repl))
+ (last-gcs (repl-gc-stats repl)))
+ ;; GC times
+ (let ((this-times (assq-ref this-gcs 'gc-times))
+ (last-times (assq-ref last-gcs 'gc-times)))
+ (display-diff-stat "GC times_" #t this-times last-times "times")
+ (newline))
+ ;; Memory size
+ (let ((this-heap (assq-ref this-gcs 'heap-size))
+ (this-free (assq-ref this-gcs 'heap-free-size)))
+ (display-stat-title "Memory size_" "current" "limit")
+ (display-stat "heap" #f (- this-heap this-free) this-heap "bytes")
+ (newline))
+ ;; Cells collected
+ (let ((this-alloc (assq-ref this-gcs 'heap-total-allocated))
+ (last-alloc (assq-ref last-gcs 'heap-total-allocated)))
+ (display-stat-title "Bytes allocated_" "diff" "total")
+ (display-diff-stat "allocated" #f this-alloc last-alloc "bytes")
+ (newline))
+ ;; GC time taken
+ (let ((this-total (assq-ref this-gcs 'gc-time-taken))
+ (last-total (assq-ref last-gcs 'gc-time-taken)))
+ (display-stat-title "GC time taken_" "diff" "total")
+ (display-time-stat "total" this-total last-total)
+ (newline))
+ ;; Process time spent
+ (let ((this-utime (tms_utime this-tms))
+ (last-utime (tms_utime last-tms))
+ (this-stime (tms_stime this-tms))
+ (last-stime (tms_stime last-tms))
+ (this-cutime (tms_cutime this-tms))
+ (last-cutime (tms_cutime last-tms))
+ (this-cstime (tms_cstime this-tms))
+ (last-cstime (tms_cstime last-tms)))
+ (display-stat-title "Process time spent_" "diff" "total")
+ (display-time-stat "user" this-utime last-utime)
+ (display-time-stat "system" this-stime last-stime)
+ (display-time-stat "child user" this-cutime last-cutime)
+ (display-time-stat "child system" this-cstime last-cstime)
+ (newline))
+ ;; Save statistics
+ ;; Save statistics
+ (set! (repl-tm-stats repl) this-tms)
+ (set! (repl-gc-stats repl) this-gcs)))
+
+(define (display-stat title flag field1 field2 unit)
+ (let ((fmt (format #f "~~20~AA ~~10@A /~~10@A ~~A~~%" (if flag "" "@"))))
+ (format #t fmt title field1 field2 unit)))
+
+(define (display-stat-title title field1 field2)
+ (display-stat title #t field1 field2 ""))
+
+(define (display-diff-stat title flag this last unit)
+ (display-stat title flag (- this last) this unit))
+
+(define (display-time-stat title this last)
+ (define (conv num)
+ (format #f "~10,2F" (exact->inexact (/ num internal-time-units-per-second))))
+ (display-stat title #f (conv (- this last)) (conv this) "s"))
+
+(define (display-mips-stat title this-time this-clock last-time last-clock)
+ (define (mips time clock)
+ (if (= time 0) "----" (format #f "~10,2F" (/ clock time 1000000.0))))
+ (display-stat title #f
+ (mips (- this-time last-time) (- this-clock last-clock))
+ (mips this-time this-clock) "mips"))
+;;; Repl common routines
+
+;; Copyright (C) 2001, 2008-2016 Free Software Foundation, Inc.
+
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code_
+
+(define-module (system repl common)
+ #\use-module (system base syntax)
+ #\use-module (system base compile)
+ #\use-module (system base language)
+ #\use-module (system base message)
+ #\use-module (system vm program)
+ #\autoload (language tree-il optimize) (optimize!)
+ #\use-module (ice-9 control)
+ #\use-module (ice-9 history)
+ #\export (<repl> make-repl repl-language repl-options
+ repl-tm-stats repl-gc-stats repl-debug
+ repl-welcome repl-prompt
+ repl-read repl-compile repl-prepare-eval-thunk repl-eval
+ repl-expand repl-optimize
+ repl-parse repl-print repl-option-ref repl-option-set!
+ repl-default-option-set! repl-default-prompt-set!
+ puts ->string user-error
+ *warranty* *copying* *version*))
+
+(define *version*
+ (format #f "GNU Guile ~A
+Copyright (C) 1995-2016 Free Software Foundation, Inc.
+
+Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
+This program is free software, and you are welcome to redistribute it
+under certain conditions; type `,show c' for details." (version)))
+
+(define *copying*
+"Guile 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.
+
+Guile 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
+<http_//www.gnu.org/licenses/lgpl.html>.")
+
+(define *warranty*
+"Guile is distributed WITHOUT ANY WARRANTY. The following
+sections from the GNU General Public License, version 3, should
+make that clear.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+See <http_//www.gnu.org/licenses/lgpl.html>, for more details.")
+
+
+;;;
+;;; Repl type
+;;;
+
+(define-record/keywords <repl>
+ language options tm-stats gc-stats debug)
+
+(define repl-default-options
+ (copy-tree
+ `((compile-options ,%auto-compilation-options #f)
+ (trace #f #f)
+ (interp #f #f)
+ (prompt #f ,(lambda (prompt)
+ (cond
+ ((not prompt) #f)
+ ((string? prompt) (lambda (repl) prompt))
+ ((thunk? prompt) (lambda (repl) (prompt)))
+ ((procedure? prompt) prompt)
+ (else (error "Invalid prompt" prompt)))))
+ (print #f ,(lambda (print)
+ (cond
+ ((not print) #f)
+ ((procedure? print) print)
+ (else (error "Invalid print procedure" print)))))
+ (value-history
+ ,(value-history-enabled?)
+ ,(lambda (x)
+ (if x (enable-value-history!) (disable-value-history!))
+ (->bool x)))
+ (on-error
+ debug
+ ,(let ((vals '(debug backtrace report pass)))
+ (lambda (x)
+ (if (memq x vals)
+ x
+ (error "Bad on-error value ~a; expected one of ~a" x vals))))))))
+
+(define %make-repl make-repl)
+(define* (make-repl lang #\optional debug)
+ (%make-repl #\language (if (language? lang)
+ lang
+ (lookup-language lang))
+ #\options (copy-tree repl-default-options)
+ #\tm-stats (times)
+ #\gc-stats (gc-stats)
+ #\debug debug))
+
+(define (repl-welcome repl)
+ (display *version*)
+ (newline)
+ (newline)
+ (display "Enter `,help' for help.\n"))
+
+(define (repl-prompt repl)
+ (cond
+ ((repl-option-ref repl 'prompt)
+ => (lambda (prompt) (prompt repl)))
+ (else
+ (format #f "~A@~A~A> " (language-name (repl-language repl))
+ (module-name (current-module))
+ (let ((level (length (cond
+ ((fluid-ref *repl-stack*) => cdr)
+ (else '())))))
+ (if (zero? level) "" (format #f " [~a]" level)))))))
+
+(define (repl-read repl)
+ (let ((reader (language-reader (repl-language repl))))
+ (reader (current-input-port) (current-module))))
+
+(define (repl-compile-options repl)
+ (repl-option-ref repl 'compile-options))
+
+(define (repl-compile repl form)
+ (let ((from (repl-language repl))
+ (opts (repl-compile-options repl)))
+ (compile form #\from from #\to 'objcode #\opts opts
+ #\env (current-module))))
+
+(define (repl-expand repl form)
+ (let ((from (repl-language repl))
+ (opts (repl-compile-options repl)))
+ (decompile (compile form #\from from #\to 'tree-il #\opts opts
+ #\env (current-module))
+ #\from 'tree-il #\to from)))
+
+(define (repl-optimize repl form)
+ (let ((from (repl-language repl))
+ (opts (repl-compile-options repl)))
+ (decompile (optimize! (compile form #\from from #\to 'tree-il #\opts opts
+ #\env (current-module))
+ (current-module)
+ opts)
+ #\from 'tree-il #\to from)))
+
+(define (repl-parse repl form)
+ (let ((parser (language-parser (repl-language repl))))
+ (if parser (parser form) form)))
+
+(define (repl-prepare-eval-thunk repl form)
+ (let* ((eval (language-evaluator (repl-language repl))))
+ (if (and eval
+ (or (null? (language-compilers (repl-language repl)))
+ (repl-option-ref repl 'interp)))
+ (lambda () (eval form (current-module)))
+ (make-program (repl-compile repl form)))))
+
+(define (repl-eval repl form)
+ (let ((thunk (repl-prepare-eval-thunk repl form)))
+ (% (thunk))))
+
+(define (repl-print repl val)
+ (if (not (eq? val *unspecified*))
+ (begin
+ (run-hook before-print-hook val)
+ (cond
+ ((repl-option-ref repl 'print)
+ => (lambda (print) (print repl val)))
+ (else
+ ;; The result of an evaluation is representable in scheme, and
+ ;; should be printed with the generic printer, `write'. The
+ ;; language-printer is something else_ it prints expressions of
+ ;; a given language, not the result of evaluation.
+ (write val)
+ (newline))))))
+
+(define (repl-option-ref repl key)
+ (cadr (or (assq key (repl-options repl))
+ (error "unknown repl option" key))))
+
+(define (repl-option-set! repl key val)
+ (let ((spec (or (assq key (repl-options repl))
+ (error "unknown repl option" key))))
+ (set-car! (cdr spec)
+ (if (procedure? (caddr spec))
+ ((caddr spec) val)
+ val))))
+
+(define (repl-default-option-set! key val)
+ (let ((spec (or (assq key repl-default-options)
+ (error "unknown repl option" key))))
+ (set-car! (cdr spec)
+ (if (procedure? (caddr spec))
+ ((caddr spec) val)
+ val))))
+
+(define (repl-default-prompt-set! prompt)
+ (repl-default-option-set! 'prompt prompt))
+
+
+;;;
+;;; Utilities
+;;;
+
+(define (puts x) (display x) (newline))
+
+(define (->string x)
+ (object->string x display))
+
+(define (user-error msg . args)
+ (throw 'user-error #f msg args #f))
+;;; Cooperative REPL server
+
+;; Copyright (C) 2014, 2016 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Code_
+
+(define-module (system repl coop-server)
+ #\use-module (ice-9 match)
+ #\use-module (ice-9 receive)
+ #\use-module (ice-9 threads)
+ #\use-module (ice-9 q)
+ #\use-module (srfi srfi-9)
+ #\use-module ((system repl repl)
+ #\select (start-repl* prompting-meta-read))
+ #\use-module ((system repl server)
+ #\select (run-server* make-tcp-server-socket
+ add-open-socket! close-socket!
+ guard-against-http-request))
+ #\export (spawn-coop-repl-server
+ poll-coop-repl-server))
+
+(define-record-type <coop-repl-server>
+ (%make-coop-repl-server mutex queue)
+ coop-repl-server?
+ (mutex coop-repl-server-mutex)
+ (queue coop-repl-server-queue))
+
+(define (make-coop-repl-server)
+ (%make-coop-repl-server (make-mutex) (make-q)))
+
+(define (coop-repl-server-eval coop-server opcode . args)
+ "Queue a new instruction with the symbolic name OPCODE and an arbitrary
+number of arguments, to be processed the next time COOP-SERVER is polled."
+ (with-mutex (coop-repl-server-mutex coop-server)
+ (enq! (coop-repl-server-queue coop-server)
+ (cons opcode args))))
+
+(define-record-type <coop-repl>
+ (%make-coop-repl mutex condvar thunk cont)
+ coop-repl?
+ (mutex coop-repl-mutex)
+ (condvar coop-repl-condvar) ; signaled when thunk becomes non-#f
+ (thunk coop-repl-read-thunk set-coop-repl-read-thunk!)
+ (cont coop-repl-cont set-coop-repl-cont!))
+
+(define (make-coop-repl)
+ (%make-coop-repl (make-mutex) (make-condition-variable) #f #f))
+
+(define (coop-repl-read coop-repl)
+ "Read an expression via the thunk stored in COOP-REPL."
+ (let ((thunk
+ (with-mutex (coop-repl-mutex coop-repl)
+ (unless (coop-repl-read-thunk coop-repl)
+ (wait-condition-variable (coop-repl-condvar coop-repl)
+ (coop-repl-mutex coop-repl)))
+ (let ((thunk (coop-repl-read-thunk coop-repl)))
+ (unless thunk
+ (error "coop-repl-read_ condvar signaled, but thunk is #f!"))
+ (set-coop-repl-read-thunk! coop-repl #f)
+ thunk))))
+ (thunk)))
+
+(define (store-repl-cont cont coop-repl)
+ "Save the partial continuation CONT within COOP-REPL."
+ (set-coop-repl-cont! coop-repl
+ (lambda (exp)
+ (coop-repl-prompt
+ (lambda () (cont exp))))))
+
+(define (coop-repl-prompt thunk)
+ "Apply THUNK within a prompt for cooperative REPLs."
+ (call-with-prompt 'coop-repl-prompt thunk store-repl-cont))
+
+(define (make-coop-reader coop-repl)
+ "Return a new procedure for reading user input from COOP-REPL. The
+generated procedure passes the responsibility of reading input to
+another thread and aborts the cooperative REPL prompt."
+ (lambda (repl)
+ (let ((read-thunk
+ ;; Need to preserve the REPL stack and current module across
+ ;; threads.
+ (let ((stack (fluid-ref *repl-stack*))
+ (module (current-module)))
+ (lambda ()
+ (with-fluids ((*repl-stack* stack))
+ (set-current-module module)
+ (prompting-meta-read repl))))))
+ (with-mutex (coop-repl-mutex coop-repl)
+ (when (coop-repl-read-thunk coop-repl)
+ (error "coop-reader_ read-thunk is not #f!"))
+ (set-coop-repl-read-thunk! coop-repl read-thunk)
+ (signal-condition-variable (coop-repl-condvar coop-repl))))
+ (abort-to-prompt 'coop-repl-prompt coop-repl)))
+
+(define (reader-loop coop-server coop-repl)
+ "Run an unbounded loop that reads an expression for COOP-REPL and
+stores the expression within COOP-SERVER for later evaluation."
+ (coop-repl-server-eval coop-server 'eval coop-repl
+ (coop-repl-read coop-repl))
+ (reader-loop coop-server coop-repl))
+
+(define (poll-coop-repl-server coop-server)
+ "Poll the cooperative REPL server COOP-SERVER and apply a pending
+operation if there is one, such as evaluating an expression typed at the
+REPL prompt. This procedure must be called from the same thread that
+called spawn-coop-repl-server."
+ (let ((op (with-mutex (coop-repl-server-mutex coop-server)
+ (let ((queue (coop-repl-server-queue coop-server)))
+ (and (not (q-empty? queue))
+ (deq! queue))))))
+ (when op
+ (match op
+ (('new-repl client)
+ (start-repl-client coop-server client))
+ (('eval coop-repl exp)
+ ((coop-repl-cont coop-repl) exp))))
+ *unspecified*))
+
+(define (start-coop-repl coop-server)
+ "Start a new cooperative REPL process for COOP-SERVER."
+ ;; Calling stop-server-and-clients! from a REPL will cause an
+ ;; exception to be thrown when trying to read from the socket that has
+ ;; been closed, so we catch that here.
+ (false-if-exception
+ (let ((coop-repl (make-coop-repl)))
+ (make-thread reader-loop coop-server coop-repl)
+ (start-repl* (current-language) #f (make-coop-reader coop-repl)))))
+
+(define (run-coop-repl-server coop-server server-socket)
+ "Start the cooperative REPL server for COOP-SERVER using the socket
+SERVER-SOCKET."
+ (run-server* server-socket (make-coop-client-proc coop-server)))
+
+(define* (spawn-coop-repl-server
+ #\optional (server-socket (make-tcp-server-socket)))
+ "Create and return a new cooperative REPL server object, and spawn a
+new thread to listen for connections on SERVER-SOCKET. Proper
+functioning of the REPL server requires that poll-coop-repl-server be
+called periodically on the returned server object."
+ (let ((coop-server (make-coop-repl-server)))
+ (make-thread run-coop-repl-server
+ coop-server
+ server-socket)
+ coop-server))
+
+(define (make-coop-client-proc coop-server)
+ "Return a new procedure that is used to schedule the creation of a new
+cooperative REPL for COOP-SERVER."
+ (lambda (client addr)
+ (coop-repl-server-eval coop-server 'new-repl client)))
+
+(define (start-repl-client coop-server client)
+ "Run a cooperative REPL for COOP-SERVER within a prompt. All input
+and output is sent over the socket CLIENT."
+
+ ;; Add the client to the list of open sockets, with a 'force-close'
+ ;; procedure that closes the underlying file descriptor. We do it
+ ;; this way because we cannot close the port itself safely from
+ ;; another thread.
+ (add-open-socket! client (lambda () (close-fdes (fileno client))))
+
+ (guard-against-http-request client)
+
+ (with-continuation-barrier
+ (lambda ()
+ (coop-repl-prompt
+ (lambda ()
+ (parameterize ((current-input-port client)
+ (current-output-port client)
+ (current-error-port client)
+ (current-warning-port client))
+ (with-fluids ((*repl-stack* '()))
+ (save-module-excursion
+ (lambda ()
+ (start-coop-repl coop-server)))))
+
+ ;; This may fail if 'stop-server-and-clients!' is called,
+ ;; because the 'force-close' procedure above closes the
+ ;; underlying file descriptor instead of the port itself.
+ (false-if-exception
+ (close-socket! client)))))))
+;;; Guile VM debugging facilities
+
+;;; Copyright (C) 2001, 2009, 2010, 2011 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code_
+
+(define-module (system repl debug)
+ #\use-module (system base pmatch)
+ #\use-module (system base syntax)
+ #\use-module (system base language)
+ #\use-module (system vm vm)
+ #\use-module (system vm frame)
+ #\use-module (ice-9 rdelim)
+ #\use-module (ice-9 pretty-print)
+ #\use-module (ice-9 format)
+ #\use-module ((system vm inspect) #\select ((inspect . %inspect)))
+ #\use-module (system vm program)
+ #\export (<debug>
+ make-debug debug?
+ debug-frames debug-index debug-error-message debug-for-trap?
+ terminal-width
+ print-registers print-locals print-frame print-frames frame->module
+ stack->vector narrow-stack->vector
+ frame->stack-vector))
+
+;; TODO_
+;;
+;; eval expression in context of frame
+;; set local variable in frame
+;; step until greater source line
+;; watch expression
+;; set printing width
+;; disassemble the current function
+;; inspect any object
+
+;;;
+;;; Debugger
+;;;
+;;; The actual interaction loop of the debugger is run by the repl. This module
+;;; simply exports a data structure to hold the debugger state, along with its
+;;; accessors, and provides some helper functions.
+;;;
+
+(define-record <debug> frames index error-message for-trap?)
+
+
+
+;; A fluid, because terminals are usually implicitly associated with
+;; threads.
+;;
+(define terminal-width
+ (let ((set-width (make-fluid)))
+ (case-lambda
+ (()
+ (or (fluid-ref set-width)
+ (let ((w (false-if-exception (string->number (getenv "COLUMNS")))))
+ (and (integer? w) (exact? w) (> w 0) w))
+ 72))
+ ((w)
+ (if (or (not w) (and (integer? w) (exact? w) (> w 0)))
+ (fluid-set! set-width w)
+ (error "Expected a column number (a positive integer)" w))))))
+
+
+
+
+(define (reverse-hashq h)
+ (let ((ret (make-hash-table)))
+ (hash-for-each
+ (lambda (k v)
+ (hashq-set! ret v (cons k (hashq-ref ret v '()))))
+ h)
+ ret))
+
+(define* (print-registers frame #\optional (port (current-output-port))
+ #\key (per-line-prefix " "))
+ (define (print fmt val)
+ (display per-line-prefix port)
+ (run-hook before-print-hook val)
+ (format port fmt val))
+
+ (format port "~aRegisters_~%" per-line-prefix)
+ (print "ip = ~d\n" (frame-instruction-pointer frame))
+ (print "sp = #x~x\n" (frame-stack-pointer frame))
+ (print "fp = #x~x\n" (frame-address frame)))
+
+(define* (print-locals frame #\optional (port (current-output-port))
+ #\key (width (terminal-width)) (per-line-prefix " "))
+ (let ((bindings (frame-bindings frame)))
+ (cond
+ ((null? bindings)
+ (format port "~aNo local variables.~%" per-line-prefix))
+ (else
+ (format port "~aLocal variables_~%" per-line-prefix)
+ (for-each
+ (lambda (binding)
+ (let ((v (let ((x (frame-local-ref frame (binding_index binding))))
+ (if (binding_boxed? binding)
+ (variable-ref x)
+ x))))
+ (display per-line-prefix port)
+ (run-hook before-print-hook v)
+ (format port "~a~_[~; (boxed)~] = ~v_@y\n"
+ (binding_name binding) (binding_boxed? binding) width v)))
+ (frame-bindings frame))))))
+
+(define* (print-frame frame #\optional (port (current-output-port))
+ #\key index (width (terminal-width)) (full? #f)
+ (last-source #f) next-source?)
+ (define (source_pretty-file source)
+ (if source
+ (or (source_file source) "current input")
+ "unknown file"))
+ (let* ((source ((if next-source? frame-next-source frame-source) frame))
+ (file (source_pretty-file source))
+ (line (and=> source source_line-for-user))
+ (col (and=> source source_column)))
+ (if (and file (not (equal? file (source_pretty-file last-source))))
+ (format port "~&In ~a_~&" file))
+ (format port "~9@a~_[~*~3_~;~3d~] ~v_@y~%"
+ (if line (format #f "~a_~a" line col) "")
+ index index width (frame-call-representation frame))
+ (if full?
+ (print-locals frame #\width width
+ #\per-line-prefix " "))))
+
+(define* (print-frames frames
+ #\optional (port (current-output-port))
+ #\key (width (terminal-width)) (full? #f)
+ (forward? #f) count for-trap?)
+ (let* ((len (vector-length frames))
+ (lower-idx (if (or (not count) (positive? count))
+ 0
+ (max 0 (+ len count))))
+ (upper-idx (if (and count (negative? count))
+ (1- len)
+ (1- (if count (min count len) len))))
+ (inc (if forward? 1 -1)))
+ (let lp ((i (if forward? lower-idx upper-idx))
+ (last-source #f))
+ (if (<= lower-idx i upper-idx)
+ (let* ((frame (vector-ref frames i)))
+ (print-frame frame port #\index i #\width width #\full? full?
+ #\last-source last-source
+ #\next-source? (and (zero? i) for-trap?))
+ (lp (+ i inc)
+ (if (and (zero? i) for-trap?)
+ (frame-next-source frame)
+ (frame-source frame))))))))
+
+;; Ideally here we would have something much more syntactic, in that a set! to a
+;; local var that is not settable would raise an error, and export etc forms
+;; would modify the module in question_ but alack, this is what we have now.
+;; Patches welcome!
+(define (frame->module frame)
+ (let ((proc (frame-procedure frame)))
+ (if (program? proc)
+ (let* ((mod (or (program-module proc) (current-module)))
+ (mod* (make-module)))
+ (module-use! mod* mod)
+ (for-each
+ (lambda (binding)
+ (let* ((x (frame-local-ref frame (binding_index binding)))
+ (var (if (binding_boxed? binding) x (make-variable x))))
+ (format #t
+ "~_[Read-only~;Mutable~] local variable ~a = ~70_@y\n"
+ (binding_boxed? binding)
+ (binding_name binding)
+ (if (variable-bound? var) (variable-ref var) var))
+ (module-add! mod* (binding_name binding) var)))
+ (frame-bindings frame))
+ mod*)
+ (current-module))))
+
+
+(define (stack->vector stack)
+ (let* ((len (stack-length stack))
+ (v (make-vector len)))
+ (if (positive? len)
+ (let lp ((i 0) (frame (stack-ref stack 0)))
+ (if (< i len)
+ (begin
+ (vector-set! v i frame)
+ (lp (1+ i) (frame-previous frame))))))
+ v))
+
+(define (narrow-stack->vector stack . args)
+ (let ((narrowed (apply make-stack (stack-ref stack 0) args)))
+ (if narrowed
+ (stack->vector narrowed)
+ #()))) ; ? Can be the case for a tail-call to `throw' tho
+
+(define (frame->stack-vector frame)
+ (let ((tag (and (pair? (fluid-ref %stacks))
+ (cdar (fluid-ref %stacks)))))
+ (narrow-stack->vector
+ (make-stack frame)
+ ;; Take the stack from the given frame, cutting 0
+ ;; frames.
+ 0
+ ;; Narrow the end of the stack to the most recent
+ ;; start-stack.
+ tag
+ ;; And one more frame, because %start-stack
+ ;; invoking the start-stack thunk has its own frame
+ ;; too.
+ 0 (and tag 1))))
+
+;; (define (debug)
+;; (run-debugger
+;; (narrow-stack->vector
+;; (make-stack #t)
+;; ;; Narrow the `make-stack' frame and the `debug' frame
+;; 2
+;; ;; Narrow the end of the stack to the most recent start-stack.
+;; (and (pair? (fluid-ref %stacks))
+;; (cdar (fluid-ref %stacks))))))
+
+;;; Describe objects
+
+;; Copyright (C) 2001, 2009, 2011 Free Software Foundation, Inc.
+
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code_
+
+(define-module (system repl describe)
+ #\use-module (oop goops)
+ #\use-module (ice-9 regex)
+ #\use-module (ice-9 format)
+ #\use-module (ice-9 and-let-star)
+ #\export (describe))
+
+(define-method (describe (symbol <symbol>))
+ (format #t "`~s' is " symbol)
+ (if (not (defined? symbol))
+ (display "not defined in the current module.\n")
+ (describe-object (module-ref (current-module) symbol))))
+
+
+;;;
+;;; Display functions
+;;;
+
+(define (safe-class-name class)
+ (if (slot-bound? class 'name)
+ (class-name class)
+ class))
+
+(define-method (display-class class . args)
+ (let* ((name (safe-class-name class))
+ (desc (if (pair? args) (car args) name)))
+ (if (eq? *describe-format* 'tag)
+ (format #t "@class{~a}{~a}" name desc)
+ (format #t "~a" desc))))
+
+(define (display-list title list)
+ (if title (begin (display title) (display "_\n\n")))
+ (if (null? list)
+ (display "(not defined)\n")
+ (for-each display-summary list)))
+
+(define (display-slot-list title instance list)
+ (if title (begin (display title) (display "_\n\n")))
+ (if (null? list)
+ (display "(not defined)\n")
+ (for-each (lambda (slot)
+ (let ((name (slot-definition-name slot)))
+ (display "Slot_ ")
+ (display name)
+ (if (and instance (slot-bound? instance name))
+ (begin
+ (display " = ")
+ (display (slot-ref instance name))))
+ (newline)))
+ list)))
+
+(define (display-file location)
+ (display "Defined in ")
+ (if (eq? *describe-format* 'tag)
+ (format #t "@location{~a}.\n" location)
+ (format #t "`~a'.\n" location)))
+
+(define (format-documentation doc)
+ (with-current-buffer (make-buffer #\text doc)
+ (lambda ()
+ (let ((regexp (make-regexp "@([a-z]*)(\\{([^}]*)\\})?")))
+ (do-while (match (re-search-forward regexp))
+ (let ((key (string->symbol (match_substring match 1)))
+ (value (match_substring match 3)))
+ (case key
+ ((deffnx)
+ (delete-region! (match_start match)
+ (begin (forward-line) (point))))
+ ((var)
+ (replace-match! match 0 (string-upcase value)))
+ ((code)
+ (replace-match! match 0 (string-append "`" value "'")))))))
+ (display (string (current-buffer)))
+ (newline))))
+
+
+;;;
+;;; Top
+;;;
+
+(define description-table
+ (list
+ (cons <boolean> "a boolean")
+ (cons <null> "an empty list")
+ (cons <integer> "an integer")
+ (cons <real> "a real number")
+ (cons <complex> "a complex number")
+ (cons <char> "a character")
+ (cons <symbol> "a symbol")
+ (cons <keyword> "a keyword")
+ (cons <promise> "a promise")
+ (cons <hook> "a hook")
+ (cons <fluid> "a fluid")
+ (cons <stack> "a stack")
+ (cons <variable> "a variable")
+ (cons <regexp> "a regexp object")
+ (cons <module> "a module object")
+ (cons <unknown> "an unknown object")))
+
+(define-generic describe-object)
+(export describe-object)
+
+(define-method (describe-object (obj <top>))
+ (display-type obj)
+ (display-location obj)
+ (newline)
+ (display-value obj)
+ (newline)
+ (display-documentation obj))
+
+(define-generic display-object)
+(define-generic display-summary)
+(define-generic display-type)
+(define-generic display-value)
+(define-generic display-location)
+(define-generic display-description)
+(define-generic display-documentation)
+(export display-object display-summary display-type display-value
+ display-location display-description display-documentation)
+
+(define-method (display-object (obj <top>))
+ (write obj))
+
+(define-method (display-summary (obj <top>))
+ (display "Value_ ")
+ (display-object obj)
+ (newline))
+
+(define-method (display-type (obj <top>))
+ (cond
+ ((eof-object? obj) (display "the end-of-file object"))
+ ((unspecified? obj) (display "unspecified"))
+ (else (let ((class (class-of obj)))
+ (display-class class (or (assq-ref description-table class)
+ (safe-class-name class))))))
+ (display ".\n"))
+
+(define-method (display-value (obj <top>))
+ (if (not (unspecified? obj))
+ (begin (display-object obj) (newline))))
+
+(define-method (display-location (obj <top>))
+ *unspecified*)
+
+(define-method (display-description (obj <top>))
+ (let* ((doc (with-output-to-string (lambda () (display-documentation obj))))
+ (index (string-index doc #\newline)))
+ (display (substring doc 0 (1+ index)))))
+
+(define-method (display-documentation (obj <top>))
+ (display "Not documented.\n"))
+
+
+;;;
+;;; Pairs
+;;;
+
+(define-method (display-type (obj <pair>))
+ (cond
+ ((list? obj) (display-class <list> "a list"))
+ ((pair? (cdr obj)) (display "an improper list"))
+ (else (display-class <pair> "a pair")))
+ (display ".\n"))
+
+
+;;;
+;;; Strings
+;;;
+
+(define-method (display-type (obj <string>))
+ (if (read-only-string? 'obj)
+ (display "a read-only string")
+ (display-class <string> "a string"))
+ (display ".\n"))
+
+
+;;;
+;;; Procedures
+;;;
+
+(define-method (display-object (obj <procedure>))
+ (cond
+ ;; FIXME_ VM programs, ...
+ (else
+ ;; Primitive procedure. Let's lookup the dictionary.
+ (and-let* ((entry (lookup-procedure obj)))
+ (let ((name (entry-property entry 'name))
+ (print-arg (lambda (arg)
+ (display " ")
+ (display (string-upcase (symbol->string arg))))))
+ (display "(")
+ (display name)
+ (and-let* ((args (entry-property entry 'args)))
+ (for-each print-arg args))
+ (and-let* ((opts (entry-property entry 'opts)))
+ (display " &optional")
+ (for-each print-arg opts))
+ (and-let* ((rest (entry-property entry 'rest)))
+ (display " &rest")
+ (print-arg rest))
+ (display ")"))))))
+
+(define-method (display-summary (obj <procedure>))
+ (display "Procedure_ ")
+ (display-object obj)
+ (newline)
+ (display " ")
+ (display-description obj))
+
+(define-method (display-type (obj <procedure>))
+ (cond
+ ((and (thunk? obj) (not (procedure-name obj))) (display "a thunk"))
+ ((procedure-with-setter? obj)
+ (display-class <procedure-with-setter> "a procedure with setter"))
+ (else (display-class <procedure> "a procedure")))
+ (display ".\n"))
+
+(define-method (display-location (obj <procedure>))
+ (and-let* ((entry (lookup-procedure obj)))
+ (display-file (entry-file entry))))
+
+(define-method (display-documentation (obj <procedure>))
+ (cond ((or (procedure-documentation obj)
+ (and=> (lookup-procedure obj) entry-text))
+ => format-documentation)
+ (else (next-method))))
+
+
+;;;
+;;; Classes
+;;;
+
+(define-method (describe-object (obj <class>))
+ (display-type obj)
+ (display-location obj)
+ (newline)
+ (display-documentation obj)
+ (newline)
+ (display-value obj))
+
+(define-method (display-summary (obj <class>))
+ (display "Class_ ")
+ (display-class obj)
+ (newline)
+ (display " ")
+ (display-description obj))
+
+(define-method (display-type (obj <class>))
+ (display-class <class> "a class")
+ (if (not (eq? (class-of obj) <class>))
+ (begin (display " of ") (display-class (class-of obj))))
+ (display ".\n"))
+
+(define-method (display-value (obj <class>))
+ (display-list "Class precedence list" (class-precedence-list obj))
+ (newline)
+ (display-list "Direct superclasses" (class-direct-supers obj))
+ (newline)
+ (display-list "Direct subclasses" (class-direct-subclasses obj))
+ (newline)
+ (display-slot-list "Direct slots" #f (class-direct-slots obj))
+ (newline)
+ (display-list "Direct methods" (class-direct-methods obj)))
+
+
+;;;
+;;; Instances
+;;;
+
+(define-method (display-type (obj <object>))
+ (display-class <object> "an instance")
+ (display " of class ")
+ (display-class (class-of obj))
+ (display ".\n"))
+
+(define-method (display-value (obj <object>))
+ (display-slot-list #f obj (class-slots (class-of obj))))
+
+
+;;;
+;;; Generic functions
+;;;
+
+(define-method (display-type (obj <generic>))
+ (display-class <generic> "a generic function")
+ (display " of class ")
+ (display-class (class-of obj))
+ (display ".\n"))
+
+(define-method (display-value (obj <generic>))
+ (display-list #f (generic-function-methods obj)))
+
+
+;;;
+;;; Methods
+;;;
+
+(define-method (display-object (obj <method>))
+ (display "(")
+ (let ((gf (method-generic-function obj)))
+ (display (if gf (generic-function-name gf) "#<anonymous>")))
+ (let loop ((args (method-specializers obj)))
+ (cond
+ ((null? args))
+ ((pair? args)
+ (display " ")
+ (display-class (car args))
+ (loop (cdr args)))
+ (else (display " . ") (display-class args))))
+ (display ")"))
+
+(define-method (display-summary (obj <method>))
+ (display "Method_ ")
+ (display-object obj)
+ (newline)
+ (display " ")
+ (display-description obj))
+
+(define-method (display-type (obj <method>))
+ (display-class <method> "a method")
+ (display " of class ")
+ (display-class (class-of obj))
+ (display ".\n"))
+
+(define-method (display-documentation (obj <method>))
+ (let ((doc (procedure-documentation (method-procedure obj))))
+ (if doc (format-documentation doc) (next-method))))
+;;; Error handling in the REPL
+
+;; Copyright (C) 2001, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Code_
+
+(define-module (system repl error-handling)
+ #\use-module (system base pmatch)
+ #\use-module (system vm trap-state)
+ #\use-module (system repl debug)
+ #\use-module (ice-9 format)
+ #\export (call-with-error-handling
+ with-error-handling))
+
+
+
+
+;;;
+;;; Error handling via repl debugging
+;;;
+
+(define (error-string stack key args)
+ (call-with-output-string
+ (lambda (port)
+ (let ((frame (and (< 0 (vector-length stack)) (vector-ref stack 0))))
+ (print-exception port frame key args)))))
+
+(define* (call-with-error-handling thunk #\key
+ (on-error 'debug) (post-error 'catch)
+ (pass-keys '(quit)) (trap-handler 'debug))
+ (let ((in (current-input-port))
+ (out (current-output-port))
+ (err (current-error-port)))
+ (define (with-saved-ports thunk)
+ (with-input-from-port in
+ (lambda ()
+ (with-output-to-port out
+ (lambda ()
+ (with-error-to-port err
+ thunk))))))
+
+ (define (debug-trap-handler frame trap-idx trap-name)
+ (let* ((tag (and (pair? (fluid-ref %stacks))
+ (cdar (fluid-ref %stacks))))
+ (stack (narrow-stack->vector
+ (make-stack frame)
+ ;; Take the stack from the given frame, cutting 0
+ ;; frames.
+ 0
+ ;; Narrow the end of the stack to the most recent
+ ;; start-stack.
+ tag
+ ;; And one more frame, because %start-stack
+ ;; invoking the start-stack thunk has its own frame
+ ;; too.
+ 0 (and tag 1)))
+ (error-msg (if trap-idx
+ (format #f "Trap ~d_ ~a" trap-idx trap-name)
+ trap-name))
+ (debug (make-debug stack 0 error-msg #t)))
+ (with-saved-ports
+ (lambda ()
+ (if trap-idx
+ (begin
+ (format #t "~a~%" error-msg)
+ (format #t "Entering a new prompt. ")
+ (format #t "Type `,bt' for a backtrace or `,q' to continue.\n")))
+ ((@ (system repl repl) start-repl) #\debug debug)))))
+
+ (define (null-trap-handler frame trap-idx trap-name)
+ #t)
+
+ (define le-trap-handler
+ (case trap-handler
+ ((debug) debug-trap-handler)
+ ((pass) null-trap-handler)
+ ((disabled) #f)
+ (else (error "Unknown trap-handler strategy" trap-handler))))
+
+ (catch #t
+ (lambda ()
+ (with-default-trap-handler le-trap-handler
+ (lambda () (%start-stack #t thunk))))
+
+ (case post-error
+ ((report)
+ (lambda (key . args)
+ (if (memq key pass-keys)
+ (apply throw key args)
+ (begin
+ (with-saved-ports
+ (lambda ()
+ (run-hook before-error-hook)
+ (print-exception err #f key args)
+ (run-hook after-error-hook)
+ (force-output err)))
+ (if #f #f)))))
+ ((catch)
+ (lambda (key . args)
+ (if (memq key pass-keys)
+ (apply throw key args))))
+ (else
+ (if (procedure? post-error)
+ (lambda (k . args)
+ (apply (if (memq k pass-keys) throw post-error) k args))
+ (error "Unknown post-error strategy" post-error))))
+
+ (case on-error
+ ((debug)
+ (lambda (key . args)
+ (if (not (memq key pass-keys))
+ (let* ((tag (and (pair? (fluid-ref %stacks))
+ (cdar (fluid-ref %stacks))))
+ (stack (narrow-stack->vector
+ (make-stack #t)
+ ;; Cut three frames from the top of the stack_
+ ;; make-stack, this one, and the throw handler.
+ 3
+ ;; Narrow the end of the stack to the most recent
+ ;; start-stack.
+ tag
+ ;; And one more frame, because %start-stack invoking
+ ;; the start-stack thunk has its own frame too.
+ 0 (and tag 1)))
+ (error-msg (error-string stack key args))
+ (debug (make-debug stack 0 error-msg #f)))
+ (with-saved-ports
+ (lambda ()
+ (format #t "~a~%" error-msg)
+ (format #t "Entering a new prompt. ")
+ (format #t "Type `,bt' for a backtrace or `,q' to continue.\n")
+ ((@ (system repl repl) start-repl) #\debug debug)))))))
+ ((report)
+ (lambda (key . args)
+ (if (not (memq key pass-keys))
+ (begin
+ (with-saved-ports
+ (lambda ()
+ (run-hook before-error-hook)
+ (print-exception err #f key args)
+ (run-hook after-error-hook)
+ (force-output err)))
+ (if #f #f)))))
+ ((backtrace)
+ (lambda (key . args)
+ (if (not (memq key pass-keys))
+ (let* ((tag (and (pair? (fluid-ref %stacks))
+ (cdar (fluid-ref %stacks))))
+ (frames (narrow-stack->vector
+ (make-stack #t)
+ ;; Narrow as above, for the debugging case.
+ 3 tag 0 (and tag 1))))
+ (with-saved-ports
+ (lambda ()
+ (print-frames frames)
+ (run-hook before-error-hook)
+ (print-exception err #f key args)
+ (run-hook after-error-hook)
+ (force-output err)))
+ (if #f #f)))))
+ ((pass)
+ (lambda (key . args)
+ ;; fall through to rethrow
+ #t))
+ (else
+ (if (procedure? on-error)
+ (lambda (k . args)
+ (apply (if (memq k pass-keys) throw on-error) k args))
+ (error "Unknown on-error strategy" on-error)))))))
+
+(define-syntax-rule (with-error-handling form)
+ (call-with-error-handling (lambda () form)))
+;;; Read-Eval-Print Loop
+
+;; Copyright (C) 2001, 2009, 2010, 2011, 2013,
+;; 2014 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Code_
+
+(define-module (system repl repl)
+ #\use-module (system base syntax)
+ #\use-module (system base pmatch)
+ #\use-module (system base compile)
+ #\use-module (system base language)
+ #\use-module (system vm vm)
+ #\use-module (system repl error-handling)
+ #\use-module (system repl common)
+ #\use-module (system repl command)
+ #\use-module (ice-9 control)
+ #\export (start-repl run-repl))
+
+
+;;;
+;;; Comments
+;;;
+;;; (You don't want a comment to force a continuation line.)
+;;;
+
+(define (read-scheme-line-comment port)
+ (let lp ()
+ (let ((ch (read-char port)))
+ (or (eof-object? ch)
+ (eqv? ch #\newline)
+ (lp)))))
+
+(define (read-scheme-datum-comment port)
+ (read port))
+
+;; ch is a peeked char
+(define (read-comment lang port ch)
+ (and (eq? (language-name lang) 'scheme)
+ (case ch
+ ((#\;)
+ (read-char port)
+ (read-scheme-line-comment port)
+ #t)
+ ((#\#)
+ (read-char port)
+ (case (peek-char port)
+ ((#\;)
+ (read-char port)
+ (read-scheme-datum-comment port)
+ #t)
+ ;; Not doing R6RS block comments because of the possibility
+ ;; of read-hash extensions. Lame excuse. Not doing scsh
+ ;; block comments either, because I don't feel like handling
+ ;; .
+ (else
+ (unread-char #\# port)
+ #f)))
+ (else
+ #f))))
+
+
+
+;;;
+;;; Meta commands
+;;;
+
+(define meta-command-token (cons 'meta 'command))
+
+(define (meta-reader lang env)
+ (lambda* (#\optional (port (current-input-port)))
+ (with-input-from-port port
+ (lambda ()
+ (let ((ch (flush-leading-whitespace)))
+ (cond ((eof-object? ch)
+ (read-char)) ; consume the EOF and return it
+ ((eqv? ch #\,)
+ (read-char)
+ meta-command-token)
+ ((read-comment lang port ch)
+ *unspecified*)
+ (else ((language-reader lang) port env))))))))
+
+(define (flush-all-input)
+ (if (and (char-ready?)
+ (not (eof-object? (peek-char))))
+ (begin
+ (read-char)
+ (flush-all-input))))
+
+;; repl-reader is a function defined in boot-9.scm, and is replaced by
+;; something else if readline has been activated. much of this hoopla is
+;; to be able to re-use the existing readline machinery.
+;;
+;; Catches read errors, returning *unspecified* in that case.
+;;
+;; Note_ although not exported, this is used by (system repl coop-server)
+(define (prompting-meta-read repl)
+ (catch #t
+ (lambda ()
+ (repl-reader (lambda () (repl-prompt repl))
+ (meta-reader (repl-language repl) (current-module))))
+ (lambda (key . args)
+ (case key
+ ((quit)
+ (apply throw key args))
+ (else
+ (format (current-output-port) "While reading expression_\n")
+ (print-exception (current-output-port) #f key args)
+ (flush-all-input)
+ *unspecified*)))))
+
+
+
+;;;
+;;; The repl
+;;;
+
+(define* (start-repl #\optional (lang (current-language)) #\key debug)
+ (start-repl* lang debug prompting-meta-read))
+
+;; Note_ although not exported, this is used by (system repl coop-server)
+(define (start-repl* lang debug prompting-meta-read)
+ ;; ,language at the REPL will update the current-language. Make
+ ;; sure that it does so in a new dynamic scope.
+ (parameterize ((current-language lang))
+ (run-repl* (make-repl lang debug) prompting-meta-read)))
+
+;; (put 'abort-on-error 'scheme-indent-function 1)
+(define-syntax-rule (abort-on-error string exp)
+ (catch #t
+ (lambda () exp)
+ (lambda (key . args)
+ (format #t "While ~A_~%" string)
+ (print-exception (current-output-port) #f key args)
+ (abort))))
+
+(define (run-repl repl)
+ (run-repl* repl prompting-meta-read))
+
+(define (run-repl* repl prompting-meta-read)
+ (define (with-stack-and-prompt thunk)
+ (call-with-prompt (default-prompt-tag)
+ (lambda () (start-stack #t (thunk)))
+ (lambda (k proc)
+ (with-stack-and-prompt (lambda () (proc k))))))
+
+ (% (with-fluids ((*repl-stack*
+ (cons repl (or (fluid-ref *repl-stack*) '()))))
+ (if (null? (cdr (fluid-ref *repl-stack*)))
+ (repl-welcome repl))
+ (let prompt-loop ()
+ (let ((exp (prompting-meta-read repl)))
+ (cond
+ ((eqv? exp *unspecified*)) ; read error or comment, pass
+ ((eq? exp meta-command-token)
+ (catch #t
+ (lambda ()
+ (meta-command repl))
+ (lambda (k . args)
+ (if (eq? k 'quit)
+ (abort args)
+ (begin
+ (format #t "While executing meta-command_~%")
+ (print-exception (current-output-port) #f k args))))))
+ ((eof-object? exp)
+ (newline)
+ (abort '()))
+ (else
+ ;; since the input port is line-buffered, consume up to the
+ ;; newline
+ (flush-to-newline)
+ (call-with-error-handling
+ (lambda ()
+ (catch 'quit
+ (lambda ()
+ (call-with-values
+ (lambda ()
+ (% (let ((thunk
+ (abort-on-error "compiling expression"
+ (repl-prepare-eval-thunk
+ repl
+ (abort-on-error "parsing expression"
+ (repl-parse repl exp))))))
+ (run-hook before-eval-hook exp)
+ (call-with-error-handling
+ (lambda ()
+ (with-stack-and-prompt thunk))
+ #\on-error (repl-option-ref repl 'on-error)))
+ (lambda (k) (values))))
+ (lambda l
+ (for-each (lambda (v)
+ (repl-print repl v))
+ l))))
+ (lambda (k . args)
+ (abort args))))
+ #\on-error (repl-option-ref repl 'on-error)
+ #\trap-handler 'disabled)))
+ (flush-to-newline) ;; consume trailing whitespace
+ (prompt-loop))))
+ (lambda (k status)
+ status)))
+
+;; Returns first non-whitespace char.
+(define (flush-leading-whitespace)
+ (let ((ch (peek-char)))
+ (cond ((eof-object? ch) ch)
+ ((char-whitespace? ch) (read-char) (flush-leading-whitespace))
+ (else ch))))
+
+(define (flush-to-newline)
+ (if (char-ready?)
+ (let ((ch (peek-char)))
+ (if (and (not (eof-object? ch)) (char-whitespace? ch))
+ (begin
+ (read-char)
+ (if (not (char=? ch #\newline))
+ (flush-to-newline)))))))
+;;; Repl server
+
+;; Copyright (C) 2003, 2010, 2011, 2014, 2016 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Code_
+
+(define-module (system repl server)
+ #\use-module (system repl repl)
+ #\use-module (ice-9 threads)
+ #\use-module (ice-9 rdelim)
+ #\use-module (ice-9 match)
+ #\use-module (ice-9 iconv)
+ #\use-module (rnrs bytevectors)
+ #\use-module (rnrs io ports)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-26) ; cut
+ #\export (make-tcp-server-socket
+ make-unix-domain-server-socket
+ run-server
+ spawn-server
+ stop-server-and-clients!))
+
+;; List of pairs of the form (SOCKET . FORCE-CLOSE), where SOCKET is a
+;; socket port, and FORCE-CLOSE is a thunk that forcefully shuts down
+;; the socket.
+(define *open-sockets* '())
+
+(define sockets-lock (make-mutex))
+
+;; WARNING_ it is unsafe to call 'close-socket!' from another thread.
+;; Note_ although not exported, this is used by (system repl coop-server)
+(define (close-socket! s)
+ (with-mutex sockets-lock
+ (set! *open-sockets* (assq-remove! *open-sockets* s)))
+ ;; Close-port could block or raise an exception flushing buffered
+ ;; output. Hmm.
+ (close-port s))
+
+;; Note_ although not exported, this is used by (system repl coop-server)
+(define (add-open-socket! s force-close)
+ (with-mutex sockets-lock
+ (set! *open-sockets* (acons s force-close *open-sockets*))))
+
+(define (stop-server-and-clients!)
+ (cond
+ ((with-mutex sockets-lock
+ (match *open-sockets*
+ (() #f)
+ (((s . force-close) . rest)
+ (set! *open-sockets* rest)
+ force-close)))
+ => (lambda (force-close)
+ (force-close)
+ (stop-server-and-clients!)))))
+
+(define* (make-tcp-server-socket #\key
+ (host #f)
+ (addr (if host (inet-aton host) INADDR_LOOPBACK))
+ (port 37146))
+ (let ((sock (socket PF_INET SOCK_STREAM 0)))
+ (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
+ (bind sock AF_INET addr port)
+ sock))
+
+(define* (make-unix-domain-server-socket #\key (path "/tmp/guile-socket"))
+ (let ((sock (socket PF_UNIX SOCK_STREAM 0)))
+ (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
+ (bind sock AF_UNIX path)
+ sock))
+
+;; List of errno values from 'select' or 'accept' that should lead to a
+;; retry in 'run-server'.
+(define errs-to-retry
+ (delete-duplicates
+ (filter-map (lambda (name)
+ (and=> (module-variable the-root-module name)
+ variable-ref))
+ '(EINTR EAGAIN EWOULDBLOCK))))
+
+(define* (run-server #\optional (server-socket (make-tcp-server-socket)))
+ (run-server* server-socket serve-client))
+
+;; Note_ although not exported, this is used by (system repl coop-server)
+(define (run-server* server-socket serve-client)
+ ;; We use a pipe to notify the server when it should shut down.
+ (define shutdown-pipes (pipe))
+ (define shutdown-read-pipe (car shutdown-pipes))
+ (define shutdown-write-pipe (cdr shutdown-pipes))
+
+ ;; 'shutdown-server' is called by 'stop-server-and-clients!'.
+ (define (shutdown-server)
+ (display #\! shutdown-write-pipe)
+ (force-output shutdown-write-pipe))
+
+ (define monitored-ports
+ (list server-socket
+ shutdown-read-pipe))
+
+ (define (accept-new-client)
+ (catch #t
+ (lambda ()
+ (let ((ready-ports (car (select monitored-ports '() '()))))
+ ;; If we've been asked to shut down, return #f.
+ (and (not (memq shutdown-read-pipe ready-ports))
+ (accept server-socket))))
+ (lambda k-args
+ (let ((err (system-error-errno k-args)))
+ (cond
+ ((memv err errs-to-retry)
+ (accept-new-client))
+ (else
+ (warn "Error accepting client" k-args)
+ ;; Retry after a timeout.
+ (sleep 1)
+ (accept-new-client)))))))
+
+ ;; Put the socket into non-blocking mode.
+ (fcntl server-socket F_SETFL
+ (logior O_NONBLOCK
+ (fcntl server-socket F_GETFL)))
+
+ (sigaction SIGPIPE SIG_IGN)
+ (add-open-socket! server-socket shutdown-server)
+ (listen server-socket 5)
+ (let lp ((client (accept-new-client)))
+ ;; If client is false, we are shutting down.
+ (if client
+ (let ((client-socket (car client))
+ (client-addr (cdr client)))
+ (make-thread serve-client client-socket client-addr)
+ (lp (accept-new-client)))
+ (begin (close shutdown-write-pipe)
+ (close shutdown-read-pipe)
+ (close server-socket)))))
+
+(define* (spawn-server #\optional (server-socket (make-tcp-server-socket)))
+ (make-thread run-server server-socket))
+
+(define (serve-client client addr)
+
+ (let ((thread (current-thread)))
+ ;; Close the socket when this thread exits, even if canceled.
+ (set-thread-cleanup! thread (lambda () (close-socket! client)))
+ ;; Arrange to cancel this thread to forcefully shut down the socket.
+ (add-open-socket! client (lambda () (cancel-thread thread))))
+
+ (guard-against-http-request client)
+
+ (with-continuation-barrier
+ (lambda ()
+ (parameterize ((current-input-port client)
+ (current-output-port client)
+ (current-error-port client)
+ (current-warning-port client))
+ (with-fluids ((*repl-stack* '()))
+ (start-repl))))))
+
+
+;;;
+;;; The following code adds protection to Guile's REPL servers against
+;;; HTTP inter-protocol exploitation attacks, a scenario whereby an
+;;; attacker can, via an HTML page, cause a web browser to send data to
+;;; TCP servers listening on a loopback interface or private network.
+;;; See <https_//en.wikipedia.org/wiki/Inter-protocol_exploitation> and
+;;; <https_//www.jochentopf.com/hfpa/hfpa.pdf>, The HTML Form Protocol
+;;; Attack (2001) by Tochen Topf <jochen@remote.org>.
+;;;
+;;; Here we add a procedure to 'before-read-hook' that looks for a possible
+;;; HTTP request-line in the first line of input from the client socket. If
+;;; present, the socket is drained and closed, and a loud warning is written
+;;; to stderr (POSIX file descriptor 2).
+;;;
+
+(define (with-temporary-port-encoding port encoding thunk)
+ "Call THUNK in a dynamic environment in which the encoding of PORT is
+temporarily set to ENCODING."
+ (let ((saved-encoding #f))
+ (dynamic-wind
+ (lambda ()
+ (unless (port-closed? port)
+ (set! saved-encoding (port-encoding port))
+ (set-port-encoding! port encoding)))
+ thunk
+ (lambda ()
+ (unless (port-closed? port)
+ (set! encoding (port-encoding port))
+ (set-port-encoding! port saved-encoding))))))
+
+(define (with-saved-port-line+column port thunk)
+ "Save the line and column of PORT before entering THUNK, and restore
+their previous values upon normal or non-local exit from THUNK."
+ (let ((saved-line #f) (saved-column #f))
+ (dynamic-wind
+ (lambda ()
+ (unless (port-closed? port)
+ (set! saved-line (port-line port))
+ (set! saved-column (port-column port))))
+ thunk
+ (lambda ()
+ (unless (port-closed? port)
+ (set-port-line! port saved-line)
+ (set-port-column! port saved-column))))))
+
+(define (drain-input-and-close socket)
+ "Drain input from SOCKET using ISO-8859-1 encoding until it would block,
+and then close it. Return the drained input as a string."
+ (dynamic-wind
+ (lambda ()
+ ;; Enable full buffering mode on the socket to allow
+ ;; 'get-bytevector-some' to return non-trivial chunks.
+ (setvbuf socket _IOFBF))
+ (lambda ()
+ (let loop ((chunks '()))
+ (let ((result (and (char-ready? socket)
+ (get-bytevector-some socket))))
+ (if (bytevector? result)
+ (loop (cons (bytevector->string result "ISO-8859-1")
+ chunks))
+ (string-concatenate-reverse chunks)))))
+ (lambda ()
+ ;; Close the socket even in case of an exception.
+ (close-port socket))))
+
+(define permissive-http-request-line?
+ ;; This predicate is deliberately permissive
+ ;; when checking the Request-URI component.
+ (let ((cs (ucs-range->char-set #x20 #x7E))
+ (rx (make-regexp
+ (string-append
+ "^(OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT) "
+ "[^ ]+ "
+ "HTTP/[0-9]+.[0-9]+$"))))
+ (lambda (line)
+ "Return true if LINE might plausibly be an HTTP request-line,
+otherwise return #f."
+ ;; We cannot simplify this to a simple 'regexp-exec', because
+ ;; 'regexp-exec' cannot cope with NUL bytes.
+ (and (string-every cs line)
+ (regexp-exec rx line)))))
+
+(define (check-for-http-request socket)
+ "Check for a possible HTTP request in the initial input from SOCKET.
+If one is found, close the socket and print a report to STDERR (fdes 2).
+Otherwise, put back the bytes."
+ ;; Temporarily set the port encoding to ISO-8859-1 to allow lossless
+ ;; reading and unreading of the first line, regardless of what bytes
+ ;; are present. Note that a valid HTTP request-line contains only
+ ;; ASCII characters.
+ (with-temporary-port-encoding socket "ISO-8859-1"
+ (lambda ()
+ ;; Save the port 'line' and 'column' counters and later restore
+ ;; them, since unreading what we read is not sufficient to do so.
+ (with-saved-port-line+column socket
+ (lambda ()
+ ;; Read up to (but not including) the first CR or LF.
+ ;; Although HTTP mandates CRLF line endings, we are permissive
+ ;; here to guard against the possibility that in some
+ ;; environments CRLF might be converted to LF before it
+ ;; reaches us.
+ (match (read-delimited "\r\n" socket 'peek)
+ ((? eof-object?)
+ ;; We found EOF before any input. Nothing to do.
+ 'done)
+
+ ((? permissive-http-request-line? request-line)
+ ;; The input from the socket began with a plausible HTTP
+ ;; request-line, which is unlikely to be legitimate and may
+ ;; indicate an possible break-in attempt.
+
+ ;; First, set the current port parameters to a void-port,
+ ;; to avoid sending any more data over the socket, to cause
+ ;; the REPL reader to see EOF, and to swallow any remaining
+ ;; output gracefully.
+ (let ((void-port (%make-void-port "rw")))
+ (current-input-port void-port)
+ (current-output-port void-port)
+ (current-error-port void-port)
+ (current-warning-port void-port))
+
+ ;; Read from the socket until we would block,
+ ;; and then close it.
+ (let ((drained-input (drain-input-and-close socket)))
+
+ ;; Print a report to STDERR (POSIX file descriptor 2).
+ ;; XXX Can we do better here?
+ (call-with-port (dup->port 2 "w")
+ (cut format <> "
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@@ POSSIBLE BREAK-IN ATTEMPT ON THE REPL SERVER @@
+@@ BY AN HTTP INTER-PROTOCOL EXPLOITATION ATTACK. See_ @@
+@@ <https_//en.wikipedia.org/wiki/Inter-protocol_exploitation> @@
+@@ Possible HTTP request received_ ~S
+@@ The associated socket has been closed. @@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
+ (string-append request-line
+ drained-input)))))
+
+ (start-line
+ ;; The HTTP request-line was not found, so
+ ;; 'unread' the characters that we have read.
+ (unread-string start-line socket))))))))
+
+(define (guard-against-http-request socket)
+ "Arrange for the Guile REPL to check for an HTTP request in the
+initial input from SOCKET, in which case the socket will be closed.
+This guards against HTTP inter-protocol exploitation attacks, a scenario
+whereby an attacker can, via an HTML page, cause a web browser to send
+data to TCP servers listening on a loopback interface or private
+network."
+ (%set-port-property! socket 'guard-against-http-request? #t))
+
+(define* (maybe-check-for-http-request
+ #\optional (socket (current-input-port)))
+ "Apply check-for-http-request to SOCKET if previously requested by
+guard-against-http-request. This procedure is intended to be added to
+before-read-hook."
+ (when (%port-property socket 'guard-against-http-request?)
+ (check-for-http-request socket)
+ (unless (port-closed? socket)
+ (%set-port-property! socket 'guard-against-http-request? #f))))
+
+;; Install the hook.
+(add-hook! before-read-hook
+ maybe-check-for-http-request)
+
+;;; Local Variables_
+;;; eval_ (put 'with-temporary-port-encoding 'scheme-indent-function 2)
+;;; eval_ (put 'with-saved-port-line+column 'scheme-indent-function 1)
+;;; End_
+;;; -*- mode_ scheme; coding_ utf-8; -*-
+;;;
+;;; Copyright (C) 2010 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (system vm coverage)
+ #\use-module (system vm vm)
+ #\use-module (system vm frame)
+ #\use-module (system vm program)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-11)
+ #\use-module (srfi srfi-26)
+ #\export (with-code-coverage
+ coverage-data?
+ instrumented-source-files
+ instrumented/executed-lines
+ line-execution-counts
+ procedure-execution-count
+ coverage-data->lcov))
+
+;;; Author_ Ludovic Courtès
+;;;
+;;; Commentary_
+;;;
+;;; This module provides support to gather code coverage data by instrumenting
+;;; the VM.
+;;;
+;;; Code_
+
+
+;;;
+;;; Gathering coverage data.
+;;;
+
+(define (hashq-proc proc n)
+ ;; Return the hash of PROC's objcode.
+ (hashq (program-objcode proc) n))
+
+(define (assq-proc proc alist)
+ ;; Instead of really looking for PROC in ALIST, look for the objcode of PROC.
+ ;; IOW the alist is indexed by procedures, not objcodes, but those procedures
+ ;; are taken as an arbitrary representative of all the procedures (closures)
+ ;; sharing that objcode. This can significantly reduce memory consumption.
+ (let ((code (program-objcode proc)))
+ (find (lambda (pair)
+ (eq? code (program-objcode (car pair))))
+ alist)))
+
+(define (with-code-coverage vm thunk)
+ "Run THUNK, a zero-argument procedure, using VM; instrument VM to collect code
+coverage data. Return code coverage data and the values returned by THUNK."
+
+ (define procedure->ip-counts
+ ;; Mapping from procedures to hash tables; said hash tables map instruction
+ ;; pointers to the number of times they were executed.
+ (make-hash-table 500))
+
+ (define (collect! frame)
+ ;; Update PROCEDURE->IP-COUNTS with info from FRAME.
+ (let* ((proc (frame-procedure frame))
+ (ip (frame-instruction-pointer frame))
+ (proc-entry (hashx-create-handle! hashq-proc assq-proc
+ procedure->ip-counts proc #f)))
+ (let loop ()
+ (define ip-counts (cdr proc-entry))
+ (if ip-counts
+ (let ((ip-entry (hashv-create-handle! ip-counts ip 0)))
+ (set-cdr! ip-entry (+ (cdr ip-entry) 1)))
+ (begin
+ (set-cdr! proc-entry (make-hash-table))
+ (loop))))))
+
+ ;; FIXME_ It's unclear what the dynamic-wind is for, given that if the
+ ;; VM is different from the current one, continuations will not be
+ ;; resumable.
+ (call-with-values (lambda ()
+ (let ((level (vm-trace-level vm))
+ (hook (vm-next-hook vm)))
+ (dynamic-wind
+ (lambda ()
+ (set-vm-trace-level! vm (+ level 1))
+ (add-hook! hook collect!))
+ (lambda ()
+ (call-with-vm vm thunk))
+ (lambda ()
+ (set-vm-trace-level! vm level)
+ (remove-hook! hook collect!)))))
+ (lambda args
+ (apply values (make-coverage-data procedure->ip-counts) args))))
+
+
+;;;
+;;; Coverage data summary.
+;;;
+
+(define-record-type <coverage-data>
+ (%make-coverage-data procedure->ip-counts
+ procedure->sources
+ file->procedures
+ file->line-counts)
+ coverage-data?
+
+ ;; Mapping from procedures to hash tables; said hash tables map instruction
+ ;; pointers to the number of times they were executed.
+ (procedure->ip-counts data-procedure->ip-counts)
+
+ ;; Mapping from procedures to the result of `program-sources'.
+ (procedure->sources data-procedure->sources)
+
+ ;; Mapping from source file names to lists of procedures defined in the file.
+ (file->procedures data-file->procedures)
+
+ ;; Mapping from file names to hash tables, which in turn map from line numbers
+ ;; to execution counts.
+ (file->line-counts data-file->line-counts))
+
+
+(define (make-coverage-data procedure->ip-counts)
+ ;; Return a `coverage-data' object based on the coverage data available in
+ ;; PROCEDURE->IP-COUNTS. Precompute the other hash tables that make up
+ ;; `coverage-data' objects.
+ (let* ((procedure->sources (make-hash-table 500))
+ (file->procedures (make-hash-table 100))
+ (file->line-counts (make-hash-table 100))
+ (data (%make-coverage-data procedure->ip-counts
+ procedure->sources
+ file->procedures
+ file->line-counts)))
+ (define (increment-execution-count! file line count)
+ ;; Make the execution count of FILE_LINE the maximum of its current value
+ ;; and COUNT. This is so that LINE's execution count is correct when
+ ;; several instruction pointers map to LINE.
+ (let ((file-entry (hash-create-handle! file->line-counts file #f)))
+ (if (not (cdr file-entry))
+ (set-cdr! file-entry (make-hash-table 500)))
+ (let ((line-entry (hashv-create-handle! (cdr file-entry) line 0)))
+ (set-cdr! line-entry (max (cdr line-entry) count)))))
+
+ ;; Update execution counts for procs that were executed.
+ (hash-for-each (lambda (proc ip-counts)
+ (let* ((sources (program-sources* data proc))
+ (file (and (pair? sources)
+ (source_file (car sources)))))
+ (and file
+ (begin
+ ;; Add a zero count for all IPs in SOURCES and in
+ ;; the sources of procedures closed over by PROC.
+ (for-each
+ (lambda (source)
+ (let ((file (source_file source))
+ (line (source_line source)))
+ (increment-execution-count! file line 0)))
+ (append-map (cut program-sources* data <>)
+ (closed-over-procedures proc)))
+
+ ;; Add the actual execution count collected.
+ (hash-for-each
+ (lambda (ip count)
+ (let ((line (closest-source-line sources ip)))
+ (increment-execution-count! file line count)))
+ ip-counts)))))
+ procedure->ip-counts)
+
+ ;; Set the execution count to zero for procedures loaded and not executed.
+ ;; FIXME_ Traversing thousands of procedures here is inefficient.
+ (for-each (lambda (proc)
+ (and (not (hashq-ref procedure->sources proc))
+ (for-each (lambda (proc)
+ (let* ((sources (program-sources* data proc))
+ (file (and (pair? sources)
+ (source_file (car sources)))))
+ (and file
+ (for-each
+ (lambda (ip)
+ (let ((line (closest-source-line sources ip)))
+ (increment-execution-count! file line 0)))
+ (map source_addr sources)))))
+ (closed-over-procedures proc))))
+ (append-map module-procedures (loaded-modules)))
+
+ data))
+
+(define (procedure-execution-count data proc)
+ "Return the number of times PROC's code was executed, according to DATA, or #f
+if PROC was not executed. When PROC is a closure, the number of times its code
+was executed is returned, not the number of times this code associated with this
+particular closure was executed."
+ (let ((sources (program-sources* data proc)))
+ (and (pair? sources)
+ (and=> (hashx-ref hashq-proc assq-proc
+ (data-procedure->ip-counts data) proc)
+ (lambda (ip-counts)
+ ;; FIXME_ broken with lambda*
+ (let ((entry-ip (source_addr (car sources))))
+ (hashv-ref ip-counts entry-ip 0)))))))
+
+(define (program-sources* data proc)
+ ;; A memoizing version of `program-sources'.
+ (or (hashq-ref (data-procedure->sources data) proc)
+ (and (program? proc)
+ (let ((sources (program-sources proc))
+ (p->s (data-procedure->sources data))
+ (f->p (data-file->procedures data)))
+ (if (pair? sources)
+ (let* ((file (source_file (car sources)))
+ (entry (hash-create-handle! f->p file '())))
+ (hashq-set! p->s proc sources)
+ (set-cdr! entry (cons proc (cdr entry)))
+ sources)
+ sources)))))
+
+(define (file-procedures data file)
+ ;; Return the list of globally bound procedures defined in FILE.
+ (hash-ref (data-file->procedures data) file '()))
+
+(define (instrumented/executed-lines data file)
+ "Return the number of instrumented and the number of executed source lines in
+FILE according to DATA."
+ (define instr+exec
+ (and=> (hash-ref (data-file->line-counts data) file)
+ (lambda (line-counts)
+ (hash-fold (lambda (line count instr+exec)
+ (let ((instr (car instr+exec))
+ (exec (cdr instr+exec)))
+ (cons (+ 1 instr)
+ (if (> count 0)
+ (+ 1 exec)
+ exec))))
+ '(0 . 0)
+ line-counts))))
+
+ (values (car instr+exec) (cdr instr+exec)))
+
+(define (line-execution-counts data file)
+ "Return a list of line number/execution count pairs for FILE, or #f if FILE
+is not among the files covered by DATA."
+ (and=> (hash-ref (data-file->line-counts data) file)
+ (lambda (line-counts)
+ (hash-fold alist-cons '() line-counts))))
+
+(define (instrumented-source-files data)
+ "Return the list of `instrumented' source files, i.e., source files whose code
+was loaded at the time DATA was collected."
+ (hash-fold (lambda (file counts files)
+ (cons file files))
+ '()
+ (data-file->line-counts data)))
+
+
+;;;
+;;; Helpers.
+;;;
+
+(define (loaded-modules)
+ ;; Return the list of all the modules currently loaded.
+ (define seen (make-hash-table))
+
+ (let loop ((modules (module-submodules (resolve-module '() #f)))
+ (result '()))
+ (hash-fold (lambda (name module result)
+ (if (hashq-ref seen module)
+ result
+ (begin
+ (hashq-set! seen module #t)
+ (loop (module-submodules module)
+ (cons module result)))))
+ result
+ modules)))
+
+(define (module-procedures module)
+ ;; Return the list of procedures bound globally in MODULE.
+ (hash-fold (lambda (binding var result)
+ (if (variable-bound? var)
+ (let ((value (variable-ref var)))
+ (if (procedure? value)
+ (cons value result)
+ result))
+ result))
+ '()
+ (module-obarray module)))
+
+(define (closest-source-line sources ip)
+ ;; Given SOURCES, as returned by `program-sources' for a given procedure,
+ ;; return the source line of code that is the closest to IP. This is similar
+ ;; to what `program-source' does.
+ (let loop ((sources sources)
+ (line (and (pair? sources) (source_line (car sources)))))
+ (if (null? sources)
+ line
+ (let ((source (car sources)))
+ (if (> (source_addr source) ip)
+ line
+ (loop (cdr sources) (source_line source)))))))
+
+(define (closed-over-procedures proc)
+ ;; Return the list of procedures PROC closes over, PROC included.
+ (let loop ((proc proc)
+ (result '()))
+ (if (and (program? proc) (not (memq proc result)))
+ (fold loop (cons proc result)
+ (append (vector->list (or (program-objects proc) #()))
+ (program-free-variables proc)))
+ result)))
+
+
+;;;
+;;; LCOV output.
+;;;
+
+(define* (coverage-data->lcov data port)
+ "Traverse code coverage information DATA, as obtained with
+`with-code-coverage', and write coverage information in the LCOV format to PORT.
+The report will include all the modules loaded at the time coverage data was
+gathered, even if their code was not executed."
+
+ (define (dump-function proc)
+ ;; Dump source location and basic coverage data for PROC.
+ (and (program? proc)
+ (let ((sources (program-sources* data proc)))
+ (and (pair? sources)
+ (let* ((line (source_line-for-user (car sources)))
+ (name (or (procedure-name proc)
+ (format #f "anonymous-l~a" line))))
+ (format port "FN_~A,~A~%" line name)
+ (and=> (procedure-execution-count data proc)
+ (lambda (count)
+ (format port "FNDA_~A,~A~%" count name))))))))
+
+ ;; Output per-file coverage data.
+ (format port "TN_~%")
+ (for-each (lambda (file)
+ (let ((procs (file-procedures data file))
+ (path (search-path %load-path file)))
+ (if (string? path)
+ (begin
+ (format port "SF_~A~%" path)
+ (for-each dump-function procs)
+ (for-each (lambda (line+count)
+ (let ((line (car line+count))
+ (count (cdr line+count)))
+ (format port "DA_~A,~A~%"
+ (+ 1 line) count)))
+ (line-execution-counts data file))
+ (let-values (((instr exec)
+ (instrumented/executed-lines data file)))
+ (format port "LH_ ~A~%" exec)
+ (format port "LF_ ~A~%" instr))
+ (format port "end_of_record~%"))
+ (begin
+ (format (current-error-port)
+ "skipping unknown source file_ ~a~%"
+ file)))))
+ (instrumented-source-files data)))
+;;; Guile VM frame functions
+
+;;; Copyright (C) 2001, 2005, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code_
+
+(define-module (system vm frame)
+ #\use-module (system base pmatch)
+ #\use-module (system vm program)
+ #\use-module (system vm instruction)
+ #\use-module (system vm objcode)
+ #\export (frame-bindings
+ frame-lookup-binding
+ frame-binding-ref frame-binding-set!
+ frame-next-source frame-call-representation
+ frame-environment
+ frame-object-binding frame-object-name
+ frame-return-values))
+
+(define (frame-bindings frame)
+ (let ((p (frame-procedure frame)))
+ (if (program? p)
+ (program-bindings-for-ip p (frame-instruction-pointer frame))
+ '())))
+
+(define (frame-lookup-binding frame var)
+ (let lp ((bindings (frame-bindings frame)))
+ (cond ((null? bindings)
+ #f)
+ ((eq? (binding_name (car bindings)) var)
+ (car bindings))
+ (else
+ (lp (cdr bindings))))))
+
+(define (frame-binding-set! frame var val)
+ (frame-local-set! frame
+ (binding_index
+ (or (frame-lookup-binding frame var)
+ (error "variable not bound in frame" var frame)))
+ val))
+
+(define (frame-binding-ref frame var)
+ (frame-local-ref frame
+ (binding_index
+ (or (frame-lookup-binding frame var)
+ (error "variable not bound in frame" var frame)))))
+
+
+;; This function is always called to get some sort of representation of the
+;; frame to present to the user, so let's do the logical thing and dispatch to
+;; frame-call-representation.
+(define (frame-arguments frame)
+ (cdr (frame-call-representation frame)))
+
+
+
+;;;
+;;; Pretty printing
+;;;
+
+(define (frame-next-source frame)
+ (let ((proc (frame-procedure frame)))
+ (if (program? proc)
+ (program-source proc
+ (frame-instruction-pointer frame)
+ (program-sources-pre-retire proc))
+ '())))
+
+
+;; Basically there are two cases to deal with here_
+;;
+;; 1. We've already parsed the arguments, and bound them to local
+;; variables. In a standard (lambda (a b c) ...) call, this doesn't
+;; involve any argument shuffling; but with rest, optional, or
+;; keyword arguments, the arguments as given to the procedure may
+;; not correspond to what's on the stack. We reconstruct the
+;; arguments using e.g. for the case above_ `(,a ,b ,c). This works
+;; for rest arguments too_ (a b . c) => `(,a ,b . ,c)
+;;
+;; 2. We have failed to parse the arguments. Perhaps it's the wrong
+;; number of arguments, or perhaps we're doing a typed dispatch and
+;; the types don't match. In that case the arguments are all on the
+;; stack, and nothing else is on the stack.
+
+(define (frame-call-representation frame)
+ (let ((p (frame-procedure frame)))
+ (cons
+ (or (false-if-exception (procedure-name p)) p)
+ (cond
+ ((and (program? p)
+ (program-arguments-alist p (frame-instruction-pointer frame)))
+ ;; case 1
+ => (lambda (arguments)
+ (define (binding-ref sym i)
+ (cond
+ ((frame-lookup-binding frame sym)
+ => (lambda (b) (frame-local-ref frame (binding_index b))))
+ ((< i (frame-num-locals frame))
+ (frame-local-ref frame i))
+ (else
+ ;; let's not error here, as we are called during backtraces...
+ '???)))
+ (let lp ((req (or (assq-ref arguments 'required) '()))
+ (opt (or (assq-ref arguments 'optional) '()))
+ (key (or (assq-ref arguments 'keyword) '()))
+ (rest (or (assq-ref arguments 'rest) #f))
+ (i 0))
+ (cond
+ ((pair? req)
+ (cons (binding-ref (car req) i)
+ (lp (cdr req) opt key rest (1+ i))))
+ ((pair? opt)
+ (cons (binding-ref (car opt) i)
+ (lp req (cdr opt) key rest (1+ i))))
+ ((pair? key)
+ (cons* (caar key)
+ (frame-local-ref frame (cdar key))
+ (lp req opt (cdr key) rest (1+ i))))
+ (rest
+ (binding-ref rest i))
+ (else
+ '())))))
+ (else
+ ;; case 2
+ (map (lambda (i)
+ (frame-local-ref frame i))
+ (iota (frame-num-locals frame))))))))
+
+
+
+;;; Misc
+;;;
+
+(define (frame-environment frame)
+ (map (lambda (binding)
+ (cons (binding_name binding) (frame-binding-ref frame binding)))
+ (frame-bindings frame)))
+
+(define (frame-object-binding frame obj)
+ (do ((bs (frame-bindings frame) (cdr bs)))
+ ((or (null? bs) (eq? obj (frame-binding-ref frame (car bs))))
+ (and (pair? bs) (car bs)))))
+
+(define (frame-object-name frame obj)
+ (cond ((frame-object-binding frame obj) => binding_name)
+ (else #f)))
+
+;; Nota bene, only if frame is in a return context (i.e. in a
+;; pop-continuation hook dispatch).
+(define (frame-return-values frame)
+ (let* ((len (frame-num-locals frame))
+ (nvalues (frame-local-ref frame (1- len))))
+ (map (lambda (i)
+ (frame-local-ref frame (+ (- len nvalues 1) i)))
+ (iota nvalues))))
+;;; Guile VM debugging facilities
+
+;;; Copyright (C) 2001, 2009, 2010, 2011 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code_
+
+(define-module (system vm inspect)
+ #\use-module (system base pmatch)
+ #\use-module (system base syntax)
+ #\use-module (system vm vm)
+ #\use-module (system vm frame)
+ #\use-module ((language assembly disassemble)
+ #\select ((disassemble . %disassemble)))
+ #\use-module (ice-9 rdelim)
+ #\use-module (ice-9 pretty-print)
+ #\use-module (ice-9 format)
+ #\use-module (system vm program)
+ #\export (inspect))
+
+
+(define (reverse-hashq h)
+ (let ((ret (make-hash-table)))
+ (hash-for-each
+ (lambda (k v)
+ (hashq-set! ret v (cons k (hashq-ref ret v '()))))
+ h)
+ ret))
+
+(define (catch-bad-arguments thunk bad-args-thunk)
+ (catch 'wrong-number-of-args
+ (lambda ()
+ (catch 'keyword-argument-error
+ thunk
+ (lambda (k . args)
+ (bad-args-thunk))))
+ (lambda (k . args)
+ (bad-args-thunk))))
+
+(define (read-args prompt)
+ (define (read* reader)
+ (repl-reader prompt reader))
+ (define (next)
+ (read* read-char))
+ (define (cmd chr)
+ (cond
+ ((eof-object? chr) (list chr))
+ ((char=? chr #\newline) (cmd (next)))
+ ((char-whitespace? chr) (cmd (next)))
+ (else
+ (unread-char chr)
+ (let ((tok (read* read)))
+ (args (list tok) (next))))))
+ (define (args out chr)
+ (cond
+ ((eof-object? chr) (reverse out))
+ ((char=? chr #\newline) (reverse out))
+ ((char-whitespace? chr) (args out (next)))
+ (else
+ (unread-char chr)
+ (let ((tok (read* read)))
+ (args (cons tok out) (next))))))
+ (cmd (next)))
+
+
+;;;
+;;; Inspector
+;;;
+
+(define (inspect x)
+ (define-syntax-rule (define-command ((mod cname alias ...) . args)
+ body ...)
+ (define cname
+ (let ((c (lambda* args body ...)))
+ (set-procedure-property! c 'name 'cname)
+ (module-define! mod 'cname c)
+ (module-add! mod 'alias (module-local-variable mod 'cname))
+ ...
+ c)))
+
+ (let ((commands (make-module)))
+ (define (prompt)
+ (format #f "~20@y inspect> " x))
+
+ (define-command ((commands quit q continue cont c))
+ "Quit the inspector."
+ (throw 'quit))
+
+ (define-command ((commands print p))
+ "Print the current object using `pretty-print'."
+ (pretty-print x))
+
+ (define-command ((commands write w))
+ "Print the current object using `write'."
+ (write x))
+
+ (define-command ((commands display d))
+ "Print the current object using `display'."
+ (display x))
+
+ (define-command ((commands disassemble x))
+ "Disassemble the current object, which should be objcode or a procedure."
+ (catch #t
+ (lambda ()
+ (%disassemble x))
+ (lambda args
+ (format #t "Error disassembling object_ ~a\n" args))))
+
+ (define-command ((commands help h ?) #\optional cmd)
+ "Show this help message."
+ (let ((rhash (reverse-hashq (module-obarray commands))))
+ (define (help-cmd cmd)
+ (let* ((v (module-local-variable commands cmd))
+ (p (variable-ref v))
+ (canonical-name (procedure-name p)))
+ ;; la la la
+ (format #t "~a~{ ~_@(~a~)~}~?~%~a~&~%"
+ canonical-name (program-lambda-list p)
+ "~#[~_;~40t(aliases_ ~@{~a~^, ~})~]"
+ (delq canonical-name (hashq-ref rhash v))
+ (procedure-documentation p))))
+ (cond
+ (cmd
+ (cond
+ ((and (symbol? cmd) (module-local-variable commands cmd))
+ (help-cmd cmd))
+ (else
+ (format #t "Invalid command ~s.~%" cmd)
+ (format #t "Try `help' for a list of commands~%"))))
+ (else
+ (let ((names (sort
+ (hash-map->list
+ (lambda (k v)
+ (procedure-name (variable-ref k)))
+ rhash)
+ (lambda (x y)
+ (string<? (symbol->string x)
+ (symbol->string y))))))
+ (format #t "Available commands_~%~%")
+ (for-each help-cmd names))))))
+
+ (define (handle cmd . args)
+ (cond
+ ((and (symbol? cmd)
+ (module-local-variable commands cmd))
+ => (lambda (var)
+ (let ((proc (variable-ref var)))
+ (catch-bad-arguments
+ (lambda ()
+ (apply (variable-ref var) args))
+ (lambda ()
+ (format (current-error-port)
+ "Invalid arguments to ~a. Try `help ~a'.~%"
+ (procedure-name proc) (procedure-name proc)))))))
+ ; ((and (integer? cmd) (exact? cmd))
+ ; (nth cmd))
+ ((eof-object? cmd)
+ (newline)
+ (throw 'quit))
+ (else
+ (format (current-error-port)
+ "~&Unknown command_ ~a. Try `help'.~%" cmd)
+ *unspecified*)))
+
+ (catch 'quit
+ (lambda ()
+ (let loop ()
+ (apply
+ handle
+ (save-module-excursion
+ (lambda ()
+ (set-current-module commands)
+ (read-args prompt))))
+ (loop)))
+ (lambda (k . args)
+ (apply values args)))))
+;;; Guile VM instructions
+
+;; Copyright (C) 2001, 2010 Free Software Foundation, Inc.
+
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code_
+
+(define-module (system vm instruction)
+ #\export (instruction-list
+ instruction? instruction-length
+ instruction-pops instruction-pushes
+ instruction->opcode opcode->instruction))
+
+(load-extension (string-append "libguile-" (effective-version))
+ "scm_init_instructions")
+;;; Guile VM object code
+
+;; Copyright (C) 2001, 2010 Free Software Foundation, Inc.
+
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code_
+
+(define-module (system vm objcode)
+ #\export (objcode? objcode-meta
+ bytecode->objcode objcode->bytecode
+ load-objcode write-objcode
+ word-size byte-order))
+
+(load-extension (string-append "libguile-" (effective-version))
+ "scm_init_objcodes")
+;;; Guile VM program functions
+
+;;; Copyright (C) 2001, 2009, 2010, 2013 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code_
+
+(define-module (system vm program)
+ #\use-module (system base pmatch)
+ #\use-module (system vm instruction)
+ #\use-module (system vm objcode)
+ #\use-module (rnrs bytevectors)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-26)
+ #\export (make-program
+
+ make-binding binding_name binding_boxed? binding_index
+ binding_start binding_end
+
+ source_addr source_line source_column source_file
+ source_line-for-user
+ program-sources program-sources-pre-retire program-source
+
+ program-bindings program-bindings-by-index program-bindings-for-ip
+ program-arities program-arity arity_start arity_end
+
+ arity_nreq arity_nopt arity_rest? arity_kw arity_allow-other-keys?
+
+ program-arguments-alist program-lambda-list
+
+ program-meta
+ program-objcode program? program-objects
+ program-module program-base
+ program-free-variables
+ program-num-free-variables
+ program-free-variable-ref program-free-variable-set!))
+
+(load-extension (string-append "libguile-" (effective-version))
+ "scm_init_programs")
+
+(define (make-binding name boxed? index start end)
+ (list name boxed? index start end))
+(define (binding_name b) (list-ref b 0))
+(define (binding_boxed? b) (list-ref b 1))
+(define (binding_index b) (list-ref b 2))
+(define (binding_start b) (list-ref b 3))
+(define (binding_end b) (list-ref b 4))
+
+(define (source_addr source)
+ (car source))
+(define (source_file source)
+ (cadr source))
+(define (source_line source)
+ (caddr source))
+(define (source_column source)
+ (cdddr source))
+
+;; Lines are zero-indexed inside Guile, but users expect them to be
+;; one-indexed. Columns, on the other hand, are zero-indexed to both. Go
+;; figure.
+(define (source_line-for-user source)
+ (1+ (source_line source)))
+
+;; FIXME_ pull this definition from elsewhere.
+(define *bytecode-header-len* 8)
+
+;; We could decompile the program to get this, but that seems like a
+;; waste.
+(define (bytecode-instruction-length bytecode ip)
+ (let* ((idx (+ ip *bytecode-header-len*))
+ (inst (opcode->instruction (bytevector-u8-ref bytecode idx))))
+ ;; 1+ for the instruction itself.
+ (1+ (cond
+ ((eq? inst 'load-program)
+ (+ (bytevector-u32-native-ref bytecode (+ idx 1))
+ (bytevector-u32-native-ref bytecode (+ idx 5))))
+ ((< (instruction-length inst) 0)
+ ;; variable length instruction -- the length is encoded in the
+ ;; instruction stream.
+ (+ (ash (bytevector-u8-ref bytecode (+ idx 1)) 16)
+ (ash (bytevector-u8-ref bytecode (+ idx 2)) 8)
+ (bytevector-u8-ref bytecode (+ idx 3))))
+ (else
+ ;; fixed length
+ (instruction-length inst))))))
+
+;; Source information could in theory be correlated with the ip of the
+;; instruction, or the ip just after the instruction is retired. Guile
+;; does the latter, to make backtraces easy -- an error produced while
+;; running an opcode always happens after it has retired its arguments.
+;;
+;; But for breakpoints and such, we need the ip before the instruction
+;; is retired -- before it has had a chance to do anything. So here we
+;; change from the post-retire addresses given by program-sources to
+;; pre-retire addresses.
+;;
+(define (program-sources-pre-retire proc)
+ (let ((bv (objcode->bytecode (program-objcode proc))))
+ (let lp ((in (program-sources proc))
+ (out '())
+ (ip 0))
+ (cond
+ ((null? in)
+ (reverse out))
+ (else
+ (pmatch (car in)
+ ((,post-ip . ,source)
+ (let lp2 ((ip ip)
+ (next ip))
+ (if (< next post-ip)
+ (lp2 next (+ next (bytecode-instruction-length bv next)))
+ (lp (cdr in)
+ (acons ip source out)
+ next))))
+ (else
+ (error "unexpected"))))))))
+
+(define (collapse-locals locs)
+ (let lp ((ret '()) (locs locs))
+ (if (null? locs)
+ (map cdr (sort! ret
+ (lambda (x y) (< (car x) (car y)))))
+ (let ((b (car locs)))
+ (cond
+ ((assv-ref ret (binding_index b))
+ => (lambda (bindings)
+ (append! bindings (list b))
+ (lp ret (cdr locs))))
+ (else
+ (lp (acons (binding_index b) (list b) ret)
+ (cdr locs))))))))
+
+;; returns list of list of bindings
+;; (list-ref ret N) == bindings bound to the Nth local slot
+(define (program-bindings-by-index prog)
+ (cond ((program-bindings prog) => collapse-locals)
+ (else '())))
+
+(define (program-bindings-for-ip prog ip)
+ (let lp ((in (program-bindings-by-index prog)) (out '()))
+ (if (null? in)
+ (reverse out)
+ (lp (cdr in)
+ (let inner ((binds (car in)))
+ (cond ((null? binds) out)
+ ((<= (binding_start (car binds))
+ ip
+ (binding_end (car binds)))
+ (cons (car binds) out))
+ (else (inner (cdr binds)))))))))
+
+(define (arity_start a)
+ (pmatch a ((,start ,end . _) start) (else (error "bad arity" a))))
+(define (arity_end a)
+ (pmatch a ((,start ,end . _) end) (else (error "bad arity" a))))
+(define (arity_nreq a)
+ (pmatch a ((_ _ ,nreq . _) nreq) (else 0)))
+(define (arity_nopt a)
+ (pmatch a ((_ _ ,nreq ,nopt . _) nopt) (else 0)))
+(define (arity_rest? a)
+ (pmatch a ((_ _ ,nreq ,nopt ,rest? . _) rest?) (else #f)))
+(define (arity_kw a)
+ (pmatch a ((_ _ ,nreq ,nopt ,rest? (_ . ,kw)) kw) (else '())))
+(define (arity_allow-other-keys? a)
+ (pmatch a ((_ _ ,nreq ,nopt ,rest? (,aok . ,kw)) aok) (else #f)))
+
+(define (program-arity prog ip)
+ (let ((arities (program-arities prog)))
+ (and arities
+ (let lp ((arities arities))
+ (cond ((null? arities) #f)
+ ((not ip) (car arities)) ; take the first one
+ ((and (< (arity_start (car arities)) ip)
+ (<= ip (arity_end (car arities))))
+ (car arities))
+ (else (lp (cdr arities))))))))
+
+(define (arglist->arguments-alist arglist)
+ (pmatch arglist
+ ((,req ,opt ,keyword ,allow-other-keys? ,rest . ,extents)
+ `((required . ,req)
+ (optional . ,opt)
+ (keyword . ,keyword)
+ (allow-other-keys? . ,allow-other-keys?)
+ (rest . ,rest)
+ (extents . ,extents)))
+ (else #f)))
+
+(define* (arity->arguments-alist prog arity
+ #\optional
+ (make-placeholder
+ (lambda (i) (string->symbol "_"))))
+ (define var-by-index
+ (let ((rbinds (map (lambda (x)
+ (cons (binding_index x) (binding_name x)))
+ (program-bindings-for-ip prog
+ (arity_start arity)))))
+ (lambda (i)
+ (or (assv-ref rbinds i)
+ ;; if we don't know the name, return a placeholder
+ (make-placeholder i)))))
+
+ (let lp ((nreq (arity_nreq arity)) (req '())
+ (nopt (arity_nopt arity)) (opt '())
+ (rest? (arity_rest? arity)) (rest #f)
+ (n 0))
+ (cond
+ ((< 0 nreq)
+ (lp (1- nreq) (cons (var-by-index n) req)
+ nopt opt rest? rest (1+ n)))
+ ((< 0 nopt)
+ (lp nreq req
+ (1- nopt) (cons (var-by-index n) opt)
+ rest? rest (1+ n)))
+ (rest?
+ (lp nreq req nopt opt
+ #f (var-by-index (+ n (length (arity_kw arity))))
+ (1+ n)))
+ (else
+ `((required . ,(reverse req))
+ (optional . ,(reverse opt))
+ (keyword . ,(arity_kw arity))
+ (allow-other-keys? . ,(arity_allow-other-keys? arity))
+ (rest . ,rest))))))
+
+;; the name "program-arguments" is taken by features.c...
+(define* (program-arguments-alist prog #\optional ip)
+ "Returns the signature of the given procedure in the form of an association list."
+ (let ((arity (program-arity prog ip)))
+ (and arity
+ (arity->arguments-alist prog arity))))
+
+(define* (program-lambda-list prog #\optional ip)
+ "Returns the signature of the given procedure in the form of an argument list."
+ (and=> (program-arguments-alist prog ip) arguments-alist->lambda-list))
+
+(define (arguments-alist->lambda-list arguments-alist)
+ (let ((req (or (assq-ref arguments-alist 'required) '()))
+ (opt (or (assq-ref arguments-alist 'optional) '()))
+ (key (map keyword->symbol
+ (map car (or (assq-ref arguments-alist 'keyword) '()))))
+ (rest (or (assq-ref arguments-alist 'rest) '())))
+ `(,@req
+ ,@(if (pair? opt) (cons #\optional opt) '())
+ ,@(if (pair? key) (cons #\key key) '())
+ . ,rest)))
+
+(define (program-free-variables prog)
+ "Return the list of free variables of PROG."
+ (let ((count (program-num-free-variables prog)))
+ (unfold (lambda (i) (>= i count))
+ (cut program-free-variable-ref prog <>)
+ 1+
+ 0)))
+
+(define (write-program prog port)
+ (format port "#<procedure ~a~a>"
+ (or (procedure-name prog)
+ (and=> (program-source prog 0)
+ (lambda (s)
+ (format #f "~a at ~a_~a_~a"
+ (number->string (object-address prog) 16)
+ (or (source_file s)
+ (if s "<current input>" "<unknown port>"))
+ (source_line-for-user s) (source_column s))))
+ (number->string (object-address prog) 16))
+ (let ((arities (program-arities prog)))
+ (if (or (not arities) (null? arities))
+ ""
+ (string-append
+ " " (string-join (map (lambda (a)
+ (object->string
+ (arguments-alist->lambda-list
+ (arity->arguments-alist prog a))))
+ arities)
+ " | "))))))
+
+;;; Guile VM tracer
+
+;; Copyright (C) 2001, 2009, 2010, 2013 Free Software Foundation, Inc.
+
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code_
+
+(define-module (system vm trace)
+ #\use-module (system base syntax)
+ #\use-module (system vm vm)
+ #\use-module (system vm frame)
+ #\use-module (system vm program)
+ #\use-module (system vm objcode)
+ #\use-module (system vm traps)
+ #\use-module (rnrs bytevectors)
+ #\use-module (system vm instruction)
+ #\use-module (ice-9 format)
+ #\export (trace-calls-in-procedure
+ trace-calls-to-procedure
+ trace-instructions-in-procedure
+ call-with-trace))
+
+;; FIXME_ this constant needs to go in system vm objcode
+(define *objcode-header-len* 8)
+
+(define (build-prefix prefix depth infix numeric-format max-indent)
+ (let lp ((indent "") (n 0))
+ (cond
+ ((= n depth)
+ (string-append prefix indent))
+ ((< (+ (string-length indent) (string-length infix)) max-indent)
+ (lp (string-append indent infix) (1+ n)))
+ (else
+ (string-append prefix indent (format #f numeric-format depth))))))
+
+(define (print-application frame depth width prefix max-indent)
+ (let ((prefix (build-prefix prefix depth "| " "~d> " max-indent)))
+ (format (current-error-port) "~a~v_@y\n"
+ prefix
+ width
+ (frame-call-representation frame))))
+
+(define* (print-return frame depth width prefix max-indent)
+ (let* ((len (frame-num-locals frame))
+ (nvalues (frame-local-ref frame (1- len)))
+ (prefix (build-prefix prefix depth "| " "~d< "max-indent)))
+ (case nvalues
+ ((0)
+ (format (current-error-port) "~ano values\n" prefix))
+ ((1)
+ (format (current-error-port) "~a~v_@y\n"
+ prefix
+ width
+ (frame-local-ref frame (- len 2))))
+ (else
+ ;; this should work, but there appears to be a bug
+ ;; "~a~d values_~_{ ~v_@y~}\n"
+ (format (current-error-port) "~a~d values_~{ ~a~}\n"
+ prefix nvalues
+ (map (lambda (val)
+ (format #f "~v_@y" width val))
+ (frame-return-values frame)))))))
+
+(define* (trace-calls-to-procedure proc #\key (width 80) (vm (the-vm))
+ (prefix "trace_ ")
+ (max-indent (- width 40)))
+ (define (apply-handler frame depth)
+ (print-application frame depth width prefix max-indent))
+ (define (return-handler frame depth)
+ (print-return frame depth width prefix max-indent))
+ (trap-calls-to-procedure proc apply-handler return-handler
+ #\vm vm))
+
+(define* (trace-calls-in-procedure proc #\key (width 80) (vm (the-vm))
+ (prefix "trace_ ")
+ (max-indent (- width 40)))
+ (define (apply-handler frame depth)
+ (print-application frame depth width prefix max-indent))
+ (define (return-handler frame depth)
+ (print-return frame depth width prefix max-indent))
+ (trap-calls-in-dynamic-extent proc apply-handler return-handler
+ #\vm vm))
+
+(define* (trace-instructions-in-procedure proc #\key (width 80) (vm (the-vm))
+ (max-indent (- width 40)))
+ (define (trace-next frame)
+ (let* ((ip (frame-instruction-pointer frame))
+ (objcode (program-objcode (frame-procedure frame)))
+ (opcode (bytevector-u8-ref (objcode->bytecode objcode)
+ (+ ip *objcode-header-len*))))
+ (format #t "~8d_ ~a\n" ip (opcode->instruction opcode))))
+
+ (trap-instructions-in-dynamic-extent proc trace-next
+ #\vm vm))
+
+;; Note that because this procedure manipulates the VM trace level
+;; directly, it doesn't compose well with traps at the REPL.
+;;
+(define* (call-with-trace thunk #\key (calls? #t) (instructions? #f)
+ (width 80) (vm (the-vm)) (max-indent (- width 40)))
+ (let ((call-trap #f)
+ (inst-trap #f))
+ (dynamic-wind
+ (lambda ()
+ (if calls?
+ (set! call-trap
+ (trace-calls-in-procedure thunk #\vm vm #\width width
+ #\max-indent max-indent)))
+ (if instructions?
+ (set! inst-trap
+ (trace-instructions-in-procedure thunk #\vm vm #\width width
+ #\max-indent max-indent)))
+ (set-vm-trace-level! vm (1+ (vm-trace-level vm))))
+ thunk
+ (lambda ()
+ (set-vm-trace-level! vm (1- (vm-trace-level vm)))
+ (if call-trap (call-trap))
+ (if inst-trap (inst-trap))
+ (set! call-trap #f)
+ (set! inst-trap #f)))))
+;;; trap-state.scm_ a set of traps
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary_
+;;;
+;;; Code_
+
+(define-module (system vm trap-state)
+ #\use-module (system base syntax)
+ #\use-module ((srfi srfi-1) #\select (fold))
+ #\use-module (system vm vm)
+ #\use-module (system vm traps)
+ #\use-module (system vm trace)
+ #\use-module (system vm frame)
+ #\use-module (system vm program)
+ #\export (add-trap!
+ list-traps
+ trap-enabled?
+ trap-name
+ enable-trap!
+ disable-trap!
+ delete-trap!
+
+ with-default-trap-handler
+ install-trap-handler!
+
+ add-trap-at-procedure-call!
+ add-trace-at-procedure-call!
+ add-trap-at-source-location!
+ add-ephemeral-trap-at-frame-finish!
+ add-ephemeral-stepping-trap!))
+
+(define %default-trap-handler (make-fluid))
+
+(define (default-trap-handler frame idx trap-name)
+ (let ((default-handler (fluid-ref %default-trap-handler)))
+ (if default-handler
+ (default-handler frame idx trap-name)
+ (warn "Trap with no handler installed" frame idx trap-name))))
+
+(define-record <trap-wrapper>
+ index
+ enabled?
+ trap
+ name)
+
+(define-record <trap-state>
+ (handler default-trap-handler)
+ (next-idx 0)
+ (next-ephemeral-idx -1)
+ (wrappers '()))
+
+(define (trap-wrapper<? t1 t2)
+ (< (trap-wrapper-index t1) (trap-wrapper-index t2)))
+
+;; The interface that a trap provides to the outside world is that of a
+;; procedure, which when called disables the trap, and returns a
+;; procedure to enable the trap. Perhaps this is a bit too odd and we
+;; should fix this.
+(define (enable-trap-wrapper! wrapper)
+ (if (trap-wrapper-enabled? wrapper)
+ (error "Trap already enabled" (trap-wrapper-index wrapper))
+ (let ((trap (trap-wrapper-trap wrapper)))
+ (set! (trap-wrapper-trap wrapper) (trap))
+ (set! (trap-wrapper-enabled? wrapper) #t))))
+
+(define (disable-trap-wrapper! wrapper)
+ (if (not (trap-wrapper-enabled? wrapper))
+ (error "Trap already disabled" (trap-wrapper-index wrapper))
+ (let ((trap (trap-wrapper-trap wrapper)))
+ (set! (trap-wrapper-trap wrapper) (trap))
+ (set! (trap-wrapper-enabled? wrapper) #f))))
+
+(define (add-trap-wrapper! trap-state wrapper)
+ (set! (trap-state-wrappers trap-state)
+ (append (trap-state-wrappers trap-state) (list wrapper)))
+ (trap-wrapper-index wrapper))
+
+(define (remove-trap-wrapper! trap-state wrapper)
+ (set! (trap-state-wrappers trap-state)
+ (delq wrapper (trap-state-wrappers trap-state))))
+
+(define (trap-state->trace-level trap-state)
+ (fold (lambda (wrapper level)
+ (if (trap-wrapper-enabled? wrapper)
+ (1+ level)
+ level))
+ 0
+ (trap-state-wrappers trap-state)))
+
+(define (wrapper-at-index trap-state idx)
+ (let lp ((wrappers (trap-state-wrappers trap-state)))
+ (cond
+ ((null? wrappers)
+ (warn "no wrapper found with index in trap-state" idx)
+ #f)
+ ((eqv? (trap-wrapper-index (car wrappers)) idx)
+ (car wrappers))
+ (else
+ (lp (cdr wrappers))))))
+
+(define (next-index! trap-state)
+ (let ((idx (trap-state-next-idx trap-state)))
+ (set! (trap-state-next-idx trap-state) (1+ idx))
+ idx))
+
+(define (next-ephemeral-index! trap-state)
+ (let ((idx (trap-state-next-ephemeral-idx trap-state)))
+ (set! (trap-state-next-ephemeral-idx trap-state) (1- idx))
+ idx))
+
+(define (handler-for-index trap-state idx)
+ (lambda (frame)
+ (let ((wrapper (wrapper-at-index trap-state idx))
+ (handler (trap-state-handler trap-state)))
+ (if wrapper
+ (handler frame
+ (trap-wrapper-index wrapper)
+ (trap-wrapper-name wrapper))))))
+
+(define (ephemeral-handler-for-index trap-state idx handler)
+ (lambda (frame)
+ (let ((wrapper (wrapper-at-index trap-state idx)))
+ (if wrapper
+ (begin
+ (if (trap-wrapper-enabled? wrapper)
+ (disable-trap-wrapper! wrapper))
+ (remove-trap-wrapper! trap-state wrapper)
+ (handler frame))))))
+
+
+
+;;;
+;;; VM-local trap states
+;;;
+
+(define *trap-states* (make-weak-key-hash-table))
+
+(define (trap-state-for-vm vm)
+ (or (hashq-ref *trap-states* vm)
+ (let ((ts (make-trap-state)))
+ (hashq-set! *trap-states* vm ts)
+ (trap-state-for-vm vm))))
+
+(define (the-trap-state)
+ (trap-state-for-vm (the-vm)))
+
+
+
+;;;
+;;; API
+;;;
+
+(define* (with-default-trap-handler handler thunk
+ #\optional (trap-state (the-trap-state)))
+ (with-fluids ((%default-trap-handler handler))
+ (dynamic-wind
+ (lambda ()
+ ;; Don't enable hooks if the handler is #f.
+ (if handler
+ (set-vm-trace-level! (the-vm) (trap-state->trace-level trap-state))))
+ thunk
+ (lambda ()
+ (if handler
+ (set-vm-trace-level! (the-vm) 0))))))
+
+(define* (list-traps #\optional (trap-state (the-trap-state)))
+ (map trap-wrapper-index (trap-state-wrappers trap-state)))
+
+(define* (trap-name idx #\optional (trap-state (the-trap-state)))
+ (and=> (wrapper-at-index trap-state idx)
+ trap-wrapper-name))
+
+(define* (trap-enabled? idx #\optional (trap-state (the-trap-state)))
+ (and=> (wrapper-at-index trap-state idx)
+ trap-wrapper-enabled?))
+
+(define* (enable-trap! idx #\optional (trap-state (the-trap-state)))
+ (and=> (wrapper-at-index trap-state idx)
+ enable-trap-wrapper!))
+
+(define* (disable-trap! idx #\optional (trap-state (the-trap-state)))
+ (and=> (wrapper-at-index trap-state idx)
+ disable-trap-wrapper!))
+
+(define* (delete-trap! idx #\optional (trap-state (the-trap-state)))
+ (and=> (wrapper-at-index trap-state idx)
+ (lambda (wrapper)
+ (if (trap-wrapper-enabled? wrapper)
+ (disable-trap-wrapper! wrapper))
+ (remove-trap-wrapper! trap-state wrapper))))
+
+(define* (install-trap-handler! handler #\optional (trap-state (the-trap-state)))
+ (set! (trap-state-handler trap-state) handler))
+
+(define* (add-trap-at-procedure-call! proc #\optional (trap-state (the-trap-state)))
+ (let* ((idx (next-index! trap-state))
+ (trap (trap-at-procedure-call
+ proc
+ (handler-for-index trap-state idx))))
+ (add-trap-wrapper!
+ trap-state
+ (make-trap-wrapper
+ idx #t trap
+ (format #f "Breakpoint at ~a" proc)))))
+
+(define* (add-trace-at-procedure-call! proc
+ #\optional (trap-state (the-trap-state)))
+ (let* ((idx (next-index! trap-state))
+ (trap (trace-calls-to-procedure
+ proc
+ #\prefix (format #f "Trap ~a_ " idx))))
+ (add-trap-wrapper!
+ trap-state
+ (make-trap-wrapper
+ idx #t trap
+ (format #f "Tracepoint at ~a" proc)))))
+
+(define* (add-trap-at-source-location! file user-line
+ #\optional (trap-state (the-trap-state)))
+ (let* ((idx (next-index! trap-state))
+ (trap (trap-at-source-location file user-line
+ (handler-for-index trap-state idx))))
+ (add-trap-wrapper!
+ trap-state
+ (make-trap-wrapper
+ idx #t trap
+ (format #f "Breakpoint at ~a_~a" file user-line)))))
+
+;; handler _= frame -> nothing
+(define* (add-ephemeral-trap-at-frame-finish! frame handler
+ #\optional (trap-state
+ (the-trap-state)))
+ (let* ((idx (next-ephemeral-index! trap-state))
+ (trap (trap-frame-finish
+ frame
+ (ephemeral-handler-for-index trap-state idx handler)
+ (lambda (frame) (delete-trap! idx trap-state)))))
+ (add-trap-wrapper!
+ trap-state
+ (make-trap-wrapper
+ idx #t trap
+ (format #f "Return from ~a" frame)))))
+
+(define (source-string source)
+ (if source
+ (format #f "~a_~a_~a" (or (source_file source) "unknown file")
+ (source_line-for-user source) (source_column source))
+ "unknown source location"))
+
+(define* (add-ephemeral-stepping-trap! frame handler
+ #\optional (trap-state
+ (the-trap-state))
+ #\key (into? #t) (instruction? #f))
+ (define (wrap-predicate-according-to-into predicate)
+ (if into?
+ predicate
+ (let ((fp (frame-address frame)))
+ (lambda (f)
+ (and (<= (frame-address f) fp)
+ (predicate f))))))
+
+ (let* ((source (frame-next-source frame))
+ (idx (next-ephemeral-index! trap-state))
+ (trap (trap-matching-instructions
+ (wrap-predicate-according-to-into
+ (if instruction?
+ (lambda (f) #t)
+ (lambda (f) (not (equal? (frame-next-source f) source)))))
+ (ephemeral-handler-for-index trap-state idx handler))))
+ (add-trap-wrapper!
+ trap-state
+ (make-trap-wrapper
+ idx #t trap
+ (if instruction?
+ (if into?
+ "Step to different instruction"
+ (format #f "Step to different instruction in ~a" frame))
+ (if into?
+ (format #f "Step into ~a" (source-string source))
+ (format #f "Step out of ~a" (source-string source))))))))
+
+(define* (add-trap! trap name #\optional (trap-state (the-trap-state)))
+ (let* ((idx (next-index! trap-state)))
+ (add-trap-wrapper!
+ trap-state
+ (make-trap-wrapper idx #t trap name))))
+;;; Traps_ stepping, breakpoints, and such.
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary_
+;;;
+;;; Guile's debugging capabilities come from the hooks that its VM
+;;; provides. For example, there is a hook that is fired when a function
+;;; is called, and even a hook that gets fired at every retired
+;;; instruction.
+;;;
+;;; But as the firing of these hooks is interleaved with the program
+;;; execution, if we want to debug a program, we have to write an
+;;; imperative program that mutates the state of these hooks, and to
+;;; dispatch the hooks to a more semantic context.
+;;;
+;;; For example if we have placed a breakpoint at foo.scm_38, and
+;;; determined that that location maps to the 18th instruction in
+;;; procedure `bar', then we will need per-instruction hooks within
+;;; `bar' -- but when running other procedures, we can have the
+;;; per-instruction hooks off.
+;;;
+;;; Our approach is to define "traps". The behavior of a trap is
+;;; specified when the trap is created. After creation, traps expose a
+;;; limited, uniform interface_ they are either on or off.
+;;;
+;;; To take our foo.scm_38 example again, we can define a trap that
+;;; calls a function when control transfers to that source line --
+;;; trap-at-source-location below. Calling the trap-at-source-location
+;;; function adds to the VM hooks in such at way that it can do its job.
+;;; The result of calling the function is a "disable-hook" closure that,
+;;; when called, will turn off that trap.
+;;;
+;;; The result of calling the "disable-hook" closure, in turn, is an
+;;; "enable-hook" closure, which when called turns the hook back on, and
+;;; returns a "disable-hook" closure.
+;;;
+;;; It's a little confusing. The summary is, call these functions to add
+;;; a trap; and call their return value to disable the trap.
+;;;
+;;; Code_
+
+(define-module (system vm traps)
+ #\use-module (system base pmatch)
+ #\use-module (system vm vm)
+ #\use-module (system vm frame)
+ #\use-module (system vm program)
+ #\use-module (system vm objcode)
+ #\use-module (system vm instruction)
+ #\use-module (system xref)
+ #\use-module (rnrs bytevectors)
+ #\export (trap-at-procedure-call
+ trap-in-procedure
+ trap-instructions-in-procedure
+ trap-at-procedure-ip-in-range
+ trap-at-source-location
+ trap-frame-finish
+ trap-in-dynamic-extent
+ trap-calls-in-dynamic-extent
+ trap-instructions-in-dynamic-extent
+ trap-calls-to-procedure
+ trap-matching-instructions))
+
+(define-syntax arg-check
+ (syntax-rules ()
+ ((_ arg predicate? message)
+ (if (not (predicate? arg))
+ (error "bad argument ~a_ ~a" 'arg message)))
+ ((_ arg predicate?)
+ (if (not (predicate? arg))
+ (error "bad argument ~a_ expected ~a" 'arg 'predicate?)))))
+
+(define (new-disabled-trap vm enable disable)
+ (let ((enabled? #f))
+ (define-syntax disabled?
+ (identifier-syntax
+ (disabled? (not enabled?))
+ ((set! disabled? val) (set! enabled? (not val)))))
+
+ (define* (enable-trap #\optional frame)
+ (if enabled? (error "trap already enabled"))
+ (enable frame)
+ (set! enabled? #t)
+ disable-trap)
+
+ (define* (disable-trap #\optional frame)
+ (if disabled? (error "trap already disabled"))
+ (disable frame)
+ (set! disabled? #t)
+ enable-trap)
+
+ enable-trap))
+
+(define (new-enabled-trap vm frame enable disable)
+ ((new-disabled-trap vm enable disable) frame))
+
+(define (frame-matcher proc match-objcode?)
+ (let ((proc (if (struct? proc)
+ (procedure proc)
+ proc)))
+ (if match-objcode?
+ (lambda (frame)
+ (let ((frame-proc (frame-procedure frame)))
+ (or (eq? frame-proc proc)
+ (and (program? frame-proc)
+ (eq? (program-objcode frame-proc)
+ (program-objcode proc))))))
+ (lambda (frame)
+ (eq? (frame-procedure frame) proc)))))
+
+;; A basic trap, fires when a procedure is called.
+;;
+(define* (trap-at-procedure-call proc handler #\key (vm (the-vm))
+ (closure? #f)
+ (our-frame? (frame-matcher proc closure?)))
+ (arg-check proc procedure?)
+ (arg-check handler procedure?)
+ (let ()
+ (define (apply-hook frame)
+ (if (our-frame? frame)
+ (handler frame)))
+
+ (new-enabled-trap
+ vm #f
+ (lambda (frame)
+ (add-hook! (vm-apply-hook vm) apply-hook))
+ (lambda (frame)
+ (remove-hook! (vm-apply-hook vm) apply-hook)))))
+
+;; A more complicated trap, traps when control enters a procedure.
+;;
+;; Control can enter a procedure via_
+;; * A procedure call.
+;; * A return to a procedure's frame on the stack.
+;; * A continuation returning directly to an application of this
+;; procedure.
+;;
+;; Control can leave a procedure via_
+;; * A normal return from the procedure.
+;; * An application of another procedure.
+;; * An invocation of a continuation.
+;; * An abort.
+;;
+(define* (trap-in-procedure proc enter-handler exit-handler
+ #\key current-frame (vm (the-vm))
+ (closure? #f)
+ (our-frame? (frame-matcher proc closure?)))
+ (arg-check proc procedure?)
+ (arg-check enter-handler procedure?)
+ (arg-check exit-handler procedure?)
+ (let ((in-proc? #f))
+ (define (enter-proc frame)
+ (if in-proc?
+ (warn "already in proc" frame)
+ (begin
+ (enter-handler frame)
+ (set! in-proc? #t))))
+
+ (define (exit-proc frame)
+ (if in-proc?
+ (begin
+ (exit-handler frame)
+ (set! in-proc? #f))
+ (warn "not in proc" frame)))
+
+ (define (apply-hook frame)
+ (if in-proc?
+ (exit-proc frame))
+ (if (our-frame? frame)
+ (enter-proc frame)))
+
+ (define (push-cont-hook frame)
+ (if in-proc?
+ (exit-proc frame)))
+
+ (define (pop-cont-hook frame)
+ (if in-proc?
+ (exit-proc frame))
+ (if (our-frame? (frame-previous frame))
+ (enter-proc (frame-previous frame))))
+
+ (define (abort-hook frame)
+ (if in-proc?
+ (exit-proc frame))
+ (if (our-frame? frame)
+ (enter-proc frame)))
+
+ (define (restore-hook frame)
+ (if in-proc?
+ (exit-proc frame))
+ (if (our-frame? frame)
+ (enter-proc frame)))
+
+ (new-enabled-trap
+ vm current-frame
+ (lambda (frame)
+ (add-hook! (vm-apply-hook vm) apply-hook)
+ (add-hook! (vm-push-continuation-hook vm) push-cont-hook)
+ (add-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
+ (add-hook! (vm-abort-continuation-hook vm) abort-hook)
+ (add-hook! (vm-restore-continuation-hook vm) restore-hook)
+ (if (and frame (our-frame? frame))
+ (enter-proc frame)))
+ (lambda (frame)
+ (if in-proc?
+ (exit-proc frame))
+ (remove-hook! (vm-apply-hook vm) apply-hook)
+ (remove-hook! (vm-push-continuation-hook vm) push-cont-hook)
+ (remove-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
+ (remove-hook! (vm-abort-continuation-hook vm) abort-hook)
+ (remove-hook! (vm-restore-continuation-hook vm) restore-hook)))))
+
+;; Building on trap-in-procedure, we have trap-instructions-in-procedure
+;;
+(define* (trap-instructions-in-procedure proc next-handler exit-handler
+ #\key current-frame (vm (the-vm))
+ (closure? #f)
+ (our-frame?
+ (frame-matcher proc closure?)))
+ (arg-check proc procedure?)
+ (arg-check next-handler procedure?)
+ (arg-check exit-handler procedure?)
+ (let ()
+ (define (next-hook frame)
+ (if (our-frame? frame)
+ (next-handler frame)))
+
+ (define (enter frame)
+ (add-hook! (vm-next-hook vm) next-hook)
+ (if frame (next-hook frame)))
+
+ (define (exit frame)
+ (exit-handler frame)
+ (remove-hook! (vm-next-hook vm) next-hook))
+
+ (trap-in-procedure proc enter exit
+ #\current-frame current-frame #\vm vm
+ #\our-frame? our-frame?)))
+
+(define (non-negative-integer? x)
+ (and (number? x) (integer? x) (exact? x) (not (negative? x))))
+
+(define (positive-integer? x)
+ (and (number? x) (integer? x) (exact? x) (positive? x)))
+
+(define (range? x)
+ (and (list? x)
+ (and-map (lambda (x)
+ (and (pair? x)
+ (non-negative-integer? (car x))
+ (non-negative-integer? (cdr x))))
+ x)))
+
+(define (in-range? range i)
+ (or-map (lambda (bounds)
+ (and (<= (car bounds) i)
+ (< i (cdr bounds))))
+ range))
+
+;; Building on trap-instructions-in-procedure, we have
+;; trap-at-procedure-ip-in-range.
+;;
+(define* (trap-at-procedure-ip-in-range proc range handler
+ #\key current-frame (vm (the-vm))
+ (closure? #f)
+ (our-frame?
+ (frame-matcher proc closure?)))
+ (arg-check proc procedure?)
+ (arg-check range range?)
+ (arg-check handler procedure?)
+ (let ((fp-stack '()))
+ (define (cull-frames! fp)
+ (let lp ((frames fp-stack))
+ (if (and (pair? frames) (< (car frames) fp))
+ (lp (cdr frames))
+ (set! fp-stack frames))))
+
+ (define (next-handler frame)
+ (let ((fp (frame-address frame))
+ (ip (frame-instruction-pointer frame)))
+ (cull-frames! fp)
+ (let ((now-in-range? (in-range? range ip))
+ (was-in-range? (and (pair? fp-stack) (= (car fp-stack) fp))))
+ (cond
+ (was-in-range?
+ (if (not now-in-range?)
+ (set! fp-stack (cdr fp-stack))))
+ (now-in-range?
+ (set! fp-stack (cons fp fp-stack))
+ (handler frame))))))
+
+ (define (exit-handler frame)
+ (if (and (pair? fp-stack)
+ (= (car fp-stack) (frame-address frame)))
+ (set! fp-stack (cdr fp-stack))))
+
+ (trap-instructions-in-procedure proc next-handler exit-handler
+ #\current-frame current-frame #\vm vm
+ #\our-frame? our-frame?)))
+
+;; FIXME_ define this in objcode somehow. We are reffing the first
+;; uint32 in the objcode, which is the length of the program (without
+;; the meta).
+(define (program-last-ip prog)
+ (bytevector-u32-native-ref (objcode->bytecode (program-objcode prog)) 0))
+
+(define (program-sources-by-line proc file)
+ (let lp ((sources (program-sources-pre-retire proc))
+ (out '()))
+ (if (pair? sources)
+ (lp (cdr sources)
+ (pmatch (car sources)
+ ((,start-ip ,start-file ,start-line . ,start-col)
+ (if (equal? start-file file)
+ (cons (cons start-line
+ (if (pair? (cdr sources))
+ (pmatch (cadr sources)
+ ((,end-ip . _)
+ (cons start-ip end-ip))
+ (else (error "unexpected")))
+ (cons start-ip (program-last-ip proc))))
+ out)
+ out))
+ (else (error "unexpected"))))
+ (let ((alist '()))
+ (for-each
+ (lambda (pair)
+ (set! alist
+ (assv-set! alist (car pair)
+ (cons (cdr pair)
+ (or (assv-ref alist (car pair))
+ '())))))
+ out)
+ (sort! alist (lambda (x y) (< (car x) (car y))))
+ alist))))
+
+(define (source->ip-range proc file line)
+ (or (or-map (lambda (line-and-ranges)
+ (cond
+ ((= (car line-and-ranges) line)
+ (cdr line-and-ranges))
+ ((> (car line-and-ranges) line)
+ (warn "no instructions found at" file "_" line
+ "; using line" (car line-and-ranges) "instead")
+ (cdr line-and-ranges))
+ (else #f)))
+ (program-sources-by-line proc file))
+ (begin
+ (warn "no instructions found for" file "_" line)
+ '())))
+
+(define (source-closures-or-procedures file line)
+ (let ((closures (source-closures file line)))
+ (if (pair? closures)
+ (values closures #t)
+ (values (source-procedures file line) #f))))
+
+;; Building on trap-on-instructions-in-procedure, we have
+;; trap-at-source-location. The parameter `user-line' is one-indexed, as
+;; a user counts lines, instead of zero-indexed, as Guile counts lines.
+;;
+(define* (trap-at-source-location file user-line handler
+ #\key current-frame (vm (the-vm)))
+ (arg-check file string?)
+ (arg-check user-line positive-integer?)
+ (arg-check handler procedure?)
+ (let ((traps #f))
+ (call-with-values
+ (lambda () (source-closures-or-procedures file (1- user-line)))
+ (lambda (procs closures?)
+ (new-enabled-trap
+ vm current-frame
+ (lambda (frame)
+ (set! traps
+ (map
+ (lambda (proc)
+ (let ((range (source->ip-range proc file (1- user-line))))
+ (trap-at-procedure-ip-in-range proc range handler
+ #\current-frame current-frame
+ #\vm vm
+ #\closure? closures?)))
+ procs))
+ (if (null? traps)
+ (error "No procedures found at ~a_~a." file user-line)))
+ (lambda (frame)
+ (for-each (lambda (trap) (trap frame)) traps)
+ (set! traps #f)))))))
+
+
+
+;; On a different tack, now we're going to build up a set of traps that
+;; do useful things during the dynamic extent of a procedure's
+;; application. First, a trap for when a frame returns.
+;;
+(define* (trap-frame-finish frame return-handler abort-handler
+ #\key (vm (the-vm)))
+ (arg-check frame frame?)
+ (arg-check return-handler procedure?)
+ (arg-check abort-handler procedure?)
+ (let ((fp (frame-address frame)))
+ (define (pop-cont-hook frame)
+ (if (and fp (eq? (frame-address frame) fp))
+ (begin
+ (set! fp #f)
+ (return-handler frame))))
+
+ (define (abort-hook frame)
+ (if (and fp (< (frame-address frame) fp))
+ (begin
+ (set! fp #f)
+ (abort-handler frame))))
+
+ (new-enabled-trap
+ vm frame
+ (lambda (frame)
+ (if (not fp)
+ (error "return-or-abort traps may only be enabled once"))
+ (add-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
+ (add-hook! (vm-abort-continuation-hook vm) abort-hook)
+ (add-hook! (vm-restore-continuation-hook vm) abort-hook))
+ (lambda (frame)
+ (set! fp #f)
+ (remove-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
+ (remove-hook! (vm-abort-continuation-hook vm) abort-hook)
+ (remove-hook! (vm-restore-continuation-hook vm) abort-hook)))))
+
+;; A more traditional dynamic-wind trap. Perhaps this should not be
+;; based on the above trap-frame-finish?
+;;
+(define* (trap-in-dynamic-extent proc enter-handler return-handler abort-handler
+ #\key current-frame (vm (the-vm))
+ (closure? #f)
+ (our-frame? (frame-matcher proc closure?)))
+ (arg-check proc procedure?)
+ (arg-check enter-handler procedure?)
+ (arg-check return-handler procedure?)
+ (arg-check abort-handler procedure?)
+ (let ((exit-trap #f))
+ (define (return-hook frame)
+ (exit-trap frame) ; disable the return/abort trap.
+ (set! exit-trap #f)
+ (return-handler frame))
+
+ (define (abort-hook frame)
+ (exit-trap frame) ; disable the return/abort trap.
+ (set! exit-trap #f)
+ (abort-handler frame))
+
+ (define (apply-hook frame)
+ (if (and (not exit-trap) (our-frame? frame))
+ (begin
+ (enter-handler frame)
+ (set! exit-trap
+ (trap-frame-finish frame return-hook abort-hook
+ #\vm vm)))))
+
+ (new-enabled-trap
+ vm current-frame
+ (lambda (frame)
+ (add-hook! (vm-apply-hook vm) apply-hook))
+ (lambda (frame)
+ (if exit-trap
+ (abort-hook frame))
+ (set! exit-trap #f)
+ (remove-hook! (vm-apply-hook vm) apply-hook)))))
+
+;; Trapping all procedure calls within a dynamic extent, recording the
+;; depth of the call stack relative to the original procedure.
+;;
+(define* (trap-calls-in-dynamic-extent proc apply-handler return-handler
+ #\key current-frame (vm (the-vm))
+ (closure? #f)
+ (our-frame?
+ (frame-matcher proc closure?)))
+ (arg-check proc procedure?)
+ (arg-check apply-handler procedure?)
+ (arg-check return-handler procedure?)
+ (let ((*call-depth* 0))
+ (define (trace-push frame)
+ (set! *call-depth* (1+ *call-depth*)))
+
+ (define (trace-pop frame)
+ (return-handler frame *call-depth*)
+ (set! *call-depth* (1- *call-depth*)))
+
+ (define (trace-apply frame)
+ (apply-handler frame *call-depth*))
+
+ ;; FIXME_ recalc depth on abort
+
+ (define (enter frame)
+ (add-hook! (vm-push-continuation-hook vm) trace-push)
+ (add-hook! (vm-pop-continuation-hook vm) trace-pop)
+ (add-hook! (vm-apply-hook vm) trace-apply))
+
+ (define (leave frame)
+ (remove-hook! (vm-push-continuation-hook vm) trace-push)
+ (remove-hook! (vm-pop-continuation-hook vm) trace-pop)
+ (remove-hook! (vm-apply-hook vm) trace-apply))
+
+ (define (return frame)
+ (leave frame))
+
+ (define (abort frame)
+ (leave frame))
+
+ (trap-in-dynamic-extent proc enter return abort
+ #\current-frame current-frame #\vm vm
+ #\our-frame? our-frame?)))
+
+;; Trapping all retired intructions within a dynamic extent.
+;;
+(define* (trap-instructions-in-dynamic-extent proc next-handler
+ #\key current-frame (vm (the-vm))
+ (closure? #f)
+ (our-frame?
+ (frame-matcher proc closure?)))
+ (arg-check proc procedure?)
+ (arg-check next-handler procedure?)
+ (let ()
+ (define (trace-next frame)
+ (next-handler frame))
+
+ (define (enter frame)
+ (add-hook! (vm-next-hook vm) trace-next))
+
+ (define (leave frame)
+ (remove-hook! (vm-next-hook vm) trace-next))
+
+ (define (return frame)
+ (leave frame))
+
+ (define (abort frame)
+ (leave frame))
+
+ (trap-in-dynamic-extent proc enter return abort
+ #\current-frame current-frame #\vm vm
+ #\our-frame? our-frame?)))
+
+;; Traps calls and returns for a given procedure, keeping track of the call depth.
+;;
+(define* (trap-calls-to-procedure proc apply-handler return-handler
+ #\key (vm (the-vm)))
+ (arg-check proc procedure?)
+ (arg-check apply-handler procedure?)
+ (arg-check return-handler procedure?)
+ (let ((pending-finish-traps '())
+ (last-fp #f))
+ (define (apply-hook frame)
+ (let ((depth (length pending-finish-traps)))
+
+ (apply-handler frame depth)
+
+ (if (not (eq? (frame-address frame) last-fp))
+ (let ((finish-trap #f))
+ (define (frame-finished frame)
+ (finish-trap frame) ;; disables the trap.
+ (set! pending-finish-traps
+ (delq finish-trap pending-finish-traps))
+ (set! finish-trap #f))
+
+ (define (return-hook frame)
+ (frame-finished frame)
+ (return-handler frame depth))
+
+ ;; FIXME_ abort handler?
+ (define (abort-hook frame)
+ (frame-finished frame))
+
+ (set! finish-trap
+ (trap-frame-finish frame return-hook abort-hook #\vm vm))
+ (set! pending-finish-traps
+ (cons finish-trap pending-finish-traps))))))
+
+ ;; The basic idea is that we install one trap that fires for calls,
+ ;; but that each call installs its own finish trap. Those finish
+ ;; traps remove themselves as their frames finish or abort.
+ ;;
+ ;; However since to the outside world we present the interface of
+ ;; just being one trap, disabling this calls-to-procedure trap
+ ;; should take care of disabling all of the pending finish traps. We
+ ;; keep track of pending traps through the pending-finish-traps
+ ;; list.
+ ;;
+ ;; So since we know that the trap-at-procedure will be enabled, and
+ ;; thus returning a disable closure, we make sure to wrap that
+ ;; closure in something that will disable pending finish traps.
+ (define (with-pending-finish-disablers trap)
+ (define (with-pending-finish-enablers trap)
+ (lambda* (#\optional frame)
+ (with-pending-finish-disablers (trap frame))))
+
+ (lambda* (#\optional frame)
+ (for-each (lambda (disable) (disable frame))
+ pending-finish-traps)
+ (set! pending-finish-traps '())
+ (with-pending-finish-enablers (trap frame))))
+
+ (with-pending-finish-disablers
+ (trap-at-procedure-call proc apply-hook #\vm vm))))
+
+;; Trap when the source location changes.
+;;
+(define* (trap-matching-instructions frame-pred handler
+ #\key (vm (the-vm)))
+ (arg-check frame-pred procedure?)
+ (arg-check handler procedure?)
+ (let ()
+ (define (next-hook frame)
+ (if (frame-pred frame)
+ (handler frame)))
+
+ (new-enabled-trap
+ vm #f
+ (lambda (frame)
+ (add-hook! (vm-next-hook vm) next-hook))
+ (lambda (frame)
+ (remove-hook! (vm-next-hook vm) next-hook)))))
+;;; Guile VM core
+
+;;; Copyright (C) 2001, 2009, 2010 Free Software Foundation, Inc.
+;;;
+;;; This library 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 library 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 library; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Code_
+
+(define-module (system vm vm)
+ #\export (vm?
+ make-vm the-vm call-with-vm
+ vm_ip vm_sp vm_fp
+
+ vm-trace-level set-vm-trace-level!
+ vm-engine set-vm-engine! set-default-vm-engine!
+ vm-push-continuation-hook vm-pop-continuation-hook
+ vm-apply-hook
+ vm-next-hook
+ vm-abort-continuation-hook vm-restore-continuation-hook))
+
+(load-extension (string-append "libguile-" (effective-version))
+ "scm_init_vm")
+;;;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+
+(define-module (system xref)
+ #\use-module (system base pmatch)
+ #\use-module (system base compile)
+ #\use-module (system vm program)
+ #\use-module (srfi srfi-1)
+ #\export (*xref-ignored-modules*
+ procedure-callees
+ procedure-callers
+ source-closures
+ source-procedures))
+
+;;;
+;;; The cross-reference database_ who calls whom.
+;;;
+
+(define (program-callee-rev-vars prog)
+ (define (cons-uniq x y)
+ (if (memq x y) y (cons x y)))
+ (cond
+ ((program-objects prog)
+ => (lambda (objects)
+ (let ((n (vector-length objects))
+ (progv (make-vector (vector-length objects) #f))
+ (asm (decompile (program-objcode prog) #\to 'assembly)))
+ (pmatch asm
+ ((load-program ,labels ,len . ,body)
+ (for-each
+ (lambda (x)
+ (pmatch x
+ ((toplevel-ref ,n) (vector-set! progv n #t))
+ ((toplevel-set ,n) (vector-set! progv n #t))))
+ body)))
+ (let lp ((i 0) (out '()))
+ (cond
+ ((= i n) out)
+ ((program? (vector-ref objects i))
+ (lp (1+ i)
+ (fold cons-uniq out
+ (program-callee-rev-vars (vector-ref objects i)))))
+ ((vector-ref progv i)
+ (let ((obj (vector-ref objects i)))
+ (if (variable? obj)
+ (lp (1+ i) (cons-uniq obj out))
+ ;; otherwise it's an unmemoized binding
+ (pmatch obj
+ (,sym (guard (symbol? sym))
+ (let ((v (module-variable (or (program-module prog)
+ the-root-module)
+ sym)))
+ (lp (1+ i) (if v (cons-uniq v out) out))))
+ ((,mod ,sym ,public?)
+ ;; hm, hacky.
+ (let* ((m (nested-ref-module (resolve-module '() #f)
+ mod))
+ (v (and m
+ (module-variable
+ (if public?
+ (module-public-interface m)
+ m)
+ sym))))
+ (lp (1+ i)
+ (if v (cons-uniq v out) out))))))))
+ (else (lp (1+ i) out)))))))
+ (else '())))
+
+(define (procedure-callee-rev-vars proc)
+ (cond
+ ((program? proc) (program-callee-rev-vars proc))
+ (else '())))
+
+(define (procedure-callees prog)
+ "Evaluates to a list of the given program callees."
+ (let lp ((in (procedure-callee-rev-vars prog)) (out '()))
+ (cond ((null? in) out)
+ ((variable-bound? (car in))
+ (lp (cdr in) (cons (variable-ref (car in)) out)))
+ (else (lp (cdr in) out)))))
+
+;; var -> ((module-name caller ...) ...)
+(define *callers-db* #f)
+;; module-name -> (callee ...)
+(define *module-callees-db* (make-hash-table))
+;; (module-name ...)
+(define *tainted-modules* '())
+
+(define *xref-ignored-modules* '((value-history)))
+(define (on-module-modified m)
+ (let ((name (module-name m)))
+ (if (and (not (member name *xref-ignored-modules*))
+ (not (member name *tainted-modules*))
+ (pair? name))
+ (set! *tainted-modules* (cons name *tainted-modules*)))))
+
+(define (add-caller callee caller mod-name)
+ (let ((all-callers (hashq-ref *callers-db* callee)))
+ (if (not all-callers)
+ (hashq-set! *callers-db* callee `((,mod-name ,caller)))
+ (let ((callers (assoc mod-name all-callers)))
+ (if callers
+ (if (not (member caller callers))
+ (set-cdr! callers (cons caller (cdr callers))))
+ (hashq-set! *callers-db* callee
+ (cons `(,mod-name ,caller) all-callers)))))))
+
+(define (forget-callers callee mod-name)
+ (hashq-set! *callers-db* callee
+ (assoc-remove! (hashq-ref *callers-db* callee '()) mod-name)))
+
+(define (add-callees callees mod-name)
+ (hash-set! *module-callees-db* mod-name
+ (append callees (hash-ref *module-callees-db* mod-name '()))))
+
+(define (untaint-modules)
+ (define (untaint m)
+ (for-each (lambda (callee) (forget-callers callee m))
+ (hash-ref *module-callees-db* m '()))
+ (ensure-callers-db m))
+ (ensure-callers-db #f)
+ (for-each untaint *tainted-modules*)
+ (set! *tainted-modules* '()))
+
+(define (ensure-callers-db mod-name)
+ (let ((mod (and mod-name (resolve-module mod-name)))
+ (visited #f))
+ (define (visit-variable var mod-name)
+ (if (variable-bound? var)
+ (let ((x (variable-ref var)))
+ (cond
+ ((and visited (hashq-ref visited x)))
+ ((procedure? x)
+ (if visited (hashq-set! visited x #t))
+ (let ((callees (filter variable-bound?
+ (procedure-callee-rev-vars x))))
+ (for-each (lambda (callee)
+ (add-caller callee x mod-name))
+ callees)
+ (add-callees callees mod-name)))))))
+
+ (define (visit-module mod)
+ (if visited (hashq-set! visited mod #t))
+ (if (not (memq on-module-modified (module-observers mod)))
+ (module-observe mod on-module-modified))
+ (let ((name (module-name mod)))
+ (module-for-each (lambda (sym var)
+ (visit-variable var name))
+ mod)))
+
+ (define (visit-submodules mod)
+ (hash-for-each
+ (lambda (name sub)
+ (if (not (and visited (hashq-ref visited sub)))
+ (begin
+ (visit-module sub)
+ (visit-submodules sub))))
+ (module-submodules mod)))
+
+ (cond ((and (not mod-name) (not *callers-db*))
+ (set! *callers-db* (make-hash-table 1000))
+ (set! visited (make-hash-table 1000))
+ (visit-submodules (resolve-module '() #f)))
+ (mod-name (visit-module mod)))))
+
+(define (procedure-callers var)
+ "Returns an association list, keyed by module name, of known callers
+of the given procedure. The latter can specified directly as a
+variable, a symbol (which gets resolved in the current module) or a
+pair of the form (module-name . variable-name), "
+ (let ((v (cond ((variable? var) var)
+ ((symbol? var) (module-variable (current-module) var))
+ (else
+ (pmatch var
+ ((,modname . ,sym)
+ (module-variable (resolve-module modname) sym))
+ (else
+ (error "expected a variable, symbol, or (modname . sym)" var)))))))
+ (untaint-modules)
+ (hashq-ref *callers-db* v '())))
+
+
+
+;;;
+;;; The source database_ procedures defined at a given source location.
+;;;
+
+;; FIXME_ refactor to share code with the xref database.
+
+;; ((ip file line . col) ...)
+(define (procedure-sources proc)
+ (cond
+ ((program? proc) (program-sources proc))
+ (else '())))
+
+;; file -> line -> (proc ...)
+(define *closure-sources-db* #f)
+;; file -> line -> (proc ...)
+(define *sources-db* #f)
+;; module-name -> proc -> sources
+(define *module-sources-db* (make-hash-table))
+;; (module-name ...)
+(define *tainted-sources* '())
+
+(define (on-source-modified m)
+ (let ((name (module-name m)))
+ (if (and (not (member name *xref-ignored-modules*))
+ (not (member name *tainted-sources*))
+ (pair? name))
+ (set! *tainted-sources* (cons name *tainted-sources*)))))
+
+(define (add-source proc file line db)
+ (let ((file-table (or (hash-ref db file)
+ (let ((table (make-hash-table)))
+ (hash-set! db file table)
+ table))))
+ (hashv-set! file-table
+ line
+ (cons proc (hashv-ref file-table line '())))))
+
+(define (forget-source proc file line db)
+ (let ((file-table (hash-ref db file)))
+ (if file-table
+ (let ((procs (delq proc (hashv-ref file-table line '()))))
+ (if (pair? procs)
+ (hashv-set! file-table line procs)
+ (hashv-remove! file-table line))))))
+
+(define (add-sources proc mod-name db)
+ (let ((sources (procedure-sources proc)))
+ (if (pair? sources)
+ (begin
+ ;; Add proc to *module-sources-db*, for book-keeping.
+ (hashq-set! (or (hash-ref *module-sources-db* mod-name)
+ (let ((table (make-hash-table)))
+ (hash-set! *module-sources-db* mod-name table)
+ table))
+ proc
+ sources)
+ ;; Actually add the source entries.
+ (for-each (lambda (source)
+ (pmatch source
+ ((,ip ,file ,line . ,col)
+ (add-source proc file line db))
+ (else (error "unexpected source format" source))))
+ sources)))
+ ;; Add source entries for nested procedures.
+ (for-each (lambda (obj)
+ (if (procedure? obj)
+ (add-sources obj mod-name *closure-sources-db*)))
+ (or (and (program? proc)
+ (and=> (program-objects proc) vector->list))
+ '()))))
+
+(define (forget-sources proc mod-name db)
+ (let ((mod-table (hash-ref *module-sources-db* mod-name)))
+ (if mod-table
+ (begin
+ ;; Forget source entries.
+ (for-each (lambda (source)
+ (pmatch source
+ ((,ip ,file ,line . ,col)
+ (forget-source proc file line db))
+ (else (error "unexpected source format" source))))
+ (hashq-ref mod-table proc '()))
+ ;; Forget the proc.
+ (hashq-remove! mod-table proc)
+ ;; Forget source entries for nested procedures.
+ (for-each (lambda (obj)
+ (if (procedure? obj)
+ (forget-sources obj mod-name *closure-sources-db*)))
+ (or (and (program? proc)
+ (and=> (program-objects proc) vector->list))
+ '()))))))
+
+(define (untaint-sources)
+ (define (untaint m)
+ (for-each (lambda (proc) (forget-sources proc m *sources-db*))
+ (cond
+ ((hash-ref *module-sources-db* m)
+ => (lambda (table)
+ (hash-for-each (lambda (proc sources) proc) table)))
+ (else '())))
+ (ensure-sources-db m))
+ (ensure-sources-db #f)
+ (for-each untaint *tainted-sources*)
+ (set! *tainted-sources* '()))
+
+(define (ensure-sources-db mod-name)
+ (define (visit-module mod)
+ (if (not (memq on-source-modified (module-observers mod)))
+ (module-observe mod on-source-modified))
+ (let ((name (module-name mod)))
+ (module-for-each
+ (lambda (sym var)
+ (if (variable-bound? var)
+ (let ((x (variable-ref var)))
+ (if (procedure? x)
+ (add-sources x name *sources-db*)))))
+ mod)))
+
+ (define visit-submodules
+ (let ((visited #f))
+ (lambda (mod)
+ (if (not visited)
+ (set! visited (make-hash-table)))
+ (hash-for-each
+ (lambda (name sub)
+ (if (not (hashq-ref visited sub))
+ (begin
+ (hashq-set! visited sub #t)
+ (visit-module sub)
+ (visit-submodules sub))))
+ (module-submodules mod)))))
+
+ (cond ((and (not mod-name) (not *sources-db*) (not *closure-sources-db*))
+ (set! *closure-sources-db* (make-hash-table 1000))
+ (set! *sources-db* (make-hash-table 1000))
+ (visit-submodules (resolve-module '() #f)))
+ (mod-name (visit-module (resolve-module mod-name)))))
+
+(define (lines->ranges file-table)
+ (let ((ranges (make-hash-table)))
+ (hash-for-each
+ (lambda (line procs)
+ (for-each
+ (lambda (proc)
+ (cond
+ ((hashq-ref ranges proc)
+ => (lambda (pair)
+ (if (< line (car pair))
+ (set-car! pair line))
+ (if (> line (cdr pair))
+ (set-cdr! pair line))))
+ (else
+ (hashq-set! ranges proc (cons line line)))))
+ procs))
+ file-table)
+ (sort! (hash-map->list cons ranges)
+ (lambda (x y) (< (cadr x) (cadr y))))))
+
+(define* (lookup-source-procedures canon-file line db)
+ (let ((file-table (hash-ref db canon-file)))
+ (let lp ((ranges (if file-table (lines->ranges file-table) '()))
+ (procs '()))
+ (cond
+ ((null? ranges) (reverse procs))
+ ((<= (cadar ranges) line (cddar ranges))
+ (lp (cdr ranges) (cons (caar ranges) procs)))
+ (else
+ (lp (cdr ranges) procs))))))
+
+(define* (source-closures file line #\key (canonicalization 'relative))
+ (ensure-sources-db #f)
+ (let* ((port (with-fluids ((%file-port-name-canonicalization canonicalization))
+ (false-if-exception (open-input-file file))))
+ (file (if port (port-filename port) file)))
+ (lookup-source-procedures file line *closure-sources-db*)))
+
+(define* (source-procedures file line #\key (canonicalization 'relative))
+ (ensure-sources-db #f)
+ (let* ((port (with-fluids ((%file-port-name-canonicalization canonicalization))
+ (false-if-exception (open-input-file file))))
+ (file (if port (port-filename port) file)))
+ (lookup-source-procedures file line *sources-db*)))
+;;;; (texinfo) -- parsing of texinfo into SXML
+;;;;
+;;;; Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
+;;;; Copyright (C) 2004, 2009 Andy Wingo <wingo at pobox dot com>
+;;;; Copyright (C) 2001,2002 Oleg Kiselyov <oleg at pobox dot com>
+;;;;
+;;;; This file is based on SSAX's SSAX.scm.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+;;; Commentary_
+;;
+;; @subheading Texinfo processing in scheme
+;;
+;; This module parses texinfo into SXML. TeX will always be the
+;; processor of choice for print output, of course. However, although
+;; @code{makeinfo} works well for info, its output in other formats is
+;; not very customizable, and the program is not extensible as a whole.
+;; This module aims to provide an extensible framework for texinfo
+;; processing that integrates texinfo into the constellation of SXML
+;; processing tools.
+;;
+;; @subheading Notes on the SXML vocabulary
+;;
+;; Consider the following texinfo fragment_
+;;
+;;@example
+;; @@deffn Primitive set-car! pair value
+;; This function...
+;; @@end deffn
+;;@end example
+;;
+;; Logically, the category (Primitive), name (set-car!), and arguments
+;; (pair value) are ``attributes'' of the deffn, with the description as
+;; the content. However, texinfo allows for @@-commands within the
+;; arguments to an environment, like @code{@@deffn}, which means that
+;; texinfo ``attributes'' are PCDATA. XML attributes, on the other hand,
+;; are CDATA. For this reason, ``attributes'' of texinfo @@-commands are
+;; called ``arguments'', and are grouped under the special element, `%'.
+;;
+;; Because `%' is not a valid NCName, stexinfo is a superset of SXML. In
+;; the interests of interoperability, this module provides a conversion
+;; function to replace the `%' with `texinfo-arguments'.
+;;
+;;; Code_
+
+;; Comparison to xml output of texinfo (which is rather undocumented)_
+;; Doesn't conform to texinfo dtd
+;; No DTD at all, in fact _-/
+;; Actually outputs valid xml, after transforming %
+;; Slower (although with caching the SXML that problem can go away)
+;; Doesn't parse menus (although menus are shite)
+;; Args go in a dedicated element, FBOFW
+;; Definitions are handled a lot better
+;; Does parse comments
+;; Outputs only significant line breaks (a biggie!)
+;; Nodes are treated as anchors, rather than content organizers (a biggie)
+;; (more book-like, less info-like)
+
+;; TODO
+;; Integration_ help, indexing, plain text
+
+(define-module (texinfo)
+ #\use-module (sxml simple)
+ #\use-module (sxml transform)
+ #\use-module (sxml ssax input-parse)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-11)
+ #\use-module (srfi srfi-13)
+ #\export (call-with-file-and-dir
+ texi-command-specs
+ texi-command-depth
+ texi-fragment->stexi
+ texi->stexi
+ stexi->sxml))
+
+;; Some utilities
+
+(define (parser-error port message . rest)
+ (apply throw 'parser-error port message rest))
+
+(define (call-with-file-and-dir filename proc)
+ "Call the one-argument procedure @var{proc} with an input port that
+reads from @var{filename}. During the dynamic extent of @var{proc}'s
+execution, the current directory will be @code{(dirname
+@var{filename})}. This is useful for parsing documents that can include
+files by relative path name."
+ (let ((current-dir (getcwd)))
+ (dynamic-wind
+ (lambda () (chdir (dirname filename)))
+ (lambda ()
+ (call-with-input-file (basename filename) proc))
+ (lambda () (chdir current-dir)))))
+
+;;========================================================================
+;; Reflection on the XML vocabulary
+
+(define texi-command-specs
+ ;~
+"A list of (@var{name} @var{content-model} . @var{args})
+
+@table @var
+@item name
+The name of an @@-command, as a symbol.
+
+@item content-model
+A symbol indicating the syntactic type of the @@-command_
+@table @code
+@item EMPTY-COMMAND
+No content, and no @code{@@end} is coming
+@item EOL-ARGS
+Unparsed arguments until end of line
+@item EOL-TEXT
+Parsed arguments until end of line
+@item INLINE-ARGS
+Unparsed arguments ending with @code{#\\@}}
+@item INLINE-TEXT
+Parsed arguments ending with @code{#\\@}}
+@item INLINE-TEXT-ARGS
+Parsed arguments ending with @code{#\\@}}
+@item ENVIRON
+The tag is an environment tag, expect @code{@@end foo}.
+@item TABLE-ENVIRON
+Like ENVIRON, but with special parsing rules for its arguments.
+@item FRAGMENT
+For @code{*fragment*}, the command used for parsing fragments of
+texinfo documents.
+@end table
+
+@code{INLINE-TEXT} commands will receive their arguments within their
+bodies, whereas the @code{-ARGS} commands will receive them in their
+attribute list.
+
+@code{EOF-TEXT} receives its arguments in its body.
+
+@code{ENVIRON} commands have both_ parsed arguments until the end of
+line, received through their attribute list, and parsed text until the
+@code{@@end}, received in their bodies.
+
+@code{EOF-TEXT-ARGS} receives its arguments in its attribute list, as in
+@code{ENVIRON}.
+
+In addition, @code{ALIAS} can alias one command to another. The alias
+will never be seen in parsed stexinfo.
+
+There are four @@-commands that are treated specially. @code{@@include}
+is a low-level token that will not be seen by higher-level parsers, so
+it has no content-model. @code{@@para} is the paragraph command, which
+is only implicit in the texinfo source. @code{@@item} has special
+syntax, as noted above, and @code{@@entry} is how this parser treats
+@code{@@item} commands within @code{@@table}, @code{@@ftable}, and
+@code{@@vtable}.
+
+Also, indexing commands (@code{@@cindex}, etc.) are treated specially.
+Their arguments are parsed, but they are needed before entering the
+element so that an anchor can be inserted into the text before the index
+entry.
+
+@item args
+Named arguments to the command, in the same format as the formals for a
+lambda. Only present for @code{INLINE-ARGS}, @code{EOL-ARGS},
+@code{INLINE-TEXT-ARGS}, @code{ENVIRON}, @code{TABLE-ENVIRON} commands.
+@end table"
+ '(;; Special commands
+ (include #f) ;; this is a low-level token
+ (para PARAGRAPH)
+ (item ITEM)
+ (entry ENTRY . heading)
+ (noindent EMPTY-COMMAND)
+ (*fragment* FRAGMENT)
+
+ ;; Inline text commands
+ (*braces* INLINE-TEXT) ;; FIXME_ make me irrelevant
+ (bold INLINE-TEXT)
+ (sample INLINE-TEXT)
+ (samp INLINE-TEXT)
+ (code INLINE-TEXT)
+ (math INLINE-TEXT)
+ (kbd INLINE-TEXT)
+ (key INLINE-TEXT)
+ (var INLINE-TEXT)
+ (env INLINE-TEXT)
+ (file INLINE-TEXT)
+ (command INLINE-TEXT)
+ (option INLINE-TEXT)
+ (dfn INLINE-TEXT)
+ (cite INLINE-TEXT)
+ (acro INLINE-TEXT)
+ (email INLINE-TEXT)
+ (emph INLINE-TEXT)
+ (strong INLINE-TEXT)
+ (sample INLINE-TEXT)
+ (sc INLINE-TEXT)
+ (titlefont INLINE-TEXT)
+ (asis INLINE-TEXT)
+ (b INLINE-TEXT)
+ (i INLINE-TEXT)
+ (r INLINE-TEXT)
+ (sansserif INLINE-TEXT)
+ (slanted INLINE-TEXT)
+ (t INLINE-TEXT)
+
+ ;; Inline args commands
+ (value INLINE-ARGS . (key))
+ (ref INLINE-ARGS . (node #\opt name section info-file manual))
+ (xref INLINE-ARGS . (node #\opt name section info-file manual))
+ (pxref INLINE-TEXT-ARGS
+ . (node #\opt name section info-file manual))
+ (url ALIAS . uref)
+ (uref INLINE-ARGS . (url #\opt title replacement))
+ (anchor INLINE-ARGS . (name))
+ (dots INLINE-ARGS . ())
+ (result INLINE-ARGS . ())
+ (bullet INLINE-ARGS . ())
+ (copyright INLINE-ARGS . ())
+ (tie INLINE-ARGS . ())
+ (image INLINE-ARGS . (file #\opt width height alt-text extension))
+
+ ;; Inline parsed args commands
+ (acronym INLINE-TEXT-ARGS . (acronym #\opt meaning))
+
+ ;; EOL args elements
+ (node EOL-ARGS . (name #\opt next previous up))
+ (c EOL-ARGS . all)
+ (comment EOL-ARGS . all)
+ (setchapternewpage EOL-ARGS . all)
+ (sp EOL-ARGS . all)
+ (page EOL-ARGS . ())
+ (vskip EOL-ARGS . all)
+ (syncodeindex EOL-ARGS . all)
+ (contents EOL-ARGS . ())
+ (shortcontents EOL-ARGS . ())
+ (summarycontents EOL-ARGS . ())
+ (insertcopying EOL-ARGS . ())
+ (dircategory EOL-ARGS . (category))
+ (top EOL-ARGS . (title))
+ (printindex EOL-ARGS . (type))
+ (paragraphindent EOL-ARGS . (indent))
+
+ ;; EOL text commands
+ (*ENVIRON-ARGS* EOL-TEXT)
+ (itemx EOL-TEXT)
+ (set EOL-TEXT)
+ (center EOL-TEXT)
+ (title EOL-TEXT)
+ (subtitle EOL-TEXT)
+ (author EOL-TEXT)
+ (chapter EOL-TEXT)
+ (section EOL-TEXT)
+ (appendix EOL-TEXT)
+ (appendixsec EOL-TEXT)
+ (unnumbered EOL-TEXT)
+ (unnumberedsec EOL-TEXT)
+ (subsection EOL-TEXT)
+ (subsubsection EOL-TEXT)
+ (appendixsubsec EOL-TEXT)
+ (appendixsubsubsec EOL-TEXT)
+ (unnumberedsubsec EOL-TEXT)
+ (unnumberedsubsubsec EOL-TEXT)
+ (chapheading EOL-TEXT)
+ (majorheading EOL-TEXT)
+ (heading EOL-TEXT)
+ (subheading EOL-TEXT)
+ (subsubheading EOL-TEXT)
+
+ (deftpx EOL-TEXT-ARGS . (category name . attributes))
+ (defcvx EOL-TEXT-ARGS . (category class name))
+ (defivarx EOL-TEXT-ARGS . (class name))
+ (deftypeivarx EOL-TEXT-ARGS . (class data-type name))
+ (defopx EOL-TEXT-ARGS . (category class name . arguments))
+ (deftypeopx EOL-TEXT-ARGS . (category class data-type name . arguments))
+ (defmethodx EOL-TEXT-ARGS . (class name . arguments))
+ (deftypemethodx EOL-TEXT-ARGS . (class data-type name . arguments))
+ (defoptx EOL-TEXT-ARGS . (name))
+ (defvrx EOL-TEXT-ARGS . (category name))
+ (defvarx EOL-TEXT-ARGS . (name))
+ (deftypevrx EOL-TEXT-ARGS . (category data-type name))
+ (deftypevarx EOL-TEXT-ARGS . (data-type name))
+ (deffnx EOL-TEXT-ARGS . (category name . arguments))
+ (deftypefnx EOL-TEXT-ARGS . (category data-type name . arguments))
+ (defspecx EOL-TEXT-ARGS . (name . arguments))
+ (defmacx EOL-TEXT-ARGS . (name . arguments))
+ (defunx EOL-TEXT-ARGS . (name . arguments))
+ (deftypefunx EOL-TEXT-ARGS . (data-type name . arguments))
+
+ ;; Indexing commands
+ (cindex INDEX . entry)
+ (findex INDEX . entry)
+ (vindex INDEX . entry)
+ (kindex INDEX . entry)
+ (pindex INDEX . entry)
+ (tindex INDEX . entry)
+
+ ;; Environment commands (those that need @end)
+ (texinfo ENVIRON . title)
+ (ignore ENVIRON . ())
+ (ifinfo ENVIRON . ())
+ (iftex ENVIRON . ())
+ (ifhtml ENVIRON . ())
+ (ifxml ENVIRON . ())
+ (ifplaintext ENVIRON . ())
+ (ifnotinfo ENVIRON . ())
+ (ifnottex ENVIRON . ())
+ (ifnothtml ENVIRON . ())
+ (ifnotxml ENVIRON . ())
+ (ifnotplaintext ENVIRON . ())
+ (titlepage ENVIRON . ())
+ (menu ENVIRON . ())
+ (direntry ENVIRON . ())
+ (copying ENVIRON . ())
+ (example ENVIRON . ())
+ (smallexample ENVIRON . ())
+ (display ENVIRON . ())
+ (smalldisplay ENVIRON . ())
+ (verbatim ENVIRON . ())
+ (format ENVIRON . ())
+ (smallformat ENVIRON . ())
+ (lisp ENVIRON . ())
+ (smalllisp ENVIRON . ())
+ (cartouche ENVIRON . ())
+ (quotation ENVIRON . ())
+
+ (deftp ENVIRON . (category name . attributes))
+ (defcv ENVIRON . (category class name))
+ (defivar ENVIRON . (class name))
+ (deftypeivar ENVIRON . (class data-type name))
+ (defop ENVIRON . (category class name . arguments))
+ (deftypeop ENVIRON . (category class data-type name . arguments))
+ (defmethod ENVIRON . (class name . arguments))
+ (deftypemethod ENVIRON . (class data-type name . arguments))
+ (defopt ENVIRON . (name))
+ (defvr ENVIRON . (category name))
+ (defvar ENVIRON . (name))
+ (deftypevr ENVIRON . (category data-type name))
+ (deftypevar ENVIRON . (data-type name))
+ (deffn ENVIRON . (category name . arguments))
+ (deftypefn ENVIRON . (category data-type name . arguments))
+ (defspec ENVIRON . (name . arguments))
+ (defmac ENVIRON . (name . arguments))
+ (defun ENVIRON . (name . arguments))
+ (deftypefun ENVIRON . (data-type name . arguments))
+
+ (table TABLE-ENVIRON . (formatter))
+ (itemize TABLE-ENVIRON . (formatter))
+ (enumerate TABLE-ENVIRON . (start))
+ (ftable TABLE-ENVIRON . (formatter))
+ (vtable TABLE-ENVIRON . (formatter))))
+
+(define command-depths
+ '((chapter . 1) (section . 2) (subsection . 3) (subsubsection . 4)
+ (top . 0) (unnumbered . 1) (unnumberedsec . 2)
+ (unnumberedsubsec . 3) (unnumberedsubsubsec . 4)
+ (appendix . 1) (appendixsec . 2) (appendixsection . 2)
+ (appendixsubsec . 3) (appendixsubsubsec . 4)))
+(define (texi-command-depth command max-depth)
+ "Given the texinfo command @var{command}, return its nesting level, or
+@code{#f} if it nests too deep for @var{max-depth}.
+
+Examples_
+@example
+ (texi-command-depth 'chapter 4) @result{} 1
+ (texi-command-depth 'top 4) @result{} 0
+ (texi-command-depth 'subsection 4) @result{} 3
+ (texi-command-depth 'appendixsubsec 4) @result{} 3
+ (texi-command-depth 'subsection 2) @result{} #f
+@end example"
+ (let ((depth (and=> (assq command command-depths) cdr)))
+ (and depth (<= depth max-depth) depth)))
+
+;; The % is for arguments
+(define (space-significant? command)
+ (memq command
+ '(example smallexample verbatim lisp smalllisp menu %)))
+
+;; Like a DTD for texinfo
+(define (command-spec command)
+ (let ((spec (assq command texi-command-specs)))
+ (cond
+ ((not spec)
+ (parser-error #f "Unknown command" command))
+ ((eq? (cadr spec) 'ALIAS)
+ (command-spec (cddr spec)))
+ (else
+ spec))))
+
+(define (inline-content? content)
+ (case content
+ ((INLINE-TEXT INLINE-ARGS INLINE-TEXT-ARGS) #t)
+ (else #f)))
+
+
+;;========================================================================
+;; Lower-level parsers and scanners
+;;
+;; They deal with primitive lexical units (Names, whitespaces, tags) and
+;; with pieces of more generic productions. Most of these parsers must
+;; be called in appropriate context. For example, complete-start-command
+;; must be called only when the @-command start has been detected and
+;; its name token has been read.
+
+;; Test if a string is made of only whitespace
+;; An empty string is considered made of whitespace as well
+(define (string-whitespace? str)
+ (or (string-null? str)
+ (string-every char-whitespace? str)))
+
+;; Like read-text-line, but allows EOF.
+(define read-eof-breaks '(*eof* #\return #\newline))
+(define (read-eof-line port)
+ (if (eof-object? (peek-char port))
+ (peek-char port)
+ (let* ((line (next-token '() read-eof-breaks
+ "reading a line" port))
+ (c (read-char port))) ; must be either \n or \r or EOF
+ (if (and (eq? c #\return) (eq? (peek-char port) #\newline))
+ (read-char port)) ; skip \n that follows \r
+ line)))
+
+(define (skip-whitespace port)
+ (skip-while '(#\space #\tab #\return #\newline) port))
+
+(define (skip-horizontal-whitespace port)
+ (skip-while '(#\space #\tab) port))
+
+;; command __= Letter+
+
+;; procedure_ read-command PORT
+;;
+;; Read a command starting from the current position in the PORT and
+;; return it as a symbol.
+(define (read-command port)
+ (let ((first-char (peek-char port)))
+ (or (char-alphabetic? first-char)
+ (parser-error port "Nonalphabetic @-command char_ '" first-char "'")))
+ (string->symbol
+ (next-token-of
+ (lambda (c)
+ (cond
+ ((eof-object? c) #f)
+ ((char-alphabetic? c) c)
+ (else #f)))
+ port)))
+
+;; A token is a primitive lexical unit. It is a record with two fields,
+;; token-head and token-kind.
+;;
+;; Token types_
+;; END The end of a texinfo command. If the command is ended by },
+;; token-head will be #f. Otherwise if the command is ended by
+;; @end COMMAND, token-head will be COMMAND. As a special case,
+;; @bye is the end of a special @texinfo command.
+;; START The start of a texinfo command. The token-head will be a
+;; symbol of the @-command name.
+;; INCLUDE An @include directive. The token-head will be empty -- the
+;; caller is responsible for reading the include file name.
+;; ITEM @item commands have an irregular syntax. They end at the
+;; next @item, or at the end of the environment. For that
+;; read-command-token treats them specially.
+
+(define (make-token kind head) (cons kind head))
+(define token? pair?)
+(define token-kind car)
+(define token-head cdr)
+
+;; procedure_ read-command-token PORT
+;;
+;; This procedure starts parsing of a command token. The current
+;; position in the stream must be #\@. This procedure scans enough of
+;; the input stream to figure out what kind of a command token it is
+;; seeing. The procedure returns a token structure describing the token.
+
+(define (read-command-token port)
+ (assert-curr-char '(#\@) "start of the command" port)
+ (let ((peeked (peek-char port)))
+ (cond
+ ((memq peeked '(#\! #\_ #\. #\? #\@ #\\ #\{ #\}))
+ ;; @-commands that escape characters
+ (make-token 'STRING (string (read-char port))))
+ (else
+ (let ((name (read-command port)))
+ (case name
+ ((end)
+ ;; got an ending tag
+ (let ((command (string-trim-both
+ (read-eof-line port))))
+ (or (and (not (string-null? command))
+ (string-every char-alphabetic? command))
+ (parser-error port "malformed @end" command))
+ (make-token 'END (string->symbol command))))
+ ((bye)
+ ;; the end of the top
+ (make-token 'END 'texinfo))
+ ((item)
+ (make-token 'ITEM 'item))
+ ((include)
+ (make-token 'INCLUDE #f))
+ (else
+ (make-token 'START name))))))))
+
+;; procedure+_ read-verbatim-body PORT STR-HANDLER SEED
+;;
+;; This procedure must be called after we have read a string
+;; "@verbatim\n" that begins a verbatim section. The current position
+;; must be the first position of the verbatim body. This function reads
+;; _lines_ of the verbatim body and passes them to a STR-HANDLER, a
+;; character data consumer.
+;;
+;; The str-handler is a STR-HANDLER, a procedure STRING1 STRING2 SEED.
+;; The first STRING1 argument to STR-HANDLER never contains a newline.
+;; The second STRING2 argument often will. On the first invocation of the
+;; STR-HANDLER, the seed is the one passed to read-verbatim-body
+;; as the third argument. The result of this first invocation will be
+;; passed as the seed argument to the second invocation of the line
+;; consumer, and so on. The result of the last invocation of the
+;; STR-HANDLER is returned by the read-verbatim-body. Note a
+;; similarity to the fundamental 'fold' iterator.
+;;
+;; Within a verbatim section all characters are taken at their face
+;; value. It ends with "\n@end verbatim(\r)?\n".
+
+;; Must be called right after the newline after @verbatim.
+(define (read-verbatim-body port str-handler seed)
+ (let loop ((seed seed))
+ (let ((fragment (next-token '() '(#\newline)
+ "reading verbatim" port)))
+ ;; We're reading the char after the 'fragment', which is
+ ;; #\newline.
+ (read-char port)
+ (if (string=? fragment "@end verbatim")
+ seed
+ (loop (str-handler fragment "\n" seed))))))
+
+;; procedure+_ read-arguments PORT
+;;
+;; This procedure reads and parses a production ArgumentList.
+;; ArgumentList __= S* Argument (S* , S* Argument)* S*
+;; Argument __= ([^@{},])*
+;;
+;; Arguments are the things in braces, i.e @ref{my node} has one
+;; argument, "my node". Most commands taking braces actually don't have
+;; arguments, they process text. For example, in
+;; @emph{@strong{emphasized}}, the emph takes text, because the parse
+;; continues into the braces.
+;;
+;; Any whitespace within Argument is replaced with a single space.
+;; Whitespace around an Argument is trimmed.
+;;
+;; The procedure returns a list of arguments. Afterwards the current
+;; character will be after the final #\}.
+
+(define (read-arguments port stop-char)
+ (define (split str)
+ (read-char port) ;; eat the delimiter
+ (let ((ret (map (lambda (x) (if (string-null? x) #f x))
+ (map string-trim-both (string-split str #\,)))))
+ (if (and (pair? ret) (eq? (car ret) #f) (null? (cdr ret)))
+ '()
+ ret)))
+ (split (next-token '() (list stop-char)
+ "arguments of @-command" port)))
+
+;; procedure+_ complete-start-command COMMAND PORT
+;;
+;; This procedure is to complete parsing of an @-command. The procedure
+;; must be called after the command token has been read. COMMAND is a
+;; TAG-NAME.
+;;
+;; This procedure returns several values_
+;; COMMAND_ a symbol.
+;; ARGUMENTS_ command's arguments, as an alist.
+;; CONTENT-MODEL_ the content model of the command.
+;;
+;; On exit, the current position in PORT will depend on the CONTENT-MODEL.
+;;
+;; Content model Port position
+;; ============= =============
+;; INLINE-TEXT One character after the #\{.
+;; INLINE-TEXT-ARGS One character after the #\{.
+;; INLINE-ARGS The first character after the #\}.
+;; EOL-TEXT The first non-whitespace character after the command.
+;; ENVIRON, TABLE-ENVIRON, EOL-ARGS, EOL-TEXT
+;; The first character on the next line.
+;; PARAGRAPH, ITEM, EMPTY-COMMAND
+;; The first character after the command.
+
+(define (arguments->attlist port args arg-names)
+ (let loop ((in args) (names arg-names) (opt? #f) (out '()))
+ (cond
+ ((symbol? names) ;; a rest arg
+ (reverse (if (null? in) out (acons names in out))))
+ ((and (not (null? names)) (eq? (car names) #\opt))
+ (loop in (cdr names) #t out))
+ ((null? in)
+ (if (or (null? names) opt?)
+ (reverse out)
+ (parser-error port "@-command expected more arguments_"
+ args arg-names names)))
+ ((null? names)
+ (parser-error port "@-command didn't expect more arguments_" in))
+ ((not (car in))
+ (or (and opt? (loop (cdr in) (cdr names) opt? out))
+ (parser-error "@-command missing required argument"
+ (car names))))
+ (else
+ (loop (cdr in) (cdr names) opt?
+ (acons (car names)
+ (if (list? (car in)) (car in) (list (car in)))
+ out))))))
+
+(define (parse-table-args command port)
+ (let* ((line (string-trim-both (read-text-line port)))
+ (length (string-length line)))
+ (define (get-formatter)
+ (or (and (not (zero? length))
+ (eq? (string-ref line 0) #\@)
+ (let ((f (string->symbol (substring line 1))))
+ (or (inline-content? (cadr (command-spec f)))
+ (parser-error
+ port "@item formatter must be INLINE" f))
+ f))
+ (parser-error port "Invalid @item formatter" line)))
+ (case command
+ ((enumerate)
+ (if (zero? length)
+ '()
+ `((start
+ ,(if (or (and (eq? length 1)
+ (char-alphabetic? (string-ref line 0)))
+ (string-every char-numeric? line))
+ line
+ (parser-error
+ port "Invalid enumerate start" line))))))
+ ((itemize)
+ `((bullet
+ ,(or (and (eq? length 1) line)
+ (and (string-null? line) '(bullet))
+ (list (get-formatter))))))
+ (else ;; tables of various varieties
+ `((formatter (,(get-formatter))))))))
+
+(define (complete-start-command command port)
+ (define (get-arguments type arg-names stop-char)
+ (arguments->attlist port (read-arguments port stop-char) arg-names))
+
+ (let* ((spec (command-spec command))
+ (command (car spec))
+ (type (cadr spec))
+ (arg-names (cddr spec)))
+ (case type
+ ((INLINE-TEXT)
+ (assert-curr-char '(#\{) "Inline element lacks {" port)
+ (values command '() type))
+ ((INLINE-ARGS)
+ (assert-curr-char '(#\{) "Inline element lacks {" port)
+ (values command (get-arguments type arg-names #\}) type))
+ ((INLINE-TEXT-ARGS)
+ (assert-curr-char '(#\{) "Inline element lacks {" port)
+ (values command '() type))
+ ((EOL-ARGS)
+ (values command (get-arguments type arg-names #\newline) type))
+ ((ENVIRON ENTRY INDEX)
+ (skip-horizontal-whitespace port)
+ (values command (parse-environment-args command port) type))
+ ((TABLE-ENVIRON)
+ (skip-horizontal-whitespace port)
+ (values command (parse-table-args command port) type))
+ ((EOL-TEXT)
+ (skip-horizontal-whitespace port)
+ (values command '() type))
+ ((EOL-TEXT-ARGS)
+ (skip-horizontal-whitespace port)
+ (values command (parse-eol-text-args command port) type))
+ ((PARAGRAPH EMPTY-COMMAND ITEM FRAGMENT)
+ (values command '() type))
+ (else ;; INCLUDE shouldn't get here
+ (parser-error port "can't happen")))))
+
+;;-----------------------------------------------------------------------------
+;; Higher-level parsers and scanners
+;;
+;; They parse productions corresponding entire @-commands.
+
+;; Only reads @settitle, leaves it to the command parser to finish
+;; reading the title.
+(define (take-until-settitle port)
+ (or (find-string-from-port? "\n@settitle " port)
+ (parser-error port "No \\n@settitle found"))
+ (skip-horizontal-whitespace port)
+ (and (eq? (peek-char port) #\newline)
+ (parser-error port "You have a @settitle, but no title")))
+
+;; procedure+_ read-char-data PORT EXPECT-EOF? STR-HANDLER SEED
+;;
+;; This procedure is to read the CharData of a texinfo document.
+;;
+;; text __= (CharData | Command)*
+;;
+;; The procedure reads CharData and stops at @-commands (or
+;; environments). It also stops at an open or close brace.
+;;
+;; port
+;; a PORT to read
+;; expect-eof?
+;; a boolean indicating if EOF is normal, i.e., the character
+;; data may be terminated by the EOF. EOF is normal
+;; while processing the main document.
+;; preserve-ws?
+;; a boolean indicating if we are within a whitespace-preserving
+;; environment. If #t, suppress paragraph detection.
+;; str-handler
+;; a STR-HANDLER, see read-verbatim-body
+;; seed
+;; an argument passed to the first invocation of STR-HANDLER.
+;;
+;; The procedure returns two results_ SEED and TOKEN. The SEED is the
+;; result of the last invocation of STR-HANDLER, or the original seed if
+;; STR-HANDLER was never called.
+;;
+;; TOKEN can be either an eof-object (this can happen only if expect-eof?
+;; was #t), or a texinfo token denoting the start or end of a tag.
+
+;; read-char-data port expect-eof? preserve-ws? str-handler seed
+(define read-char-data
+ (let* ((end-chars-eof '(*eof* #\{ #\} #\@ #\newline)))
+ (define (handle str-handler str1 str2 seed)
+ (if (and (string-null? str1) (string-null? str2))
+ seed
+ (str-handler str1 str2 seed)))
+
+ (lambda (port expect-eof? preserve-ws? str-handler seed)
+ (let ((end-chars ((if expect-eof? identity cdr) end-chars-eof)))
+ (let loop ((seed seed))
+ (let* ((fragment (next-token '() end-chars "reading char data" port))
+ (term-char (peek-char port))) ; one of end-chars
+ (cond
+ ((eof-object? term-char) ; only if expect-eof?
+ (values (handle str-handler fragment "" seed) term-char))
+ ((memq term-char '(#\@ #\{ #\}))
+ (values (handle str-handler fragment "" seed)
+ (case term-char
+ ((#\@) (read-command-token port))
+ ((#\{) (make-token 'START '*braces*))
+ ((#\}) (read-char port) (make-token 'END #f)))))
+ ((eq? term-char #\newline)
+ ;; Always significant, unless directly before an end token.
+ (let ((c (peek-next-char port)))
+ (cond
+ ((eof-object? c)
+ (or expect-eof?
+ (parser-error port "EOF while reading char data"))
+ (values (handle str-handler fragment "" seed) c))
+ ((eq? c #\@)
+ (let* ((token (read-command-token port))
+ (end? (eq? (token-kind token) 'END)))
+ (values
+ (handle str-handler fragment
+ (if end? "" (if preserve-ws? "\n" " "))
+ seed)
+ token)))
+ ((and (not preserve-ws?) (eq? c #\newline))
+ ;; paragraph-separator __= #\newline #\newline+
+ (skip-while '(#\newline) port)
+ (skip-horizontal-whitespace port)
+ (values (handle str-handler fragment "" seed)
+ (make-token 'PARA 'para)))
+ (else
+ (loop (handle str-handler fragment
+ (if preserve-ws? "\n" " ") seed)))))))))))))
+
+; procedure+_ assert-token TOKEN KIND NAME
+; Make sure that TOKEN is of anticipated KIND and has anticipated NAME
+(define (assert-token token kind name)
+ (or (and (token? token)
+ (eq? kind (token-kind token))
+ (equal? name (token-head token)))
+ (parser-error #f "Expecting @end for " name ", got " token)))
+
+;;========================================================================
+;; Highest-level parsers_ Texinfo to SXML
+
+;; These parsers are a set of syntactic forms to instantiate a SSAX
+;; parser. The user tells what to do with the parsed character and
+;; element data. These latter handlers determine if the parsing follows a
+;; SAX or a DOM model.
+
+;; syntax_ make-command-parser fdown fup str-handler
+
+;; Create a parser to parse and process one element, including its
+;; character content or children elements. The parser is typically
+;; applied to the root element of a document.
+
+;; fdown
+;; procedure COMMAND ARGUMENTS EXPECTED-CONTENT SEED
+;;
+;; This procedure is to generate the seed to be passed to handlers
+;; that process the content of the element. This is the function
+;; identified as 'fdown' in the denotational semantics of the XML
+;; parser given in the title comments to (sxml ssax).
+;;
+;; fup
+;; procedure COMMAND ARGUMENTS PARENT-SEED SEED
+;;
+;; This procedure is called when parsing of COMMAND is finished.
+;; The SEED is the result from the last content parser (or from
+;; fdown if the element has the empty content). PARENT-SEED is the
+;; same seed as was passed to fdown. The procedure is to generate a
+;; seed that will be the result of the element parser. This is the
+;; function identified as 'fup' in the denotational semantics of
+;; the XML parser given in the title comments to (sxml ssax).
+;;
+;; str-handler
+;; A STR-HANDLER, see read-verbatim-body
+;;
+
+;; The generated parser is a
+;; procedure COMMAND PORT SEED
+;;
+;; The procedure must be called *after* the command token has been read.
+
+(define (read-include-file-name port)
+ (let ((x (string-trim-both (read-eof-line port))))
+ (if (string-null? x)
+ (error "no file listed")
+ x))) ;; fixme_ should expand @value{} references
+
+(define (sxml->node-name sxml)
+ "Turn some sxml string into a valid node name."
+ (let loop ((in (string->list (sxml->string sxml))) (out '()))
+ (if (null? in)
+ (apply string (reverse out))
+ (if (memq (car in) '(#\{ #\} #\@ #\,))
+ (loop (cdr in) out)
+ (loop (cdr in) (cons (car in) out))))))
+
+(define (index command arguments fdown fup parent-seed)
+ (case command
+ ((deftp defcv defivar deftypeivar defop deftypeop defmethod
+ deftypemethod defopt defvr defvar deftypevr deftypevar deffn
+ deftypefn defspec defmac defun deftypefun)
+ (let ((args `((name ,(string-append (symbol->string command) "-"
+ (cadr (assq 'name arguments)))))))
+ (fup 'anchor args parent-seed
+ (fdown 'anchor args 'INLINE-ARGS '()))))
+ ((cindex findex vindex kindex pindex tindex)
+ (let ((args `((name ,(string-append (symbol->string command) "-"
+ (sxml->node-name
+ (assq 'entry arguments)))))))
+ (fup 'anchor args parent-seed
+ (fdown 'anchor args 'INLINE-ARGS '()))))
+ (else parent-seed)))
+
+(define (make-command-parser fdown fup str-handler)
+ (lambda (command port seed)
+ (let visit ((command command) (port port) (sig-ws? #f) (parent-seed seed))
+ (let*-values (((command arguments expected-content)
+ (complete-start-command command port)))
+ (let* ((parent-seed (index command arguments fdown fup parent-seed))
+ (seed (fdown command arguments expected-content parent-seed))
+ (eof-closes? (or (memq command '(texinfo para *fragment*))
+ (eq? expected-content 'EOL-TEXT)))
+ (sig-ws? (or sig-ws? (space-significant? command)))
+ (up (lambda (s) (fup command arguments parent-seed s)))
+ (new-para (lambda (s) (fdown 'para '() 'PARAGRAPH s)))
+ (make-end-para (lambda (p) (lambda (s) (fup 'para '() p s)))))
+
+ (define (port-for-content)
+ (if (eq? expected-content 'EOL-TEXT)
+ (call-with-input-string (read-text-line port) identity)
+ port))
+
+ (cond
+ ((memq expected-content '(EMPTY-COMMAND INLINE-ARGS EOL-ARGS INDEX
+ EOL-TEXT-ARGS))
+ ;; empty or finished by complete-start-command
+ (up seed))
+ ((eq? command 'verbatim)
+ (up (read-verbatim-body port str-handler seed)))
+ (else
+ (let loop ((port (port-for-content))
+ (expect-eof? eof-closes?)
+ (end-para identity)
+ (need-break? (and (not sig-ws?)
+ (memq expected-content
+ '(ENVIRON TABLE-ENVIRON
+ ENTRY ITEM FRAGMENT))))
+ (seed seed))
+ (cond
+ ((and need-break? (or sig-ws? (skip-whitespace port))
+ (not (memq (peek-char port) '(#\@ #\})))
+ (not (eof-object? (peek-char port))))
+ ;; Even if we have an @, it might be inline -- check
+ ;; that later
+ (let ((seed (end-para seed)))
+ (loop port expect-eof? (make-end-para seed) #f
+ (new-para seed))))
+ (else
+ (let*-values (((seed token)
+ (read-char-data
+ port expect-eof? sig-ws? str-handler seed)))
+ (cond
+ ((eof-object? token)
+ (case expect-eof?
+ ((include #f) (end-para seed))
+ (else (up (end-para seed)))))
+ (else
+ (case (token-kind token)
+ ((STRING)
+ ;; this is only @-commands that escape
+ ;; characters_ @}, @@, @{ -- new para if need-break
+ (let ((seed ((if need-break? end-para identity) seed)))
+ (loop port expect-eof?
+ (if need-break? (make-end-para seed) end-para) #f
+ (str-handler (token-head token) ""
+ ((if need-break? new-para identity)
+ seed)))))
+ ((END)
+ ;; The end will only have a name if it's for an
+ ;; environment
+ (cond
+ ((memq command '(item entry))
+ (let ((spec (command-spec (token-head token))))
+ (or (eq? (cadr spec) 'TABLE-ENVIRON)
+ (parser-error
+ port "@item not ended by @end table/enumerate/itemize"
+ token))))
+ ((eq? expected-content 'ENVIRON)
+ (assert-token token 'END command)))
+ (up (end-para seed)))
+ ((ITEM)
+ (cond
+ ((memq command '(enumerate itemize))
+ (up (visit 'item port sig-ws? (end-para seed))))
+ ((eq? expected-content 'TABLE-ENVIRON)
+ (up (visit 'entry port sig-ws? (end-para seed))))
+ ((memq command '(item entry))
+ (visit command port sig-ws? (up (end-para seed))))
+ (else
+ (parser-error
+ port "@item must be within a table environment"
+ command))))
+ ((PARA)
+ ;; examine valid paragraphs?
+ (loop port expect-eof? end-para (not sig-ws?) seed))
+ ((INCLUDE)
+ ;; Recurse for include files
+ (let ((seed (call-with-file-and-dir
+ (read-include-file-name port)
+ (lambda (port)
+ (loop port 'include end-para
+ need-break? seed)))))
+ (loop port expect-eof? end-para need-break? seed)))
+ ((START) ; Start of an @-command
+ (let* ((head (token-head token))
+ (spec (command-spec head))
+ (head (car spec))
+ (type (cadr spec))
+ (inline? (inline-content? type))
+ (seed ((if (and inline? (not need-break?))
+ identity end-para) seed))
+ (end-para (if inline?
+ (if need-break? (make-end-para seed)
+ end-para)
+ identity))
+ (new-para (if (and inline? need-break?)
+ new-para identity)))
+ (loop port expect-eof? end-para (not inline?)
+ (visit head port sig-ws? (new-para seed)))))
+ (else
+ (parser-error port "Unknown token type" token))))))))))))))))
+
+;; procedure_ reverse-collect-str-drop-ws fragments
+;;
+;; Given the list of fragments (some of which are text strings), reverse
+;; the list and concatenate adjacent text strings. We also drop
+;; "unsignificant" whitespace, that is, whitespace in front, behind and
+;; between elements. The whitespace that is included in character data
+;; is not affected.
+(define (reverse-collect-str-drop-ws fragments)
+ (cond
+ ((null? fragments) ; a shortcut
+ '())
+ ((and (string? (car fragments)) ; another shortcut
+ (null? (cdr fragments)) ; remove single ws-only string
+ (string-whitespace? (car fragments)))
+ '())
+ (else
+ (let loop ((fragments fragments) (result '()) (strs '())
+ (all-whitespace? #t))
+ (cond
+ ((null? fragments)
+ (if all-whitespace?
+ result ; remove leading ws
+ (cons (apply string-append strs) result)))
+ ((string? (car fragments))
+ (loop (cdr fragments) result (cons (car fragments) strs)
+ (and all-whitespace?
+ (string-whitespace? (car fragments)))))
+ (else
+ (loop (cdr fragments)
+ (cons
+ (car fragments)
+ (cond
+ ((null? strs) result)
+ (all-whitespace?
+ (if (null? result)
+ result ; remove trailing whitespace
+ (cons " " result))); replace interstitial ws with
+ ; one space
+ (else
+ (cons (apply string-append strs) result))))
+ '() #t)))))))
+
+(define (parse-inline-text-args port spec text)
+ (let lp ((in text) (cur '()) (out '()))
+ (cond
+ ((null? in)
+ (if (and (pair? cur)
+ (string? (car cur))
+ (string-whitespace? (car cur)))
+ (lp in (cdr cur) out)
+ (let ((args (reverse (if (null? cur)
+ out
+ (cons (reverse cur) out)))))
+ (arguments->attlist port args (cddr spec)))))
+ ((pair? (car in))
+ (lp (cdr in) (cons (car in) cur) out))
+ ((string-index (car in) #\,)
+ (let* ((parts (string-split (car in) #\,))
+ (head (string-trim-right (car parts)))
+ (rev-tail (reverse (cdr parts)))
+ (last (string-trim (car rev-tail))))
+ (lp (cdr in)
+ (if (string-null? last) cur (cons last cur))
+ (append (cdr rev-tail)
+ (cons (reverse (if (string-null? head) cur (cons head cur)))
+ out)))))
+ (else
+ (lp (cdr in)
+ (cons (if (null? cur) (string-trim (car in)) (car in)) cur)
+ out)))))
+
+(define (make-dom-parser)
+ (make-command-parser
+ (lambda (command args content seed) ; fdown
+ '())
+ (lambda (command args parent-seed seed) ; fup
+ (let* ((seed (reverse-collect-str-drop-ws seed))
+ (spec (command-spec command))
+ (command (car spec)))
+ (if (eq? (cadr spec) 'INLINE-TEXT-ARGS)
+ (cons (list command (cons '% (parse-inline-text-args #f spec seed)))
+ parent-seed)
+ (acons command
+ (if (null? args) seed (acons '% args seed))
+ parent-seed))))
+ (lambda (string1 string2 seed) ; str-handler
+ (if (string-null? string2)
+ (cons string1 seed)
+ (cons* string2 string1 seed)))))
+
+(define parse-environment-args
+ (let ((parser (make-dom-parser)))
+ ;; duplicate arguments->attlist to avoid unnecessary splitting
+ (lambda (command port)
+ (let* ((args (cdar (parser '*ENVIRON-ARGS* port '())))
+ (spec (command-spec command))
+ (command (car spec))
+ (arg-names (cddr spec)))
+ (cond
+ ((not arg-names)
+ (if (null? args) '()
+ (parser-error port "@-command doesn't take args" command)))
+ ((eq? arg-names #t)
+ (list (cons 'arguments args)))
+ (else
+ (let loop ((args args) (arg-names arg-names) (out '()))
+ (cond
+ ((null? arg-names)
+ (if (null? args) (reverse! out)
+ (parser-error port "@-command didn't expect more args"
+ command args)))
+ ((symbol? arg-names)
+ (reverse! (acons arg-names args out)))
+ ((null? args)
+ (parser-error port "@-command expects more args"
+ command arg-names))
+ ((and (string? (car args)) (string-index (car args) #\space))
+ => (lambda (i)
+ (let ((rest (substring/shared (car args) (1+ i))))
+ (if (zero? i)
+ (loop (cons rest (cdr args)) arg-names out)
+ (loop (cons rest (cdr args)) (cdr arg-names)
+ (cons (list (car arg-names)
+ (substring (car args) 0 i))
+ out))))))
+ (else
+ (loop (cdr args) (cdr arg-names)
+ (if (and (pair? (car args)) (eq? (caar args) '*braces*))
+ (acons (car arg-names) (cdar args) out)
+ (cons (list (car arg-names) (car args)) out))))))))))))
+
+(define (parse-eol-text-args command port)
+ ;; perhaps parse-environment-args should be named more
+ ;; generically.
+ (parse-environment-args command port))
+
+;; procedure_ texi-fragment->stexi STRING
+;;
+;; A DOM parser for a texinfo fragment STRING.
+;;
+;; The procedure returns an SXML tree headed by the special tag,
+;; *fragment*.
+
+(define (texi-fragment->stexi string-or-port)
+ "Parse the texinfo commands in @var{string-or-port}, and return the
+resultant stexi tree. The head of the tree will be the special command,
+@code{*fragment*}."
+ (define (parse port)
+ (postprocess (car ((make-dom-parser) '*fragment* port '()))))
+ (if (input-port? string-or-port)
+ (parse string-or-port)
+ (call-with-input-string string-or-port parse)))
+
+;; procedure_ texi->stexi PORT
+;;
+;; This is an instance of a SSAX parser above that returns an SXML
+;; representation of the texinfo document ready to be read at PORT.
+;;
+;; The procedure returns an SXML tree. The port points to the
+;; first character after the @bye, or to the end of the file.
+
+(define (texi->stexi port)
+ "Read a full texinfo document from @var{port} and return the parsed
+stexi tree. The parsing will start at the @code{@@settitle} and end at
+@code{@@bye} or EOF."
+ (let ((parser (make-dom-parser)))
+ (take-until-settitle port)
+ (postprocess (car (parser 'texinfo port '())))))
+
+(define (car-eq? x y) (and (pair? x) (eq? (car x) y)))
+(define (make-contents tree)
+ (define (lp in out depth)
+ (cond
+ ((null? in) (values in (cons 'enumerate (reverse! out))))
+ ((and (pair? (cdr in)) (texi-command-depth (caadr in) 4))
+ => (lambda (new-depth)
+ (let ((node-name (and (car-eq? (car in) 'node)
+ (cadr (assq 'name (cdadar in))))))
+ (cond
+ ((< new-depth depth)
+ (values in (cons 'enumerate (reverse! out))))
+ ((> new-depth depth)
+ (let ((out-cdr (if (null? out) '() (cdr out)))
+ (out-car (if (null? out) (list 'item) (car out))))
+ (let*-values (((new-in new-out) (lp in '() (1+ depth))))
+ (lp new-in
+ (cons (append out-car (list new-out)) out-cdr)
+ depth))))
+ (else ;; same depth
+ (lp (cddr in)
+ (cons
+ `(item (para
+ ,@(if node-name
+ `((ref (% (node ,node-name))))
+ (cdadr in))))
+ out)
+ depth))))))
+ (else (lp (cdr in) out depth))))
+ (let*-values (((_ contents) (lp tree '() 1)))
+ `((chapheading "Table of Contents") ,contents)))
+
+(define (trim-whitespace str trim-left? trim-right?)
+ (let* ((left-space? (and (not trim-left?)
+ (string-prefix? " " str)))
+ (right-space? (and (not trim-right?)
+ (string-suffix? " " str)))
+ (tail (append! (string-tokenize str)
+ (if right-space? '("") '()))))
+ (string-join (if left-space? (cons "" tail) tail))))
+
+(define (postprocess tree)
+ (define (loop in out state first? sig-ws?)
+ (cond
+ ((null? in)
+ (values (reverse! out) state))
+ ((string? (car in))
+ (loop (cdr in)
+ (cons (if sig-ws? (car in)
+ (trim-whitespace (car in) first? (null? (cdr in))))
+ out)
+ state #f sig-ws?))
+ ((pair? (car in))
+ (case (caar in)
+ ((set)
+ (if (null? (cdar in)) (error "@set missing arguments" in))
+ (if (string? (cadar in))
+ (let ((i (string-index (cadar in) #\space)))
+ (if i
+ (loop (cdr in) out
+ (acons (substring (cadar in) 0 i)
+ (cons (substring (cadar in) (1+ i)) (cddar in))
+ state)
+ #f sig-ws?)
+ (loop (cdr in) out (acons (cadar in) (cddar in) state)
+ #f sig-ws?)))
+ (error "expected a constant to define for @set" in)))
+ ((value)
+ (loop (fold-right cons (cdr in)
+ (or (and=>
+ (assoc (cadr (assq 'key (cdadar in))) state) cdr)
+ (error "unknown value" (cdadar in) state)))
+ out
+ state #f sig-ws?))
+ ((copying)
+ (loop (cdr in) out (cons (car in) state) #f sig-ws?))
+ ((insertcopying)
+ (loop (fold-right cons (cdr in)
+ (or (cdr (assoc 'copying state))
+ (error "copying isn't set yet")))
+ out
+ state #f sig-ws?))
+ ((contents)
+ (loop (cdr in) (fold cons out (make-contents tree)) state #f sig-ws?))
+ (else
+ (let*-values (((kid-out state)
+ (loop (car in) '() state #t
+ (or sig-ws? (space-significant? (caar in))))))
+ (loop (cdr in) (cons kid-out out) state #f sig-ws?)))))
+ (else ; a symbol
+ (loop (cdr in) (cons (car in) out) state #t sig-ws?))))
+
+ (call-with-values
+ (lambda () (loop tree '() '() #t #f))
+ (lambda (out state) out)))
+
+;; Replace % with texinfo-arguments.
+(define (stexi->sxml tree)
+ "Transform the stexi tree @var{tree} into sxml. This involves
+replacing the @code{%} element that keeps the texinfo arguments with an
+element for each argument.
+
+FIXME_ right now it just changes % to @code{texinfo-arguments} -- that
+doesn't hang with the idea of making a dtd at some point"
+ (pre-post-order
+ tree
+ `((% . ,(lambda (x . t) (cons 'texinfo-arguments t)))
+ (*text* . ,(lambda (x t) t))
+ (*default* . ,(lambda (x . t) (cons x t))))))
+
+;;; arch-tag_ 73890afa-597c-4264-ae70-46fe7756ffb5
+;;; texinfo.scm ends here
+;;;; (texinfo docbook) -- translating sdocbook into stexinfo
+;;;;
+;;;; Copyright (C) 2009, 2010, 2012 Free Software Foundation, Inc.
+;;;; Copyright (C) 2007, 2009 Andy Wingo <wingo at pobox dot com>
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary_
+;;
+;; @c
+;; This module exports procedures for transforming a limited subset of
+;; the SXML representation of docbook into stexi. It is not complete by
+;; any means. The intention is to gather a number of routines and
+;; stylesheets so that external modules can parse specific subsets of
+;; docbook, for example that set generated by certain tools.
+;;
+;;; Code_
+
+(define-module (texinfo docbook)
+ #\use-module (sxml fold)
+ #\use-module ((srfi srfi-1) #\select (fold))
+ #\export (*sdocbook->stexi-rules*
+ *sdocbook-block-commands*
+ sdocbook-flatten
+ filter-empty-elements
+ replace-titles))
+
+(define (identity . args)
+ args)
+
+(define (identity-deattr tag . body)
+ `(,tag ,@(if (and (pair? body) (pair? (car body))
+ (eq? (caar body) '@))
+ (cdr body)
+ body)))
+
+(define (detag-one tag body)
+ body)
+
+(define tag-replacements
+ '((parameter var)
+ (replaceable var)
+ (type code)
+ (function code)
+ (literal samp)
+ (emphasis emph)
+ (simpara para)
+ (programlisting example)
+ (firstterm dfn)
+ (filename file)
+ (quote cite)
+ (application cite)
+ (symbol code)
+ (note cartouche)
+ (envar env)))
+
+(define ignore-list '())
+
+(define (stringify exp)
+ (with-output-to-string (lambda () (write exp))))
+
+(define *sdocbook->stexi-rules*
+ ;~
+ "A stylesheet for use with SSAX's @code{pre-post-order}, which defines
+a number of generic rules for transforming docbook into texinfo."
+ `((@ *preorder* . ,identity)
+ (% *preorder* . ,identity)
+ (para . ,identity-deattr)
+ (orderedlist ((listitem
+ . ,(lambda (tag . body)
+ `(item ,@body))))
+ . ,(lambda (tag . body)
+ `(enumerate ,@body)))
+ (itemizedlist ((listitem
+ . ,(lambda (tag . body)
+ `(item ,@body))))
+ . ,(lambda (tag . body)
+ `(itemize ,@body)))
+ (acronym . ,(lambda (tag . body)
+ `(acronym (% (acronym . ,body)))))
+ (term . ,detag-one)
+ (informalexample . ,detag-one)
+ (section . ,identity)
+ (subsection . ,identity)
+ (subsubsection . ,identity)
+ (ulink . ,(lambda (tag attrs . body)
+ (cond
+ ((assq 'url (cdr attrs))
+ => (lambda (url)
+ `(uref (% ,url (title ,@body)))))
+ (else
+ (car body)))))
+ (*text* . ,detag-one)
+ (*default* . ,(lambda (tag . body)
+ (let ((subst (assq tag tag-replacements)))
+ (cond
+ (subst
+ (if (and (pair? body) (pair? (car body)) (eq? (caar body) '@))
+ (begin
+ (warn "Ignoring" tag "attributes" (car body))
+ (append (cdr subst) (cdr body)))
+ (append (cdr subst) body)))
+ ((memq tag ignore-list) #f)
+ (else
+ (warn "Don't know how to convert" tag "to stexi")
+ `(c (% (all ,(stringify (cons tag body))))))))))))
+
+;; (variablelist
+;; ((varlistentry
+;; . ,(lambda (tag term . body)
+;; `(entry (% (heading ,@(cdr term))) ,@body)))
+;; (listitem
+;; . ,(lambda (tag simpara)
+;; simpara)))
+;; . ,(lambda (tag attrs . body)
+;; `(table (% (formatter (var))) ,@body)))
+
+(define *sdocbook-block-commands*
+ ;~
+ "The set of sdocbook element tags that should not be nested inside
+each other. @xref{texinfo docbook sdocbook-flatten,,sdocbook-flatten},
+for more information."
+ '(para programlisting informalexample indexterm variablelist
+ orderedlist refsect1 refsect2 refsect3 refsect4 title example
+ note itemizedlist informaltable))
+
+(define (inline-command? command)
+ (not (memq command *sdocbook-block-commands*)))
+
+(define (sdocbook-flatten sdocbook)
+ "\"Flatten\" a fragment of sdocbook so that block elements do not nest
+inside each other.
+
+Docbook is a nested format, where e.g. a @code{refsect2} normally
+appears inside a @code{refsect1}. Logical divisions in the document are
+represented via the tree topology; a @code{refsect2} element
+@emph{contains} all of the elements in its section.
+
+On the contrary, texinfo is a flat format, in which sections are marked
+off by standalone section headers like @code{@@chapter}, and block
+elements do not nest inside each other.
+
+This function takes a nested sdocbook fragment @var{sdocbook} and
+flattens all of the sections, such that e.g.
+@example
+ (refsect1 (refsect2 (para \"Hello\")))
+@end example
+becomes
+@example
+ ((refsect1) (refsect2) (para \"Hello\"))
+@end example
+
+Oftentimes (always?) sectioning elements have @code{<title>} as their
+first element child; users interested in processing the @code{refsect*}
+elements into proper sectioning elements like @code{chapter} might be
+interested in @code{replace-titles} and @code{filter-empty-elements}.
+@xref{texinfo docbook replace-titles,,replace-titles}, and @ref{texinfo
+docbook filter-empty-elements,,filter-empty-elements}.
+
+Returns a nodeset, as described in @ref{sxml xpath}. That is to say,
+this function returns an untagged list of stexi elements."
+ (define (fhere str accum block cont)
+ (values (cons str accum)
+ block
+ cont))
+ (define (fdown node accum block cont)
+ (let ((command (car node))
+ (attrs (and (pair? (cdr node)) (pair? (cadr node))
+ (eq? (caadr node) '%)
+ (cadr node))))
+ (values (if attrs (cddr node) (cdr node))
+ '()
+ '()
+ (lambda (accum block)
+ (values
+ `(,command ,@(if attrs (list attrs) '())
+ ,@(reverse accum))
+ block)))))
+ (define (fup node paccum pblock pcont kaccum kblock kcont)
+ (call-with-values (lambda () (kcont kaccum kblock))
+ (lambda (ret block)
+ (if (inline-command? (car ret))
+ (values (cons ret paccum) (append kblock pblock) pcont)
+ (values paccum (append kblock (cons ret pblock)) pcont)))))
+ (call-with-values
+ (lambda () (foldts*-values fdown fup fhere sdocbook '() '() #f))
+ (lambda (accum block cont)
+ (reverse block))))
+
+(define (filter-empty-elements sdocbook)
+ "Filters out empty elements in an sdocbook nodeset. Mostly useful
+after running @code{sdocbook-flatten}."
+ (reverse
+ (fold
+ (lambda (x rest)
+ (if (and (pair? x) (null? (cdr x)))
+ rest
+ (cons x rest)))
+ '()
+ sdocbook)))
+
+(define (replace-titles sdocbook-fragment)
+ "Iterate over the sdocbook nodeset @var{sdocbook-fragment},
+transforming contiguous @code{refsect} and @code{title} elements into
+the appropriate texinfo sectioning command. Most useful after having run
+@code{sdocbook-flatten}.
+
+For example_
+@example
+ (replace-titles '((refsect1) (title \"Foo\") (para \"Bar.\")))
+ @result{} '((chapter \"Foo\") (para \"Bar.\"))
+@end example
+"
+ (define sections '((refsect1 . chapter)
+ (refsect2 . section)
+ (refsect3 . subsection)
+ (refsect4 . subsubsection)))
+ (let lp ((in sdocbook-fragment) (out '()))
+ (cond
+ ((null? in)
+ (reverse out))
+ ((and (pair? (car in)) (assq (caar in) sections))
+ ;; pull out the title
+ => (lambda (pair)
+ (lp (cddr in) (cons `(,(cdr pair) ,@(cdadr in)) out))))
+ (else
+ (lp (cdr in) (cons (car in) out))))))
+;;;; (texinfo html) -- translating stexinfo into shtml
+;;;;
+;;;; Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.
+;;;; Copyright (C) 2003,2004,2009 Andy Wingo <wingo at pobox dot com>
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary_
+;;
+;;This module implements transformation from @code{stexi} to HTML. Note
+;;that the output of @code{stexi->shtml} is actually SXML with the HTML
+;;vocabulary. This means that the output can be further processed, and
+;;that it must eventually be serialized by
+;;@ref{sxml simple sxml->xml,sxml->xml}.
+;;
+;;References (i.e., the @code{@@ref} family of commands) are resolved by
+;;a @dfn{ref-resolver}.
+;;@xref{texinfo html add-ref-resolver!,add-ref-resolver!}, for more
+;;information.
+;;
+;;; Code_
+
+;; TODO_ nice ref resolving API, default CSS stylesheet (esp. to remove
+;; margin-top on dd > p)
+
+(define-module (texinfo html)
+ #\use-module (texinfo)
+ #\use-module (sxml transform)
+ #\use-module (ice-9 match)
+ #\use-module (srfi srfi-13)
+ #\export (stexi->shtml add-ref-resolver! urlify))
+
+;; The caller is responsible for carring the returned list.
+(define (arg-ref key %-args)
+ (and=> (assq key (cdr %-args)) (lambda (x) (stexi->shtml (cdr x)))))
+(define (arg-req key %-args)
+ (or (arg-ref key %-args)
+ (error "Missing argument_" key %-args)))
+(define (car* x) (and x (car x)))
+
+(define (urlify str)
+ (string-downcase
+ (string-map
+ (lambda (c)
+ (case c
+ ((#\space #\/ #\_) #\-)
+ (else c)))
+ str)))
+
+(define ref-resolvers
+ (list
+ (lambda (node-name manual-name) ;; the default
+ (urlify (string-append (or manual-name "") "#" node-name)))))
+
+(define (add-ref-resolver! proc)
+ "Add @var{proc} to the head of the list of ref-resolvers. @var{proc}
+will be expected to take the name of a node and the name of a manual and
+return the URL of the referent, or @code{#f} to pass control to the next
+ref-resolver in the list.
+
+The default ref-resolver will return the concatenation of the manual
+name, @code{#}, and the node name."
+ (set! ref-resolvers (cons proc ref-resolvers)))
+
+(define (resolve-ref node manual)
+ (or (or-map (lambda (x) (x node manual)) ref-resolvers)
+ (error "Could not resolve reference" node manual)))
+
+(define (ref tag args)
+ (let* ((node (car (arg-req 'node args)))
+ (section (or (car* (arg-ref 'section args)) node))
+ (manual (car* (arg-ref 'manual args)))
+ (target (resolve-ref node manual)))
+ `(span ,(and=> (assq tag '((xref "See ") (pxref "see "))) cdr)
+ (a (@ (href ,target)) ,section))))
+
+(define (uref tag args)
+ (let ((url (car (arg-req 'url args))))
+ `(a (@ (href ,url)) ,(or (car* (arg-ref 'title args)) url))))
+
+;; @!*&%( Mozilla gets confused at an empty ("<a .. />") a tag. Put an
+;; empty string here to placate the reptile.
+(define (node tag args)
+ `(a (@ (name ,(urlify (car (arg-req 'name args))))) ""))
+
+(define (def tag args . body)
+ (define (code x) (and x (cons 'code x)))
+ (define (var x) (and x (cons 'var x)))
+ (define (b x) (and x (cons 'b x)))
+ (define (list/spaces . elts)
+ (let lp ((in elts) (out '()))
+ (cond ((null? in) (reverse! out))
+ ((null? (car in)) (lp (cdr in) out))
+ (else (lp (cdr in)
+ (cons (car in)
+ (if (null? out) out (cons " " out))))))))
+ (define (left-td-contents)
+ (list/spaces (code (arg-ref 'data-type args))
+ (b (list (code (arg-ref 'class args)))) ;; is this right?
+ (b (list (code (arg-ref 'name args))))
+ (if (memq tag '(deftypeop deftypefn deftypefun))
+ (code (arg-ref 'arguments args))
+ (var (list (code (arg-ref 'arguments args)))))))
+
+ (let* ((category (case tag
+ ((defun) "Function")
+ ((defspec) "Special Form")
+ ((defvar) "Variable")
+ (else (car (arg-req 'category args))))))
+ `(div
+ (table
+ (@ (cellpadding "0") (cellspacing "0") (width "100%") (class "def"))
+ (tr (td ,@(left-td-contents))
+ (td (div (@ (class "right")) "[" ,category "]"))))
+ (div (@ (class "description")) ,@body))))
+
+(define (enumerate tag . elts)
+ (define (tonumber start)
+ (let ((c (string-ref start 0)))
+ (cond ((number? c) (string->number start))
+ (else (1+ (- (char->integer c)
+ (char->integer (if (char-upper-case? c) #\A #\a))))))))
+ `(ol ,@(if (and (pair? elts) (pair? (car elts)) (eq? (caar elts) '%))
+ (cons `(@ (start ,@(tonumber (arg-req 'start (car elts)))))
+ ;; (type ,(type (arg-ref 'start (car elts)))))
+ (cdr elts))
+ elts)))
+
+(define (itemize tag . elts)
+ `(ul ,@(match elts
+ ;; Strip `bullet' attribute.
+ ((('% . attrs) . elts) elts)
+ (elts elts))))
+
+(define (acronym tag . elts)
+ (match elts
+ ;; FIXME_ Need attribute matcher that doesn't depend on attribute
+ ;; order.
+ ((('% ('acronym text) . _)) `(acronym ,text))))
+
+(define (table tag args . body)
+ (let ((formatter (caar (arg-req 'formatter args))))
+ (cons 'dl
+ (map (lambda (x)
+ (cond ((and (pair? x) (eq? (car x) 'dt))
+ (list (car x) (cons formatter (cdr x))))
+ (else x)))
+ (apply append body)))))
+
+(define (entry tag args . body)
+ (let lp ((out `((dt ,@(arg-req 'heading args))))
+ (body body))
+ (if (and (pair? body) (pair? (car body)) (eq? (caar body) 'itemx))
+ (lp (append out `(dt ,@(map stexi->shtml (cdar body))))
+ (cdr body))
+ (append out `((dd ,@(map stexi->shtml body)))))))
+
+(define tag-replacements
+ '((titlepage div (@ (class "titlepage")))
+ (title h2 (@ (class "title")))
+ (subtitle h3 (@ (class "subtitle")))
+ (author h3 (@ (class "author")))
+ (example pre)
+ (lisp pre)
+ (smallexample pre (@ (class "smaller")))
+ (smalllisp pre (@ (class "smaller")))
+ (cartouche div (@ (class "cartouche")))
+ (verbatim pre (@ (class "verbatim")))
+ (chapter h2)
+ (section h3)
+ (subsection h4)
+ (subsubsection h5)
+ (appendix h2)
+ (appendixsec h3)
+ (appendixsubsec h4)
+ (appendixsubsubsec h5)
+ (unnumbered h2)
+ (unnumberedsec h3)
+ (unnumberedsubsec h4)
+ (unnumberedsubsubsec h5)
+ (majorheading h2)
+ (chapheading h2)
+ (heading h3)
+ (subheading h4)
+ (subsubheading h5)
+ (quotation blockquote)
+ (item li) ;; itemx ?
+ (para p)
+ (*fragment* div) ;; should be ok
+
+ (asis span)
+ (bold b)
+ (sample samp)
+ (samp samp)
+ (code code)
+ (kbd kbd)
+ (key code (@ (class "key")))
+ (var var)
+ (env code (@ (class "env")))
+ (file code (@ (class "file")))
+ (command code (@ (class "command")))
+ (option code (@ (class "option")))
+ (url code (@ (class "url")))
+ (dfn dfn)
+ (cite cite)
+ (acro acronym)
+ (email code (@ (class "email")))
+ (emph em)
+ (strong strong)
+ (sc span (@ (class "small-caps")))))
+
+(define ignore-list
+ '(page setfilename setchapternewpage iftex ifinfo ifplaintext ifxml sp vskip
+ menu ignore syncodeindex comment c dircategory direntry top shortcontents
+ cindex printindex))
+
+(define rules
+ `((% *preorder* . ,(lambda args args)) ;; Keep these around...
+ (texinfo . ,(lambda (tag args . body)
+ (pre-post-order
+ `(html
+ (@ (xmlns "http_//www.w3.org/1999/xhtml"))
+ (head (title ,(car (arg-req 'title args))))
+ (body ,@body))
+ `((% *preorder* . ,(lambda args #f)) ;; ... filter out.
+ (*text* . ,(lambda (tag x) x))
+ (*default* . ,(lambda (tag . body)
+ (cons tag body)))))))
+ (copyright . ,(lambda args '(*ENTITY* "copy")))
+ (result . ,(lambda args '(*ENTITY* "rArr")))
+ (xref . ,ref) (ref . ,ref) (pxref . ,ref)
+ (uref . ,uref)
+ (node . ,node) (anchor . ,node)
+ (table . ,table)
+ (enumerate . ,enumerate)
+ (itemize . ,itemize)
+ (acronym . ,acronym)
+ (entry *preorder* . ,entry)
+
+ (deftp . ,def) (defcv . ,def) (defivar . ,def) (deftypeivar . ,def)
+ (defop . ,def) (deftypeop . ,def) (defmethod . ,def)
+ (deftypemethod . ,def) (defopt . ,def) (defvr . ,def) (defvar . ,def)
+ (deftypevr . ,def) (deftypevar . ,def) (deffn . ,def)
+ (deftypefn . ,def) (defmac . ,def) (defspec . ,def) (defun . ,def)
+ (deftypefun . ,def)
+ (ifnottex . ,(lambda (tag . body) body))
+ (*text* . ,(lambda (tag x) x))
+ (*default* . ,(lambda (tag . body)
+ (let ((subst (assq tag tag-replacements)))
+ (cond
+ (subst (append (cdr subst) body))
+ ((memq tag ignore-list) #f)
+ (else
+ (warn "Don't know how to convert" tag "to HTML")
+ body)))))))
+
+(define (stexi->shtml tree)
+ "Transform the stexi @var{tree} into shtml, resolving references via
+ref-resolvers. See the module commentary for more details."
+ (pre-post-order tree rules))
+
+;;; arch-tag_ ab05f3fe-9981-4a78-b64c-48efcd9983a6
+;;;; (texinfo indexing) -- indexing stexinfo
+;;;;
+;;;; Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+;;;; Copyright (C) 2003,2004,2009 Andy Wingo <wingo at pobox dot com>
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary_
+;;
+;;@c texinfo formatting
+;;Given a piece of stexi, return an index of a specified variety.
+;;
+;;Note that currently, @code{stexi-extract-index} doesn't differentiate
+;;between different kinds of index entries. That's a bug ;)
+;;; Code_
+
+(define-module (texinfo indexing)
+ #\use-module (sxml simple)
+ #\use-module (srfi srfi-13)
+ #\export (stexi-extract-index))
+
+(define defines
+ '(deftp defcv defivar deftypeivar defop deftypeop defmethod
+ deftypemethod defopt defvr defvar deftypevr deftypevar deffn
+ deftypefn defspec defmac defun deftypefun))
+
+(define indices
+ '(cindex findex vindex kindex pindex tindex))
+
+(define (stexi-extract-index tree manual-name kind)
+ "Given an stexi tree @var{tree}, index all of the entries of type
+@var{kind}. @var{kind} can be one of the predefined texinfo indices
+(@code{concept}, @code{variable}, @code{function}, @code{key},
+@code{program}, @code{type}) or one of the special symbols @code{auto}
+or @code{all}. @code{auto} will scan the stext for a @code{(printindex)}
+statement, and @code{all} will generate an index from all entries,
+regardless of type.
+
+The returned index is a list of pairs, the @sc{car} of which is the
+entry (a string) and the @sc{cdr} of which is a node name (a string)."
+ (let loop ((in tree) (entries '()))
+ (cond
+ ((null? in)
+ entries)
+ ((pair? (car in))
+ (cond
+ ((and (pair? (cdr in)) (pair? (cadr in))
+ (eq? (caar in) 'anchor) (memq (caadr in) defines))
+ (loop (cddr in) (acons (cadr (assq 'name (cdr (cadadr in))))
+ (cadr (assq 'name (cdadar in)))
+ entries)))
+ ((and (pair? (cdr in)) (pair? (cadr in))
+ (eq? (caar in) 'anchor) (memq (caadr in) indices))
+ (loop (cddr in) (acons (sxml->string (cadr in))
+ (cadr (assq 'name (cdadar in)))
+ entries)))
+ (else
+ (loop (cdr in) (loop (car in) entries)))))
+ (else
+ (loop (cdr in) entries)))))
+
+;;; arch-tag_ 216d29d3-1ed9-433f-9c19-0dc4d6b439b6
+;;;; (texinfo plain-text) -- rendering stexinfo as plain text
+;;;;
+;;;; Copyright (C) 2009, 2010, 2011, 2013 Free Software Foundation, Inc.
+;;;; Copyright (C) 2003,2004,2009 Andy Wingo <wingo at pobox dot com>
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary_
+;;
+;;Transformation from stexi to plain-text. Strives to re-create the
+;;output from @code{info}; comes pretty damn close.
+;;
+;;; Code_
+
+(define-module (texinfo plain-text)
+ #\use-module (texinfo)
+ #\use-module (texinfo string-utils)
+ #\use-module (sxml transform)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-13)
+ #\use-module (ice-9 match)
+ #\export (stexi->plain-text))
+
+;; The return value is a string.
+(define (arg-ref key %-args)
+ (and=> (and=> (assq key (cdr %-args)) cdr)
+ stexi->plain-text))
+(define (arg-req key %-args)
+ (or (arg-ref key %-args)
+ (error "Missing argument_" key %-args)))
+
+(define (make-ticker str)
+ (lambda () str))
+(define (make-enumerator n)
+ (lambda ()
+ (let ((last n))
+ (set! n (1+ n))
+ (format #f "~A. " last))))
+
+(define *indent* (make-fluid ""))
+(define *itemizer* (make-fluid (make-ticker "* ")))
+
+(define-macro (with-indent n . body)
+ `(with-fluids ((*indent* (string-append (fluid-ref *indent*)
+ (make-string ,n #\space))))
+ ,@body))
+
+(define (make-indenter n proc)
+ (lambda args (with-indent n (apply proc args))))
+
+(define (string-indent str)
+ (string-append (fluid-ref *indent*) str "\n"))
+
+(define-macro (with-itemizer itemizer . body)
+ `(with-fluids ((*itemizer* ,itemizer))
+ ,@body))
+
+(define (wrap* . strings)
+ (let ((indent (fluid-ref *indent*)))
+ (fill-string (string-concatenate strings)
+ #\line-width 72 #\initial-indent indent
+ #\subsequent-indent indent)))
+(define (wrap . strings)
+ (string-append (apply wrap* strings) "\n\n"))
+(define (wrap-heading . strings)
+ (string-append (apply wrap* strings) "\n"))
+
+(define (ref tag args)
+ (let* ((node (arg-req 'node args))
+ (name (or (arg-ref 'name args) node))
+ (manual (arg-ref 'manual args)))
+ (string-concatenate
+ (cons*
+ (or (and=> (assq tag '((xref "See ") (pxref "see "))) cadr) "")
+ name
+ (if manual `(" in manual " ,manual) '())))))
+
+(define (uref tag args)
+ (let ((url (arg-req 'url args))
+ (title (arg-ref 'title args)))
+ (if title
+ (string-append title " (" url ")")
+ (string-append "`" url "'"))))
+
+(define (def tag args . body)
+ (define (first-line)
+ (string-join
+ (filter identity
+ (map (lambda (x) (arg-ref x args))
+ '(data-type class name arguments)))
+ " "))
+
+ (let* ((category (case tag
+ ((defun) "Function")
+ ((defspec) "Special Form")
+ ((defvar) "Variable")
+ (else (arg-req 'category args)))))
+ (string-append
+ (wrap-heading (string-append " - " category "_ " (first-line)))
+ (with-indent 5 (stexi->plain-text body)))))
+
+(define (enumerate tag . elts)
+ (define (tonumber start)
+ (let ((c (string-ref start 0)))
+ (cond ((number? c) (string->number start))
+ (else (1+ (- (char->integer c)
+ (char->integer (if (char-upper-case? c) #\A #\a))))))))
+ (let* ((args? (and (pair? elts) (pair? (car elts))
+ (eq? (caar elts) '%)))
+ (start (and args? (arg-ref 'start (car elts)))))
+ (with-itemizer (make-enumerator (if start (tonumber start) 1))
+ (with-indent 5
+ (stexi->plain-text (if start (cdr elts) elts))))))
+
+(define (itemize tag args . elts)
+ (with-itemizer (make-ticker "* ")
+ (with-indent 5
+ (stexi->plain-text elts))))
+
+(define (item tag . elts)
+ (let* ((ret (stexi->plain-text elts))
+ (tick ((fluid-ref *itemizer*)))
+ (tick-pos (- (string-length (fluid-ref *indent*))
+ (string-length tick))))
+ (if (and (not (string-null? ret)) (not (negative? tick-pos)))
+ (string-copy! ret tick-pos tick))
+ ret))
+
+(define (table tag args . body)
+ (stexi->plain-text body))
+
+(define (entry tag args . body)
+ (let ((heading (wrap-heading
+ (stexi->plain-text (arg-req 'heading args)))))
+ (string-append heading
+ (with-indent 5 (stexi->plain-text body)))))
+
+(define (make-underliner char)
+ (lambda (tag . body)
+ (let ((str (stexi->plain-text body)))
+ (string-append
+ "\n"
+ (string-indent str)
+ (string-indent (make-string (string-length str) char))
+ "\n"))))
+
+(define chapter (make-underliner #\*))
+(define section (make-underliner #\=))
+(define subsection (make-underliner #\-))
+(define subsubsection (make-underliner #\.))
+
+(define (example tag . body)
+ (let ((ret (stexi->plain-text body)))
+ (string-append
+ (string-concatenate
+ (with-indent 5 (map string-indent (string-split ret #\newline))))
+ "\n")))
+
+(define (verbatim tag . body)
+ (let ((ret (stexi->plain-text body)))
+ (string-append
+ (string-concatenate
+ (map string-indent (string-split ret #\newline)))
+ "\n")))
+
+(define (fragment tag . body)
+ (string-concatenate (map-in-order stexi->plain-text body)))
+
+(define (para tag . body)
+ (wrap (stexi->plain-text body)))
+
+(define (make-surrounder str)
+ (lambda (tag . body)
+ (string-append str (stexi->plain-text body) str)))
+
+(define (code tag . body)
+ (string-append "`" (stexi->plain-text body) "'"))
+
+(define (key tag . body)
+ (string-append "<" (stexi->plain-text body) ">"))
+
+(define (var tag . body)
+ (string-upcase (stexi->plain-text body)))
+
+(define (passthrough tag . body)
+ (stexi->plain-text body))
+
+(define (texinfo tag args . body)
+ (let ((title (chapter 'foo (arg-req 'title args))))
+ (string-append title (stexi->plain-text body))))
+
+(define ignore-list
+ '(page setfilename setchapternewpage iftex ifinfo ifplaintext ifxml sp vskip
+ menu ignore syncodeindex comment c % node anchor))
+(define (ignored? tag)
+ (memq tag ignore-list))
+
+(define tag-handlers
+ `((title ,chapter)
+ (chapter ,chapter)
+ (section ,section)
+ (subsection ,subsection)
+ (subsubsection ,subsubsection)
+ (appendix ,chapter)
+ (appendixsec ,section)
+ (appendixsubsec ,subsection)
+ (appendixsubsubsec ,subsubsection)
+ (unnumbered ,chapter)
+ (unnumberedsec ,section)
+ (unnumberedsubsec ,subsection)
+ (unnumberedsubsubsec ,subsubsection)
+ (majorheading ,chapter)
+ (chapheading ,chapter)
+ (heading ,section)
+ (subheading ,subsection)
+ (subsubheading ,subsubsection)
+
+ (strong ,(make-surrounder "*"))
+ (sample ,code)
+ (samp ,code)
+ (code ,code)
+ (math ,passthrough)
+ (kbd ,code)
+ (key ,key)
+ (var ,var)
+ (env ,code)
+ (file ,code)
+ (command ,code)
+ (option ,code)
+ (url ,code)
+ (dfn ,(make-surrounder "\""))
+ (cite ,(make-surrounder "\""))
+ (acro ,passthrough)
+ (email ,key)
+ (emph ,(make-surrounder "_"))
+ (sc ,var)
+ (copyright ,(lambda args "(C)"))
+ (result ,(lambda args "==>"))
+ (dots ,(lambda args "..."))
+ (xref ,ref)
+ (ref ,ref)
+ (pxref ,ref)
+ (uref ,uref)
+
+ (texinfo ,texinfo)
+ (quotation ,(make-indenter 5 para))
+ (itemize ,itemize)
+ (enumerate ,enumerate)
+ (item ,item)
+ (table ,table)
+ (entry ,entry)
+ (example ,example)
+ (lisp ,example)
+ (smallexample ,example)
+ (smalllisp ,example)
+ (verbatim ,verbatim)
+ (*fragment* ,fragment)
+
+ (deftp ,def)
+ (defcv ,def)
+ (defivar ,def)
+ (deftypeivar ,def)
+ (defop ,def)
+ (deftypeop ,def)
+ (defmethod ,def)
+ (deftypemethod ,def)
+ (defopt ,def)
+ (defvr ,def)
+ (defvar ,def)
+ (deftypevr ,def)
+ (deftypevar ,def)
+ (deffn ,def)
+ (deftypefn ,def)
+ (defmac ,def)
+ (defspec ,def)
+ (defun ,def)
+ (deftypefun ,def)))
+
+(define (stexi->plain-text tree)
+ "Transform @var{tree} into plain text. Returns a string."
+ (match tree
+ (() "")
+ ((? string?) tree)
+ (((? symbol? tag) body ...)
+ (let ((handler (and (not (ignored? tag))
+ (or (and=> (assq tag tag-handlers) cadr)
+ para))))
+ (if handler
+ (apply handler tree)
+ "")))
+ ((tree ...)
+ (string-concatenate (map-in-order stexi->plain-text tree)))
+ (_ "")))
+
+;;; arch-tag_ f966c3f6-3b46-4790-bbf9-3ad27e4917c2
+;;;; (texinfo reflection) -- documenting Scheme as stexinfo
+;;;;
+;;;; Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.
+;;;; Copyright (C) 2003,2004,2009 Andy Wingo <wingo at pobox dot com>
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary_
+;;
+;;Routines to generare @code{stexi} documentation for objects and
+;;modules.
+;;
+;;Note that in this context, an @dfn{object} is just a value associated
+;;with a location. It has nothing to do with GOOPS.
+;;
+;;; Code_
+
+(define-module (texinfo reflection)
+ #\use-module ((srfi srfi-1) #\select (append-map))
+ #\use-module (oop goops)
+ #\use-module (texinfo)
+ #\use-module (texinfo plain-text)
+ #\use-module (srfi srfi-13)
+ #\use-module (ice-9 session)
+ #\use-module (ice-9 documentation)
+ #\use-module (ice-9 optargs)
+ #\use-module ((sxml transform) #\select (pre-post-order))
+ #\export (module-stexi-documentation
+ script-stexi-documentation
+ object-stexi-documentation
+ package-stexi-standard-copying
+ package-stexi-standard-titlepage
+ package-stexi-generic-menu
+ package-stexi-standard-menu
+ package-stexi-extended-menu
+ package-stexi-standard-prologue
+ package-stexi-documentation
+ package-stexi-documentation-for-include))
+
+;; List for sorting the definitions in a module
+(define defs
+ '(deftp defcv defivar deftypeivar defop deftypeop defmethod
+ deftypemethod defopt defvr defvar deftypevr deftypevar deffn
+ deftypefn defmac defspec defun deftypefun))
+
+(define (sort-defs ordering a b)
+ (define (def x)
+ ;; a and b are lists of the form ((anchor ...) (def* ...)...)
+ (cadr x))
+ (define (name x)
+ (cadr (assq 'name (cdadr (def x)))))
+ (define (priority x)
+ (list-index defs (car (def x))))
+ (define (order x)
+ (or (list-index ordering (string->symbol (name x)))
+ ;; if the def is not in the list, a big number
+ 1234567890))
+ (define (compare-in-order proc eq? < . args)
+ (if (not (eq? (proc a) (proc b)))
+ (< (proc a) (proc b))
+ (or (null? args)
+ (apply compare-in-order args))))
+ (compare-in-order order = <
+ priority = <
+ name string=? string<=?))
+
+(define (list*-join l infix restfix)
+ (let lp ((in l) (out '()))
+ (cond ((null? in) (reverse! out))
+ ((symbol? in) (reverse! (cons* in restfix out)))
+ (else (lp (cdr in) (if (null? out)
+ (list (car in))
+ (cons* (car in) infix out)))))))
+
+(define (process-args args)
+ (map (lambda (x) (if (string? x) x (object->string x)))
+ (list*-join (or args '())
+ " " " . ")))
+
+(define (get-proc-args proc)
+ (cond
+ ((procedure-arguments proc)
+ => (lambda (args)
+ (let ((required-args (assq-ref args 'required))
+ (optional-args (assq-ref args 'optional))
+ (keyword-args (assq-ref args 'keyword))
+ (rest-arg (assq-ref args 'rest)))
+ (process-args
+ (append
+ ;; start with the required args...
+ (map symbol->string required-args)
+
+ ;; add any optional args if needed...
+ (map (lambda (a)
+ (if (list? a)
+ (format #f "[~a = ~s]" (car a) (cadr a))
+ (format #f "[~a]" a)))
+ optional-args)
+
+ ;; now the keyword args..
+ (map (lambda (a)
+ (if (pair? a)
+ (format #f "[~a]" (car a))
+ (format #f "[#:~a]" a)))
+ keyword-args)
+
+ ;; now the rest arg...
+ (if rest-arg
+ (list "." (symbol->string rest-arg))
+ '()))))))))
+
+(define (macro-arguments name type transformer)
+ (process-args
+ (case type
+ ((syntax-rules)
+ (let ((patterns (procedure-property transformer 'patterns)))
+ (if (pair? patterns)
+ (car patterns)
+ '())))
+ ((identifier-syntax)
+ '())
+ ((defmacro)
+ (or (procedure-property transformer 'defmacro-args)
+ '()))
+ (else
+ ;; a procedural (syntax-case) macro. how to document these?
+ '()))))
+
+(define (macro-additional-stexi name type transformer)
+ (case type
+ ((syntax-rules)
+ (let ((patterns (procedure-property transformer 'patterns)))
+ (if (pair? patterns)
+ (map (lambda (x)
+ `(defspecx (% (name ,name)
+ (arguments ,@(process-args x)))))
+ (cdr patterns))
+ '())))
+ (else
+ '())))
+
+(define many-space? (make-regexp "[[_space_]][[_space_]][[_space_]]"))
+(define initial-space? (make-regexp "^[[_space_]]"))
+(define (string->stexi str)
+ (or (and (or (not str) (string-null? str))
+ '(*fragment*))
+ (and (or (string-index str #\@)
+ (and (not (regexp-exec many-space? str))
+ (not (regexp-exec initial-space? str))))
+ (false-if-exception
+ (texi-fragment->stexi str)))
+ `(*fragment* (verbatim ,str))))
+
+(define method-formals
+ (and (defined? 'method-formals) method-formals))
+
+(define (method-stexi-arguments method)
+ (cond
+ (method-formals
+ (let lp ((formals (method-formals method))
+ (specializers (method-specializers method))
+ (out '()))
+ (define (arg-texinfo formal specializer)
+ `(" (" (var ,(symbol->string formal)) " "
+ (code ,(symbol->string (class-name specializer))) ")"))
+ (cond
+ ((null? formals) (reverse out))
+ ((pair? formals)
+ (lp (cdr formals) (cdr specializers)
+ (append (reverse (arg-texinfo (car formals) (car specializers)))
+ out)))
+ (else
+ (append (reverse out) (arg-texinfo formals specializers)
+ (list "..."))))))
+ ((method-source method)
+ (let lp ((bindings (cadr (method-source method))) (out '()))
+ (define (arg-texinfo arg)
+ `(" (" (var ,(symbol->string (car arg))) " "
+ (code ,(symbol->string (cadr arg))) ")"))
+ (cond
+ ((null? bindings)
+ (reverse out))
+ ((not (pair? (car bindings)))
+ (append (reverse out) (arg-texinfo bindings) (list "...")))
+ (else
+ (lp (cdr bindings)
+ (append (reverse (arg-texinfo (car bindings))) out))))))
+ (else (warn method) '())))
+
+(define* (object-stexi-documentation object #\optional (name "[unknown]")
+ #\key (force #f))
+ (if (symbol? name)
+ (set! name (symbol->string name)))
+ (let ((stexi ((lambda (x)
+ (cond ((string? x) (string->stexi x))
+ ((and (pair? x) (eq? (car x) '*fragment*)) x)
+ (force `(*fragment*))
+ (else #f)))
+ (object-documentation
+ (if (is-a? object <method>)
+ (method-procedure object)
+ object)))))
+ (define (make-def type args)
+ `(,type (% ,@args) ,@(cdr stexi)))
+ (cond
+ ((not stexi) #f)
+ ;; stexi is now a list, headed by *fragment*.
+ ((and (pair? (cdr stexi)) (pair? (cadr stexi))
+ (memq (caadr stexi) defs))
+ ;; it's already a deffoo.
+ stexi)
+ ((is-a? object <class>)
+ (make-def 'deftp `((name ,name)
+ (category "Class"))))
+ ((is-a? object <macro>)
+ (let* ((proc (macro-transformer object))
+ (type (and proc (procedure-property proc 'macro-type))))
+ `(defspec (% (name ,name)
+ (arguments ,@(macro-arguments name type proc)))
+ ,@(macro-additional-stexi name type proc)
+ ,@(cdr stexi))))
+
+ ((is-a? object <procedure>)
+ (make-def 'defun `((name ,name)
+ (arguments ,@(get-proc-args object)))))
+ ((is-a? object <method>)
+ (make-def 'deffn `((category "Method")
+ (name ,name)
+ (arguments ,@(method-stexi-arguments object)))))
+ ((is-a? object <generic>)
+ `(*fragment*
+ ,(make-def 'deffn `((name ,name)
+ (category "Generic")))
+ ,@(map
+ (lambda (method)
+ (object-stexi-documentation method name #\force force))
+ (generic-function-methods object))))
+ (else
+ (make-def 'defvar `((name ,name)))))))
+
+(define (module-name->node-name sym-name)
+ (string-join (map symbol->string sym-name) " "))
+
+;; this copied from (ice-9 session); need to find a better way
+(define (module-filename name)
+ (let* ((name (map symbol->string name))
+ (reverse-name (reverse name))
+ (leaf (car reverse-name))
+ (dir-hint-module-name (reverse (cdr reverse-name)))
+ (dir-hint (apply string-append
+ (map (lambda (elt)
+ (string-append elt "/"))
+ dir-hint-module-name))))
+ (%search-load-path (in-vicinity dir-hint leaf))))
+
+(define (read-module name)
+ (let ((filename (module-filename name)))
+ (if filename
+ (let ((port (open-input-file filename)))
+ (let lp ((out '()) (form (read port)))
+ (if (eof-object? form)
+ (reverse out)
+ (lp (cons form out) (read port)))))
+ '())))
+
+(define (module-export-list sym-name)
+ (define (module-form-export-list form)
+ (and (pair? form)
+ (eq? (car form) 'define-module)
+ (equal? (cadr form) sym-name)
+ (and=> (memq #\export (cddr form)) cadr)))
+ (let lp ((forms (read-module sym-name)))
+ (cond ((null? forms) '())
+ ((module-form-export-list (car forms)) => identity)
+ (else (lp (cdr forms))))))
+
+(define* (module-stexi-documentation sym-name
+ #\optional %docs-resolver
+ #\key (docs-resolver
+ (or %docs-resolver
+ (lambda (name def) def))))
+ "Return documentation for the module named @var{sym-name}. The
+documentation will be formatted as @code{stexi}
+ (@pxref{texinfo,texinfo})."
+ (if %docs-resolver
+ (issue-deprecation-warning
+ "module-stexi-documentation_ use #:docs-resolver instead of a positional argument."))
+ (let* ((commentary (and=> (module-commentary sym-name)
+ (lambda (x) (string-trim-both x #\newline))))
+ (stexi (string->stexi commentary))
+ (node-name (module-name->node-name sym-name))
+ (name-str (with-output-to-string
+ (lambda () (display sym-name))))
+ (module (resolve-interface sym-name))
+ (export-list (module-export-list sym-name)))
+ (define (anchor-name sym)
+ (string-append node-name " " (symbol->string sym)))
+ (define (make-defs)
+ (sort!
+ (module-map
+ (lambda (sym var)
+ `((anchor (% (name ,(anchor-name sym))))
+ ,@((lambda (x)
+ (if (eq? (car x) '*fragment*)
+ (cdr x)
+ (list x)))
+ (if (variable-bound? var)
+ (docs-resolver
+ sym
+ (object-stexi-documentation (variable-ref var) sym
+ #\force #t))
+ (begin
+ (warn "variable unbound!" sym)
+ `(defvar (% (name ,(symbol->string sym)))
+ "[unbound!]"))))))
+ module)
+ (lambda (a b) (sort-defs export-list a b))))
+
+ `(texinfo (% (title ,name-str))
+ (node (% (name ,node-name)))
+ (section "Overview")
+ ,@(cdr stexi)
+ (section "Usage")
+ ,@(apply append! (make-defs)))))
+
+(define (script-stexi-documentation scriptpath)
+ "Return documentation for given script. The documentation will be
+taken from the script's commentary, and will be returned in the
+@code{stexi} format (@pxref{texinfo,texinfo})."
+ (let ((commentary (file-commentary scriptpath)))
+ `(texinfo (% (title ,(basename scriptpath)))
+ (node (% (name ,(basename scriptpath))))
+ ,@(if commentary
+ (cdr
+ (string->stexi
+ (string-trim-both commentary #\newline)))
+ '()))))
+
+(cond
+ ((defined? 'add-value-help-handler!)
+ (add-value-help-handler!
+ (lambda (name value)
+ (stexi->plain-text
+ (object-stexi-documentation value name #\force #t))))
+ (add-name-help-handler!
+ (lambda (name)
+ (and (list? name)
+ (and-map symbol? name)
+ (stexi->plain-text (module-stexi-documentation name)))))))
+
+;; we could be dealing with an old (ice-9 session); fondle it to get
+;; module-commentary
+(define module-commentary (@@ (ice-9 session) module-commentary))
+
+(define (package-stexi-standard-copying name version updated years
+ copyright-holder permissions)
+ "Create a standard texinfo @code{copying} section.
+
+@var{years} is a list of years (as integers) in which the modules
+being documented were released. All other arguments are strings."
+ `(copying
+ (para "This manual is for " ,name
+ " (version " ,version ", updated " ,updated ")")
+ (para "Copyright " ,(string-join (map number->string years) ",")
+ " " ,copyright-holder)
+ (quotation
+ (para ,permissions))))
+
+(define (package-stexi-standard-titlepage name version updated authors)
+ "Create a standard GNU title page.
+
+@var{authors} is a list of @code{(@var{name} . @var{email})}
+pairs. All other arguments are strings.
+
+Here is an example of the usage of this procedure_
+
+@smallexample
+ (package-stexi-standard-titlepage
+ \"Foolib\"
+ \"3.2\"
+ \"26 September 2006\"
+ '((\"Alyssa P Hacker\" . \"alyssa@@example.com\"))
+ '(2004 2005 2006)
+ \"Free Software Foundation, Inc.\"
+ \"Standard GPL permissions blurb goes here\")
+@end smallexample
+"
+ `(;(setchapternewpage (% (all "odd"))) makes manuals too long
+ (titlepage
+ (title ,name)
+ (subtitle "version " ,version ", updated " ,updated)
+ ,@(map (lambda (pair)
+ `(author ,(car pair)
+ " (" (email ,(cdr pair)) ")"))
+ authors)
+ (page)
+ (vskip (% (all "0pt plus 1filll")))
+ (insertcopying))))
+
+(define (package-stexi-generic-menu name entries)
+ "Create a menu from a generic alist of entries, the car of which
+should be the node name, and the cdr the description. As an exception,
+an entry of @code{#f} will produce a separator."
+ (define (make-entry node description)
+ `("* " ,node "__"
+ ,(make-string (max (- 21 (string-length node)) 2) #\space)
+ ,@description "\n"))
+ `((ifnottex
+ (node (% (name "Top")))
+ (top (% (title ,name)))
+ (insertcopying)
+ (menu
+ ,@(apply
+ append
+ (map
+ (lambda (entry)
+ (if entry
+ (make-entry (car entry) (cdr entry))
+ '("\n")))
+ entries))))
+ (iftex
+ (shortcontents))))
+
+
+(define (package-stexi-standard-menu name modules module-descriptions
+ extra-entries)
+ "Create a standard top node and menu, suitable for processing
+by makeinfo."
+ (package-stexi-generic-menu
+ name
+ (let ((module-entries (map cons
+ (map module-name->node-name modules)
+ module-descriptions))
+ (separate-sections (lambda (x) (if (null? x) x (cons #f x)))))
+ `(,@module-entries
+ ,@(separate-sections extra-entries)))))
+
+(define (package-stexi-extended-menu name module-pairs script-pairs
+ extra-entries)
+ "Create an \"extended\" menu, like the standard menu but with a
+section for scripts."
+ (package-stexi-generic-menu
+ name
+ (let ((module-entries (map cons
+ (map module-name->node-name
+ (map car module-pairs))
+ (map cdr module-pairs)))
+ (script-entries (map cons
+ (map basename (map car script-pairs))
+ (map cdr script-pairs)))
+ (separate-sections (lambda (x) (if (null? x) x (cons #f x)))))
+ `(,@module-entries
+ ,@(separate-sections script-entries)
+ ,@(separate-sections extra-entries)))))
+
+(define (package-stexi-standard-prologue name filename category
+ description copying titlepage
+ menu)
+ "Create a standard prologue, suitable for later serialization
+to texinfo and .info creation with makeinfo.
+
+Returns a list of stexinfo forms suitable for passing to
+@code{package-stexi-documentation} as the prologue. @xref{texinfo
+reflection package-stexi-documentation}, @ref{texinfo reflection
+package-stexi-standard-titlepage,package-stexi-standard-titlepage},
+@ref{texinfo reflection
+package-stexi-standard-copying,package-stexi-standard-copying},
+and @ref{texinfo reflection
+package-stexi-standard-menu,package-stexi-standard-menu}."
+ `(,copying
+ (dircategory (% (category ,category)))
+ (direntry
+ "* " ,name "_ (" ,filename "). " ,description ".")
+ ,@titlepage
+ ,@menu))
+
+(define (stexi->chapter stexi)
+ (pre-post-order
+ stexi
+ `((texinfo . ,(lambda (tag attrs node . body)
+ `(,node
+ (chapter ,@(assq-ref (cdr attrs) 'title))
+ ,@body)))
+ (*text* . ,(lambda (tag text) text))
+ (*default* . ,(lambda args args)))))
+
+(define* (package-stexi-documentation modules name filename
+ prologue epilogue
+ #\key
+ (module-stexi-documentation-args
+ '())
+ (scripts '()))
+ "Create stexi documentation for a @dfn{package}, where a
+package is a set of modules that is released together.
+
+@var{modules} is expected to be a list of module names, where a
+module name is a list of symbols. The stexi that is returned will
+be titled @var{name} and a texinfo filename of @var{filename}.
+
+@var{prologue} and @var{epilogue} are lists of stexi forms that
+will be spliced into the output document before and after the
+generated modules documentation, respectively.
+@xref{texinfo reflection package-stexi-standard-prologue}, to
+create a conventional GNU texinfo prologue.
+
+@var{module-stexi-documentation-args} is an optional argument that, if
+given, will be added to the argument list when
+@code{module-texi-documentation} is called. For example, it might be
+useful to define a @code{#:docs-resolver} argument."
+ (define (verify-modules-list l)
+ (define (all pred l)
+ (and (pred (car l))
+ (or (null? (cdr l)) (all pred (cdr l)))))
+ (false-if-exception
+ (all (lambda (x) (all symbol? x)) modules)))
+ (if (not (verify-modules-list modules))
+ (error "expected modules to be a list of a list of symbols"
+ modules))
+
+ `(texinfo
+ (% (title ,name)
+ (filename ,filename))
+ ,@prologue
+ ,@(append-map (lambda (mod)
+ (stexi->chapter
+ (apply module-stexi-documentation
+ mod module-stexi-documentation-args)))
+ modules)
+ ,@(append-map (lambda (script)
+ (stexi->chapter
+ (script-stexi-documentation script)))
+ scripts)
+ ,@epilogue))
+
+(define* (package-stexi-documentation-for-include modules module-descriptions
+ #\key
+ (module-stexi-documentation-args '()))
+ "Create stexi documentation for a @dfn{package}, where a
+package is a set of modules that is released together.
+
+@var{modules} is expected to be a list of module names, where a
+module name is a list of symbols. Returns an stexinfo fragment.
+
+Unlike @code{package-stexi-documentation}, this function simply produces
+a menu and the module documentations instead of producing a full texinfo
+document. This can be useful if you write part of your manual by hand,
+and just use @code{@@include} to pull in the automatically generated
+parts.
+
+@var{module-stexi-documentation-args} is an optional argument that, if
+given, will be added to the argument list when
+@code{module-texi-documentation} is called. For example, it might be
+useful to define a @code{#:docs-resolver} argument."
+ (define (make-entry node description)
+ `("* " ,node "__"
+ ,(make-string (max (- 21 (string-length node)) 2) #\space)
+ ,@description "\n"))
+ `(*fragment*
+ (menu
+ ,@(append-map (lambda (modname desc)
+ (make-entry (module-name->node-name modname)
+ desc))
+ modules
+ module-descriptions))
+ ,@(append-map (lambda (modname)
+ (stexi->chapter
+ (apply module-stexi-documentation
+ modname
+ module-stexi-documentation-args)))
+ modules)))
+
+;;; arch-tag_ bbe2bc03-e16d-4a9e-87b9-55225dc9836c
+;;;; (texinfo serialize) -- rendering stexinfo as texinfo
+;;;;
+;;;; Copyright (C) 2009, 2012, 2013 Free Software Foundation, Inc.
+;;;; Copyright (C) 2003,2004,2009 Andy Wingo <wingo at pobox dot com>
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary_
+;;
+;;Serialization of @code{stexi} to plain texinfo.
+;;
+;;; Code_
+
+(define-module (texinfo serialize)
+ #\use-module (texinfo)
+ #\use-module (texinfo string-utils)
+ #\use-module (sxml transform)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-13)
+ #\export (stexi->texi))
+
+(define (list-intersperse src-l elem)
+ (if (null? src-l) src-l
+ (let loop ((l (cdr src-l)) (dest (cons (car src-l) '())))
+ (if (null? l) (reverse dest)
+ (loop (cdr l) (cons (car l) (cons elem dest)))))))
+
+;; converts improper lists to proper lists.
+(define (filter* pred l)
+ (let lp ((in l) (out '()))
+ (cond ((null? in)
+ (reverse! out))
+ ((pair? in)
+ (lp (cdr in) (if (pred (car in)) (cons (car in) out) out)))
+ (else
+ (lp '() (if (pred in) (cons in out) out))))))
+
+;; (list* 'a '(b c) 'd '(e f g)) => '(a b c d e f g)
+(define (list* . args)
+ (let* ((args (reverse args))
+ (tail (car args)))
+ (let lp ((in (cdr args)) (out tail))
+ (cond ((null? in) out)
+ ((pair? (car in)) (lp (cdr in) (append (car in) out)))
+ ((null? (car in)) (lp (cdr in) out))
+ (else (lp (cdr in) (cons (car in) out)))))))
+
+;; Why? Well, because syntax-case defines `include', and carps about its
+;; wrong usage below...
+(eval-when (expand load eval)
+ (define (include exp lp command type formals args accum)
+ (list* "\n"
+ (list-intersperse
+ args
+ " ")
+ " " command "@" accum)))
+
+(define (empty-command exp lp command type formals args accum)
+ (list* " " command "@" accum))
+
+(define (inline-text exp lp command type formals args accum)
+ (if (not (string=? command "*braces*")) ;; fixme _(
+ (list* "}"
+ (append-map (lambda (x) (lp x '())) (reverse (cdr exp)))
+ "{" command "@" accum)
+ (list* "@}"
+ (append-map (lambda (x) (lp x '())) (reverse (cdr exp)))
+ "@{" accum)))
+
+(define (inline-args exp lp command type formals args accum)
+ (list* "}"
+ (if (not args) ""
+ (list-intersperse
+ (map
+ (lambda (x)
+ (cond ((not x) "")
+ ((pair? x)
+ (if (pair? (cdr x))
+ (warn "Strange inline-args!" args))
+ (car x))
+ (else (error "Invalid inline-args" args))))
+ (drop-while not
+ (map (lambda (x) (assq-ref args x))
+ (reverse formals))))
+ ","))
+ "{" command "@" accum))
+
+(define (inline-text-args exp lp command type formals args accum)
+ (list* "}"
+ (if (not args) ""
+ (apply
+ append
+ (list-intersperse
+ (map
+ (lambda (x) (append-map (lambda (x) (lp x '())) (reverse x)))
+ (drop-while not
+ (map (lambda (x) (assq-ref args x))
+ (reverse formals))))
+ '(","))))
+ "{" command "@" accum))
+
+(define (serialize-text-args lp formals args)
+ (apply
+ append
+ (list-intersperse
+ (map (lambda (arg) (append-map (lambda (x) (lp x '())) arg))
+ (map
+ reverse
+ (drop-while
+ not (map (lambda (x) (assq-ref args x))
+ (reverse formals)))))
+ '(" "))))
+
+(define (eol-text-args exp lp command type formals args accum)
+ (list* "\n"
+ (serialize-text-args lp formals args)
+ " " command "@" accum))
+
+(define (eol-text exp lp command type formals args accum)
+ (list* "\n"
+ (append-map (lambda (x) (lp x '()))
+ (reverse (if args (cddr exp) (cdr exp))))
+ " " command "@" accum))
+
+(define (eol-args exp lp command type formals args accum)
+ (list* "\n"
+ (list-intersperse
+ (apply append
+ (drop-while not
+ (map (lambda (x) (assq-ref args x))
+ (reverse formals))))
+ ", ")
+ " " command "@" accum))
+
+(define (environ exp lp command type formals args accum)
+ (case (car exp)
+ ((texinfo)
+ (list* "@bye\n"
+ (append-map (lambda (x) (lp x '())) (reverse (cddr exp)))
+ "\n@c %**end of header\n\n"
+ (reverse (assq-ref args 'title)) "@settitle "
+ (or (and=> (assq-ref args 'filename)
+ (lambda (filename)
+ (cons "\n" (reverse (cons "@setfilename " filename)))))
+ "")
+ "\\input texinfo @c -*-texinfo-*-\n@c %**start of header\n"
+ accum))
+ (else
+ (list* "\n\n" command "@end "
+ (let ((body (append-map (lambda (x) (lp x '()))
+ (reverse (if args (cddr exp) (cdr exp))))))
+ (if (or (null? body)
+ (eqv? (string-ref (car body)
+ (1- (string-length (car body))))
+ #\newline))
+ body
+ (cons "\n" body)))
+ "\n"
+ (serialize-text-args lp formals args)
+ " " command "@" accum))))
+
+(define (table-environ exp lp command type formals args accum)
+ (list* "\n\n" command "@end "
+ (append-map (lambda (x) (lp x '()))
+ (reverse (if args (cddr exp) (cdr exp))))
+ "\n"
+ (let* ((arg (if args (cadar args) ""))) ;; zero or one args
+ (if (pair? arg)
+ (list (symbol->string (car arg)) "@")
+ arg))
+ " " command "@" accum))
+
+(define (wrap strings)
+ (fill-string (string-concatenate strings)
+ #\line-width 72
+ #\break-long-words? #f))
+
+(define (paragraph exp lp command type formals args accum)
+ (list* "\n\n"
+ (wrap
+ (reverse
+ (append-map (lambda (x) (lp x '())) (reverse (cdr exp)))))
+ accum))
+
+(define (item exp lp command type formals args accum)
+ (list* (append-map (lambda (x) (lp x '())) (reverse (cdr exp)))
+ "@item\n"
+ accum))
+
+(define (entry exp lp command type formals args accum)
+ (list* (append-map (lambda (x) (lp x '())) (reverse (cddr exp)))
+ "\n"
+ (append-map (lambda (x) (lp x '())) (reverse (cdar args)))
+ "@item "
+ accum))
+
+(define (fragment exp lp command type formals args accum)
+ (list* "\n@c %end of fragment\n"
+ (append-map (lambda (x) (lp x '())) (reverse (cdr exp)))
+ "\n@c %start of fragment\n\n"
+ accum))
+
+(define serializers
+ `((EMPTY-COMMAND . ,empty-command)
+ (INLINE-TEXT . ,inline-text)
+ (INLINE-ARGS . ,inline-args)
+ (INLINE-TEXT-ARGS . ,inline-text-args)
+ (EOL-TEXT . ,eol-text)
+ (EOL-TEXT-ARGS . ,eol-text-args)
+ (INDEX . ,eol-text-args)
+ (EOL-ARGS . ,eol-args)
+ (ENVIRON . ,environ)
+ (TABLE-ENVIRON . ,table-environ)
+ (ENTRY . ,entry)
+ (ITEM . ,item)
+ (PARAGRAPH . ,paragraph)
+ (FRAGMENT . ,fragment)
+ (#f . ,include))) ; support writing include statements
+
+(define (serialize exp lp command type formals args accum)
+ ((or (assq-ref serializers type)
+ (error "Unknown command type" exp type))
+ exp lp command type formals args accum))
+
+(define escaped-chars '(#\} #\{ #\@))
+(define (escape str)
+ "Escapes any illegal texinfo characters (currently @{, @}, and @@)."
+ (let loop ((in (string->list str)) (out '()))
+ (if (null? in)
+ (apply string (reverse out))
+ (if (memq (car in) escaped-chars)
+ (loop (cdr in) (cons* (car in) #\@ out))
+ (loop (cdr in) (cons (car in) out))))))
+
+(define (stexi->texi tree)
+ "Serialize the stexi @var{tree} into plain texinfo."
+ (string-concatenate-reverse
+ (let lp ((in tree) (out '()))
+ (cond
+ ((or (not in) (null? in)) out)
+ ((string? in) (cons (escape in) out))
+ ((pair? in)
+ (let ((command-spec (assq (car in) texi-command-specs)))
+ (if (not command-spec)
+ (begin
+ (warn "Unknown stexi command, not rendering" in)
+ out)
+ (serialize in
+ lp
+ (symbol->string (car in))
+ (cadr command-spec)
+ (filter* symbol? (cddr command-spec))
+ (cond
+ ((and (pair? (cdr in)) (pair? (cadr in))
+ (eq? (caadr in) '%))
+ (cdadr in))
+ ((not (cadr command-spec))
+ ;; include
+ (cdr in))
+ (else
+ #f))
+ out))))
+ (else
+ (error "Invalid stexi" in))))))
+
+;;; arch-tag_ d3fa16ea-0bf7-4ec5-ab9f-3f08490f77f5
+;;;; (texinfo string-utils) -- text filling and wrapping
+;;;;
+;;;; Copyright (C) 2009, 2013 Free Software Foundation, Inc.
+;;;; Copyright (C) 2003 Richard Todd
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary_
+;; Module @samp{(texinfo string-utils)} provides various string-related
+;; functions useful to Guile's texinfo support.
+;;; Code_
+
+(define-module (texinfo string-utils)
+ #\use-module (srfi srfi-13)
+ #\use-module (srfi srfi-14)
+ #\export (escape-special-chars
+ transform-string
+ expand-tabs
+ center-string
+ left-justify-string
+ right-justify-string
+ collapse-repeated-chars
+ make-text-wrapper
+ fill-string
+ string->wrapped-lines))
+
+(define* (transform-string str match? replace #\optional (start #f) (end #f))
+"Uses @var{match?} against each character in @var{str}, and performs a
+replacement on each character for which matches are found.
+
+@var{match?} may either be a function, a character, a string, or
+@code{#t}. If @var{match?} is a function, then it takes a single
+character as input, and should return @samp{#t} for matches.
+@var{match?} is a character, it is compared to each string character
+using @code{char=?}. If @var{match?} is a string, then any character
+in that string will be considered a match. @code{#t} will cause
+every character to be a match.
+
+If @var{replace} is a function, it is called with the matched
+character as an argument, and the returned value is sent to the output
+string via @samp{display}. If @var{replace} is anything else, it is
+sent through the output string via @samp{display}.
+
+Note that te replacement for the matched characters does not need to
+be a single character. That is what differentiates this function from
+@samp{string-map}, and what makes it useful for applications such as
+converting @samp{#\\&} to @samp{\"&amp;\"} in web page text. Some other
+functions in this module are just wrappers around common uses of
+@samp{transform-string}. Transformations not possible with this
+function should probably be done with regular expressions.
+
+If @var{start} and @var{end} are given, they control which portion
+of the string undergoes transformation. The entire input string
+is still output, though. So, if @var{start} is @samp{5}, then the
+first five characters of @var{str} will still appear in the returned
+string.
+
+@lisp
+; these two are equivalent...
+ (transform-string str #\\space #\\-) ; change all spaces to -'s
+ (transform-string str (lambda (c) (char=? #\\space c)) #\\-)
+@end lisp"
+ ;; I had implemented this with string-fold, but it was
+ ;; slower...
+ (let* ((os (open-output-string))
+ (matcher (cond ((char? match?)
+ (lambda (c) (char=? match? c)))
+ ((procedure? match?)
+ match?)
+ ((string? match?)
+ (lambda (c) (string-index match? c)))
+ ((boolean? match?)
+ (lambda (c) match?))
+ (else (throw 'bad-type "expected #t, char, string, or procedure"))))
+ (replacer (if (procedure? replace)
+ (lambda (c) (display (replace c) os))
+ (lambda (c) (display replace os)))))
+
+ ;; put the first part in, un-transformed if they asked for it...
+ (if (and start (<= start (string-length str)))
+ (display (substring str 0 start) os))
+
+ ;; process the portion they want processed....
+ (string-for-each
+ (lambda (c)
+ (if (matcher c)
+ ;; we have a match! replace the char as directed...
+ (replacer c)
+
+ ;; not a match, just insert the character itself...
+ (write-char c os)))
+ str
+ (or start 0)
+ (or end (string-length str)))
+
+ ;; if there was any at the end, tack it on...
+ (if (and end (< end (string-length str)))
+ (display (substring str end) os))
+
+ (get-output-string os)))
+
+(define* (expand-tabs str #\optional (tab-size 8))
+"Returns a copy of @var{str} with all tabs expanded to spaces. @var{tab-size} defaults to 8.
+
+Assuming tab size of 8, this is equivalent to_ @lisp
+ (transform-string str #\\tab \" \")
+@end lisp"
+ (transform-string str
+ #\tab
+ (make-string tab-size #\space)))
+
+(define (escape-special-chars str special-chars escape-char)
+"Returns a copy of @var{str} with all given special characters preceded
+by the given @var{escape-char}.
+
+@var{special-chars} can either be a single character, or a string consisting
+of all the special characters.
+
+@lisp
+;; make a string regexp-safe...
+ (escape-special-chars \"***(Example String)***\"
+ \"[]()/*.\"
+ #\\\\)
+=> \"\\\\*\\\\*\\\\*\\\\(Example String\\\\)\\\\*\\\\*\\\\*\"
+
+;; also can escape a singe char...
+ (escape-special-chars \"richardt@@vzavenue.net\"
+ #\\@@
+ #\\@@)
+=> \"richardt@@@@vzavenue.net\"
+@end lisp"
+ (transform-string str
+ (if (char? special-chars)
+ ;; if they gave us a char, use char=?
+ (lambda (c) (char=? c special-chars))
+
+ ;; if they gave us a string, see if our character is in it
+ (lambda (c) (string-index special-chars c)))
+
+ ;; replace matches with the character preceded by the escape character
+ (lambda (c) (string escape-char c))))
+
+(define* (center-string str #\optional (width 80) (chr #\space) (rchr #f))
+"Returns a copy of @var{str} centered in a field of @var{width}
+characters. Any needed padding is done by character @var{chr}, which
+defaults to @samp{#\\space}. If @var{rchr} is provided, then the
+padding to the right will use it instead. See the examples below.
+left and @var{rchr} on the right. The default @var{width} is 80. The
+default @var{chr} and @var{rchr} is @samp{#\\space}. The string is
+never truncated.
+@lisp
+ (center-string \"Richard Todd\" 24)
+=> \" Richard Todd \"
+
+ (center-string \" Richard Todd \" 24 #\\=)
+=> \"===== Richard Todd =====\"
+
+ (center-string \" Richard Todd \" 24 #\\< #\\>)
+=> \"<<<<< Richard Todd >>>>>\"
+@end lisp"
+ (let* ((len (string-length str))
+ (lpad (make-string (max (quotient (- width len) 2) 0) chr))
+ ;; right-char == char unless it has been provided by the user
+ (right-chr (or rchr chr))
+ (rpad (if (char=? right-chr chr)
+ lpad
+ (make-string (max (quotient (- width len) 2) 0) right-chr))))
+ (if (>= len width)
+ str
+ (string-append lpad str rpad (if (odd? (- width len)) (string right-chr) "")))))
+
+(define* (left-justify-string str #\optional (width 80) (chr #\space))
+"@code{left-justify-string str [width chr]}.
+Returns a copy of @var{str} padded with @var{chr} such that it is left
+justified in a field of @var{width} characters. The default
+@var{width} is 80. Unlike @samp{string-pad} from srfi-13, the string
+is never truncated."
+ (let* ((len (string-length str))
+ (pad (make-string (max (- width len) 0) chr)))
+ (if (>= len width)
+ str
+ (string-append str pad))))
+
+(define* (right-justify-string str #\optional (width 80) (chr #\space))
+"Returns a copy of @var{str} padded with @var{chr} such that it is
+right justified in a field of @var{width} characters. The default
+@var{width} is 80. The default @var{chr} is @samp{#\\space}. Unlike
+@samp{string-pad} from srfi-13, the string is never truncated."
+ (let* ((len (string-length str))
+ (pad (make-string (max (- width len) 0) chr)))
+ (if (>= len width)
+ str
+ (string-append pad str))))
+
+ (define* (collapse-repeated-chars str #\optional (chr #\space) (num 1))
+"Returns a copy of @var{str} with all repeated instances of
+@var{chr} collapsed down to at most @var{num} instances.
+The default value for @var{chr} is @samp{#\\space}, and
+the default value for @var{num} is 1.
+
+@lisp
+ (collapse-repeated-chars \"H e l l o\")
+=> \"H e l l o\"
+ (collapse-repeated-chars \"H--e--l--l--o\" #\\-)
+=> \"H-e-l-l-o\"
+ (collapse-repeated-chars \"H-e--l---l----o\" #\\- 2)
+=> \"H-e--l--l--o\"
+@end lisp"
+ ;; define repeat-locator as a stateful match? function which remembers
+ ;; the last character it had seen.
+ (let ((repeat-locator
+ ;; initialize prev-chr to something other than what we're seeking...
+ (let ((prev-chr (if (char=? chr #\space) #\A #\space))
+ (match-count 0))
+ (lambda (c)
+ (if (and (char=? c prev-chr)
+ (char=? prev-chr chr))
+ ;; found enough duplicates if the match-count is high enough
+ (begin
+ (set! match-count (+ 1 match-count))
+ (>= match-count num))
+
+ ;; did not find a duplicate
+ (begin (set! match-count 0)
+ (set! prev-chr c)
+ #f))))))
+
+ ;; transform the string with our stateful matcher...
+ ;; deleting matches...
+ (transform-string str repeat-locator "")))
+
+;; split a text string into segments that have the form...
+;; <ws non-ws> <ws non-ws> etc..
+(define (split-by-single-words str)
+ (let ((non-wschars (char-set-complement char-set_whitespace)))
+ (let loop ((ans '())
+ (index 0))
+ (let ((next-non-ws (string-index str non-wschars index)))
+ (if next-non-ws
+ ;; found non-ws...look for ws following...
+ (let ((next-ws (string-index str char-set_whitespace next-non-ws)))
+ (if next-ws
+ ;; found the ws following...
+ (loop (cons (substring str index next-ws) ans)
+ next-ws)
+ ;; did not find ws...must be the end...
+ (reverse (cons (substring str index) ans))))
+ ;; did not find non-ws... only ws at end of the string...
+ (reverse ans))))))
+
+(define (end-of-sentence? str)
+ "Return #t when STR likely denotes the end of sentence."
+ (let ((len (string-length str)))
+ (and (> len 1)
+ (eqv? #\. (string-ref str (- len 1)))
+ (not (eqv? #\. (string-ref str (- len 2)))))))
+
+(define* (make-text-wrapper #\key
+ (line-width 80)
+ (expand-tabs? #t)
+ (tab-width 8)
+ (collapse-whitespace? #t)
+ (subsequent-indent "")
+ (initial-indent "")
+ (break-long-words? #t))
+ "Returns a procedure that will split a string into lines according to the
+given parameters.
+
+@table @code
+@item #:line-width
+This is the target length used when deciding where to wrap lines.
+Default is 80.
+
+@item #:expand-tabs?
+Boolean describing whether tabs in the input should be expanded. Default
+is #t.
+
+@item #:tab-width
+If tabs are expanded, this will be the number of spaces to which they
+expand. Default is 8.
+
+@item #:collapse-whitespace?
+Boolean describing whether the whitespace inside the existing text
+should be removed or not. Default is #t.
+
+If text is already well-formatted, and is just being wrapped to fit in a
+different width, then set this to @samp{#f}. This way, many common text
+conventions (such as two spaces between sentences) can be preserved if
+in the original text. If the input text spacing cannot be trusted, then
+leave this setting at the default, and all repeated whitespace will be
+collapsed down to a single space.
+
+@item #:initial-indent
+Defines a string that will be put in front of the first line of wrapped
+text. Default is the empty string, ``''.
+
+@item #:subsequent-indent
+Defines a string that will be put in front of all lines of wrapped
+text, except the first one. Default is the empty string, ``''.
+
+@item #:break-long-words?
+If a single word is too big to fit on a line, this setting tells the
+wrapper what to do. Defaults to #t, which will break up long words.
+When set to #f, the line will be allowed, even though it is longer
+than the defined @code{#:line-width}.
+@end table
+
+The return value is a procedure of one argument, the input string, which
+returns a list of strings, where each element of the list is one line."
+ (lambda (str)
+ ;; replace newlines with spaces
+ (set! str (transform-string str (lambda (c) (char=? c #\nl)) #\space))
+
+ ;; expand tabs if they wanted us to...
+ (if expand-tabs?
+ (set! str (expand-tabs str tab-width)))
+
+ ;; collapse whitespace if they wanted us to...
+ (if collapse-whitespace?
+ (set! str (collapse-repeated-chars str)))
+
+ ;; drop any whitespace from the front...
+ (set! str (string-trim str))
+
+ ;; now start breaking the text into lines...
+ (let loop ((ans '())
+ (words (split-by-single-words str))
+ (line initial-indent)
+ (count 0))
+ (if (null? words)
+ ;; out of words? ...done!
+ (reverse (if (> count 0)
+ (cons line ans)
+ ans))
+
+ ;; not out of words...keep going...
+ (let ((length-left (- line-width
+ (string-length line)))
+ (next-word (if (= count 0)
+ (string-trim (car words))
+ (car words))))
+ (cond
+ ;; does the next entry fit?
+ ((<= (string-length next-word)
+ length-left)
+ (loop ans
+ (cdr words)
+ (if (and collapse-whitespace?
+ (end-of-sentence? line))
+ ;; Add an extra space after the period.
+ (string-append line " " next-word)
+ (string-append line next-word))
+ (+ count 1)))
+
+ ;; ok, it didn't fit...is there already at least one word on the line?
+ ((> count 0)
+ ;; try to use it for the next line, then...
+ (loop (cons line ans)
+ words
+ subsequent-indent
+ 0))
+
+ ;; ok, it didn't fit...and it's the first word.
+ ;; were we told to break up long words?
+ (break-long-words?
+ ;; break the like at the limit, since the user wants us to...
+ (loop (cons (string-append line (substring next-word 0 length-left))
+ ans)
+ (cons (substring next-word length-left)
+ (cdr words))
+ subsequent-indent
+ 0))
+
+ ;; well, then is it the first word and we *shouldn't* break long words, then...
+ (else
+ (loop (cons (string-append line next-word)
+ ans)
+ (cdr words)
+ subsequent-indent
+ 0))))))))
+
+(define (string->wrapped-lines str . kwargs)
+ "@code{string->wrapped-lines str keywds ...}. Wraps the text given in
+string @var{str} according to the parameters provided in @var{keywds},
+or the default setting if they are not given. Returns a list of strings
+representing the formatted lines. Valid keyword arguments are discussed
+in @code{make-text-wrapper}."
+ ((apply make-text-wrapper kwargs) str))
+
+(define (fill-string str . kwargs)
+ "Wraps the text given in string @var{str} according to the parameters
+provided in @var{kwargs}, or the default setting if they are not
+given. Returns a single string with the wrapped text. Valid keyword
+arguments are discussed in @code{make-text-wrapper}."
+ (string-join (apply string->wrapped-lines str kwargs)
+ "\n"
+ 'infix))
+;;; Web client
+
+;; Copyright (C) 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Commentary_
+;;;
+;;; (web client) is a simple HTTP URL fetcher for Guile.
+;;;
+;;; In its current incarnation, (web client) is synchronous. If you
+;;; want to fetch a number of URLs at once, probably the best thing to
+;;; do is to write an event-driven URL fetcher, similar in structure to
+;;; the web server.
+;;;
+;;; Another option, good but not as performant, would be to use threads,
+;;; possibly via a thread pool.
+;;;
+;;; Code_
+
+(define-module (web client)
+ #\use-module (rnrs bytevectors)
+ #\use-module (ice-9 binary-ports)
+ #\use-module (ice-9 iconv)
+ #\use-module (ice-9 rdelim)
+ #\use-module (web request)
+ #\use-module (web response)
+ #\use-module (web uri)
+ #\use-module (web http)
+ #\use-module (srfi srfi-1)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-9 gnu)
+ #\export (current-http-proxy
+ open-socket-for-uri
+ http-get
+ http-get*
+ http-head
+ http-post
+ http-put
+ http-delete
+ http-trace
+ http-options))
+
+(define current-http-proxy
+ (make-parameter (let ((proxy (getenv "http_proxy")))
+ (and (not (equal? proxy ""))
+ proxy))))
+
+(define (ensure-uri uri-or-string)
+ (cond
+ ((string? uri-or-string) (string->uri uri-or-string))
+ ((uri? uri-or-string) uri-or-string)
+ (else (error "Invalid URI" uri-or-string))))
+
+(define (open-socket-for-uri uri-or-string)
+ "Return an open input/output port for a connection to URI."
+ (define http-proxy (current-http-proxy))
+ (define uri (ensure-uri (or http-proxy uri-or-string)))
+ (define addresses
+ (let ((port (uri-port uri)))
+ (delete-duplicates
+ (getaddrinfo (uri-host uri)
+ (cond (port => number->string)
+ (else (symbol->string (uri-scheme uri))))
+ (if port
+ AI_NUMERICSERV
+ 0))
+ (lambda (ai1 ai2)
+ (equal? (addrinfo_addr ai1) (addrinfo_addr ai2))))))
+
+ (let loop ((addresses addresses))
+ (let* ((ai (car addresses))
+ (s (with-fluids ((%default-port-encoding #f))
+ ;; Restrict ourselves to TCP.
+ (socket (addrinfo_fam ai) SOCK_STREAM IPPROTO_IP))))
+ (catch 'system-error
+ (lambda ()
+ (connect s (addrinfo_addr ai))
+
+ ;; Buffer input and output on this port.
+ (setvbuf s _IOFBF)
+ ;; If we're using a proxy, make a note of that.
+ (when http-proxy (set-http-proxy-port?! s #t))
+ s)
+ (lambda args
+ ;; Connection failed, so try one of the other addresses.
+ (close s)
+ (if (null? (cdr addresses))
+ (apply throw args)
+ (loop (cdr addresses))))))))
+
+(define (extend-request r k v . additional)
+ (let ((r (set-field r (request-headers)
+ (assoc-set! (copy-tree (request-headers r))
+ k v))))
+ (if (null? additional)
+ r
+ (apply extend-request r additional))))
+
+;; -> request body
+(define (sanitize-request request body)
+ "\"Sanitize\" the given request and body, ensuring that they are
+complete and coherent. This method is most useful for methods that send
+data to the server, like POST, but can be used for any method. Return
+two values_ a request and a bytevector, possibly the same ones that were
+passed as arguments.
+
+If BODY is a string, encodes the string to a bytevector, in an encoding
+appropriate for REQUEST. Adds a ‘content-length’ and ‘content-type’
+header, as necessary.
+
+If BODY is a procedure, it is called with a port as an argument, and the
+output collected as a bytevector. In the future we might try to instead
+use a compressing, chunk-encoded port, and call this procedure later.
+Authors are advised not to rely on the procedure being called at any
+particular time.
+
+Note that we rely on the request itself already having been validated,
+as is the case by default with a request returned by `build-request'."
+ (cond
+ ((not body)
+ (let ((length (request-content-length request)))
+ (if length
+ ;; FIXME make this stricter_ content-length header should be
+ ;; prohibited if there's no body, even if the content-length
+ ;; is 0.
+ (unless (zero? length)
+ (error "content-length, but no body"))
+ (when (assq 'transfer-encoding (request-headers request))
+ (error "transfer-encoding not allowed with no body")))
+ (values request #vu8())))
+ ((string? body)
+ (let* ((type (request-content-type request '(text/plain)))
+ (declared-charset (assq-ref (cdr type) 'charset))
+ (charset (or declared-charset "utf-8")))
+ (sanitize-request
+ (if declared-charset
+ request
+ (extend-request request 'content-type
+ `(,@type (charset . ,charset))))
+ (string->bytevector body charset))))
+ ((procedure? body)
+ (let* ((type (request-content-type request
+ '(text/plain)))
+ (declared-charset (assq-ref (cdr type) 'charset))
+ (charset (or declared-charset "utf-8")))
+ (sanitize-request
+ (if declared-charset
+ request
+ (extend-request request 'content-type
+ `(,@type (charset . ,charset))))
+ (call-with-encoded-output-string charset body))))
+ ((not (bytevector? body))
+ (error "unexpected body type"))
+ (else
+ (values (let ((rlen (request-content-length request))
+ (blen (bytevector-length body)))
+ (cond
+ (rlen (if (= rlen blen)
+ request
+ (error "bad content-length" rlen blen)))
+ (else (extend-request request 'content-length blen))))
+ body))))
+
+(define (decode-response-body response body)
+ ;; `body' is either #f or a bytevector.
+ (cond
+ ((not body) body)
+ ((bytevector? body)
+ (let ((rlen (response-content-length response))
+ (blen (bytevector-length body)))
+ (cond
+ ((and rlen (not (= rlen blen)))
+ (error "bad content-length" rlen blen))
+ ((response-content-type response)
+ => (lambda (type)
+ (cond
+ ((text-content-type? (car type))
+ ;; RFC 2616 3.7.1_ "When no explicit charset parameter is
+ ;; provided by the sender, media subtypes of the "text"
+ ;; type are defined to have a default charset value of
+ ;; "ISO-8859-1" when received via HTTP."
+ (bytevector->string body (or (assq-ref (cdr type) 'charset)
+ "iso-8859-1")))
+ (else body))))
+ (else body))))
+ (else
+ (error "unexpected body type" body))))
+
+;; We could expose this to user code if there is demand.
+(define* (request uri #\key
+ (body #f)
+ (port (open-socket-for-uri uri))
+ (method 'GET)
+ (version '(1 . 1))
+ (keep-alive? #f)
+ (headers '())
+ (decode-body? #t)
+ (streaming? #f)
+ (request
+ (build-request
+ (ensure-uri uri)
+ #\method method
+ #\version version
+ #\headers (if keep-alive?
+ headers
+ (cons '(connection close) headers))
+ #\port port)))
+ (call-with-values (lambda () (sanitize-request request body))
+ (lambda (request body)
+ (let ((request (write-request request port)))
+ (when body
+ (write-request-body request body))
+ (force-output (request-port request))
+ (let ((response (read-response port)))
+ (cond
+ ((eq? (request-method request) 'HEAD)
+ (unless keep-alive?
+ (close-port port))
+ (values response #f))
+ (streaming?
+ (values response
+ (response-body-port response
+ #\keep-alive? keep-alive?
+ #\decode? decode-body?)))
+ (else
+ (let ((body (read-response-body response)))
+ (unless keep-alive?
+ (close-port port))
+ (values response
+ (if decode-body?
+ (decode-response-body response body)
+ body))))))))))
+
+(define* (http-get uri #\key
+ (body #f)
+ (port (open-socket-for-uri uri))
+ (version '(1 . 1)) (keep-alive? #f)
+ ;; #\headers is the new name of #\extra-headers.
+ (extra-headers #f) (headers (or extra-headers '()))
+ (decode-body? #t) (streaming? #f))
+ "Connect to the server corresponding to URI and ask for the
+resource, using the ‘GET’ method. If you already have a port open,
+pass it as PORT. The port will be closed at the end of the
+request unless KEEP-ALIVE? is true. Any extra headers in the
+alist HEADERS will be added to the request.
+
+If BODY is not ‘#f’, a message body will also be sent with the HTTP
+request. If BODY is a string, it is encoded according to the
+content-type in HEADERS, defaulting to UTF-8. Otherwise BODY should be
+a bytevector, or ‘#f’ for no body. Although it's allowed to send a
+message body along with any request, usually only POST and PUT requests
+have bodies. See ‘http-put’ and ‘http-post’ documentation, for more.
+
+If DECODE-BODY? is true, as is the default, the body of the
+response will be decoded to string, if it is a textual content-type.
+Otherwise it will be returned as a bytevector.
+
+However, if STREAMING? is true, instead of eagerly reading the response
+body from the server, this function only reads off the headers. The
+response body will be returned as a port on which the data may be read.
+Unless KEEP-ALIVE? is true, the port will be closed after the full
+response body has been read.
+
+Returns two values_ the response read from the server, and the response
+body as a string, bytevector, #f value, or as a port (if STREAMING? is
+true)."
+ (when extra-headers
+ (issue-deprecation-warning
+ "The #\extra-headers argument to http-get has been renamed to #:headers. "
+ "Please update your code."))
+ (request uri #\method 'GET #\body body
+ #\port port #\version version #\keep-alive? keep-alive?
+ #\headers headers #\decode-body? decode-body?
+ #\streaming? streaming?))
+
+(define* (http-get* uri #\key
+ (body #f)
+ (port (open-socket-for-uri uri))
+ (version '(1 . 1)) (keep-alive? #f)
+ ;; #\headers is the new name of #\extra-headers.
+ (extra-headers #f) (headers (or extra-headers '()))
+ (decode-body? #t))
+ "Deprecated in favor of (http-get #:streaming? #t)."
+ (issue-deprecation-warning
+ "`http-get*' has been deprecated. "
+ "Instead, use `http-get' with the #:streaming? #t keyword argument.")
+ (http-get uri #\body body
+ #\port port #\version version #\keep-alive? keep-alive?
+ #\headers headers #\decode-body? #t #\streaming? #t))
+
+(define-syntax-rule (define-http-verb http-verb method doc)
+ (define* (http-verb uri #\key
+ (body #f)
+ (port (open-socket-for-uri uri))
+ (version '(1 . 1))
+ (keep-alive? #f)
+ (headers '())
+ (decode-body? #t)
+ (streaming? #f))
+ doc
+ (request uri
+ #\body body #\method method
+ #\port port #\version version #\keep-alive? keep-alive?
+ #\headers headers #\decode-body? decode-body?
+ #\streaming? streaming?)))
+
+(define-http-verb http-head
+ 'HEAD
+ "Fetch message headers for the given URI using the HTTP \"HEAD\"
+method.
+
+This function is similar to ‘http-get’, except it uses the \"HEAD\"
+method. See ‘http-get’ for full documentation on the various keyword
+arguments that are accepted by this function.
+
+Returns two values_ the resulting response, and ‘#f’. Responses to HEAD
+requests do not have a body. The second value is only returned so that
+other procedures can treat all of the http-foo verbs identically.")
+
+(define-http-verb http-post
+ 'POST
+ "Post data to the given URI using the HTTP \"POST\" method.
+
+This function is similar to ‘http-get’, except it uses the \"POST\"
+method. See ‘http-get’ for full documentation on the various keyword
+arguments that are accepted by this function.
+
+Returns two values_ the resulting response, and the response body.")
+
+(define-http-verb http-put
+ 'PUT
+ "Put data at the given URI using the HTTP \"PUT\" method.
+
+This function is similar to ‘http-get’, except it uses the \"PUT\"
+method. See ‘http-get’ for full documentation on the various keyword
+arguments that are accepted by this function.
+
+Returns two values_ the resulting response, and the response body.")
+
+(define-http-verb http-delete
+ 'DELETE
+ "Delete data at the given URI using the HTTP \"DELETE\" method.
+
+This function is similar to ‘http-get’, except it uses the \"DELETE\"
+method. See ‘http-get’ for full documentation on the various keyword
+arguments that are accepted by this function.
+
+Returns two values_ the resulting response, and the response body.")
+
+(define-http-verb http-trace
+ 'TRACE
+ "Send an HTTP \"TRACE\" request.
+
+This function is similar to ‘http-get’, except it uses the \"TRACE\"
+method. See ‘http-get’ for full documentation on the various keyword
+arguments that are accepted by this function.
+
+Returns two values_ the resulting response, and the response body.")
+
+(define-http-verb http-options
+ 'OPTIONS
+ "Query characteristics of an HTTP resource using the HTTP \"OPTIONS\"
+method.
+
+This function is similar to ‘http-get’, except it uses the \"OPTIONS\"
+method. See ‘http-get’ for full documentation on the various keyword
+arguments that are accepted by this function.
+
+Returns two values_ the resulting response, and the response body.")
+;;; HTTP messages
+
+;; Copyright (C) 2010-2016 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Commentary_
+;;;
+;;; This module has a number of routines to parse textual
+;;; representations of HTTP data into native Scheme data structures.
+;;;
+;;; It tries to follow RFCs fairly strictly---the road to perdition
+;;; being paved with compatibility hacks---though some allowances are
+;;; made for not-too-divergent texts (like a quality of .2 which should
+;;; be 0.2, etc).
+;;;
+;;; Code_
+
+(define-module (web http)
+ #\use-module ((srfi srfi-1) #\select (append-map! map!))
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-19)
+ #\use-module (ice-9 rdelim)
+ #\use-module (ice-9 match)
+ #\use-module (ice-9 q)
+ #\use-module (ice-9 binary-ports)
+ #\use-module (rnrs bytevectors)
+ #\use-module (web uri)
+ #\export (string->header
+ header->string
+
+ declare-header!
+ declare-opaque-header!
+ known-header?
+ header-parser
+ header-validator
+ header-writer
+
+ read-header
+ parse-header
+ valid-header?
+ write-header
+
+ read-headers
+ write-headers
+
+ parse-http-method
+ parse-http-version
+ parse-request-uri
+
+ read-request-line
+ write-request-line
+ read-response-line
+ write-response-line
+
+ make-chunked-input-port
+ make-chunked-output-port
+
+ http-proxy-port?
+ set-http-proxy-port?!))
+
+
+(define (string->header name)
+ "Parse NAME to a symbolic header name."
+ (string->symbol (string-downcase name)))
+
+(define-record-type <header-decl>
+ (make-header-decl name parser validator writer multiple?)
+ header-decl?
+ (name header-decl-name)
+ (parser header-decl-parser)
+ (validator header-decl-validator)
+ (writer header-decl-writer)
+ (multiple? header-decl-multiple?))
+
+;; sym -> header
+(define *declared-headers* (make-hash-table))
+
+(define (lookup-header-decl sym)
+ (hashq-ref *declared-headers* sym))
+
+(define* (declare-header! name
+ parser
+ validator
+ writer
+ #\key multiple?)
+ "Declare a parser, validator, and writer for a given header."
+ (if (and (string? name) parser validator writer)
+ (let ((decl (make-header-decl name parser validator writer multiple?)))
+ (hashq-set! *declared-headers* (string->header name) decl)
+ decl)
+ (error "bad header decl" name parser validator writer multiple?)))
+
+(define (header->string sym)
+ "Return the string form for the header named SYM."
+ (let ((decl (lookup-header-decl sym)))
+ (if decl
+ (header-decl-name decl)
+ (string-titlecase (symbol->string sym)))))
+
+(define (known-header? sym)
+ "Return ‘#t’ iff SYM is a known header, with associated
+parsers and serialization procedures."
+ (and (lookup-header-decl sym) #t))
+
+(define (header-parser sym)
+ "Return the value parser for headers named SYM. The result is a
+procedure that takes one argument, a string, and returns the parsed
+value. If the header isn't known to Guile, a default parser is returned
+that passes through the string unchanged."
+ (let ((decl (lookup-header-decl sym)))
+ (if decl
+ (header-decl-parser decl)
+ (lambda (x) x))))
+
+(define (header-validator sym)
+ "Return a predicate which returns ‘#t’ if the given value is valid
+for headers named SYM. The default validator for unknown headers
+is ‘string?’."
+ (let ((decl (lookup-header-decl sym)))
+ (if decl
+ (header-decl-validator decl)
+ string?)))
+
+(define (header-writer sym)
+ "Return a procedure that writes values for headers named SYM to a
+port. The resulting procedure takes two arguments_ a value and a port.
+The default writer is ‘display’."
+ (let ((decl (lookup-header-decl sym)))
+ (if decl
+ (header-decl-writer decl)
+ display)))
+
+(define (read-header-line port)
+ "Read an HTTP header line and return it without its final CRLF or LF.
+Raise a 'bad-header' exception if the line does not end in CRLF or LF,
+or if EOF is reached."
+ (match (%read-line port)
+ (((? string? line) . #\newline)
+ ;; '%read-line' does not consider #\return a delimiter; so if it's
+ ;; there, remove it. We are more tolerant than the RFC in that we
+ ;; tolerate LF-only endings.
+ (if (string-suffix? "\r" line)
+ (string-drop-right line 1)
+ line))
+ ((line . _) ;EOF or missing delimiter
+ (bad-header 'read-header-line line))))
+
+(define (read-continuation-line port val)
+ (if (or (eqv? (peek-char port) #\space)
+ (eqv? (peek-char port) #\tab))
+ (read-continuation-line port
+ (string-append val
+ (read-header-line port)))
+ val))
+
+(define *eof* (call-with-input-string "" read))
+
+(define (read-header port)
+ "Read one HTTP header from PORT. Return two values_ the header
+name and the parsed Scheme value. May raise an exception if the header
+was known but the value was invalid.
+
+Returns the end-of-file object for both values if the end of the message
+body was reached (i.e., a blank line)."
+ (let ((line (read-header-line port)))
+ (if (or (string-null? line)
+ (string=? line "\r"))
+ (values *eof* *eof*)
+ (let* ((delim (or (string-index line #\_)
+ (bad-header '%read line)))
+ (sym (string->header (substring line 0 delim))))
+ (values
+ sym
+ (parse-header
+ sym
+ (read-continuation-line
+ port
+ (string-trim-both line char-set_whitespace (1+ delim)))))))))
+
+(define (parse-header sym val)
+ "Parse VAL, a string, with the parser registered for the header
+named SYM. Returns the parsed value."
+ ((header-parser sym) val))
+
+(define (valid-header? sym val)
+ "Returns a true value iff VAL is a valid Scheme value for the
+header with name SYM."
+ (if (symbol? sym)
+ ((header-validator sym) val)
+ (error "header name not a symbol" sym)))
+
+(define (write-header sym val port)
+ "Write the given header name and value to PORT, using the writer
+from ‘header-writer’."
+ (display (header->string sym) port)
+ (display "_ " port)
+ ((header-writer sym) val port)
+ (display "\r\n" port))
+
+(define (read-headers port)
+ "Read the headers of an HTTP message from PORT, returning them
+as an ordered alist."
+ (let lp ((headers '()))
+ (call-with-values (lambda () (read-header port))
+ (lambda (k v)
+ (if (eof-object? k)
+ (reverse! headers)
+ (lp (acons k v headers)))))))
+
+(define (write-headers headers port)
+ "Write the given header alist to PORT. Doesn't write the final
+‘\\r\\n’, as the user might want to add another header."
+ (let lp ((headers headers))
+ (if (pair? headers)
+ (begin
+ (write-header (caar headers) (cdar headers) port)
+ (lp (cdr headers))))))
+
+
+
+
+;;;
+;;; Utilities
+;;;
+
+(define (bad-header sym val)
+ (throw 'bad-header sym val))
+(define (bad-header-component sym val)
+ (throw 'bad-header-component sym val))
+
+(define (bad-header-printer port key args default-printer)
+ (apply (case-lambda
+ ((sym val)
+ (format port "Bad ~a header_ ~a\n" (header->string sym) val))
+ (_ (default-printer)))
+ args))
+(define (bad-header-component-printer port key args default-printer)
+ (apply (case-lambda
+ ((sym val)
+ (format port "Bad ~a header component_ ~a\n" sym val))
+ (_ (default-printer)))
+ args))
+(set-exception-printer! 'bad-header bad-header-printer)
+(set-exception-printer! 'bad-header-component bad-header-component-printer)
+
+(define (parse-opaque-string str)
+ str)
+(define (validate-opaque-string val)
+ (string? val))
+(define (write-opaque-string val port)
+ (display val port))
+
+(define separators-without-slash
+ (string->char-set "[^][()<>@,;_\\\"?= \t]"))
+(define (validate-media-type str)
+ (let ((idx (string-index str #\/)))
+ (and idx (= idx (string-rindex str #\/))
+ (not (string-index str separators-without-slash)))))
+(define (parse-media-type str)
+ (if (validate-media-type str)
+ (string->symbol str)
+ (bad-header-component 'media-type str)))
+
+(define* (skip-whitespace str #\optional (start 0) (end (string-length str)))
+ (let lp ((i start))
+ (if (and (< i end) (char-whitespace? (string-ref str i)))
+ (lp (1+ i))
+ i)))
+
+(define* (trim-whitespace str #\optional (start 0) (end (string-length str)))
+ (let lp ((i end))
+ (if (and (< start i) (char-whitespace? (string-ref str (1- i))))
+ (lp (1- i))
+ i)))
+
+(define* (split-and-trim str #\optional (delim #\,)
+ (start 0) (end (string-length str)))
+ (let lp ((i start))
+ (if (< i end)
+ (let* ((idx (string-index str delim i end))
+ (tok (string-trim-both str char-set_whitespace i (or idx end))))
+ (cons tok (split-and-trim str delim (if idx (1+ idx) end) end)))
+ '())))
+
+(define (list-of-strings? val)
+ (list-of? val string?))
+
+(define (write-list-of-strings val port)
+ (write-list val port display ", "))
+
+(define (split-header-names str)
+ (map string->header (split-and-trim str)))
+
+(define (list-of-header-names? val)
+ (list-of? val symbol?))
+
+(define (write-header-list val port)
+ (write-list val port
+ (lambda (x port)
+ (display (header->string x) port))
+ ", "))
+
+(define (collect-escaped-string from start len escapes)
+ (let ((to (make-string len)))
+ (let lp ((start start) (i 0) (escapes escapes))
+ (if (null? escapes)
+ (begin
+ (substring-move! from start (+ start (- len i)) to i)
+ to)
+ (let* ((e (car escapes))
+ (next-start (+ start (- e i) 2)))
+ (substring-move! from start (- next-start 2) to i)
+ (string-set! to e (string-ref from (- next-start 1)))
+ (lp next-start (1+ e) (cdr escapes)))))))
+
+;; in incremental mode, returns two values_ the string, and the index at
+;; which the string ended
+(define* (parse-qstring str #\optional
+ (start 0) (end (trim-whitespace str start))
+ #\key incremental?)
+ (if (and (< start end) (eqv? (string-ref str start) #\"))
+ (let lp ((i (1+ start)) (qi 0) (escapes '()))
+ (if (< i end)
+ (case (string-ref str i)
+ ((#\\)
+ (lp (+ i 2) (1+ qi) (cons qi escapes)))
+ ((#\")
+ (let ((out (collect-escaped-string str (1+ start) qi escapes)))
+ (if incremental?
+ (values out (1+ i))
+ (if (= (1+ i) end)
+ out
+ (bad-header-component 'qstring str)))))
+ (else
+ (lp (1+ i) (1+ qi) escapes)))
+ (bad-header-component 'qstring str)))
+ (bad-header-component 'qstring str)))
+
+(define (write-list l port write-item delim)
+ (if (pair? l)
+ (let lp ((l l))
+ (write-item (car l) port)
+ (if (pair? (cdr l))
+ (begin
+ (display delim port)
+ (lp (cdr l)))))))
+
+(define (write-qstring str port)
+ (display #\" port)
+ (if (string-index str #\")
+ ;; optimize me
+ (write-list (string-split str #\") port display "\\\"")
+ (display str port))
+ (display #\" port))
+
+(define* (parse-quality str #\optional (start 0) (end (string-length str)))
+ (define (char->decimal c)
+ (let ((i (- (char->integer c) (char->integer #\0))))
+ (if (and (<= 0 i) (< i 10))
+ i
+ (bad-header-component 'quality str))))
+ (cond
+ ((not (< start end))
+ (bad-header-component 'quality str))
+ ((eqv? (string-ref str start) #\1)
+ (if (or (string= str "1" start end)
+ (string= str "1." start end)
+ (string= str "1.0" start end)
+ (string= str "1.00" start end)
+ (string= str "1.000" start end))
+ 1000
+ (bad-header-component 'quality str)))
+ ((eqv? (string-ref str start) #\0)
+ (if (or (string= str "0" start end)
+ (string= str "0." start end))
+ 0
+ (if (< 2 (- end start) 6)
+ (let lp ((place 1) (i (+ start 4)) (q 0))
+ (if (= i (1+ start))
+ (if (eqv? (string-ref str (1+ start)) #\.)
+ q
+ (bad-header-component 'quality str))
+ (lp (* 10 place) (1- i)
+ (if (< i end)
+ (+ q (* place (char->decimal (string-ref str i))))
+ q))))
+ (bad-header-component 'quality str))))
+ ;; Allow the nonstandard .2 instead of 0.2.
+ ((and (eqv? (string-ref str start) #\.)
+ (< 1 (- end start) 5))
+ (let lp ((place 1) (i (+ start 3)) (q 0))
+ (if (= i start)
+ q
+ (lp (* 10 place) (1- i)
+ (if (< i end)
+ (+ q (* place (char->decimal (string-ref str i))))
+ q)))))
+ (else
+ (bad-header-component 'quality str))))
+
+(define (valid-quality? q)
+ (and (non-negative-integer? q) (<= q 1000)))
+
+(define (write-quality q port)
+ (define (digit->char d)
+ (integer->char (+ (char->integer #\0) d)))
+ (display (digit->char (modulo (quotient q 1000) 10)) port)
+ (display #\. port)
+ (display (digit->char (modulo (quotient q 100) 10)) port)
+ (display (digit->char (modulo (quotient q 10) 10)) port)
+ (display (digit->char (modulo q 10)) port))
+
+(define (list-of? val pred)
+ (or (null? val)
+ (and (pair? val)
+ (pred (car val))
+ (list-of? (cdr val) pred))))
+
+(define* (parse-quality-list str)
+ (map (lambda (part)
+ (cond
+ ((string-rindex part #\;)
+ => (lambda (idx)
+ (let ((qpart (string-trim-both part char-set_whitespace (1+ idx))))
+ (if (string-prefix? "q=" qpart)
+ (cons (parse-quality qpart 2)
+ (string-trim-both part char-set_whitespace 0 idx))
+ (bad-header-component 'quality qpart)))))
+ (else
+ (cons 1000 (string-trim-both part char-set_whitespace)))))
+ (string-split str #\,)))
+
+(define (validate-quality-list l)
+ (list-of? l
+ (lambda (elt)
+ (and (pair? elt)
+ (valid-quality? (car elt))
+ (string? (cdr elt))))))
+
+(define (write-quality-list l port)
+ (write-list l port
+ (lambda (x port)
+ (let ((q (car x))
+ (str (cdr x)))
+ (display str port)
+ (if (< q 1000)
+ (begin
+ (display ";q=" port)
+ (write-quality q port)))))
+ ","))
+
+(define* (parse-non-negative-integer val #\optional (start 0)
+ (end (string-length val)))
+ (define (char->decimal c)
+ (let ((i (- (char->integer c) (char->integer #\0))))
+ (if (and (<= 0 i) (< i 10))
+ i
+ (bad-header-component 'non-negative-integer val))))
+ (if (not (< start end))
+ (bad-header-component 'non-negative-integer val)
+ (let lp ((i start) (out 0))
+ (if (< i end)
+ (lp (1+ i)
+ (+ (* out 10) (char->decimal (string-ref val i))))
+ out))))
+
+(define (non-negative-integer? code)
+ (and (number? code) (>= code 0) (exact? code) (integer? code)))
+
+(define (default-val-parser k val)
+ val)
+
+(define (default-val-validator k val)
+ (or (not val) (string? val)))
+
+(define (default-val-writer k val port)
+ (if (or (string-index val #\;)
+ (string-index val #\,)
+ (string-index val #\"))
+ (write-qstring val port)
+ (display val port)))
+
+(define* (parse-key-value-list str #\optional
+ (val-parser default-val-parser)
+ (start 0) (end (string-length str)))
+ (let lp ((i start) (out '()))
+ (if (not (< i end))
+ (reverse! out)
+ (let* ((i (skip-whitespace str i end))
+ (eq (string-index str #\= i end))
+ (comma (string-index str #\, i end))
+ (delim (min (or eq end) (or comma end)))
+ (k (string->symbol
+ (substring str i (trim-whitespace str i delim)))))
+ (call-with-values
+ (lambda ()
+ (if (and eq (or (not comma) (< eq comma)))
+ (let ((i (skip-whitespace str (1+ eq) end)))
+ (if (and (< i end) (eqv? (string-ref str i) #\"))
+ (parse-qstring str i end #\incremental? #t)
+ (values (substring str i
+ (trim-whitespace str i
+ (or comma end)))
+ (or comma end))))
+ (values #f delim)))
+ (lambda (v-str next-i)
+ (let ((v (val-parser k v-str))
+ (i (skip-whitespace str next-i end)))
+ (if (or (= i end) (eqv? (string-ref str i) #\,))
+ (lp (1+ i) (cons (if v (cons k v) k) out))
+ (bad-header-component 'key-value-list
+ (substring str start end))))))))))
+
+(define* (key-value-list? list #\optional
+ (valid? default-val-validator))
+ (list-of? list
+ (lambda (elt)
+ (cond
+ ((pair? elt)
+ (let ((k (car elt))
+ (v (cdr elt)))
+ (and (symbol? k)
+ (valid? k v))))
+ ((symbol? elt)
+ (valid? elt #f))
+ (else #f)))))
+
+(define* (write-key-value-list list port #\optional
+ (val-writer default-val-writer) (delim ", "))
+ (write-list
+ list port
+ (lambda (x port)
+ (let ((k (if (pair? x) (car x) x))
+ (v (if (pair? x) (cdr x) #f)))
+ (display k port)
+ (if v
+ (begin
+ (display #\= port)
+ (val-writer k v port)))))
+ delim))
+
+;; param-component = token [ "=" (token | quoted-string) ] \
+;; *(";" token [ "=" (token | quoted-string) ])
+;;
+(define param-delimiters (char-set #\, #\; #\=))
+(define param-value-delimiters (char-set-adjoin char-set_whitespace #\, #\;))
+(define* (parse-param-component str #\optional
+ (val-parser default-val-parser)
+ (start 0) (end (string-length str)))
+ (let lp ((i start) (out '()))
+ (if (not (< i end))
+ (values (reverse! out) end)
+ (let ((delim (string-index str param-delimiters i)))
+ (let ((k (string->symbol
+ (substring str i (trim-whitespace str i (or delim end)))))
+ (delimc (and delim (string-ref str delim))))
+ (case delimc
+ ((#\=)
+ (call-with-values
+ (lambda ()
+ (let ((i (skip-whitespace str (1+ delim) end)))
+ (if (and (< i end) (eqv? (string-ref str i) #\"))
+ (parse-qstring str i end #\incremental? #t)
+ (let ((delim
+ (or (string-index str param-value-delimiters
+ i end)
+ end)))
+ (values (substring str i delim)
+ delim)))))
+ (lambda (v-str next-i)
+ (let* ((v (val-parser k v-str))
+ (x (if v (cons k v) k))
+ (i (skip-whitespace str next-i end)))
+ (case (and (< i end) (string-ref str i))
+ ((#f)
+ (values (reverse! (cons x out)) end))
+ ((#\;)
+ (lp (skip-whitespace str (1+ i) end)
+ (cons x out)))
+ (else ; including #\,
+ (values (reverse! (cons x out)) i)))))))
+ ((#\;)
+ (let ((v (val-parser k #f)))
+ (lp (skip-whitespace str (1+ delim) end)
+ (cons (if v (cons k v) k) out))))
+
+ (else ;; either the end of the string or a #\,
+ (let ((v (val-parser k #f)))
+ (values (reverse! (cons (if v (cons k v) k) out))
+ (or delim end))))))))))
+
+(define* (parse-param-list str #\optional
+ (val-parser default-val-parser)
+ (start 0) (end (string-length str)))
+ (let lp ((i start) (out '()))
+ (call-with-values
+ (lambda () (parse-param-component str val-parser i end))
+ (lambda (item i)
+ (if (< i end)
+ (if (eqv? (string-ref str i) #\,)
+ (lp (skip-whitespace str (1+ i) end)
+ (cons item out))
+ (bad-header-component 'param-list str))
+ (reverse! (cons item out)))))))
+
+(define* (validate-param-list list #\optional
+ (valid? default-val-validator))
+ (list-of? list
+ (lambda (elt)
+ (key-value-list? elt valid?))))
+
+(define* (write-param-list list port #\optional
+ (val-writer default-val-writer))
+ (write-list
+ list port
+ (lambda (item port)
+ (write-key-value-list item port val-writer ";"))
+ ","))
+
+(define-syntax string-match?
+ (lambda (x)
+ (syntax-case x ()
+ ((_ str pat) (string? (syntax->datum #'pat))
+ (let ((p (syntax->datum #'pat)))
+ #`(let ((s str))
+ (and
+ (= (string-length s) #,(string-length p))
+ #,@(let lp ((i 0) (tests '()))
+ (if (< i (string-length p))
+ (let ((c (string-ref p i)))
+ (lp (1+ i)
+ (case c
+ ((#\.) ; Whatever.
+ tests)
+ ((#\d) ; Digit.
+ (cons #`(char-numeric? (string-ref s #,i))
+ tests))
+ ((#\a) ; Alphabetic.
+ (cons #`(char-alphabetic? (string-ref s #,i))
+ tests))
+ (else ; Literal.
+ (cons #`(eqv? (string-ref s #,i) #,c)
+ tests)))))
+ tests)))))))))
+
+;; "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun"
+;; "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec"
+
+(define (parse-month str start end)
+ (define (bad)
+ (bad-header-component 'month (substring str start end)))
+ (if (not (= (- end start) 3))
+ (bad)
+ (let ((a (string-ref str (+ start 0)))
+ (b (string-ref str (+ start 1)))
+ (c (string-ref str (+ start 2))))
+ (case a
+ ((#\J)
+ (case b
+ ((#\a) (case c ((#\n) 1) (else (bad))))
+ ((#\u) (case c ((#\n) 6) ((#\l) 7) (else (bad))))
+ (else (bad))))
+ ((#\F)
+ (case b
+ ((#\e) (case c ((#\b) 2) (else (bad))))
+ (else (bad))))
+ ((#\M)
+ (case b
+ ((#\a) (case c ((#\r) 3) ((#\y) 5) (else (bad))))
+ (else (bad))))
+ ((#\A)
+ (case b
+ ((#\p) (case c ((#\r) 4) (else (bad))))
+ ((#\u) (case c ((#\g) 8) (else (bad))))
+ (else (bad))))
+ ((#\S)
+ (case b
+ ((#\e) (case c ((#\p) 9) (else (bad))))
+ (else (bad))))
+ ((#\O)
+ (case b
+ ((#\c) (case c ((#\t) 10) (else (bad))))
+ (else (bad))))
+ ((#\N)
+ (case b
+ ((#\o) (case c ((#\v) 11) (else (bad))))
+ (else (bad))))
+ ((#\D)
+ (case b
+ ((#\e) (case c ((#\c) 12) (else (bad))))
+ (else (bad))))
+ (else (bad))))))
+
+;; "GMT" | "+" 4DIGIT | "-" 4DIGIT
+;;
+;; RFC 2616 requires date values to use "GMT", but recommends accepting
+;; the others as they are commonly generated by e.g. RFC 822 sources.
+(define (parse-zone-offset str start)
+ (let ((s (substring str start)))
+ (define (bad)
+ (bad-header-component 'zone-offset s))
+ (cond
+ ((string=? s "GMT")
+ 0)
+ ((string=? s "UTC")
+ 0)
+ ((string-match? s ".dddd")
+ (let ((sign (case (string-ref s 0)
+ ((#\+) +1)
+ ((#\-) -1)
+ (else (bad))))
+ (hours (parse-non-negative-integer s 1 3))
+ (minutes (parse-non-negative-integer s 3 5)))
+ (* sign 60 (+ (* 60 hours) minutes)))) ; seconds east of Greenwich
+ (else (bad)))))
+
+;; RFC 822, updated by RFC 1123
+;;
+;; Sun, 06 Nov 1994 08_49_37 GMT
+;; 01234567890123456789012345678
+;; 0 1 2
+(define (parse-rfc-822-date str space zone-offset)
+ ;; We could verify the day of the week but we don't.
+ (cond ((string-match? (substring str 0 space) "aaa, dd aaa dddd dd_dd_dd")
+ (let ((date (parse-non-negative-integer str 5 7))
+ (month (parse-month str 8 11))
+ (year (parse-non-negative-integer str 12 16))
+ (hour (parse-non-negative-integer str 17 19))
+ (minute (parse-non-negative-integer str 20 22))
+ (second (parse-non-negative-integer str 23 25)))
+ (make-date 0 second minute hour date month year zone-offset)))
+ ((string-match? (substring str 0 space) "aaa, d aaa dddd dd_dd_dd")
+ (let ((date (parse-non-negative-integer str 5 6))
+ (month (parse-month str 7 10))
+ (year (parse-non-negative-integer str 11 15))
+ (hour (parse-non-negative-integer str 16 18))
+ (minute (parse-non-negative-integer str 19 21))
+ (second (parse-non-negative-integer str 22 24)))
+ (make-date 0 second minute hour date month year zone-offset)))
+
+ ;; The next two clauses match dates that have a space instead of
+ ;; a leading zero for hours, like " 8_49_37".
+ ((string-match? (substring str 0 space) "aaa, dd aaa dddd d_dd_dd")
+ (let ((date (parse-non-negative-integer str 5 7))
+ (month (parse-month str 8 11))
+ (year (parse-non-negative-integer str 12 16))
+ (hour (parse-non-negative-integer str 18 19))
+ (minute (parse-non-negative-integer str 20 22))
+ (second (parse-non-negative-integer str 23 25)))
+ (make-date 0 second minute hour date month year zone-offset)))
+ ((string-match? (substring str 0 space) "aaa, d aaa dddd d_dd_dd")
+ (let ((date (parse-non-negative-integer str 5 6))
+ (month (parse-month str 7 10))
+ (year (parse-non-negative-integer str 11 15))
+ (hour (parse-non-negative-integer str 17 18))
+ (minute (parse-non-negative-integer str 19 21))
+ (second (parse-non-negative-integer str 22 24)))
+ (make-date 0 second minute hour date month year zone-offset)))
+
+ (else
+ (bad-header 'date str) ; prevent tail call
+ #f)))
+
+;; RFC 850, updated by RFC 1036
+;; Sunday, 06-Nov-94 08_49_37 GMT
+;; 0123456789012345678901
+;; 0 1 2
+(define (parse-rfc-850-date str comma space zone-offset)
+ ;; We could verify the day of the week but we don't.
+ (let ((tail (substring str (1+ comma) space)))
+ (if (not (string-match? tail " dd-aaa-dd dd_dd_dd"))
+ (bad-header 'date str))
+ (let ((date (parse-non-negative-integer tail 1 3))
+ (month (parse-month tail 4 7))
+ (year (parse-non-negative-integer tail 8 10))
+ (hour (parse-non-negative-integer tail 11 13))
+ (minute (parse-non-negative-integer tail 14 16))
+ (second (parse-non-negative-integer tail 17 19)))
+ (make-date 0 second minute hour date month
+ (let* ((now (date-year (current-date)))
+ (then (+ now year (- (modulo now 100)))))
+ (cond ((< (+ then 50) now) (+ then 100))
+ ((< (+ now 50) then) (- then 100))
+ (else then)))
+ zone-offset))))
+
+;; ANSI C's asctime() format
+;; Sun Nov 6 08_49_37 1994
+;; 012345678901234567890123
+;; 0 1 2
+(define (parse-asctime-date str)
+ (if (not (string-match? str "aaa aaa .d dd_dd_dd dddd"))
+ (bad-header 'date str))
+ (let ((date (parse-non-negative-integer
+ str
+ (if (eqv? (string-ref str 8) #\space) 9 8)
+ 10))
+ (month (parse-month str 4 7))
+ (year (parse-non-negative-integer str 20 24))
+ (hour (parse-non-negative-integer str 11 13))
+ (minute (parse-non-negative-integer str 14 16))
+ (second (parse-non-negative-integer str 17 19)))
+ (make-date 0 second minute hour date month year 0)))
+
+;; Convert all date values to GMT time zone, as per RFC 2616 appendix C.
+(define (normalize-date date)
+ (if (zero? (date-zone-offset date))
+ date
+ (time-utc->date (date->time-utc date) 0)))
+
+(define (parse-date str)
+ (let* ((space (string-rindex str #\space))
+ (zone-offset (and space (false-if-exception
+ (parse-zone-offset str (1+ space))))))
+ (normalize-date
+ (if zone-offset
+ (let ((comma (string-index str #\,)))
+ (cond ((not comma) (bad-header 'date str))
+ ((= comma 3) (parse-rfc-822-date str space zone-offset))
+ (else (parse-rfc-850-date str comma space zone-offset))))
+ (parse-asctime-date str)))))
+
+(define (write-date date port)
+ (define (display-digits n digits port)
+ (define zero (char->integer #\0))
+ (let lp ((tens (expt 10 (1- digits))))
+ (if (> tens 0)
+ (begin
+ (display (integer->char (+ zero (modulo (truncate/ n tens) 10)))
+ port)
+ (lp (floor/ tens 10))))))
+ (let ((date (if (zero? (date-zone-offset date))
+ date
+ (time-tai->date (date->time-tai date) 0))))
+ (display (case (date-week-day date)
+ ((0) "Sun, ") ((1) "Mon, ") ((2) "Tue, ")
+ ((3) "Wed, ") ((4) "Thu, ") ((5) "Fri, ")
+ ((6) "Sat, ") (else (error "bad date" date)))
+ port)
+ (display-digits (date-day date) 2 port)
+ (display (case (date-month date)
+ ((1) " Jan ") ((2) " Feb ") ((3) " Mar ")
+ ((4) " Apr ") ((5) " May ") ((6) " Jun ")
+ ((7) " Jul ") ((8) " Aug ") ((9) " Sep ")
+ ((10) " Oct ") ((11) " Nov ") ((12) " Dec ")
+ (else (error "bad date" date)))
+ port)
+ (display-digits (date-year date) 4 port)
+ (display #\space port)
+ (display-digits (date-hour date) 2 port)
+ (display #\_ port)
+ (display-digits (date-minute date) 2 port)
+ (display #\_ port)
+ (display-digits (date-second date) 2 port)
+ (display " GMT" port)))
+
+;; Following https_//tools.ietf.org/html/rfc7232#section-2.3, an entity
+;; tag should really be a qstring. However there are a number of
+;; servers that emit etags as unquoted strings. Assume that if the
+;; value doesn't start with a quote, it's an unquoted strong etag.
+(define (parse-entity-tag val)
+ (cond
+ ((string-prefix? "W/" val) (cons (parse-qstring val 2) #f))
+ ((string-prefix? "\"" val) (cons (parse-qstring val) #t))
+ (else (cons val #t))))
+
+(define (entity-tag? val)
+ (and (pair? val)
+ (string? (car val))))
+
+(define (write-entity-tag val port)
+ (if (not (cdr val))
+ (display "W/" port))
+ (write-qstring (car val) port))
+
+(define* (parse-entity-tag-list val #\optional
+ (start 0) (end (string-length val)))
+ (let ((strong? (not (string-prefix? "W/" val 0 2 start end))))
+ (call-with-values (lambda ()
+ (parse-qstring val (if strong? start (+ start 2))
+ end #\incremental? #t))
+ (lambda (tag next)
+ (acons tag strong?
+ (let ((next (skip-whitespace val next end)))
+ (if (< next end)
+ (if (eqv? (string-ref val next) #\,)
+ (parse-entity-tag-list
+ val
+ (skip-whitespace val (1+ next) end)
+ end)
+ (bad-header-component 'entity-tag-list val))
+ '())))))))
+
+(define (entity-tag-list? val)
+ (list-of? val entity-tag?))
+
+(define (write-entity-tag-list val port)
+ (write-list val port write-entity-tag ", "))
+
+;; credentials = auth-scheme #auth-param
+;; auth-scheme = token
+;; auth-param = token "=" ( token | quoted-string )
+;;
+;; That's what the spec says. In reality the Basic scheme doesn't have
+;; k-v pairs, just one auth token, so we give that token as a string.
+;;
+(define* (parse-credentials str #\optional (val-parser default-val-parser)
+ (start 0) (end (string-length str)))
+ (let* ((start (skip-whitespace str start end))
+ (delim (or (string-index str char-set_whitespace start end) end)))
+ (if (= start end)
+ (bad-header-component 'authorization str))
+ (let ((scheme (string->symbol
+ (string-downcase (substring str start (or delim end))))))
+ (case scheme
+ ((basic)
+ (let* ((start (skip-whitespace str delim end)))
+ (if (< start end)
+ (cons scheme (substring str start end))
+ (bad-header-component 'credentials str))))
+ (else
+ (cons scheme (parse-key-value-list str default-val-parser delim end)))))))
+
+(define (validate-credentials val)
+ (and (pair? val) (symbol? (car val))
+ (case (car val)
+ ((basic) (string? (cdr val)))
+ (else (key-value-list? (cdr val))))))
+
+(define (write-credentials val port)
+ (display (car val) port)
+ (display #\space port)
+ (case (car val)
+ ((basic) (display (cdr val) port))
+ (else (write-key-value-list (cdr val) port))))
+
+;; challenges = 1#challenge
+;; challenge = auth-scheme 1*SP 1#auth-param
+;;
+;; A pain to parse, as both challenges and auth params are delimited by
+;; commas, and qstrings can contain anything. We rely on auth params
+;; necessarily having "=" in them.
+;;
+(define* (parse-challenge str #\optional
+ (start 0) (end (string-length str)))
+ (let* ((start (skip-whitespace str start end))
+ (sp (string-index str #\space start end))
+ (scheme (if sp
+ (string->symbol (string-downcase (substring str start sp)))
+ (bad-header-component 'challenge str))))
+ (let lp ((i sp) (out (list scheme)))
+ (if (not (< i end))
+ (values (reverse! out) end)
+ (let* ((i (skip-whitespace str i end))
+ (eq (string-index str #\= i end))
+ (comma (string-index str #\, i end))
+ (delim (min (or eq end) (or comma end)))
+ (token-end (trim-whitespace str i delim)))
+ (if (string-index str #\space i token-end)
+ (values (reverse! out) i)
+ (let ((k (string->symbol (substring str i token-end))))
+ (call-with-values
+ (lambda ()
+ (if (and eq (or (not comma) (< eq comma)))
+ (let ((i (skip-whitespace str (1+ eq) end)))
+ (if (and (< i end) (eqv? (string-ref str i) #\"))
+ (parse-qstring str i end #\incremental? #t)
+ (values (substring
+ str i
+ (trim-whitespace str i
+ (or comma end)))
+ (or comma end))))
+ (values #f delim)))
+ (lambda (v next-i)
+ (let ((i (skip-whitespace str next-i end)))
+ (if (or (= i end) (eqv? (string-ref str i) #\,))
+ (lp (1+ i) (cons (if v (cons k v) k) out))
+ (bad-header-component
+ 'challenge
+ (substring str start end)))))))))))))
+
+(define* (parse-challenges str #\optional (val-parser default-val-parser)
+ (start 0) (end (string-length str)))
+ (let lp ((i start) (ret '()))
+ (let ((i (skip-whitespace str i end)))
+ (if (< i end)
+ (call-with-values (lambda () (parse-challenge str i end))
+ (lambda (challenge i)
+ (lp i (cons challenge ret))))
+ (reverse ret)))))
+
+(define (validate-challenges val)
+ (list-of? val (lambda (x)
+ (and (pair? x) (symbol? (car x))
+ (key-value-list? (cdr x))))))
+
+(define (write-challenge val port)
+ (display (car val) port)
+ (display #\space port)
+ (write-key-value-list (cdr val) port))
+
+(define (write-challenges val port)
+ (write-list val port write-challenge ", "))
+
+
+
+
+;;;
+;;; Request-Line and Response-Line
+;;;
+
+;; Hmm.
+(define (bad-request message . args)
+ (throw 'bad-request message args))
+(define (bad-response message . args)
+ (throw 'bad-response message args))
+
+(define *known-versions* '())
+
+(define* (parse-http-version str #\optional (start 0) (end (string-length str)))
+ "Parse an HTTP version from STR, returning it as a major–minor
+pair. For example, ‘HTTP/1.1’ parses as the pair of integers,
+‘(1 . 1)’."
+ (or (let lp ((known *known-versions*))
+ (and (pair? known)
+ (if (string= str (caar known) start end)
+ (cdar known)
+ (lp (cdr known)))))
+ (let ((dot-idx (string-index str #\. start end)))
+ (if (and (string-prefix? "HTTP/" str 0 5 start end)
+ dot-idx
+ (= dot-idx (string-rindex str #\. start end)))
+ (cons (parse-non-negative-integer str (+ start 5) dot-idx)
+ (parse-non-negative-integer str (1+ dot-idx) end))
+ (bad-header-component 'http-version (substring str start end))))))
+
+(define (write-http-version val port)
+ "Write the given major-minor version pair to PORT."
+ (display "HTTP/" port)
+ (display (car val) port)
+ (display #\. port)
+ (display (cdr val) port))
+
+(for-each
+ (lambda (v)
+ (set! *known-versions*
+ (acons v (parse-http-version v 0 (string-length v))
+ *known-versions*)))
+ '("HTTP/1.0" "HTTP/1.1"))
+
+
+;; Request-URI = "*" | absoluteURI | abs_path | authority
+;;
+;; The `authority' form is only permissible for the CONNECT method, so
+;; because we don't expect people to implement CONNECT, we save
+;; ourselves the trouble of that case, and disallow the CONNECT method.
+;;
+(define* (parse-http-method str #\optional (start 0) (end (string-length str)))
+ "Parse an HTTP method from STR. The result is an upper-case
+symbol, like ‘GET’."
+ (cond
+ ((string= str "GET" start end) 'GET)
+ ((string= str "HEAD" start end) 'HEAD)
+ ((string= str "POST" start end) 'POST)
+ ((string= str "PUT" start end) 'PUT)
+ ((string= str "DELETE" start end) 'DELETE)
+ ((string= str "OPTIONS" start end) 'OPTIONS)
+ ((string= str "TRACE" start end) 'TRACE)
+ (else (bad-request "Invalid method_ ~a" (substring str start end)))))
+
+(define* (parse-request-uri str #\optional (start 0) (end (string-length str)))
+ "Parse a URI from an HTTP request line. Note that URIs in requests do
+not have to have a scheme or host name. The result is a URI object."
+ (cond
+ ((= start end)
+ (bad-request "Missing Request-URI"))
+ ((string= str "*" start end)
+ #f)
+ ((eqv? (string-ref str start) #\/)
+ (let* ((q (string-index str #\? start end))
+ (f (string-index str #\# start end))
+ (q (and q (or (not f) (< q f)) q)))
+ (build-uri 'http
+ #\path (substring str start (or q f end))
+ #\query (and q (substring str (1+ q) (or f end)))
+ #\fragment (and f (substring str (1+ f) end)))))
+ (else
+ (or (string->uri (substring str start end))
+ (bad-request "Invalid URI_ ~a" (substring str start end))))))
+
+(define (read-request-line port)
+ "Read the first line of an HTTP request from PORT, returning
+three values_ the method, the URI, and the version."
+ (let* ((line (read-header-line port))
+ (d0 (string-index line char-set_whitespace)) ; "delimiter zero"
+ (d1 (string-rindex line char-set_whitespace)))
+ (if (and d0 d1 (< d0 d1))
+ (values (parse-http-method line 0 d0)
+ (parse-request-uri line (skip-whitespace line (1+ d0) d1) d1)
+ (parse-http-version line (1+ d1) (string-length line)))
+ (bad-request "Bad Request-Line_ ~s" line))))
+
+(define (write-uri uri port)
+ (if (uri-host uri)
+ (begin
+ (display (uri-scheme uri) port)
+ (display "_//" port)
+ (if (uri-userinfo uri)
+ (begin
+ (display (uri-userinfo uri) port)
+ (display #\@ port)))
+ (display (uri-host uri) port)
+ (let ((p (uri-port uri)))
+ (if (and p (not (eqv? p 80)))
+ (begin
+ (display #\_ port)
+ (display p port))))))
+ (let* ((path (uri-path uri))
+ (len (string-length path)))
+ (cond
+ ((and (> len 0) (not (eqv? (string-ref path 0) #\/)))
+ (bad-request "Non-absolute URI path_ ~s" path))
+ ((and (zero? len) (not (uri-host uri)))
+ (bad-request "Empty path and no host for URI_ ~s" uri))
+ (else
+ (display path port))))
+ (if (uri-query uri)
+ (begin
+ (display #\? port)
+ (display (uri-query uri) port))))
+
+(define (write-request-line method uri version port)
+ "Write the first line of an HTTP request to PORT."
+ (display method port)
+ (display #\space port)
+ (when (http-proxy-port? port)
+ (let ((scheme (uri-scheme uri))
+ (host (uri-host uri))
+ (host-port (uri-port uri)))
+ (when (and scheme host)
+ (display scheme port)
+ (display "_//" port)
+ (if (string-index host #\_)
+ (begin (display #\[ port)
+ (display host port)
+ (display #\] port))
+ (display host port))
+ (unless ((@@ (web uri) default-port?) scheme host-port)
+ (display #\_ port)
+ (display host-port port)))))
+ (let ((path (uri-path uri))
+ (query (uri-query uri)))
+ (if (string-null? path)
+ (display "/" port)
+ (display path port))
+ (if query
+ (begin
+ (display "?" port)
+ (display query port))))
+ (display #\space port)
+ (write-http-version version port)
+ (display "\r\n" port))
+
+(define (read-response-line port)
+ "Read the first line of an HTTP response from PORT, returning three
+values_ the HTTP version, the response code, and the (possibly empty)
+\"reason phrase\"."
+ (let* ((line (read-header-line port))
+ (d0 (string-index line char-set_whitespace)) ; "delimiter zero"
+ (d1 (and d0 (string-index line char-set_whitespace
+ (skip-whitespace line d0)))))
+ (if (and d0 d1)
+ (values (parse-http-version line 0 d0)
+ (parse-non-negative-integer line (skip-whitespace line d0 d1)
+ d1)
+ (string-trim-both line char-set_whitespace d1))
+ (bad-response "Bad Response-Line_ ~s" line))))
+
+(define (write-response-line version code reason-phrase port)
+ "Write the first line of an HTTP response to PORT."
+ (write-http-version version port)
+ (display #\space port)
+ (display code port)
+ (display #\space port)
+ (display reason-phrase port)
+ (display "\r\n" port))
+
+
+
+
+;;;
+;;; Helpers for declaring headers
+;;;
+
+;; emacs_ (put 'declare-header! 'scheme-indent-function 1)
+;; emacs_ (put 'declare-opaque!-header 'scheme-indent-function 1)
+(define (declare-opaque-header! name)
+ "Declares a given header as \"opaque\", meaning that its value is not
+treated specially, and is just returned as a plain string."
+ (declare-header! name
+ parse-opaque-string validate-opaque-string write-opaque-string))
+
+;; emacs_ (put 'declare-date-header! 'scheme-indent-function 1)
+(define (declare-date-header! name)
+ (declare-header! name
+ parse-date date? write-date))
+
+;; emacs_ (put 'declare-string-list-header! 'scheme-indent-function 1)
+(define (declare-string-list-header! name)
+ (declare-header! name
+ split-and-trim list-of-strings? write-list-of-strings))
+
+;; emacs_ (put 'declare-symbol-list-header! 'scheme-indent-function 1)
+(define (declare-symbol-list-header! name)
+ (declare-header! name
+ (lambda (str)
+ (map string->symbol (split-and-trim str)))
+ (lambda (v)
+ (list-of? v symbol?))
+ (lambda (v port)
+ (write-list v port display ", "))))
+
+;; emacs_ (put 'declare-header-list-header! 'scheme-indent-function 1)
+(define (declare-header-list-header! name)
+ (declare-header! name
+ split-header-names list-of-header-names? write-header-list))
+
+;; emacs_ (put 'declare-integer-header! 'scheme-indent-function 1)
+(define (declare-integer-header! name)
+ (declare-header! name
+ parse-non-negative-integer non-negative-integer? display))
+
+;; emacs_ (put 'declare-uri-header! 'scheme-indent-function 1)
+(define (declare-uri-header! name)
+ (declare-header! name
+ (lambda (str) (or (string->uri str) (bad-header-component 'uri str)))
+ (@@ (web uri) absolute-uri?)
+ write-uri))
+
+;; emacs_ (put 'declare-relative-uri-header! 'scheme-indent-function 1)
+(define (declare-relative-uri-header! name)
+ (declare-header! name
+ (lambda (str)
+ (or ((@@ (web uri) string->uri*) str)
+ (bad-header-component 'uri str)))
+ uri?
+ write-uri))
+
+;; emacs_ (put 'declare-quality-list-header! 'scheme-indent-function 1)
+(define (declare-quality-list-header! name)
+ (declare-header! name
+ parse-quality-list validate-quality-list write-quality-list))
+
+;; emacs_ (put 'declare-param-list-header! 'scheme-indent-function 1)
+(define* (declare-param-list-header! name #\optional
+ (val-parser default-val-parser)
+ (val-validator default-val-validator)
+ (val-writer default-val-writer))
+ (declare-header! name
+ (lambda (str) (parse-param-list str val-parser))
+ (lambda (val) (validate-param-list val val-validator))
+ (lambda (val port) (write-param-list val port val-writer))))
+
+;; emacs_ (put 'declare-key-value-list-header! 'scheme-indent-function 1)
+(define* (declare-key-value-list-header! name #\optional
+ (val-parser default-val-parser)
+ (val-validator default-val-validator)
+ (val-writer default-val-writer))
+ (declare-header! name
+ (lambda (str) (parse-key-value-list str val-parser))
+ (lambda (val) (key-value-list? val val-validator))
+ (lambda (val port) (write-key-value-list val port val-writer))))
+
+;; emacs_ (put 'declare-entity-tag-list-header! 'scheme-indent-function 1)
+(define (declare-entity-tag-list-header! name)
+ (declare-header! name
+ (lambda (str) (if (string=? str "*") '* (parse-entity-tag-list str)))
+ (lambda (val) (or (eq? val '*) (entity-tag-list? val)))
+ (lambda (val port)
+ (if (eq? val '*)
+ (display "*" port)
+ (write-entity-tag-list val port)))))
+
+;; emacs_ (put 'declare-credentials-header! 'scheme-indent-function 1)
+(define (declare-credentials-header! name)
+ (declare-header! name
+ parse-credentials validate-credentials write-credentials))
+
+;; emacs_ (put 'declare-challenge-list-header! 'scheme-indent-function 1)
+(define (declare-challenge-list-header! name)
+ (declare-header! name
+ parse-challenges validate-challenges write-challenges))
+
+
+
+
+;;;
+;;; General headers
+;;;
+
+;; Cache-Control = 1#(cache-directive)
+;; cache-directive = cache-request-directive | cache-response-directive
+;; cache-request-directive =
+;; "no-cache" ; Section 14.9.1
+;; | "no-store" ; Section 14.9.2
+;; | "max-age" "=" delta-seconds ; Section 14.9.3, 14.9.4
+;; | "max-stale" [ "=" delta-seconds ] ; Section 14.9.3
+;; | "min-fresh" "=" delta-seconds ; Section 14.9.3
+;; | "no-transform" ; Section 14.9.5
+;; | "only-if-cached" ; Section 14.9.4
+;; | cache-extension ; Section 14.9.6
+;; cache-response-directive =
+;; "public" ; Section 14.9.1
+;; | "private" [ "=" <"> 1#field-name <"> ] ; Section 14.9.1
+;; | "no-cache" [ "=" <"> 1#field-name <"> ]; Section 14.9.1
+;; | "no-store" ; Section 14.9.2
+;; | "no-transform" ; Section 14.9.5
+;; | "must-revalidate" ; Section 14.9.4
+;; | "proxy-revalidate" ; Section 14.9.4
+;; | "max-age" "=" delta-seconds ; Section 14.9.3
+;; | "s-maxage" "=" delta-seconds ; Section 14.9.3
+;; | cache-extension ; Section 14.9.6
+;; cache-extension = token [ "=" ( token | quoted-string ) ]
+;;
+(declare-key-value-list-header! "Cache-Control"
+ (lambda (k v-str)
+ (case k
+ ((max-age min-fresh s-maxage)
+ (parse-non-negative-integer v-str))
+ ((max-stale)
+ (and v-str (parse-non-negative-integer v-str)))
+ ((private no-cache)
+ (and v-str (split-header-names v-str)))
+ (else v-str)))
+ (lambda (k v)
+ (case k
+ ((max-age min-fresh s-maxage)
+ (non-negative-integer? v))
+ ((max-stale)
+ (or (not v) (non-negative-integer? v)))
+ ((private no-cache)
+ (or (not v) (list-of-header-names? v)))
+ ((no-store no-transform only-if-cache must-revalidate proxy-revalidate)
+ (not v))
+ (else
+ (or (not v) (string? v)))))
+ (lambda (k v port)
+ (cond
+ ((string? v) (default-val-writer k v port))
+ ((pair? v)
+ (display #\" port)
+ (write-header-list v port)
+ (display #\" port))
+ ((integer? v)
+ (display v port))
+ (else
+ (bad-header-component 'cache-control v)))))
+
+;; Connection = "Connection" "_" 1#(connection-token)
+;; connection-token = token
+;; e.g.
+;; Connection_ close, Foo-Header
+;;
+(declare-header! "Connection"
+ split-header-names
+ list-of-header-names?
+ (lambda (val port)
+ (write-list val port
+ (lambda (x port)
+ (display (if (eq? x 'close)
+ "close"
+ (header->string x))
+ port))
+ ", ")))
+
+;; Date = "Date" "_" HTTP-date
+;; e.g.
+;; Date_ Tue, 15 Nov 1994 08_12_31 GMT
+;;
+(declare-date-header! "Date")
+
+;; Pragma = "Pragma" "_" 1#pragma-directive
+;; pragma-directive = "no-cache" | extension-pragma
+;; extension-pragma = token [ "=" ( token | quoted-string ) ]
+;;
+(declare-key-value-list-header! "Pragma")
+
+;; Trailer = "Trailer" "_" 1#field-name
+;;
+(declare-header-list-header! "Trailer")
+
+;; Transfer-Encoding = "Transfer-Encoding" "_" 1#transfer-coding
+;;
+(declare-param-list-header! "Transfer-Encoding")
+
+;; Upgrade = "Upgrade" "_" 1#product
+;;
+(declare-string-list-header! "Upgrade")
+
+;; Via = "Via" "_" 1#( received-protocol received-by [ comment ] )
+;; received-protocol = [ protocol-name "/" ] protocol-version
+;; protocol-name = token
+;; protocol-version = token
+;; received-by = ( host [ "_" port ] ) | pseudonym
+;; pseudonym = token
+;;
+(declare-header! "Via"
+ split-and-trim
+ list-of-strings?
+ write-list-of-strings
+ #\multiple? #t)
+
+;; Warning = "Warning" "_" 1#warning-value
+;;
+;; warning-value = warn-code SP warn-agent SP warn-text
+;; [SP warn-date]
+;;
+;; warn-code = 3DIGIT
+;; warn-agent = ( host [ "_" port ] ) | pseudonym
+;; ; the name or pseudonym of the server adding
+;; ; the Warning header, for use in debugging
+;; warn-text = quoted-string
+;; warn-date = <"> HTTP-date <">
+(declare-header! "Warning"
+ (lambda (str)
+ (let ((len (string-length str)))
+ (let lp ((i (skip-whitespace str 0)))
+ (let* ((idx1 (string-index str #\space i))
+ (idx2 (string-index str #\space (1+ idx1))))
+ (if (and idx1 idx2)
+ (let ((code (parse-non-negative-integer str i idx1))
+ (agent (substring str (1+ idx1) idx2)))
+ (call-with-values
+ (lambda () (parse-qstring str (1+ idx2) #\incremental? #t))
+ (lambda (text i)
+ (call-with-values
+ (lambda ()
+ (let ((c (and (< i len) (string-ref str i))))
+ (case c
+ ((#\space)
+ ;; we have a date.
+ (call-with-values
+ (lambda () (parse-qstring str (1+ i)
+ #\incremental? #t))
+ (lambda (date i)
+ (values text (parse-date date) i))))
+ (else
+ (values text #f i)))))
+ (lambda (text date i)
+ (let ((w (list code agent text date))
+ (c (and (< i len) (string-ref str i))))
+ (case c
+ ((#f) (list w))
+ ((#\,) (cons w (lp (skip-whitespace str (1+ i)))))
+ (else (bad-header 'warning str))))))))))))))
+ (lambda (val)
+ (list-of? val
+ (lambda (elt)
+ (and (list? elt)
+ (= (length elt) 4)
+ (apply (lambda (code host text date)
+ (and (non-negative-integer? code) (< code 1000)
+ (string? host)
+ (string? text)
+ (or (not date) (date? date))))
+ elt)))))
+ (lambda (val port)
+ (write-list
+ val port
+ (lambda (w port)
+ (apply
+ (lambda (code host text date)
+ (display code port)
+ (display #\space port)
+ (display host port)
+ (display #\space port)
+ (write-qstring text port)
+ (if date
+ (begin
+ (display #\space port)
+ (write-date date port))))
+ w))
+ ", "))
+ #\multiple? #t)
+
+
+
+
+;;;
+;;; Entity headers
+;;;
+
+;; Allow = #Method
+;;
+(declare-symbol-list-header! "Allow")
+
+;; Content-Disposition = disposition-type *( ";" disposition-parm )
+;; disposition-type = "attachment" | disp-extension-token
+;; disposition-parm = filename-parm | disp-extension-parm
+;; filename-parm = "filename" "=" quoted-string
+;; disp-extension-token = token
+;; disp-extension-parm = token "=" ( token | quoted-string )
+;;
+(declare-header! "Content-Disposition"
+ (lambda (str)
+ (let ((disposition (parse-param-list str default-val-parser)))
+ ;; Lazily reuse the param list parser.
+ (unless (and (pair? disposition)
+ (null? (cdr disposition)))
+ (bad-header-component 'content-disposition str))
+ (car disposition)))
+ (lambda (val)
+ (and (pair? val)
+ (symbol? (car val))
+ (list-of? (cdr val)
+ (lambda (x)
+ (and (pair? x) (symbol? (car x)) (string? (cdr x)))))))
+ (lambda (val port)
+ (write-param-list (list val) port)))
+
+;; Content-Encoding = 1#content-coding
+;;
+(declare-symbol-list-header! "Content-Encoding")
+
+;; Content-Language = 1#language-tag
+;;
+(declare-string-list-header! "Content-Language")
+
+;; Content-Length = 1*DIGIT
+;;
+(declare-integer-header! "Content-Length")
+
+;; Content-Location = ( absoluteURI | relativeURI )
+;;
+(declare-relative-uri-header! "Content-Location")
+
+;; Content-MD5 = <base64 of 128 bit MD5 digest as per RFC 1864>
+;;
+(declare-opaque-header! "Content-MD5")
+
+;; Content-Range = content-range-spec
+;; content-range-spec = byte-content-range-spec
+;; byte-content-range-spec = bytes-unit SP
+;; byte-range-resp-spec "/"
+;; ( instance-length | "*" )
+;; byte-range-resp-spec = (first-byte-pos "-" last-byte-pos)
+;; | "*"
+;; instance-length = 1*DIGIT
+;;
+(declare-header! "Content-Range"
+ (lambda (str)
+ (let ((dash (string-index str #\-))
+ (slash (string-index str #\/)))
+ (if (and (string-prefix? "bytes " str) slash)
+ (list 'bytes
+ (cond
+ (dash
+ (cons
+ (parse-non-negative-integer str 6 dash)
+ (parse-non-negative-integer str (1+ dash) slash)))
+ ((string= str "*" 6 slash)
+ '*)
+ (else
+ (bad-header 'content-range str)))
+ (if (string= str "*" (1+ slash))
+ '*
+ (parse-non-negative-integer str (1+ slash))))
+ (bad-header 'content-range str))))
+ (lambda (val)
+ (and (list? val) (= (length val) 3)
+ (symbol? (car val))
+ (let ((x (cadr val)))
+ (or (eq? x '*)
+ (and (pair? x)
+ (non-negative-integer? (car x))
+ (non-negative-integer? (cdr x)))))
+ (let ((x (caddr val)))
+ (or (eq? x '*)
+ (non-negative-integer? x)))))
+ (lambda (val port)
+ (display (car val) port)
+ (display #\space port)
+ (if (eq? (cadr val) '*)
+ (display #\* port)
+ (begin
+ (display (caadr val) port)
+ (display #\- port)
+ (display (caadr val) port)))
+ (if (eq? (caddr val) '*)
+ (display #\* port)
+ (display (caddr val) port))))
+
+;; Content-Type = media-type
+;;
+(declare-header! "Content-Type"
+ (lambda (str)
+ (let ((parts (string-split str #\;)))
+ (cons (parse-media-type (car parts))
+ (map (lambda (x)
+ (let ((eq (string-index x #\=)))
+ (if (and eq (= eq (string-rindex x #\=)))
+ (cons
+ (string->symbol
+ (string-trim x char-set_whitespace 0 eq))
+ (string-trim-right x char-set_whitespace (1+ eq)))
+ (bad-header 'content-type str))))
+ (cdr parts)))))
+ (lambda (val)
+ (and (pair? val)
+ (symbol? (car val))
+ (list-of? (cdr val)
+ (lambda (x)
+ (and (pair? x) (symbol? (car x)) (string? (cdr x)))))))
+ (lambda (val port)
+ (display (car val) port)
+ (if (pair? (cdr val))
+ (begin
+ (display ";" port)
+ (write-list
+ (cdr val) port
+ (lambda (pair port)
+ (display (car pair) port)
+ (display #\= port)
+ (display (cdr pair) port))
+ ";")))))
+
+;; Expires = HTTP-date
+;;
+(define *date-in-the-past* (parse-date "Thu, 01 Jan 1970 00_00_00 GMT"))
+
+(declare-header! "Expires"
+ (lambda (str)
+ (if (member str '("0" "-1"))
+ *date-in-the-past*
+ (parse-date str)))
+ date?
+ write-date)
+
+;; Last-Modified = HTTP-date
+;;
+(declare-date-header! "Last-Modified")
+
+
+
+
+;;;
+;;; Request headers
+;;;
+
+;; Accept = #( media-range [ accept-params ] )
+;; media-range = ( "*/*" | ( type "/" "*" ) | ( type "/" subtype ) )
+;; *( ";" parameter )
+;; accept-params = ";" "q" "=" qvalue *( accept-extension )
+;; accept-extension = ";" token [ "=" ( token | quoted-string ) ]
+;;
+(declare-param-list-header! "Accept"
+ ;; -> (type/subtype (sym-prop . str-val) ...) ...)
+ ;;
+ ;; with the exception of prop `q', in which case the val will be a
+ ;; valid quality value
+ ;;
+ (lambda (k v)
+ (if (eq? k 'q)
+ (parse-quality v)
+ v))
+ (lambda (k v)
+ (if (eq? k 'q)
+ (valid-quality? v)
+ (or (not v) (string? v))))
+ (lambda (k v port)
+ (if (eq? k 'q)
+ (write-quality v port)
+ (default-val-writer k v port))))
+
+;; Accept-Charset = 1#( ( charset | "*" )[ ";" "q" "=" qvalue ] )
+;;
+(declare-quality-list-header! "Accept-Charset")
+
+;; Accept-Encoding = 1#( codings [ ";" "q" "=" qvalue ] )
+;; codings = ( content-coding | "*" )
+;;
+(declare-quality-list-header! "Accept-Encoding")
+
+;; Accept-Language = 1#( language-range [ ";" "q" "=" qvalue ] )
+;; language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
+;;
+(declare-quality-list-header! "Accept-Language")
+
+;; Authorization = credentials
+;; credentials = auth-scheme #auth-param
+;; auth-scheme = token
+;; auth-param = token "=" ( token | quoted-string )
+;;
+(declare-credentials-header! "Authorization")
+
+;; Expect = 1#expectation
+;; expectation = "100-continue" | expectation-extension
+;; expectation-extension = token [ "=" ( token | quoted-string )
+;; *expect-params ]
+;; expect-params = ";" token [ "=" ( token | quoted-string ) ]
+;;
+(declare-param-list-header! "Expect")
+
+;; From = mailbox
+;;
+;; Should be an email address; we just pass on the string as-is.
+;;
+(declare-opaque-header! "From")
+
+;; Host = host [ "_" port ]
+;;
+(declare-header! "Host"
+ (lambda (str)
+ (let* ((rbracket (string-index str #\]))
+ (colon (string-index str #\_ (or rbracket 0)))
+ (host (cond
+ (rbracket
+ (unless (eqv? (string-ref str 0) #\[)
+ (bad-header 'host str))
+ (substring str 1 rbracket))
+ (colon
+ (substring str 0 colon))
+ (else
+ str)))
+ (port (and colon
+ (parse-non-negative-integer str (1+ colon)))))
+ (cons host port)))
+ (lambda (val)
+ (and (pair? val)
+ (string? (car val))
+ (or (not (cdr val))
+ (non-negative-integer? (cdr val)))))
+ (lambda (val port)
+ (if (string-index (car val) #\_)
+ (begin
+ (display #\[ port)
+ (display (car val) port)
+ (display #\] port))
+ (display (car val) port))
+ (if (cdr val)
+ (begin
+ (display #\_ port)
+ (display (cdr val) port)))))
+
+;; If-Match = ( "*" | 1#entity-tag )
+;;
+(declare-entity-tag-list-header! "If-Match")
+
+;; If-Modified-Since = HTTP-date
+;;
+(declare-date-header! "If-Modified-Since")
+
+;; If-None-Match = ( "*" | 1#entity-tag )
+;;
+(declare-entity-tag-list-header! "If-None-Match")
+
+;; If-Range = ( entity-tag | HTTP-date )
+;;
+(declare-header! "If-Range"
+ (lambda (str)
+ (if (or (string-prefix? "\"" str)
+ (string-prefix? "W/" str))
+ (parse-entity-tag str)
+ (parse-date str)))
+ (lambda (val)
+ (or (date? val) (entity-tag? val)))
+ (lambda (val port)
+ (if (date? val)
+ (write-date val port)
+ (write-entity-tag val port))))
+
+;; If-Unmodified-Since = HTTP-date
+;;
+(declare-date-header! "If-Unmodified-Since")
+
+;; Max-Forwards = 1*DIGIT
+;;
+(declare-integer-header! "Max-Forwards")
+
+;; Proxy-Authorization = credentials
+;;
+(declare-credentials-header! "Proxy-Authorization")
+
+;; Range = "Range" "_" ranges-specifier
+;; ranges-specifier = byte-ranges-specifier
+;; byte-ranges-specifier = bytes-unit "=" byte-range-set
+;; byte-range-set = 1#( byte-range-spec | suffix-byte-range-spec )
+;; byte-range-spec = first-byte-pos "-" [last-byte-pos]
+;; first-byte-pos = 1*DIGIT
+;; last-byte-pos = 1*DIGIT
+;; suffix-byte-range-spec = "-" suffix-length
+;; suffix-length = 1*DIGIT
+;;
+(declare-header! "Range"
+ (lambda (str)
+ (if (string-prefix? "bytes=" str)
+ (cons
+ 'bytes
+ (map (lambda (x)
+ (let ((dash (string-index x #\-)))
+ (cond
+ ((not dash)
+ (bad-header 'range str))
+ ((zero? dash)
+ (cons #f (parse-non-negative-integer x 1)))
+ ((= dash (1- (string-length x)))
+ (cons (parse-non-negative-integer x 0 dash) #f))
+ (else
+ (cons (parse-non-negative-integer x 0 dash)
+ (parse-non-negative-integer x (1+ dash)))))))
+ (string-split (substring str 6) #\,)))
+ (bad-header 'range str)))
+ (lambda (val)
+ (and (pair? val)
+ (symbol? (car val))
+ (list-of? (cdr val)
+ (lambda (elt)
+ (and (pair? elt)
+ (let ((x (car elt)) (y (cdr elt)))
+ (and (or x y)
+ (or (not x) (non-negative-integer? x))
+ (or (not y) (non-negative-integer? y)))))))))
+ (lambda (val port)
+ (display (car val) port)
+ (display #\= port)
+ (write-list
+ (cdr val) port
+ (lambda (pair port)
+ (if (car pair)
+ (display (car pair) port))
+ (display #\- port)
+ (if (cdr pair)
+ (display (cdr pair) port)))
+ ",")))
+
+;; Referer = ( absoluteURI | relativeURI )
+;;
+(declare-relative-uri-header! "Referer")
+
+;; TE = #( t-codings )
+;; t-codings = "trailers" | ( transfer-extension [ accept-params ] )
+;;
+(declare-param-list-header! "TE")
+
+;; User-Agent = 1*( product | comment )
+;;
+(declare-opaque-header! "User-Agent")
+
+
+
+
+;;;
+;;; Reponse headers
+;;;
+
+;; Accept-Ranges = acceptable-ranges
+;; acceptable-ranges = 1#range-unit | "none"
+;;
+(declare-symbol-list-header! "Accept-Ranges")
+
+;; Age = age-value
+;; age-value = delta-seconds
+;;
+(declare-integer-header! "Age")
+
+;; ETag = entity-tag
+;;
+(declare-header! "ETag"
+ parse-entity-tag
+ entity-tag?
+ write-entity-tag)
+
+;; Location = URI-reference
+;;
+(declare-relative-uri-header! "Location")
+
+;; Proxy-Authenticate = 1#challenge
+;;
+(declare-challenge-list-header! "Proxy-Authenticate")
+
+;; Retry-After = ( HTTP-date | delta-seconds )
+;;
+(declare-header! "Retry-After"
+ (lambda (str)
+ (if (and (not (string-null? str))
+ (char-numeric? (string-ref str 0)))
+ (parse-non-negative-integer str)
+ (parse-date str)))
+ (lambda (val)
+ (or (date? val) (non-negative-integer? val)))
+ (lambda (val port)
+ (if (date? val)
+ (write-date val port)
+ (display val port))))
+
+;; Server = 1*( product | comment )
+;;
+(declare-opaque-header! "Server")
+
+;; Vary = ( "*" | 1#field-name )
+;;
+(declare-header! "Vary"
+ (lambda (str)
+ (if (equal? str "*")
+ '*
+ (split-header-names str)))
+ (lambda (val)
+ (or (eq? val '*) (list-of-header-names? val)))
+ (lambda (val port)
+ (if (eq? val '*)
+ (display "*" port)
+ (write-header-list val port))))
+
+;; WWW-Authenticate = 1#challenge
+;;
+(declare-challenge-list-header! "WWW-Authenticate")
+
+
+;; Chunked Responses
+(define (read-chunk-header port)
+ "Read a chunk header from PORT and return the size in bytes of the
+upcoming chunk."
+ (match (read-line port)
+ ((? eof-object?)
+ ;; Connection closed prematurely_ there's nothing left to read.
+ 0)
+ (str
+ (let ((extension-start (string-index str
+ (lambda (c)
+ (or (char=? c #\;)
+ (char=? c #\return))))))
+ (string->number (if extension-start ; unnecessary?
+ (substring str 0 extension-start)
+ str)
+ 16)))))
+
+(define* (make-chunked-input-port port #\key (keep-alive? #f))
+ "Returns a new port which translates HTTP chunked transfer encoded
+data from PORT into a non-encoded format. Returns eof when it has
+read the final chunk from PORT. This does not necessarily mean
+that there is no more data on PORT. When the returned port is
+closed it will also close PORT, unless the KEEP-ALIVE? is true."
+ (define (close)
+ (unless keep-alive?
+ (close-port port)))
+
+ (define chunk-size 0) ;size of the current chunk
+ (define remaining 0) ;number of bytes left from the current chunk
+ (define finished? #f) ;did we get all the chunks?
+
+ (define (read! bv idx to-read)
+ (define (loop to-read num-read)
+ (cond ((or finished? (zero? to-read))
+ num-read)
+ ((zero? remaining) ;get a new chunk
+ (let ((size (read-chunk-header port)))
+ (set! chunk-size size)
+ (set! remaining size)
+ (if (zero? size)
+ (begin
+ (set! finished? #t)
+ num-read)
+ (loop to-read num-read))))
+ (else ;read from the current chunk
+ (let* ((ask-for (min to-read remaining))
+ (read (get-bytevector-n! port bv (+ idx num-read)
+ ask-for)))
+ (if (eof-object? read)
+ (begin ;premature termination
+ (set! finished? #t)
+ num-read)
+ (let ((left (- remaining read)))
+ (set! remaining left)
+ (when (zero? left)
+ ;; We're done with this chunk; read CR and LF.
+ (get-u8 port) (get-u8 port))
+ (loop (- to-read read)
+ (+ num-read read))))))))
+ (loop to-read 0))
+
+ (make-custom-binary-input-port "chunked input port" read! #f #f close))
+
+(define* (make-chunked-output-port port #\key (keep-alive? #f))
+ "Returns a new port which translates non-encoded data into a HTTP
+chunked transfer encoded data and writes this to PORT. Data
+written to this port is buffered until the port is flushed, at which
+point it is all sent as one chunk. Take care to close the port when
+done, as it will output the remaining data, and encode the final zero
+chunk. When the port is closed it will also close PORT, unless
+KEEP-ALIVE? is true."
+ (define (q-for-each f q)
+ (while (not (q-empty? q))
+ (f (deq! q))))
+ (define queue (make-q))
+ (define (put-char c)
+ (enq! queue c))
+ (define (put-string s)
+ (string-for-each (lambda (c) (enq! queue c))
+ s))
+ (define (flush)
+ ;; It is important that we do _not_ write a chunk if the queue is
+ ;; empty, since it will be treated as the final chunk.
+ (unless (q-empty? queue)
+ (let ((len (q-length queue)))
+ (display (number->string len 16) port)
+ (display "\r\n" port)
+ (q-for-each (lambda (elem) (write-char elem port))
+ queue)
+ (display "\r\n" port))))
+ (define (close)
+ (flush)
+ (display "0\r\n" port)
+ (force-output port)
+ (unless keep-alive?
+ (close-port port)))
+ (make-soft-port (vector put-char put-string flush #f close) "w"))
+
+(define %http-proxy-port? (make-object-property))
+(define (http-proxy-port? port) (%http-proxy-port? port))
+(define (set-http-proxy-port?! port flag)
+ (set! (%http-proxy-port? port) flag))
+;;; HTTP request objects
+
+;; Copyright (C) 2010, 2011, 2012 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Code_
+
+(define-module (web request)
+ #\use-module (rnrs bytevectors)
+ #\use-module (ice-9 binary-ports)
+ #\use-module (ice-9 rdelim)
+ #\use-module (srfi srfi-9)
+ #\use-module (web uri)
+ #\use-module (web http)
+ #\export (request?
+ request-method
+ request-uri
+ request-version
+ request-headers
+ request-meta
+ request-port
+
+ read-request
+ build-request
+ write-request
+
+ read-request-body
+ write-request-body
+
+ ;; General headers
+ ;;
+ request-cache-control
+ request-connection
+ request-date
+ request-pragma
+ request-trailer
+ request-transfer-encoding
+ request-upgrade
+ request-via
+ request-warning
+
+ ;; Entity headers
+ ;;
+ request-allow
+ request-content-encoding
+ request-content-language
+ request-content-length
+ request-content-location
+ request-content-md5
+ request-content-range
+ request-content-type
+ request-expires
+ request-last-modified
+
+ ;; Request headers
+ ;;
+ request-accept
+ request-accept-charset
+ request-accept-encoding
+ request-accept-language
+ request-authorization
+ request-expect
+ request-from
+ request-host
+ request-if-match
+ request-if-modified-since
+ request-if-none-match
+ request-if-range
+ request-if-unmodified-since
+ request-max-forwards
+ request-proxy-authorization
+ request-range
+ request-referer
+ request-te
+ request-user-agent
+
+ ;; Misc
+ request-absolute-uri))
+
+
+;;; {Character Encodings, Strings, and Bytevectors}
+;;;
+;;; Requests are read from over the wire, and as such have to be treated
+;;; very carefully.
+;;;
+;;; The header portion of the message is defined to be in a subset of
+;;; ASCII, and may be processed either byte-wise (using bytevectors and
+;;; binary I/O) or as characters in a single-byte ASCII-compatible
+;;; encoding.
+;;;
+;;; We choose the latter, processing as strings in the latin-1
+;;; encoding. This allows us to use all the read-delimited machinery,
+;;; character sets, and regular expressions, shared substrings, etc.
+;;;
+;;; The characters in the header values may themselves encode other
+;;; bytes or characters -- basically each header has its own parser. We
+;;; leave that as a header-specific topic.
+;;;
+;;; The body is present if the content-length header is present. Its
+;;; format and, if textual, encoding is determined by the headers, but
+;;; its length is encoded in bytes. So we just slurp that number of
+;;; characters in latin-1, knowing that the number of characters
+;;; corresponds to the number of bytes, and then convert to a
+;;; bytevector, perhaps for later decoding.
+;;;
+
+(define-record-type <request>
+ (make-request method uri version headers meta port)
+ request?
+ (method request-method)
+ (uri request-uri)
+ (version request-version)
+ (headers request-headers)
+ (meta request-meta)
+ (port request-port))
+
+(define (bad-request message . args)
+ (throw 'bad-request message args))
+
+(define (bad-request-printer port key args default-printer)
+ (apply (case-lambda
+ ((msg args)
+ (display "Bad request_ " port)
+ (apply format port msg args)
+ (newline port))
+ (_ (default-printer)))
+ args))
+
+(set-exception-printer! 'bad-request bad-request-printer)
+
+(define (non-negative-integer? n)
+ (and (number? n) (>= n 0) (exact? n) (integer? n)))
+
+(define (validate-headers headers)
+ (if (pair? headers)
+ (let ((h (car headers)))
+ (if (pair? h)
+ (let ((k (car h)) (v (cdr h)))
+ (if (valid-header? k v)
+ (validate-headers (cdr headers))
+ (bad-request "Bad value for header ~a_ ~s" k v)))
+ (bad-request "Header not a pair_ ~a" h)))
+ (if (not (null? headers))
+ (bad-request "Headers not a list_ ~a" headers))))
+
+(define* (build-request uri #\key (method 'GET) (version '(1 . 1))
+ (headers '()) port (meta '())
+ (validate-headers? #t))
+ "Construct an HTTP request object. If VALIDATE-HEADERS? is true,
+the headers are each run through their respective validators."
+ (let ((needs-host? (and (equal? version '(1 . 1))
+ (not (assq-ref headers 'host)))))
+ (cond
+ ((not (and (pair? version)
+ (non-negative-integer? (car version))
+ (non-negative-integer? (cdr version))))
+ (bad-request "Bad version_ ~a" version))
+ ((not (uri? uri))
+ (bad-request "Bad uri_ ~a" uri))
+ ((and (not port) (memq method '(POST PUT)))
+ (bad-request "Missing port for message ~a" method))
+ ((not (list? meta))
+ (bad-request "Bad metadata alist" meta))
+ ((and needs-host? (not (uri-host uri)))
+ (bad-request "HTTP/1.1 request without Host header and no host in URI_ ~a"
+ uri))
+ (else
+ (if validate-headers?
+ (validate-headers headers))))
+ (make-request method uri version
+ (if needs-host?
+ (acons 'host (cons (uri-host uri) (uri-port uri))
+ headers)
+ headers)
+ meta port)))
+
+(define* (read-request port #\optional (meta '()))
+ "Read an HTTP request from PORT, optionally attaching the given
+metadata, META.
+
+As a side effect, sets the encoding on PORT to
+ISO-8859-1 (latin-1), so that reading one character reads one byte. See
+the discussion of character sets in \"HTTP Requests\" in the manual, for
+more information.
+
+Note that the body is not part of the request. Once you have read a
+request, you may read the body separately, and likewise for writing
+requests."
+ (set-port-encoding! port "ISO-8859-1")
+ (call-with-values (lambda () (read-request-line port))
+ (lambda (method uri version)
+ (make-request method uri version (read-headers port) meta port))))
+
+;; FIXME_ really return a new request?
+(define (write-request r port)
+ "Write the given HTTP request to PORT.
+
+Return a new request, whose ‘request-port’ will continue writing
+on PORT, perhaps using some transfer encoding."
+ (write-request-line (request-method r) (request-uri r)
+ (request-version r) port)
+ (write-headers (request-headers r) port)
+ (display "\r\n" port)
+ (if (eq? port (request-port r))
+ r
+ (make-request (request-method r) (request-uri r) (request-version r)
+ (request-headers r) (request-meta r) port)))
+
+(define (read-request-body r)
+ "Reads the request body from R, as a bytevector. Return ‘#f’
+if there was no request body."
+ (let ((nbytes (request-content-length r)))
+ (and nbytes
+ (let ((bv (get-bytevector-n (request-port r) nbytes)))
+ (if (= (bytevector-length bv) nbytes)
+ bv
+ (bad-request "EOF while reading request body_ ~a bytes of ~a"
+ (bytevector-length bv) nbytes))))))
+
+(define (write-request-body r bv)
+ "Write BV, a bytevector, to the port corresponding to the HTTP
+request R."
+ (put-bytevector (request-port r) bv))
+
+(define-syntax define-request-accessor
+ (lambda (x)
+ (syntax-case x ()
+ ((_ field)
+ #'(define-request-accessor field #f))
+ ((_ field def) (identifier? #'field)
+ #`(define* (#,(datum->syntax
+ #'field
+ (symbol-append 'request- (syntax->datum #'field)))
+ request
+ #\optional (default def))
+ (cond
+ ((assq 'field (request-headers request)) => cdr)
+ (else default)))))))
+
+;; General headers
+;;
+(define-request-accessor cache-control '())
+(define-request-accessor connection '())
+(define-request-accessor date #f)
+(define-request-accessor pragma '())
+(define-request-accessor trailer '())
+(define-request-accessor transfer-encoding '())
+(define-request-accessor upgrade '())
+(define-request-accessor via '())
+(define-request-accessor warning '())
+
+;; Entity headers
+;;
+(define-request-accessor allow '())
+(define-request-accessor content-encoding '())
+(define-request-accessor content-language '())
+(define-request-accessor content-length #f)
+(define-request-accessor content-location #f)
+(define-request-accessor content-md5 #f)
+(define-request-accessor content-range #f)
+(define-request-accessor content-type #f)
+(define-request-accessor expires #f)
+(define-request-accessor last-modified #f)
+
+;; Request headers
+;;
+(define-request-accessor accept '())
+(define-request-accessor accept-charset '())
+(define-request-accessor accept-encoding '())
+(define-request-accessor accept-language '())
+(define-request-accessor authorization #f)
+(define-request-accessor expect '())
+(define-request-accessor from #f)
+(define-request-accessor host #f)
+;; Absence of an if-directive appears to be different from `*'.
+(define-request-accessor if-match #f)
+(define-request-accessor if-modified-since #f)
+(define-request-accessor if-none-match #f)
+(define-request-accessor if-range #f)
+(define-request-accessor if-unmodified-since #f)
+(define-request-accessor max-forwards #f)
+(define-request-accessor proxy-authorization #f)
+(define-request-accessor range #f)
+(define-request-accessor referer #f)
+(define-request-accessor te '())
+(define-request-accessor user-agent #f)
+
+;; Misc accessors
+(define* (request-absolute-uri r #\optional default-host default-port)
+ "A helper routine to determine the absolute URI of a request, using the
+‘host’ header and the default host and port."
+ (let ((uri (request-uri r)))
+ (if (uri-host uri)
+ uri
+ (let ((host
+ (or (request-host r)
+ (if default-host
+ (cons default-host default-port)
+ (bad-request
+ "URI not absolute, no Host header, and no default_ ~s"
+ uri)))))
+ (build-uri (uri-scheme uri)
+ #\host (car host)
+ #\port (cdr host)
+ #\path (uri-path uri)
+ #\query (uri-query uri)
+ #\fragment (uri-fragment uri))))))
+;;; HTTP response objects
+
+;; Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Code_
+
+(define-module (web response)
+ #\use-module (rnrs bytevectors)
+ #\use-module (ice-9 binary-ports)
+ #\use-module (ice-9 rdelim)
+ #\use-module (ice-9 match)
+ #\use-module (srfi srfi-9)
+ #\use-module (web http)
+ #\export (response?
+ response-version
+ response-code
+ response-reason-phrase
+ response-headers
+ response-port
+ read-response
+ build-response
+ adapt-response-version
+ write-response
+
+ response-must-not-include-body?
+ response-body-port
+ read-response-body
+ write-response-body
+
+ ;; General headers
+ ;;
+ response-cache-control
+ response-connection
+ response-date
+ response-pragma
+ response-trailer
+ response-transfer-encoding
+ response-upgrade
+ response-via
+ response-warning
+
+ ;; Entity headers
+ ;;
+ response-allow
+ response-content-encoding
+ response-content-language
+ response-content-length
+ response-content-location
+ response-content-md5
+ response-content-range
+ response-content-type
+ text-content-type?
+ response-expires
+ response-last-modified
+
+ ;; Response headers
+ ;;
+ response-accept-ranges
+ response-age
+ response-etag
+ response-location
+ response-proxy-authenticate
+ response-retry-after
+ response-server
+ response-vary
+ response-www-authenticate))
+
+
+(define-record-type <response>
+ (make-response version code reason-phrase headers port)
+ response?
+ (version response-version)
+ (code response-code)
+ (reason-phrase %response-reason-phrase)
+ (headers response-headers)
+ (port response-port))
+
+(define (bad-response message . args)
+ (throw 'bad-response message args))
+
+(define (non-negative-integer? n)
+ (and (number? n) (>= n 0) (exact? n) (integer? n)))
+
+(define (validate-headers headers)
+ (if (pair? headers)
+ (let ((h (car headers)))
+ (if (pair? h)
+ (let ((k (car h)) (v (cdr h)))
+ (if (valid-header? k v)
+ (validate-headers (cdr headers))
+ (bad-response "Bad value for header ~a_ ~s" k v)))
+ (bad-response "Header not a pair_ ~a" h)))
+ (if (not (null? headers))
+ (bad-response "Headers not a list_ ~a" headers))))
+
+(define* (build-response #\key (version '(1 . 1)) (code 200) reason-phrase
+ (headers '()) port (validate-headers? #t))
+ "Construct an HTTP response object. If VALIDATE-HEADERS? is true,
+the headers are each run through their respective validators."
+ (cond
+ ((not (and (pair? version)
+ (non-negative-integer? (car version))
+ (non-negative-integer? (cdr version))))
+ (bad-response "Bad version_ ~a" version))
+ ((not (and (non-negative-integer? code) (< code 600)))
+ (bad-response "Bad code_ ~a" code))
+ ((and reason-phrase (not (string? reason-phrase)))
+ (bad-response "Bad reason phrase" reason-phrase))
+ (else
+ (if validate-headers?
+ (validate-headers headers))))
+ (make-response version code reason-phrase headers port))
+
+(define *reason-phrases*
+ '((100 . "Continue")
+ (101 . "Switching Protocols")
+ (200 . "OK")
+ (201 . "Created")
+ (202 . "Accepted")
+ (203 . "Non-Authoritative Information")
+ (204 . "No Content")
+ (205 . "Reset Content")
+ (206 . "Partial Content")
+ (300 . "Multiple Choices")
+ (301 . "Moved Permanently")
+ (302 . "Found")
+ (303 . "See Other")
+ (304 . "Not Modified")
+ (305 . "Use Proxy")
+ (307 . "Temporary Redirect")
+ (400 . "Bad Request")
+ (401 . "Unauthorized")
+ (402 . "Payment Required")
+ (403 . "Forbidden")
+ (404 . "Not Found")
+ (405 . "Method Not Allowed")
+ (406 . "Not Acceptable")
+ (407 . "Proxy Authentication Required")
+ (408 . "Request Timeout")
+ (409 . "Conflict")
+ (410 . "Gone")
+ (411 . "Length Required")
+ (412 . "Precondition Failed")
+ (413 . "Request Entity Too Large")
+ (414 . "Request-URI Too Long")
+ (415 . "Unsupported Media Type")
+ (416 . "Requested Range Not Satisfiable")
+ (417 . "Expectation Failed")
+ (500 . "Internal Server Error")
+ (501 . "Not Implemented")
+ (502 . "Bad Gateway")
+ (503 . "Service Unavailable")
+ (504 . "Gateway Timeout")
+ (505 . "HTTP Version Not Supported")))
+
+(define (code->reason-phrase code)
+ (or (assv-ref *reason-phrases* code)
+ "(Unknown)"))
+
+(define (response-reason-phrase response)
+ "Return the reason phrase given in RESPONSE, or the standard
+reason phrase for the response's code."
+ (or (%response-reason-phrase response)
+ (code->reason-phrase (response-code response))))
+
+(define (text-content-type? type)
+ "Return #t if TYPE, a symbol as returned by `response-content-type',
+represents a textual type such as `text/plain'."
+ (let ((type (symbol->string type)))
+ (or (string-prefix? "text/" type)
+ (string-suffix? "/xml" type)
+ (string-suffix? "+xml" type))))
+
+(define (read-response port)
+ "Read an HTTP response from PORT.
+
+As a side effect, sets the encoding on PORT to
+ISO-8859-1 (latin-1), so that reading one character reads one byte. See
+the discussion of character sets in \"HTTP Responses\" in the manual,
+for more information."
+ (set-port-encoding! port "ISO-8859-1")
+ (call-with-values (lambda () (read-response-line port))
+ (lambda (version code reason-phrase)
+ (make-response version code reason-phrase (read-headers port) port))))
+
+(define (adapt-response-version response version)
+ "Adapt the given response to a different HTTP version. Returns a new
+HTTP response.
+
+The idea is that many applications might just build a response for the
+default HTTP version, and this method could handle a number of
+programmatic transformations to respond to older HTTP versions (0.9 and
+1.0). But currently this function is a bit heavy-handed, just updating
+the version field."
+ (build-response #\code (response-code response)
+ #\version version
+ #\headers (response-headers response)
+ #\port (response-port response)))
+
+(define (write-response r port)
+ "Write the given HTTP response to PORT.
+
+Returns a new response, whose ‘response-port’ will continue writing
+on PORT, perhaps using some transfer encoding."
+ (write-response-line (response-version r) (response-code r)
+ (response-reason-phrase r) port)
+ (write-headers (response-headers r) port)
+ (display "\r\n" port)
+ (if (eq? port (response-port r))
+ r
+ (make-response (response-version r) (response-code r)
+ (response-reason-phrase r) (response-headers r) port)))
+
+(define (response-must-not-include-body? r)
+ "Returns ‘#t’ if the response R is not permitted to have a body.
+
+This is true for some response types, like those with code 304."
+ ;; RFC 2616, section 4.3.
+ (or (<= 100 (response-code r) 199)
+ (= (response-code r) 204)
+ (= (response-code r) 304)))
+
+(define (make-delimited-input-port port len keep-alive?)
+ "Return an input port that reads from PORT, and makes sure that
+exactly LEN bytes are available from PORT. Closing the returned port
+closes PORT, unless KEEP-ALIVE? is true."
+ (define bytes-read 0)
+
+ (define (fail)
+ (bad-response "EOF while reading response body_ ~a bytes of ~a"
+ bytes-read len))
+
+ (define (read! bv start count)
+ ;; Read at most LEN bytes in total. HTTP/1.1 doesn't say what to do
+ ;; when a server provides more than the Content-Length, but it seems
+ ;; wise to just stop reading at LEN.
+ (let ((count (min count (- len bytes-read))))
+ (let loop ((ret (get-bytevector-n! port bv start count)))
+ (cond ((eof-object? ret)
+ (if (= bytes-read len)
+ 0 ; EOF
+ (fail)))
+ ((and (zero? ret) (> count 0))
+ ;; Do not return zero since zero means EOF, so try again.
+ (loop (get-bytevector-n! port bv start count)))
+ (else
+ (set! bytes-read (+ bytes-read ret))
+ ret)))))
+
+ (define close
+ (and (not keep-alive?)
+ (lambda ()
+ (close-port port))))
+
+ (make-custom-binary-input-port "delimited input port" read! #f #f close))
+
+(define* (response-body-port r #\key (decode? #t) (keep-alive? #t))
+ "Return an input port from which the body of R can be read. The
+encoding of the returned port is set according to R's ‘content-type’
+header, when it's textual, except if DECODE? is ‘#f’. Return #f when
+no body is available.
+
+When KEEP-ALIVE? is ‘#f’, closing the returned port also closes R's
+response port."
+ (define port
+ (cond
+ ((member '(chunked) (response-transfer-encoding r))
+ (make-chunked-input-port (response-port r)
+ #\keep-alive? keep-alive?))
+ ((response-content-length r)
+ => (lambda (len)
+ (make-delimited-input-port (response-port r)
+ len keep-alive?)))
+ ((response-must-not-include-body? r)
+ #f)
+ ((or (memq 'close (response-connection r))
+ (and (equal? (response-version r) '(1 . 0))
+ (not (memq 'keep-alive (response-connection r)))))
+ (response-port r))
+ (else
+ ;; Here we have a message with no transfer encoding, no
+ ;; content-length, and a response that won't necessarily be closed
+ ;; by the server. Not much we can do; assume that the client
+ ;; knows how to handle it.
+ (response-port r))))
+
+ (when (and decode? port)
+ (match (response-content-type r)
+ (((? text-content-type?) . props)
+ (set-port-encoding! port
+ (or (assq-ref props 'charset)
+ "ISO-8859-1")))
+ (_ #f)))
+
+ port)
+
+(define (read-response-body r)
+ "Reads the response body from R, as a bytevector. Returns
+‘#f’ if there was no response body."
+ (let ((body (and=> (response-body-port r #\decode? #f)
+ get-bytevector-all)))
+ ;; Reading a body of length 0 will result in get-bytevector-all
+ ;; returning the EOF object.
+ (if (eof-object? body)
+ #vu8()
+ body)))
+
+(define (write-response-body r bv)
+ "Write BV, a bytevector, to the port corresponding to the HTTP
+response R."
+ (put-bytevector (response-port r) bv))
+
+(define-syntax define-response-accessor
+ (lambda (x)
+ (syntax-case x ()
+ ((_ field)
+ #'(define-response-accessor field #f))
+ ((_ field def) (identifier? #'field)
+ #`(define* (#,(datum->syntax
+ #'field
+ (symbol-append 'response- (syntax->datum #'field)))
+ response
+ #\optional (default def))
+ (cond
+ ((assq 'field (response-headers response)) => cdr)
+ (else default)))))))
+
+;; General headers
+;;
+(define-response-accessor cache-control '())
+(define-response-accessor connection '())
+(define-response-accessor date #f)
+(define-response-accessor pragma '())
+(define-response-accessor trailer '())
+(define-response-accessor transfer-encoding '())
+(define-response-accessor upgrade '())
+(define-response-accessor via '())
+(define-response-accessor warning '())
+
+;; Entity headers
+;;
+(define-response-accessor allow '())
+(define-response-accessor content-encoding '())
+(define-response-accessor content-language '())
+(define-response-accessor content-length #f)
+(define-response-accessor content-location #f)
+(define-response-accessor content-md5 #f)
+(define-response-accessor content-range #f)
+(define-response-accessor content-type #f)
+(define-response-accessor expires #f)
+(define-response-accessor last-modified #f)
+
+;; Response headers
+;;
+(define-response-accessor accept-ranges #f)
+(define-response-accessor age #f)
+(define-response-accessor etag #f)
+(define-response-accessor location #f)
+(define-response-accessor proxy-authenticate #f)
+(define-response-accessor retry-after #f)
+(define-response-accessor server #f)
+(define-response-accessor vary '())
+(define-response-accessor www-authenticate #f)
+;;; Web server
+
+;; Copyright (C) 2010, 2011, 2012, 2013, 2015 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Commentary_
+;;;
+;;; (web server) is a generic web server interface, along with a main
+;;; loop implementation for web servers controlled by Guile.
+;;;
+;;; The lowest layer is the <server-impl> object, which defines a set of
+;;; hooks to open a server, read a request from a client, write a
+;;; response to a client, and close a server. These hooks -- open,
+;;; read, write, and close, respectively -- are bound together in a
+;;; <server-impl> object. Procedures in this module take a
+;;; <server-impl> object, if needed.
+;;;
+;;; A <server-impl> may also be looked up by name. If you pass the
+;;; `http' symbol to `run-server', Guile looks for a variable named
+;;; `http' in the `(web server http)' module, which should be bound to a
+;;; <server-impl> object. Such a binding is made by instantiation of
+;;; the `define-server-impl' syntax. In this way the run-server loop can
+;;; automatically load other backends if available.
+;;;
+;;; The life cycle of a server goes as follows_
+;;;
+;;; * The `open' hook is called, to open the server. `open' takes 0 or
+;;; more arguments, depending on the backend, and returns an opaque
+;;; server socket object, or signals an error.
+;;;
+;;; * The `read' hook is called, to read a request from a new client.
+;;; The `read' hook takes one arguments, the server socket. It
+;;; should return three values_ an opaque client socket, the
+;;; request, and the request body. The request should be a
+;;; `<request>' object, from `(web request)'. The body should be a
+;;; string or a bytevector, or `#f' if there is no body.
+;;;
+;;; If the read failed, the `read' hook may return #f for the client
+;;; socket, request, and body.
+;;;
+;;; * A user-provided handler procedure is called, with the request
+;;; and body as its arguments. The handler should return two
+;;; values_ the response, as a `<response>' record from `(web
+;;; response)', and the response body as a string, bytevector, or
+;;; `#f' if not present. We also allow the reponse to be simply an
+;;; alist of headers, in which case a default response object is
+;;; constructed with those headers.
+;;;
+;;; * The `write' hook is called with three arguments_ the client
+;;; socket, the response, and the body. The `write' hook returns no
+;;; values.
+;;;
+;;; * At this point the request handling is complete. For a loop, we
+;;; loop back and try to read a new request.
+;;;
+;;; * If the user interrupts the loop, the `close' hook is called on
+;;; the server socket.
+;;;
+;;; Code_
+
+(define-module (web server)
+ #\use-module (srfi srfi-9)
+ #\use-module (srfi srfi-9 gnu)
+ #\use-module (rnrs bytevectors)
+ #\use-module (ice-9 binary-ports)
+ #\use-module (web request)
+ #\use-module (web response)
+ #\use-module (system repl error-handling)
+ #\use-module (ice-9 control)
+ #\use-module (ice-9 iconv)
+ #\export (define-server-impl
+ lookup-server-impl
+
+ make-server-impl
+ server-impl?
+ server-impl-name
+ server-impl-open
+ server-impl-read
+ server-impl-write
+ server-impl-close
+
+ open-server
+ read-client
+ handle-request
+ sanitize-response
+ write-client
+ close-server
+ serve-one-client
+ run-server))
+
+(define *timer* (gettimeofday))
+(define (print-elapsed who)
+ (let ((t (gettimeofday)))
+ (pk who (+ (* (- (car t) (car *timer*)) 1000000)
+ (- (cdr t) (cdr *timer*))))
+ (set! *timer* t)))
+
+(eval-when (expand)
+ (define *time-debug?* #f))
+
+(define-syntax debug-elapsed
+ (lambda (x)
+ (syntax-case x ()
+ ((_ who)
+ (if *time-debug?*
+ #'(print-elapsed who)
+ #'*unspecified*)))))
+
+(define-record-type server-impl
+ (make-server-impl name open read write close)
+ server-impl?
+ (name server-impl-name)
+ (open server-impl-open)
+ (read server-impl-read)
+ (write server-impl-write)
+ (close server-impl-close))
+
+(define-syntax-rule (define-server-impl name open read write close)
+ (define name
+ (make-server-impl 'name open read write close)))
+
+(define (lookup-server-impl impl)
+ "Look up a server implementation. If IMPL is a server
+implementation already, it is returned directly. If it is a symbol, the
+binding named IMPL in the ‘(web server IMPL)’ module is
+looked up. Otherwise an error is signaled.
+
+Currently a server implementation is a somewhat opaque type, useful only
+for passing to other procedures in this module, like
+‘read-client’."
+ (cond
+ ((server-impl? impl) impl)
+ ((symbol? impl)
+ (let ((impl (module-ref (resolve-module `(web server ,impl)) impl)))
+ (if (server-impl? impl)
+ impl
+ (error "expected a server impl in module" `(web server ,impl)))))
+ (else
+ (error "expected a server-impl or a symbol" impl))))
+
+;; -> server
+(define (open-server impl open-params)
+ "Open a server for the given implementation. Return one value, the
+new server object. The implementation's ‘open’ procedure is
+applied to OPEN-PARAMS, which should be a list."
+ (apply (server-impl-open impl) open-params))
+
+;; -> (client request body | #f #f #f)
+(define (read-client impl server)
+ "Read a new client from SERVER, by applying the implementation's
+‘read’ procedure to the server. If successful, return three
+values_ an object corresponding to the client, a request object, and the
+request body. If any exception occurs, return ‘#f’ for all three
+values."
+ (call-with-error-handling
+ (lambda ()
+ ((server-impl-read impl) server))
+ #\pass-keys '(quit interrupt)
+ #\on-error (if (batch-mode?) 'backtrace 'debug)
+ #\post-error (lambda _ (values #f #f #f))))
+
+(define (extend-response r k v . additional)
+ (let ((r (set-field r (response-headers)
+ (assoc-set! (copy-tree (response-headers r))
+ k v))))
+ (if (null? additional)
+ r
+ (apply extend-response r additional))))
+
+;; -> response body
+(define (sanitize-response request response body)
+ "\"Sanitize\" the given response and body, making them appropriate for
+the given request.
+
+As a convenience to web handler authors, RESPONSE may be given as
+an alist of headers, in which case it is used to construct a default
+response. Ensures that the response version corresponds to the request
+version. If BODY is a string, encodes the string to a bytevector,
+in an encoding appropriate for RESPONSE. Adds a
+‘content-length’ and ‘content-type’ header, as necessary.
+
+If BODY is a procedure, it is called with a port as an argument,
+and the output collected as a bytevector. In the future we might try to
+instead use a compressing, chunk-encoded port, and call this procedure
+later, in the write-client procedure. Authors are advised not to rely
+on the procedure being called at any particular time."
+ (cond
+ ((list? response)
+ (sanitize-response request
+ (build-response #\version (request-version request)
+ #\headers response)
+ body))
+ ((not (equal? (request-version request) (response-version response)))
+ (sanitize-response request
+ (adapt-response-version response
+ (request-version request))
+ body))
+ ((not body)
+ (values response #vu8()))
+ ((string? body)
+ (let* ((type (response-content-type response
+ '(text/plain)))
+ (declared-charset (assq-ref (cdr type) 'charset))
+ (charset (or declared-charset "utf-8")))
+ (sanitize-response
+ request
+ (if declared-charset
+ response
+ (extend-response response 'content-type
+ `(,@type (charset . ,charset))))
+ (string->bytevector body charset))))
+ ((procedure? body)
+ (let* ((type (response-content-type response
+ '(text/plain)))
+ (declared-charset (assq-ref (cdr type) 'charset))
+ (charset (or declared-charset "utf-8")))
+ (sanitize-response
+ request
+ (if declared-charset
+ response
+ (extend-response response 'content-type
+ `(,@type (charset . ,charset))))
+ (call-with-encoded-output-string charset body))))
+ ((not (bytevector? body))
+ (error "unexpected body type"))
+ ((and (response-must-not-include-body? response)
+ body
+ ;; FIXME make this stricter_ even an empty body should be prohibited.
+ (not (zero? (bytevector-length body))))
+ (error "response with this status code must not include body" response))
+ (else
+ ;; check length; assert type; add other required fields?
+ (values (let ((rlen (response-content-length response))
+ (blen (bytevector-length body)))
+ (cond
+ (rlen (if (= rlen blen)
+ response
+ (error "bad content-length" rlen blen)))
+ (else (extend-response response 'content-length blen))))
+ (if (eq? (request-method request) 'HEAD)
+ ;; Responses to HEAD requests must not include bodies.
+ ;; We could raise an error here, but it seems more
+ ;; appropriate to just do something sensible.
+ #f
+ body)))))
+
+;; -> response body state
+(define (handle-request handler request body state)
+ "Handle a given request, returning the response and body.
+
+The response and response body are produced by calling the given
+HANDLER with REQUEST and BODY as arguments.
+
+The elements of STATE are also passed to HANDLER as
+arguments, and may be returned as additional values. The new
+STATE, collected from the HANDLER's return values, is then
+returned as a list. The idea is that a server loop receives a handler
+from the user, along with whatever state values the user is interested
+in, allowing the user's handler to explicitly manage its state."
+ (call-with-error-handling
+ (lambda ()
+ (call-with-values (lambda ()
+ (with-stack-and-prompt
+ (lambda ()
+ (apply handler request body state))))
+ (lambda (response body . state)
+ (call-with-values (lambda ()
+ (debug-elapsed 'handler)
+ (sanitize-response request response body))
+ (lambda (response body)
+ (debug-elapsed 'sanitize)
+ (values response body state))))))
+ #\pass-keys '(quit interrupt)
+ #\on-error (if (batch-mode?) 'backtrace 'debug)
+ #\post-error (lambda _
+ (values (build-response #\code 500) #f state))))
+
+;; -> unspecified values
+(define (write-client impl server client response body)
+ "Write an HTTP response and body to CLIENT. If the server and
+client support persistent connections, it is the implementation's
+responsibility to keep track of the client thereafter, presumably by
+attaching it to the SERVER argument somehow."
+ (call-with-error-handling
+ (lambda ()
+ ((server-impl-write impl) server client response body))
+ #\pass-keys '(quit interrupt)
+ #\on-error (if (batch-mode?) 'backtrace 'debug)
+ #\post-error (lambda _ (values))))
+
+;; -> unspecified values
+(define (close-server impl server)
+ "Release resources allocated by a previous invocation of
+‘open-server’."
+ ((server-impl-close impl) server))
+
+(define call-with-sigint
+ (if (not (provided? 'posix))
+ (lambda (thunk handler-thunk) (thunk))
+ (lambda (thunk handler-thunk)
+ (let ((handler #f))
+ (catch 'interrupt
+ (lambda ()
+ (dynamic-wind
+ (lambda ()
+ (set! handler
+ (sigaction SIGINT (lambda (sig) (throw 'interrupt)))))
+ thunk
+ (lambda ()
+ (if handler
+ ;; restore Scheme handler, SIG_IGN or SIG_DFL.
+ (sigaction SIGINT (car handler) (cdr handler))
+ ;; restore original C handler.
+ (sigaction SIGINT #f)))))
+ (lambda (k . _) (handler-thunk)))))))
+
+(define (with-stack-and-prompt thunk)
+ (call-with-prompt (default-prompt-tag)
+ (lambda () (start-stack #t (thunk)))
+ (lambda (k proc)
+ (with-stack-and-prompt (lambda () (proc k))))))
+
+;; -> new-state
+(define (serve-one-client handler impl server state)
+ "Read one request from SERVER, call HANDLER on the request
+and body, and write the response to the client. Return the new state
+produced by the handler procedure."
+ (debug-elapsed 'serve-again)
+ (call-with-values
+ (lambda ()
+ (read-client impl server))
+ (lambda (client request body)
+ (debug-elapsed 'read-client)
+ (if client
+ (call-with-values
+ (lambda ()
+ (handle-request handler request body state))
+ (lambda (response body state)
+ (debug-elapsed 'handle-request)
+ (write-client impl server client response body)
+ (debug-elapsed 'write-client)
+ state))
+ state))))
+
+(define* (run-server handler #\optional (impl 'http) (open-params '())
+ . state)
+ "Run Guile's built-in web server.
+
+HANDLER should be a procedure that takes two or more arguments,
+the HTTP request and request body, and returns two or more values, the
+response and response body.
+
+For example, here is a simple \"Hello, World!\" server_
+
+@example
+ (define (handler request body)
+ (values '((content-type . (text/plain)))
+ \"Hello, World!\"))
+ (run-server handler)
+@end example
+
+The response and body will be run through ‘sanitize-response’
+before sending back to the client.
+
+Additional arguments to HANDLER are taken from
+STATE. Additional return values are accumulated into a new
+STATE, which will be used for subsequent requests. In this way a
+handler can explicitly manage its state.
+
+The default server implementation is ‘http’, which accepts
+OPEN-PARAMS like ‘(#:port 8081)’, among others. See \"Web
+Server\" in the manual, for more information."
+ (let* ((impl (lookup-server-impl impl))
+ (server (open-server impl open-params)))
+ (call-with-sigint
+ (lambda ()
+ (let lp ((state state))
+ (lp (serve-one-client handler impl server state))))
+ (lambda ()
+ (close-server impl server)
+ (values)))))
+;;; Web I/O_ HTTP
+
+;; Copyright (C) 2010, 2011, 2012, 2015 Free Software Foundation, Inc.
+
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301 USA
+
+;;; Commentary_
+;;;
+;;; This is the HTTP implementation of the (web server) interface.
+;;;
+;;; `read-request' sets the character encoding on the new port to
+;;; latin-1. See the note in request.scm regarding character sets,
+;;; strings, and bytevectors for more information.
+;;;
+;;; Code_
+
+(define-module (web server http)
+ #\use-module ((srfi srfi-1) #\select (fold))
+ #\use-module (srfi srfi-9)
+ #\use-module (rnrs bytevectors)
+ #\use-module (web request)
+ #\use-module (web response)
+ #\use-module (web server)
+ #\use-module (ice-9 poll)
+ #\export (http))
+
+
+(define (make-default-socket family addr port)
+ (let ((sock (socket PF_INET SOCK_STREAM 0)))
+ (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
+ (bind sock family addr port)
+ sock))
+
+(define-record-type <http-server>
+ (make-http-server socket poll-idx poll-set)
+ http-server?
+ (socket http-socket)
+ (poll-idx http-poll-idx set-http-poll-idx!)
+ (poll-set http-poll-set))
+
+(define *error-events* (logior POLLHUP POLLERR))
+(define *read-events* POLLIN)
+(define *events* (logior *error-events* *read-events*))
+
+;; -> server
+(define* (http-open #\key
+ (host #f)
+ (family AF_INET)
+ (addr (if host
+ (inet-pton family host)
+ INADDR_LOOPBACK))
+ (port 8080)
+ (socket (make-default-socket family addr port)))
+ (listen socket 128)
+ (sigaction SIGPIPE SIG_IGN)
+ (let ((poll-set (make-empty-poll-set)))
+ (poll-set-add! poll-set socket *events*)
+ (make-http-server socket 0 poll-set)))
+
+(define (bad-request port)
+ (write-response (build-response #\version '(1 . 0) #\code 400
+ #\headers '((content-length . 0)))
+ port))
+
+;; -> (client request body | #f #f #f)
+(define (http-read server)
+ (let* ((poll-set (http-poll-set server)))
+ (let lp ((idx (http-poll-idx server)))
+ (let ((revents (poll-set-revents poll-set idx)))
+ (cond
+ ((zero? idx)
+ ;; The server socket, and the end of our downward loop.
+ (cond
+ ((zero? revents)
+ ;; No client ready, and no error; poll and loop.
+ (poll poll-set)
+ (lp (1- (poll-set-nfds poll-set))))
+ ((not (zero? (logand revents *error-events*)))
+ ;; An error.
+ (set-http-poll-idx! server idx)
+ (throw 'interrupt))
+ (else
+ ;; A new client. Add to set, poll, and loop.
+ ;;
+ ;; FIXME_ preserve meta-info.
+ (let ((client (accept (poll-set-port poll-set idx))))
+ ;; Buffer input and output on this port.
+ (setvbuf (car client) _IOFBF)
+ ;; From "HOP, A Fast Server for the Diffuse Web", Serrano.
+ (setsockopt (car client) SOL_SOCKET SO_SNDBUF (* 12 1024))
+ (poll-set-add! poll-set (car client) *events*)
+ (poll poll-set)
+ (lp (1- (poll-set-nfds poll-set)))))))
+ ((zero? revents)
+ ;; Nothing on this port.
+ (lp (1- idx)))
+ ;; Otherwise, a client socket with some activity on
+ ;; it. Remove it from the poll set.
+ (else
+ (let ((port (poll-set-remove! poll-set idx)))
+ ;; Record the next index in all cases, in case the EOF check
+ ;; throws an error.
+ (set-http-poll-idx! server (1- idx))
+ (cond
+ ((eof-object? (peek-char port))
+ ;; EOF.
+ (close-port port)
+ (lp (1- idx)))
+ (else
+ ;; Otherwise, try to read a request from this port.
+ (with-throw-handler
+ #t
+ (lambda ()
+ (let ((req (read-request port)))
+ (values port
+ req
+ (read-request-body req))))
+ (lambda (k . args)
+ (define-syntax-rule (cleanup-catch statement)
+ (catch #t
+ (lambda () statement)
+ (lambda (k . args)
+ (format (current-error-port) "In ~a_\n" 'statement)
+ (print-exception (current-error-port) #f k args))))
+ (cleanup-catch (bad-request port))
+ (cleanup-catch (close-port port)))))))))))))
+
+(define (keep-alive? response)
+ (let ((v (response-version response)))
+ (and (or (< (response-code response) 400)
+ (= (response-code response) 404))
+ (case (car v)
+ ((1)
+ (case (cdr v)
+ ((1) (not (memq 'close (response-connection response))))
+ ((0) (memq 'keep-alive (response-connection response)))))
+ (else #f)))))
+
+;; -> 0 values
+(define (http-write server client response body)
+ (let* ((response (write-response response client))
+ (port (response-port response)))
+ (cond
+ ((not body)) ; pass
+ ((bytevector? body)
+ (write-response-body response body))
+ (else
+ (error "Expected a bytevector for body" body)))
+ (cond
+ ((keep-alive? response)
+ (force-output port)
+ (poll-set-add! (http-poll-set server) port *events*))
+ (else
+ (close-port port)))
+ (values)))
+
+;; -> unspecified values
+(define (http-close server)
+ (let ((poll-set (http-poll-set server)))
+ (let lp ((n (poll-set-nfds poll-set)))
+ (if (positive? n)
+ (begin
+ (close-port (poll-set-remove! poll-set (1- n)))
+ (lp (1- n)))))))
+
+(define-server-impl http
+ http-open
+ http-read
+ http-write
+ http-close)
+;;;; (web uri) --- URI manipulation tools
+;;;;
+;;;; Copyright (C) 1997,2001,2002,2010,2011,2012,2013 Free Software Foundation, Inc.
+;;;;
+;;;; This library 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 library 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 library; if not, write to the Free Software
+;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;;;;
+
+;;; Commentary_
+
+;; A data type for Universal Resource Identifiers, as defined in RFC
+;; 3986.
+
+;;; Code_
+
+(define-module (web uri)
+ #\use-module (srfi srfi-9)
+ #\use-module (ice-9 regex)
+ #\use-module (ice-9 rdelim)
+ #\use-module (ice-9 control)
+ #\use-module (rnrs bytevectors)
+ #\use-module (ice-9 binary-ports)
+ #\export (uri?
+ uri-scheme uri-userinfo uri-host uri-port
+ uri-path uri-query uri-fragment
+
+ build-uri
+ declare-default-port!
+ string->uri uri->string
+ uri-decode uri-encode
+ split-and-decode-uri-path
+ encode-and-join-uri-path))
+
+(define-record-type <uri>
+ (make-uri scheme userinfo host port path query fragment)
+ uri?
+ (scheme uri-scheme)
+ (userinfo uri-userinfo)
+ (host uri-host)
+ (port uri-port)
+ (path uri-path)
+ (query uri-query)
+ (fragment uri-fragment))
+
+(define (absolute-uri? obj)
+ (and (uri? obj) (uri-scheme obj) #t))
+
+(define (uri-error message . args)
+ (throw 'uri-error message args))
+
+(define (positive-exact-integer? port)
+ (and (number? port) (exact? port) (integer? port) (positive? port)))
+
+(define (validate-uri scheme userinfo host port path query fragment)
+ (cond
+ ((not (symbol? scheme))
+ (uri-error "Expected a symbol for the URI scheme_ ~s" scheme))
+ ((and (or userinfo port) (not host))
+ (uri-error "Expected a host, given userinfo or port"))
+ ((and port (not (positive-exact-integer? port)))
+ (uri-error "Expected port to be an integer_ ~s" port))
+ ((and host (or (not (string? host)) (not (valid-host? host))))
+ (uri-error "Expected valid host_ ~s" host))
+ ((and userinfo (not (string? userinfo)))
+ (uri-error "Expected string for userinfo_ ~s" userinfo))
+ ((not (string? path))
+ (uri-error "Expected string for path_ ~s" path))
+ ((and host (not (string-null? path))
+ (not (eqv? (string-ref path 0) #\/)))
+ (uri-error "Expected path of absolute URI to start with a /_ ~a" path))))
+
+(define* (build-uri scheme #\key userinfo host port (path "") query fragment
+ (validate? #t))
+ "Construct a URI object. SCHEME should be a symbol, PORT
+either a positive, exact integer or ‘#f’, and the rest of the
+fields are either strings or ‘#f’. If VALIDATE? is true,
+also run some consistency checks to make sure that the constructed URI
+is valid."
+ (if validate?
+ (validate-uri scheme userinfo host port path query fragment))
+ (make-uri scheme userinfo host port path query fragment))
+
+;; See RFC 3986 #3.2.2 for comments on percent-encodings, IDNA (RFC
+;; 3490), and non-ASCII host names.
+;;
+(define ipv4-regexp
+ (make-regexp "^([0-9.]+)$"))
+(define ipv6-regexp
+ (make-regexp "^([0-9a-fA-F_.]+)$"))
+(define domain-label-regexp
+ (make-regexp "^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$"))
+(define top-label-regexp
+ (make-regexp "^[a-zA-Z]([a-zA-Z0-9-]*[a-zA-Z0-9])?$"))
+
+(define (valid-host? host)
+ (cond
+ ((regexp-exec ipv4-regexp host)
+ (false-if-exception (inet-pton AF_INET host)))
+ ((regexp-exec ipv6-regexp host)
+ (false-if-exception (inet-pton AF_INET6 host)))
+ (else
+ (let lp ((start 0))
+ (let ((end (string-index host #\. start)))
+ (if end
+ (and (regexp-exec domain-label-regexp
+ (substring host start end))
+ (lp (1+ end)))
+ (regexp-exec top-label-regexp host start)))))))
+
+(define userinfo-pat
+ "[a-zA-Z0-9_.!~*'();_&=+$,-]+")
+(define host-pat
+ "[a-zA-Z0-9.-]+")
+(define ipv6-host-pat
+ "[0-9a-fA-F_.]+")
+(define port-pat
+ "[0-9]*")
+(define authority-regexp
+ (make-regexp
+ (format #f "^//((~a)@)?((~a)|(\\[(~a)\\]))(_(~a))?$"
+ userinfo-pat host-pat ipv6-host-pat port-pat)))
+
+(define (parse-authority authority fail)
+ (if (equal? authority "//")
+ ;; Allow empty authorities_ file_///etc/hosts is a synonym of
+ ;; file_/etc/hosts.
+ (values #f #f #f)
+ (let ((m (regexp-exec authority-regexp authority)))
+ (if (and m (valid-host? (or (match_substring m 4)
+ (match_substring m 6))))
+ (values (match_substring m 2)
+ (or (match_substring m 4)
+ (match_substring m 6))
+ (let ((port (match_substring m 8)))
+ (and port (not (string-null? port))
+ (string->number port))))
+ (fail)))))
+
+
+;;; RFC 3986, #3.
+;;;
+;;; URI = scheme "_" hier-part [ "?" query ] [ "#" fragment ]
+;;;
+;;; hier-part = "//" authority path-abempty
+;;; / path-absolute
+;;; / path-rootless
+;;; / path-empty
+
+(define scheme-pat
+ "[a-zA-Z][a-zA-Z0-9+.-]*")
+(define authority-pat
+ "[^/?#]*")
+(define path-pat
+ "[^?#]*")
+(define query-pat
+ "[^#]*")
+(define fragment-pat
+ ".*")
+(define uri-pat
+ (format #f "^((~a)_)?(//~a)?(~a)(\\?(~a))?(#(~a))?$"
+ scheme-pat authority-pat path-pat query-pat fragment-pat))
+(define uri-regexp
+ (make-regexp uri-pat))
+
+(define (string->uri* string)
+ "Parse STRING into a URI object. Return ‘#f’ if the string
+could not be parsed."
+ (% (let ((m (regexp-exec uri-regexp string)))
+ (if (not m) (abort))
+ (let ((scheme (let ((str (match_substring m 2)))
+ (and str (string->symbol (string-downcase str)))))
+ (authority (match_substring m 3))
+ (path (match_substring m 4))
+ (query (match_substring m 6))
+ (fragment (match_substring m 7)))
+ (call-with-values
+ (lambda ()
+ (if authority
+ (parse-authority authority abort)
+ (values #f #f #f)))
+ (lambda (userinfo host port)
+ (make-uri scheme userinfo host port path query fragment)))))
+ (lambda (k)
+ #f)))
+
+(define (string->uri string)
+ "Parse STRING into a URI object. Return ‘#f’ if the string
+could not be parsed."
+ (let ((uri (string->uri* string)))
+ (and uri (uri-scheme uri) uri)))
+
+(define *default-ports* (make-hash-table))
+
+(define (declare-default-port! scheme port)
+ "Declare a default port for the given URI scheme."
+ (hashq-set! *default-ports* scheme port))
+
+(define (default-port? scheme port)
+ (or (not port)
+ (eqv? port (hashq-ref *default-ports* scheme))))
+
+(declare-default-port! 'http 80)
+(declare-default-port! 'https 443)
+
+(define (uri->string uri)
+ "Serialize URI to a string. If the URI has a port that is the
+default port for its scheme, the port is not included in the
+serialization."
+ (let* ((scheme (uri-scheme uri))
+ (userinfo (uri-userinfo uri))
+ (host (uri-host uri))
+ (port (uri-port uri))
+ (path (uri-path uri))
+ (query (uri-query uri))
+ (fragment (uri-fragment uri)))
+ (string-append
+ (if scheme
+ (string-append (symbol->string scheme) "_")
+ "")
+ (if host
+ (string-append "//"
+ (if userinfo (string-append userinfo "@")
+ "")
+ (if (string-index host #\_)
+ (string-append "[" host "]")
+ host)
+ (if (default-port? (uri-scheme uri) port)
+ ""
+ (string-append "_" (number->string port))))
+ "")
+ path
+ (if query
+ (string-append "?" query)
+ "")
+ (if fragment
+ (string-append "#" fragment)
+ ""))))
+
+
+;; like call-with-output-string, but actually closes the port (doh)
+(define (call-with-output-string* proc)
+ (let ((port (open-output-string)))
+ (proc port)
+ (let ((str (get-output-string port)))
+ (close-port port)
+ str)))
+
+(define (call-with-output-bytevector* proc)
+ (call-with-values
+ (lambda ()
+ (open-bytevector-output-port))
+ (lambda (port get-bytevector)
+ (proc port)
+ (let ((bv (get-bytevector)))
+ (close-port port)
+ bv))))
+
+(define (call-with-encoded-output-string encoding proc)
+ (if (string-ci=? encoding "utf-8")
+ (string->utf8 (call-with-output-string* proc))
+ (call-with-output-bytevector*
+ (lambda (port)
+ (set-port-encoding! port encoding)
+ (proc port)))))
+
+(define (encode-string str encoding)
+ (if (string-ci=? encoding "utf-8")
+ (string->utf8 str)
+ (call-with-encoded-output-string encoding
+ (lambda (port)
+ (display str port)))))
+
+(define (decode-string bv encoding)
+ (if (string-ci=? encoding "utf-8")
+ (utf8->string bv)
+ (let ((p (open-bytevector-input-port bv)))
+ (set-port-encoding! p encoding)
+ (let ((res (read-string p)))
+ (close-port p)
+ res))))
+
+
+;; A note on characters and bytes_ URIs are defined to be sequences of
+;; characters in a subset of ASCII. Those characters may encode a
+;; sequence of bytes (octets), which in turn may encode sequences of
+;; characters in other character sets.
+;;
+
+;; Return a new string made from uri-decoding STR. Specifically,
+;; turn ‘+’ into space, and hex-encoded ‘%XX’ strings into
+;; their eight-bit characters.
+;;
+(define hex-chars
+ (string->char-set "0123456789abcdefABCDEF"))
+
+(define* (uri-decode str #\key (encoding "utf-8") (decode-plus-to-space? #t))
+ "Percent-decode the given STR, according to ENCODING,
+which should be the name of a character encoding.
+
+Note that this function should not generally be applied to a full URI
+string. For paths, use ‘split-and-decode-uri-path’ instead. For query
+strings, split the query on ‘&’ and ‘=’ boundaries, and decode
+the components separately.
+
+Note also that percent-encoded strings encode _bytes_, not characters.
+There is no guarantee that a given byte sequence is a valid string
+encoding. Therefore this routine may signal an error if the decoded
+bytes are not valid for the given encoding. Pass ‘#f’ for ENCODING if
+you want decoded bytes as a bytevector directly. ‘set-port-encoding!’,
+for more information on character encodings.
+
+If DECODE-PLUS-TO-SPACE? is true, which is the default, also replace
+instances of the plus character (+) with a space character. This is
+needed when parsing application/x-www-form-urlencoded data.
+
+Returns a string of the decoded characters, or a bytevector if
+ENCODING was ‘#f’."
+ (let* ((len (string-length str))
+ (bv
+ (call-with-output-bytevector*
+ (lambda (port)
+ (let lp ((i 0))
+ (if (< i len)
+ (let ((ch (string-ref str i)))
+ (cond
+ ((and (eqv? ch #\+) decode-plus-to-space?)
+ (put-u8 port (char->integer #\space))
+ (lp (1+ i)))
+ ((and (< (+ i 2) len) (eqv? ch #\%)
+ (let ((a (string-ref str (+ i 1)))
+ (b (string-ref str (+ i 2))))
+ (and (char-set-contains? hex-chars a)
+ (char-set-contains? hex-chars b)
+ (string->number (string a b) 16))))
+ => (lambda (u8)
+ (put-u8 port u8)
+ (lp (+ i 3))))
+ ((< (char->integer ch) 128)
+ (put-u8 port (char->integer ch))
+ (lp (1+ i)))
+ (else
+ (uri-error "Invalid character in encoded URI ~a_ ~s"
+ str ch))))))))))
+ (if encoding
+ (decode-string bv encoding)
+ ;; Otherwise return raw bytevector
+ bv)))
+
+(define ascii-alnum-chars
+ (string->char-set
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"))
+
+;; RFC 3986, #2.2.
+(define gen-delims
+ (string->char-set "_/?#[]@"))
+(define sub-delims
+ (string->char-set "!$&'()*+,l="))
+(define reserved-chars
+ (char-set-union gen-delims sub-delims))
+
+;; RFC 3986, #2.3
+(define unreserved-chars
+ (char-set-union ascii-alnum-chars
+ (string->char-set "-._~")))
+
+;; Return a new string made from uri-encoding STR, unconditionally
+;; transforming any characters not in UNESCAPED-CHARS.
+;;
+(define* (uri-encode str #\key (encoding "utf-8")
+ (unescaped-chars unreserved-chars))
+ "Percent-encode any character not in the character set,
+UNESCAPED-CHARS.
+
+The default character set includes alphanumerics from ASCII, as well as
+the special characters ‘-’, ‘.’, ‘_’, and ‘~’. Any other character will
+be percent-encoded, by writing out the character to a bytevector within
+the given ENCODING, then encoding each byte as ‘%HH’, where HH is the
+uppercase hexadecimal representation of the byte."
+ (define (needs-escaped? ch)
+ (not (char-set-contains? unescaped-chars ch)))
+ (if (string-index str needs-escaped?)
+ (call-with-output-string*
+ (lambda (port)
+ (string-for-each
+ (lambda (ch)
+ (if (char-set-contains? unescaped-chars ch)
+ (display ch port)
+ (let* ((bv (encode-string (string ch) encoding))
+ (len (bytevector-length bv)))
+ (let lp ((i 0))
+ (if (< i len)
+ (let ((byte (bytevector-u8-ref bv i)))
+ (display #\% port)
+ (when (< byte 16)
+ (display #\0 port))
+ (display (string-upcase (number->string byte 16))
+ port)
+ (lp (1+ i))))))))
+ str)))
+ str))
+
+(define (split-and-decode-uri-path path)
+ "Split PATH into its components, and decode each component,
+removing empty components.
+
+For example, ‘\"/foo/bar%20baz/\"’ decodes to the two-element list,
+‘(\"foo\" \"bar baz\")’."
+ (filter (lambda (x) (not (string-null? x)))
+ (map (lambda (s) (uri-decode s #\decode-plus-to-space? #f))
+ (string-split path #\/))))
+
+(define (encode-and-join-uri-path parts)
+ "URI-encode each element of PARTS, which should be a list of
+strings, and join the parts together with ‘/’ as a delimiter.
+
+For example, the list ‘(\"scrambled eggs\" \"biscuits&gravy\")’
+encodes as ‘\"scrambled%20eggs/biscuits%26gravy\"’."
+ (string-join (map uri-encode parts) "/"))
+;;; common-test.scm --
+;;;
+
+;; Slightly modified for Guile by Ludovic Courtès <ludo@gnu.org>, 2010.
+
+(use-modules (system base lalr)
+ (ice-9 pretty-print))
+
+(define *error* '())
+
+(define-syntax check
+ (syntax-rules (=>)
+ ((_ ?expr => ?expected-result)
+ (check ?expr (=> equal?) ?expected-result))
+
+ ((_ ?expr (=> ?equal) ?expected-result)
+ (let ((result ?expr)
+ (expected ?expected-result))
+ (set! *error* '())
+ (when (not (?equal result expected))
+ (display "Failed test_ \n")
+ (pretty-print (quote ?expr))(newline)
+ (display "\tresult was_ ")
+ (pretty-print result)(newline)
+ (display "\texpected_ ")
+ (pretty-print expected)(newline)
+ (exit 1))))))
+
+;;; --------------------------------------------------------------------
+
+(define (display-result v)
+ (if v
+ (begin
+ (display "==> ")
+ (display v)
+ (newline))))
+
+(define eoi-token
+ (make-lexical-token '*eoi* #f #f))
+
+(define (make-lexer tokens)
+ (lambda ()
+ (if (null? tokens)
+ eoi-token
+ (let ((t (car tokens)))
+ (set! tokens (cdr tokens))
+ t))))
+
+(define (error-handler message . args)
+ (set! *error* (cons `(error-handler ,message . ,(if (pair? args)
+ (lexical-token-category (car args))
+ '()))
+ *error*))
+ (cons message args))
+
+;;; end of file
+"_";exec snow -- "$0" "$@"
+;;;
+;;;; Tests for the GLR parser generator
+;;;
+;;
+;; @created "Fri Aug 19 11_23_48 EDT 2005"
+;;
+
+(package* glr-test/v1.0.0
+ (require_ lalr/v2.4.0))
+
+
+(define (syntax-error msg . args)
+ (display msg (current-error-port))
+ (for-each (cut format (current-error-port) " ~A" <>) args)
+ (newline (current-error-port))
+ (throw 'misc-error))
+
+
+(define (make-lexer words)
+ (let ((phrase words))
+ (lambda ()
+ (if (null? phrase)
+ '*eoi*
+ (let ((word (car phrase)))
+ (set! phrase (cdr phrase))
+ word)))))
+
+
+;;;
+;;;; Test 1
+;;;
+
+
+(define parser-1
+ ;; Grammar taken from Tomita's "An Efficient Augmented-Context-Free Parsing Algorithm"
+ (lalr-parser
+ (driver_ glr)
+ (expect_ 2)
+ (*n *v *d *p)
+ (<s> (<np> <vp>)
+ (<s> <pp>))
+ (<np> (*n)
+ (*d *n)
+ (<np> <pp>))
+ (<pp> (*p <np>))
+ (<vp> (*v <np>))))
+
+
+(define *phrase-1* '(*n *v *d *n *p *d *n *p *d *n *p *d *n))
+
+(define (test-1)
+ (parser-1 (make-lexer *phrase-1*) syntax-error))
+
+
+;;;
+;;;; Test 2
+;;;
+
+
+(define parser-2
+ ;; The dangling-else problem
+ (lalr-parser
+ (driver_ glr)
+ (expect_ 1)
+ ((nonassoc_ if then else e s))
+ (<s> (s)
+ (if e then <s>)
+ (if e then <s> else <s>))))
+
+
+(define *phrase-2* '(if e then if e then s else s))
+
+(define (test-2)
+ (parser-2 (make-lexer *phrase-2*) syntax-error))
+
+
+
+
+(define (assert-length l n test-name)
+ (display "Test '")
+ (display test-name)
+ (display (if (not (= (length l) n)) "' failed!" "' passed!"))
+ (newline))
+
+(assert-length (test-1) 14 1)
+(assert-length (test-2) 2 2)
+
+;;; test-glr-associativity.scm
+;;
+;;With the GLR parser both the terminal precedence and the non-terminal
+;;associativity are not respected; rather they generate two child
+;;processes.
+;;
+
+(load "common-test.scm")
+
+(define parser
+ (lalr-parser
+ (driver\_ glr)
+ (expect\_ 0)
+
+ (N LPAREN RPAREN
+ (left\_ + -)
+ (right\_ * /)
+ (nonassoc\_ uminus))
+
+ (output (expr) \_ $1)
+ (expr (expr + expr) \_ (list $1 '+ $3)
+ (expr - expr) \_ (list $1 '- $3)
+ (expr * expr) \_ (list $1 '* $3)
+ (expr / expr) \_ (list $1 '/ $3)
+ (- expr (prec\_ uminus)) \_ (list '- $2)
+ (N) \_ $1
+ (LPAREN expr RPAREN) \_ $2)))
+
+(define (doit . tokens)
+ (parser (make-lexer tokens) error-handler))
+
+;;; --------------------------------------------------------------------
+
+;;Remember that the result of the GLR driver is a list of parses, not a
+;;single parse.
+
+(check
+ (doit (make-lexical-token 'N #f 1))
+ => '(1))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token '+ #f '+)
+ (make-lexical-token 'N #f 2))
+ => '((1 + 2)))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token '* #f '*)
+ (make-lexical-token 'N #f 2))
+ => '((1 * 2)))
+
+(check
+ (doit (make-lexical-token '- #f '-)
+ (make-lexical-token 'N #f 1))
+ => '((- 1)))
+
+(check
+ (doit (make-lexical-token '- #f '-)
+ (make-lexical-token '- #f '-)
+ (make-lexical-token 'N #f 1))
+ => '((- (- 1))))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token '+ #f '+)
+ (make-lexical-token '- #f '-)
+ (make-lexical-token 'N #f 2))
+ => '((1 + (- 2))))
+
+;;; --------------------------------------------------------------------
+
+(check
+ ;;left-associativity
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token '+ #f '+)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token '+ #f '+)
+ (make-lexical-token 'N #f 3))
+ => '(((1 + 2) + 3)))
+
+(check
+ ;;right-associativity
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token '* #f '*)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token '* #f '*)
+ (make-lexical-token 'N #f 3))
+ => '(((1 * 2) * 3)
+ (1 * (2 * 3))))
+
+(check
+ ;;precedence
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token '+ #f '+)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token '* #f '*)
+ (make-lexical-token 'N #f 3))
+ => '(((1 + 2) * 3)
+ (1 + (2 * 3))))
+
+;;; end of file
+;;; test-lr-basics-01.scm --
+;;
+;;A grammar that only accept a single terminal as input. It refuses the
+;;end-of-input as first token.
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let* ((lexer (make-lexer tokens))
+ (parser (lalr-parser (expect\_ 0)
+ (driver\_ glr)
+ (A)
+ (e (A) \_ $1))))
+ (parser lexer error-handler)))
+
+(check
+ (doit (make-lexical-token 'A #f 1))
+ => '(1))
+
+(check
+ (doit)
+ => '())
+
+(check
+ ;;Parse correctly the first A and reduce it. The second A triggers
+ ;;an error which empties the stack and consumes all the input
+ ;;tokens. Finally, an unexpected end-of-input error is returned
+ ;;because EOI is invalid as first token after the start.
+ (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'A #f 2)
+ (make-lexical-token 'A #f 3))
+ => '())
+
+;;; end of file
+;;; test-lr-basics-02.scm --
+;;
+;;A grammar that only accept a single terminal or the EOI.
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser (expect\_ 0)
+ (driver\_ glr)
+ (A)
+ (e (A) \_ $1
+ () \_ 0))))
+ (parser (make-lexer tokens) error-handler)))
+
+(check
+ (doit)
+ => '(0))
+
+(check
+ (doit (make-lexical-token 'A #f 1))
+ => '(1))
+
+(check
+ (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'A #f 2)
+ (make-lexical-token 'A #f 3))
+ => '())
+
+;;; end of file
+;;; test-lr-basics-03.scm --
+;;
+;;A grammar that accepts fixed sequences of a single terminal or the
+;;EOI.
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser (expect\_ 0)
+ (driver\_ glr)
+ (A)
+ (e (A) \_ (list $1)
+ (A A) \_ (list $1 $2)
+ (A A A) \_ (list $1 $2 $3)
+ () \_ 0))))
+ (parser (make-lexer tokens) error-handler)))
+
+(check
+ (doit (make-lexical-token 'A #f 1))
+ => '((1)))
+
+(check
+ (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'A #f 2))
+ => '((1 2)))
+
+(check
+ (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'A #f 2)
+ (make-lexical-token 'A #f 3))
+ => '((1 2 3)))
+
+(check
+ (doit)
+ => '(0))
+
+;;; end of file
+;;; test-lr-basics-04.scm --
+;;
+;;A grammar accepting a sequence of equal tokens of arbitrary length.
+;;The return value is the value of the last parsed token.
+
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser (expect\_ 0)
+ (driver\_ glr)
+ (A)
+ (e (e A) \_ $2
+ (A) \_ $1
+ () \_ 0))))
+ (parser (make-lexer tokens) error-handler)))
+
+(check
+ (doit)
+ => '(0))
+
+(check
+ ;;Two results because there is a shift/reduce conflict, so two
+ ;;processes are generated.
+ (doit (make-lexical-token 'A #f 1))
+ => '(1 1))
+
+(check
+ ;;Two results because there is a shift/reduce conflict, so two
+ ;;processes are generated. Notice that the rules_
+ ;;
+ ;; (e A) (A)
+ ;;
+ ;;generate only one conflict when the second "A" comes. The third
+ ;;"A" comes when the state is inside the rule "(e A)", so there is
+ ;;no conflict.
+ ;;
+ (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'A #f 2)
+ (make-lexical-token 'A #f 3))
+ => '(3 3))
+
+;;; end of file
+;;; test-lr-basics-05.scm --
+;;
+;;A grammar accepting a sequence of equal tokens of arbitrary length.
+;;The return value is the list of values.
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser (expect\_ 0)
+ (driver\_ glr)
+ (A)
+ (e (e A) \_ (cons $2 $1)
+ (A) \_ (list $1)
+ () \_ (list 0)))))
+ (parser (make-lexer tokens) error-handler)))
+
+(check
+ (doit)
+ => '((0)))
+
+(check
+ (doit (make-lexical-token 'A #f 1))
+ => '((1 0)
+ (1)))
+
+(check
+ (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'A #f 2))
+ => '((2 1 0)
+ (2 1)))
+
+(check
+ (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'A #f 2)
+ (make-lexical-token 'A #f 3))
+ => '((3 2 1 0)
+ (3 2 1)))
+
+;;; end of file
+;;; test-lr-script-expression.scm --
+;;
+;;Parse scripts, each line an expression.
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser (expect\_ 0)
+ (driver\_ glr)
+ (N O C T (left\_ A) (left\_ M) (nonassoc\_ U))
+
+ (script (lines) \_ (reverse $1))
+
+ (lines (lines line) \_ (cons $2 $1)
+ (line) \_ (list $1))
+
+ (line (T) \_ #\newline
+ (E T) \_ $1
+ (error T) \_ (list 'error-clause $2))
+
+ (E (N) \_ $1
+ (E A E) \_ ($2 $1 $3)
+ (E M E) \_ ($2 $1 $3)
+ (A E (prec\_ U)) \_ ($1 $2)
+ (O E C) \_ $2))))
+ (parser (make-lexer tokens) error-handler)))
+
+;;; --------------------------------------------------------------------
+;;; Correct input
+
+(check
+ (doit (make-lexical-token 'T #f #\newline))
+ => '((#\newline)))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'T #f #\newline))
+ => '((1)))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'T #f #\newline))
+ => '((3)))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'M #f *)
+ (make-lexical-token 'N #f 3)
+ (make-lexical-token 'T #f #\newline))
+ => '((9) (7)))
+
+(check
+ (doit (make-lexical-token 'N #f 10)
+ (make-lexical-token 'M #f *)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 3)
+ (make-lexical-token 'T #f #\newline))
+ => '((23)))
+
+(check
+ (doit (make-lexical-token 'O #f #\()
+ (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'C #f #\))
+ (make-lexical-token 'M #f *)
+ (make-lexical-token 'N #f 3)
+ (make-lexical-token 'T #f #\newline))
+ => '((9)))
+
+(check
+ (doit (make-lexical-token 'O #f #\()
+ (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'C #f #\))
+ (make-lexical-token 'M #f *)
+ (make-lexical-token 'N #f 3)
+ (make-lexical-token 'T #f #\newline)
+
+ (make-lexical-token 'N #f 4)
+ (make-lexical-token 'M #f /)
+ (make-lexical-token 'N #f 5)
+ (make-lexical-token 'T #f #\newline))
+ => '((9 4/5)))
+
+;;; --------------------------------------------------------------------
+
+(check
+ ;;Successful error recovery.
+ (doit (make-lexical-token 'O #f #\()
+ (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'M #f *)
+ (make-lexical-token 'N #f 3)
+ (make-lexical-token 'T #f #\newline)
+
+ (make-lexical-token 'N #f 4)
+ (make-lexical-token 'M #f /)
+ (make-lexical-token 'N #f 5)
+ (make-lexical-token 'T #f #\newline))
+ => '())
+
+(check
+ ;;Unexpected end of input.
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2))
+ => '())
+
+(check
+ ;;Unexpected end of input.
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'T #f #\newline))
+ => '())
+
+;;; end of file
+;;; test-lr-single-expressions.scm --
+;;
+;;Grammar accepting single expressions.
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser (expect\_ 0)
+ (driver\_ glr)
+ (N O C (left\_ A) (left\_ M) (nonassoc\_ U))
+
+ (E (N) \_ $1
+ (E A E) \_ ($2 $1 $3)
+ (E M E) \_ ($2 $1 $3)
+ (A E (prec\_ U)) \_ ($1 $2)
+ (O E C) \_ $2))))
+ (parser (make-lexer tokens) error-handler)))
+
+;;; --------------------------------------------------------------------
+
+(check ;correct input
+ (doit (make-lexical-token 'N #f 1))
+ => '(1))
+
+(check ;correct input
+ (doit (make-lexical-token 'A #f -)
+ (make-lexical-token 'N #f 1))
+ => '(-1))
+
+(check ;correct input
+ (doit (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 1))
+ => '(1))
+
+(check ;correct input
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2))
+ => '(3))
+
+(check ;correct input
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'M #f *)
+ (make-lexical-token 'N #f 3))
+ => '(9 7))
+
+(check ;correct input
+ (doit (make-lexical-token 'O #f #\()
+ (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'C #f #\))
+ (make-lexical-token 'M #f *)
+ (make-lexical-token 'N #f 3))
+ => '(9))
+
+;;; end of file
+;;; test-lr-associativity-01.scm --
+;;
+;;Show how to use left and right associativity. Notice that the
+;;terminal M is declared as right associative; this influences the
+;;binding of values to the $n symbols in the semantic clauses. The
+;;semantic clause in the rule_
+;;
+;; (E M E M E) _ (list $1 $2 (list $3 $4 $5))
+;;
+;;looks like it is right-associated, and it is because we have declared
+;;M as "right_".
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser
+ (expect\_ 0)
+ (N (left\_ A)
+ (right\_ M)
+ (nonassoc\_ U))
+ (E (N) \_ $1
+ (E A E) \_ (list $1 $2 $3)
+ (E M E) \_ (list $1 $2 $3)
+ (E M E M E) \_ (list $1 $2 (list $3 $4 $5))
+ (A E (prec\_ U)) \_ (list '- $2)))))
+ (parser (make-lexer tokens) error-handler)))
+
+;;; --------------------------------------------------------------------
+;;; Single operator.
+
+(check
+ (doit (make-lexical-token 'N #f 1))
+ => 1)
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 2))
+ => '(1 + 2))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 2))
+ => '(1 * 2))
+
+(check
+ (doit (make-lexical-token 'A #f '-)
+ (make-lexical-token 'N #f 1))
+ => '(- 1))
+
+;;; --------------------------------------------------------------------
+;;; Precedence.
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 3))
+ => '(1 + (2 * 3)))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 3))
+ => '((1 * 2) + 3))
+
+;;; --------------------------------------------------------------------
+;;; Associativity.
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 3))
+ => '((1 + 2) + 3))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 3))
+ => '(1 * (2 * 3)))
+
+;;; end of file
+;;; test-lr-associativity-02.scm --
+;;
+;;Show how to use left and right associativity. Notice that the
+;;terminal M is declared as left associative; this influences the
+;;binding of values to the $n symbols in the semantic clauses. The
+;;semantic clause in the rule_
+;;
+;; (E M E M E) \_ (list $1 $2 (list $3 $4 $5))
+;;
+;;looks like it is right-associated, but the result is left-associated
+;;because we have declared M as "left_".
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser
+ (expect\_ 0)
+ (N (left\_ A)
+ (left\_ M)
+ (nonassoc\_ U))
+ (E (N) \_ $1
+ (E A E) \_ (list $1 $2 $3)
+ (E M E) \_ (list $1 $2 $3)
+ (E M E M E) \_ (list $1 $2 (list $3 $4 $5))
+ (A E (prec\_ U)) \_ (list '- $2)))))
+ (parser (make-lexer tokens) error-handler)))
+
+;;; --------------------------------------------------------------------
+;;; Single operator.
+
+(check
+ (doit (make-lexical-token 'N #f 1))
+ => 1)
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 2))
+ => '(1 + 2))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 2))
+ => '(1 * 2))
+
+(check
+ (doit (make-lexical-token 'A #f '-)
+ (make-lexical-token 'N #f 1))
+ => '(- 1))
+
+;;; --------------------------------------------------------------------
+;;; Precedence.
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 3))
+ => '(1 + (2 * 3)))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 3))
+ => '((1 * 2) + 3))
+
+;;; --------------------------------------------------------------------
+;;; Associativity.
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 3))
+ => '((1 + 2) + 3))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 3))
+ => '((1 * 2) * 3))
+
+;;; end of file
+;;; test-lr-associativity-01.scm --
+;;
+;;Show how to use left and right associativity. Notice that the
+;;terminal M is declared as non-associative; this influences the binding
+;;of values to the $n symbols in the semantic clauses. The semantic
+;;clause in the rule_
+;;
+;; (E M E M E) \_ (list $1 $2 (list $3 $4 $5))
+;;
+;;looks like it is right-associated, and it is because we have declared
+;;M as "right_".
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser
+ (expect\_ 0)
+ (N (nonassoc\_ A)
+ (nonassoc\_ M))
+ (E (N) \_ $1
+ (E A E) \_ (list $1 $2 $3)
+ (E A E A E) \_ (list (list $1 $2 $3) $4 $5)
+ (E M E) \_ (list $1 $2 $3)
+ (E M E M E) \_ (list $1 $2 (list $3 $4 $5))))))
+ (parser (make-lexer tokens) error-handler)))
+
+;;; --------------------------------------------------------------------
+;;; Single operator.
+
+(check
+ (doit (make-lexical-token 'N #f 1))
+ => 1)
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 2))
+ => '(1 + 2))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 2))
+ => '(1 * 2))
+
+;;; --------------------------------------------------------------------
+;;; Precedence.
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 3))
+ => '(1 + (2 * 3)))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 3))
+ => '((1 * 2) + 3))
+
+;;; --------------------------------------------------------------------
+;;; Associativity.
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 3))
+ => '((1 + 2) + 3))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 3))
+ => '(1 * (2 * 3)))
+
+;;; end of file
+;;; test-lr-associativity-04.scm --
+;;
+;;Show how to use associativity.
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser
+ (expect\_ 0)
+ (N (left\_ A)
+ (left\_ M))
+ (E (N) \_ $1
+
+ (E A E) \_ (list $1 $2 $3)
+ (E A E A E) \_ (list (list $1 $2 $3) $4 $5)
+
+ (E M E) \_ (list $1 $2 $3)
+ (E M E M E) \_ (list $1 $2 (list $3 $4 $5))
+
+ (E A E M E) \_ (list $1 $2 $3 $4 $5)
+ (E M E A E) \_ (list $1 $2 $3 $4 $5)
+ ))))
+ (parser (make-lexer tokens) error-handler)))
+
+;;; --------------------------------------------------------------------
+;;; Single operator.
+
+(check
+ (doit (make-lexical-token 'N #f 1))
+ => 1)
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 2))
+ => '(1 + 2))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 2))
+ => '(1 * 2))
+
+;;; --------------------------------------------------------------------
+;;; Precedence.
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 3))
+ => '(1 + (2 * 3)))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 3))
+ => '((1 * 2) + 3))
+
+;;; --------------------------------------------------------------------
+;;; Associativity.
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'A #f '+)
+ (make-lexical-token 'N #f 3))
+ => '((1 + 2) + 3))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'M #f '*)
+ (make-lexical-token 'N #f 3))
+ => '((1 * 2) * 3))
+
+;;; end of file
+;;; test-lr-basics-01.scm --
+;;
+;;A grammar that only accept a single terminal as input. It refuses the
+;;end-of-input as first token.
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let* ((lexer (make-lexer tokens))
+ (parser (lalr-parser (expect\_ 0)
+ (A)
+ (e (A) \_ $1))))
+ (parser lexer error-handler)))
+
+(check
+ (doit (make-lexical-token 'A #f 1))
+ => 1)
+
+(check
+ (let ((r (doit)))
+ (cons r *error*))
+ => '(#f (error-handler "Syntax error: unexpected end of input")))
+
+(check
+ ;;Parse correctly the first A and reduce it. The second A triggers
+ ;;an error which empties the stack and consumes all the input
+ ;;tokens. Finally, an unexpected end-of-input error is returned
+ ;;because EOI is invalid as first token after the start.
+ (let ((r (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'A #f 2)
+ (make-lexical-token 'A #f 3))))
+ (cons r *error*))
+ => '(#f
+ (error-handler "Syntax error: unexpected end of input")
+ (error-handler "Syntax error: unexpected token : " . A)))
+
+;;; end of file
+;;; test-lr-basics-02.scm --
+;;
+;;A grammar that only accept a single terminal or the EOI.
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser (expect\_ 0)
+ (A)
+ (e (A) \_ $1
+ () \_ 0))))
+ (parser (make-lexer tokens) error-handler)))
+
+(check
+ (doit)
+ => 0)
+
+(check
+ (doit (make-lexical-token 'A #f 1))
+ => 1)
+
+(check
+ ;;Parse correctly the first A and reduce it. The second A triggers
+ ;;an error which empties the stack and consumes all the input
+ ;;tokens. Finally, the end-of-input token is correctly parsed.
+ (let ((r (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'A #f 2)
+ (make-lexical-token 'A #f 3))))
+ (cons r *error*))
+ => '(0 (error-handler "Syntax error: unexpected token : " . A)))
+
+;;; end of file
+;;; test-lr-basics-03.scm --
+;;
+;;A grammar that accepts fixed sequences of a single terminal or the
+;;EOI.
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser (expect\_ 0)
+ (A)
+ (e (A) \_ (list $1)
+ (A A) \_ (list $1 $2)
+ (A A A) \_ (list $1 $2 $3)
+ () \_ 0))))
+ (parser (make-lexer tokens) error-handler)))
+
+(check
+ (doit (make-lexical-token 'A #f 1))
+ => '(1))
+
+(check
+ (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'A #f 2))
+ => '(1 2))
+
+(check
+ (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'A #f 2)
+ (make-lexical-token 'A #f 3))
+ => '(1 2 3))
+
+(check
+ (doit)
+ => 0)
+
+;;; end of file
+;;; test-lr-basics-04.scm --
+;;
+;;A grammar accepting a sequence of equal tokens of arbitrary length.
+;;The return value is the value of the last parsed token.
+
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser (expect\_ 0)
+ (A)
+ (e (e A) \_ $2
+ (A) \_ $1
+ () \_ 0))))
+ (parser (make-lexer tokens) error-handler)))
+
+(check
+ (doit)
+ => 0)
+
+(check
+ (doit (make-lexical-token 'A #f 1))
+ => 1)
+
+(check
+ (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'A #f 2)
+ (make-lexical-token 'A #f 3))
+ => 3)
+
+;;; end of file
+;;; test-lr-basics-05.scm --
+;;
+;;A grammar accepting a sequence of equal tokens of arbitrary length.
+;;The return value is the list of values.
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser (expect\_ 0)
+ (A)
+ (e (e A) \_ (cons $2 $1)
+ (A) \_ (list $1)
+ () \_ 0))))
+ (parser (make-lexer tokens) error-handler)))
+
+(check
+ (doit)
+ => 0)
+
+(check
+ (doit (make-lexical-token 'A #f 1))
+ => '(1))
+
+(check
+ (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'A #f 2))
+ => '(2 1))
+
+(check
+ (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'A #f 2)
+ (make-lexical-token 'A #f 3))
+ => '(3 2 1))
+
+;;; end of file
+;;; test-lr-error-recovery-01.scm --
+;;
+;;Test error recovery with a terminator terminal.
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser
+ (expect\_ 0)
+ (NUMBER BAD NEWLINE)
+
+ (script (lines) \_ (reverse $1)
+ () \_ 0)
+ (lines (lines line) \_ (cons $2 $1)
+ (line) \_ (list $1))
+ (line (NEWLINE) \_ (list 'line $1)
+ (NUMBER NEWLINE) \_ (list 'line $1 $2)
+ (NUMBER NUMBER NEWLINE) \_ (list 'line $1 $2 $3)
+
+ ;;This semantic action will cause "(recover $1
+ ;;$2)" to be the result of the offending line.
+ (error NEWLINE) \_ (list 'recover $1 $2)))))
+ (parser (make-lexer tokens) error-handler)))
+
+;;; --------------------------------------------------------------------
+;;; No errors, grammar tests.
+
+(check
+ (doit)
+ => 0)
+
+(check
+ (doit (make-lexical-token 'NEWLINE #f #\newline))
+ => '((line #\newline)))
+
+(check
+ (doit (make-lexical-token 'NUMBER #f 1)
+ (make-lexical-token 'NEWLINE #f #\newline))
+ => '((line 1 #\newline)))
+
+(check
+ (doit (make-lexical-token 'NUMBER #f 1)
+ (make-lexical-token 'NUMBER #f 2)
+ (make-lexical-token 'NEWLINE #f #\newline))
+ => '((line 1 2 #\newline)))
+
+(check
+ (doit (make-lexical-token 'NUMBER #f 1)
+ (make-lexical-token 'NEWLINE #f #\newline)
+ (make-lexical-token 'NUMBER #f 2)
+ (make-lexical-token 'NEWLINE #f #\newline))
+ => '((line 1 #\newline)
+ (line 2 #\newline)))
+
+(check
+ (doit (make-lexical-token 'NUMBER #f 1)
+ (make-lexical-token 'NEWLINE #f #\newline)
+ (make-lexical-token 'NUMBER #f 2)
+ (make-lexical-token 'NEWLINE #f #\newline)
+ (make-lexical-token 'NUMBER #f 3)
+ (make-lexical-token 'NEWLINE #f #\newline))
+ => '((line 1 #\newline)
+ (line 2 #\newline)
+ (line 3 #\newline)))
+
+(check
+ (doit (make-lexical-token 'NUMBER #f 1)
+ (make-lexical-token 'NEWLINE #f #\newline)
+ (make-lexical-token 'NUMBER #f 2)
+ (make-lexical-token 'NEWLINE #f #\newline)
+ (make-lexical-token 'NUMBER #f 3)
+ (make-lexical-token 'NEWLINE #f #\newline)
+ (make-lexical-token 'NUMBER #f 41)
+ (make-lexical-token 'NUMBER #f 42)
+ (make-lexical-token 'NEWLINE #f #\newline))
+ => '((line 1 #\newline)
+ (line 2 #\newline)
+ (line 3 #\newline)
+ (line 41 42 #\newline)))
+
+;;; --------------------------------------------------------------------
+;;; Successful error recovery.
+
+(check
+ ;;The BAD triggers an error, recovery happens, the first NEWLINE is
+ ;;correctly parsed as recovery token; the second line is correct.
+ (let ((r (doit (make-lexical-token 'NUMBER #f 1)
+ (make-lexical-token 'BAD #f 'alpha)
+ (make-lexical-token 'NEWLINE #f #\newline)
+ (make-lexical-token 'NUMBER #f 2)
+ (make-lexical-token 'NEWLINE #f #\newline))))
+ (cons r *error*))
+ => '(((recover #f #f)
+ (line 2 #\newline))
+ (error-handler "Syntax error: unexpected token : " . BAD)))
+
+
+(check
+ ;;The first BAD triggers an error, recovery happens skipping the
+ ;;second and third BADs, the first NEWLINE is detected as
+ ;;synchronisation token; the second line is correct.
+ (let ((r (doit (make-lexical-token 'NUMBER #f 1)
+ (make-lexical-token 'BAD #f 'alpha)
+ (make-lexical-token 'BAD #f 'beta)
+ (make-lexical-token 'BAD #f 'delta)
+ (make-lexical-token 'NEWLINE #f #\newline)
+ (make-lexical-token 'NUMBER #f 2)
+ (make-lexical-token 'NEWLINE #f #\newline))))
+ (cons r *error*))
+ => '(((recover #f #f)
+ (line 2 #\newline))
+ (error-handler "Syntax error: unexpected token : " . BAD)))
+
+;;; --------------------------------------------------------------------
+;;; Failed error recovery.
+
+(check
+ ;;End-of-input is found after NUMBER.
+ (let ((r (doit (make-lexical-token 'NUMBER #f 1))))
+ (cons r *error*))
+ => '(#f (error-handler "Syntax error: unexpected end of input")))
+
+(check
+ ;;The BAD triggers the error, the stack is rewind up to the start,
+ ;;then end-of-input happens while trying to skip tokens until the
+ ;;synchronisation one is found. End-of-input is an acceptable token
+ ;;after the start.
+ (let ((r (doit (make-lexical-token 'NUMBER #f 1)
+ (make-lexical-token 'BAD #f 'alpha)
+ (make-lexical-token 'BAD #f 'beta)
+ (make-lexical-token 'BAD #f 'delta))))
+ (cons r *error*))
+ => '(0 (error-handler "Syntax error: unexpected token : " . BAD)))
+
+(check
+ ;;The BAD triggers the error, the stack is rewind up to the start,
+ ;;then end-of-input happens while trying to skip tokens until the
+ ;;synchronisation one is found. End-of-input is an acceptable token
+ ;;after the start.
+ (let ((r (doit (make-lexical-token 'BAD #f 'alpha))))
+ (cons r *error*))
+ => '(0 (error-handler "Syntax error: unexpected token : " . BAD)))
+
+;;; end of file
+;;; test-lr-error-recovery-02.scm --
+;;
+;;Test error recovery policy when the synchronisation terminal has the
+;;same category of the lookahead that raises the error.
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser (expect\_ 0)
+ (A B C)
+ (alphas (alpha) \_ $1
+ (alphas alpha) \_ $2)
+ (alpha (A B) \_ (list $1 $2)
+ (C) \_ $1
+ (error C) \_ 'error-form))))
+ (parser (make-lexer tokens) error-handler)))
+
+;;; --------------------------------------------------------------------
+;;; No error, just grammar tests.
+
+(check
+ (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'B #f 2))
+ => '(1 2))
+
+(check
+ (doit (make-lexical-token 'C #f 3))
+ => '3)
+
+;;; --------------------------------------------------------------------
+;;; Successful error recovery.
+
+(check
+ ;;Error, recovery, end-of-input.
+ (let ((r (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'C #f 3))))
+ (cons r *error*))
+ => '(error-form (error-handler "Syntax error: unexpected token : " . C)))
+
+(check
+ ;;Error, recovery, correct parse of "A B".
+ (let ((r (doit (make-lexical-token 'A #f 1)
+ (make-lexical-token 'C #f 3)
+ (make-lexical-token 'A #f 1)
+ (make-lexical-token 'B #f 2))))
+ (cons r *error*))
+ => '((1 2)
+ (error-handler "Syntax error: unexpected token : " . C)))
+
+;;; end of file
+;;; test-lr-no-clause.scm --
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser (expect\_ 0)
+ (NUMBER COMMA NEWLINE)
+
+ (lines (lines line) \_ (list $2)
+ (line) \_ (list $1))
+ (line (NEWLINE) \_ #\newline
+ (NUMBER NEWLINE) \_ $1
+ ;;this is a rule with no semantic action
+ (COMMA NUMBER NEWLINE)))))
+ (parser (make-lexer tokens) error-handler)))
+
+(check
+ ;;correct input
+ (doit (make-lexical-token 'NUMBER #f 1)
+ (make-lexical-token 'NEWLINE #f #\newline))
+ => '(1))
+
+(check
+ ;;correct input with comma, which is a rule with no client form
+ (doit (make-lexical-token 'COMMA #f #\,)
+ (make-lexical-token 'NUMBER #f 1)
+ (make-lexical-token 'NEWLINE #f #\newline))
+ => '(#(line-3 #\, 1 #\newline)))
+
+(check
+ ;;correct input with comma, which is a rule with no client form
+ (doit (make-lexical-token 'NUMBER #f 1)
+ (make-lexical-token 'NEWLINE #f #\newline)
+ (make-lexical-token 'COMMA #f #\,)
+ (make-lexical-token 'NUMBER #f 2)
+ (make-lexical-token 'NEWLINE #f #\newline))
+ => '(#(line-3 #\, 2 #\newline)))
+
+;;; end of file
+;;; test-lr-script-expression.scm --
+;;
+;;Parse scripts, each line an expression.
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser (expect\_ 0)
+ (N O C T (left\_ A) (left\_ M) (nonassoc\_ U))
+
+ (script (lines) \_ (reverse $1))
+
+ (lines (lines line) \_ (cons $2 $1)
+ (line) \_ (list $1))
+
+ (line (T) \_ #\newline
+ (E T) \_ $1
+ (error T) \_ (list 'error-clause $2))
+
+ (E (N) \_ $1
+ (E A E) \_ ($2 $1 $3)
+ (E M E) \_ ($2 $1 $3)
+ (A E (prec\_ U)) \_ ($1 $2)
+ (O E C) \_ $2))))
+ (parser (make-lexer tokens) error-handler)))
+
+;;; --------------------------------------------------------------------
+;;; Correct input
+
+(check
+ (doit (make-lexical-token 'T #f #\newline))
+ => '(#\newline))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'T #f #\newline))
+ => '(1))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'T #f #\newline))
+ => '(3))
+
+(check
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'M #f *)
+ (make-lexical-token 'N #f 3)
+ (make-lexical-token 'T #f #\newline))
+ => '(7))
+
+(check
+ (doit (make-lexical-token 'O #f #\()
+ (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'C #f #\))
+ (make-lexical-token 'M #f *)
+ (make-lexical-token 'N #f 3)
+ (make-lexical-token 'T #f #\newline))
+ => '(9))
+
+(check
+ (doit (make-lexical-token 'O #f #\()
+ (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'C #f #\))
+ (make-lexical-token 'M #f *)
+ (make-lexical-token 'N #f 3)
+ (make-lexical-token 'T #f #\newline)
+
+ (make-lexical-token 'N #f 4)
+ (make-lexical-token 'M #f /)
+ (make-lexical-token 'N #f 5)
+ (make-lexical-token 'T #f #\newline))
+ => '(9 4/5))
+
+;;; --------------------------------------------------------------------
+
+(check
+ ;;Successful error recovery.
+ (doit (make-lexical-token 'O #f #\()
+ (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'M #f *)
+ (make-lexical-token 'N #f 3)
+ (make-lexical-token 'T #f #\newline)
+
+ (make-lexical-token 'N #f 4)
+ (make-lexical-token 'M #f /)
+ (make-lexical-token 'N #f 5)
+ (make-lexical-token 'T #f #\newline))
+ => '((error-clause #f)
+ 4/5))
+
+(check
+ ;;Unexpected end of input.
+ (let ((r (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2))))
+ (cons r *error*))
+ => '(#f (error-handler "Syntax error: unexpected end of input")))
+
+(check
+ ;;Unexpected end of input.
+ (let ((r (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'T #f #\newline))))
+ (cons r *error*))
+ => '(((error-clause #f))
+ (error-handler "Syntax error: unexpected token : " . T)))
+
+;;; end of file
+;;; test-lr-single-expressions.scm --
+;;
+;;Grammar accepting single expressions.
+;;
+
+(load "common-test.scm")
+
+(define (doit . tokens)
+ (let ((parser (lalr-parser (expect\_ 0)
+ (N O C (left\_ A) (left\_ M) (nonassoc\_ U))
+
+ (E (N) \_ $1
+ (E A E) \_ ($2 $1 $3)
+ (E M E) \_ ($2 $1 $3)
+ (A E (prec\_ U)) \_ ($1 $2)
+ (O E C) \_ $2))))
+ (parser (make-lexer tokens) error-handler)))
+
+;;; --------------------------------------------------------------------
+
+(check ;correct input
+ (doit (make-lexical-token 'N #f 1))
+ => 1)
+
+(check ;correct input
+ (doit (make-lexical-token 'A #f -)
+ (make-lexical-token 'N #f 1))
+ => -1)
+
+(check ;correct input
+ (doit (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 1))
+ => 1)
+
+(check ;correct input
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2))
+ => 3)
+
+(check ;correct input
+ (doit (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'M #f *)
+ (make-lexical-token 'N #f 3))
+ => 7)
+
+(check ;correct input
+ (doit (make-lexical-token 'O #f #\()
+ (make-lexical-token 'N #f 1)
+ (make-lexical-token 'A #f +)
+ (make-lexical-token 'N #f 2)
+ (make-lexical-token 'C #f #\))
+ (make-lexical-token 'M #f *)
+ (make-lexical-token 'N #f 3))
+ => 9)
+
+;;; end of file
+(define-module (test-import-order-a)
+ #\use-module (base))
+
+(push!)
+(define-module (test-import-order-b)
+ #\use-module (base))
+
+(push!)
+(define-module (test-import-order-c)
+ #\use-module (base))
+
+(push!)
+(define-module (test-import-order-d)
+ #\use-module (base))
+
+(push!)
+;;;; test-suite/lib.scm --- generic support for testing
+;;;; Copyright (C) 1999, 2000, 2001, 2004, 2006, 2007, 2009, 2010,
+;;;; 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
+;;;;
+;;;; 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, 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 software; see the file COPYING.LESSER.
+;;;; If not, write to the Free Software Foundation, Inc., 51 Franklin
+;;;; Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+(define-module (test-suite lib)
+ #\use-module (ice-9 stack-catch)
+ #\use-module (ice-9 regex)
+ #\autoload (srfi srfi-1) (append-map)
+ #\autoload (system base compile) (compile)
+ #\export (
+
+ ;; Exceptions which are commonly being tested for.
+ exception_syntax-pattern-unmatched
+ exception_bad-variable
+ exception_missing-expression
+ exception_out-of-range exception_unbound-var
+ exception_used-before-defined
+ exception_wrong-num-args exception_wrong-type-arg
+ exception_numerical-overflow
+ exception_struct-set!-denied
+ exception_system-error
+ exception_encoding-error
+ exception_miscellaneous-error
+ exception_string-contains-nul
+ exception_read-error
+ exception_null-pointer-error
+ exception_vm-error
+
+ ;; Reporting passes and failures.
+ run-test
+ pass-if expect-fail
+ pass-if-equal
+ pass-if-exception expect-fail-exception
+
+ ;; Naming groups of tests in a regular fashion.
+ with-test-prefix
+ with-test-prefix*
+ with-test-prefix/c&e
+ current-test-prefix
+ format-test-name
+
+ ;; Using the debugging evaluator.
+ with-debugging-evaluator with-debugging-evaluator*
+
+ ;; Clearing stale references on the C stack for GC-sensitive tests.
+ clear-stale-stack-references
+
+ ;; Using a given locale
+ with-locale with-locale* with-latin1-locale with-latin1-locale*
+
+ ;; The bit bucket.
+ %null-device
+
+ ;; Reporting results in various ways.
+ register-reporter unregister-reporter reporter-registered?
+ make-count-reporter print-counts
+ make-log-reporter
+ full-reporter
+ user-reporter))
+
+
+;;;; If you're using Emacs's Scheme mode_
+;;;; (put 'with-test-prefix 'scheme-indent-function 1)
+
+
+;;;; CORE FUNCTIONS
+;;;;
+;;;; The function (run-test name expected-result thunk) is the heart of the
+;;;; testing environment. The first parameter NAME is a unique name for the
+;;;; test to be executed (for an explanation of this parameter see below under
+;;;; TEST NAMES). The second parameter EXPECTED-RESULT is a boolean value
+;;;; that indicates whether the corresponding test is expected to pass. If
+;;;; EXPECTED-RESULT is #t the test is expected to pass, if EXPECTED-RESULT is
+;;;; #f the test is expected to fail. Finally, THUNK is the function that
+;;;; actually performs the test. For example_
+;;;;
+;;;; (run-test "integer addition" #t (lambda () (= 2 (+ 1 1))))
+;;;;
+;;;; To report success, THUNK should either return #t or throw 'pass. To
+;;;; report failure, THUNK should either return #f or throw 'fail. If THUNK
+;;;; returns a non boolean value or throws 'unresolved, this indicates that
+;;;; the test did not perform as expected. For example the property that was
+;;;; to be tested could not be tested because something else went wrong.
+;;;; THUNK may also throw 'untested to indicate that the test was deliberately
+;;;; not performed, for example because the test case is not complete yet.
+;;;; Finally, if THUNK throws 'unsupported, this indicates that this test
+;;;; requires some feature that is not available in the configured testing
+;;;; environment. All other exceptions thrown by THUNK are considered as
+;;;; errors.
+;;;;
+;;;;
+;;;; Convenience macros for tests expected to pass or fail
+;;;;
+;;;; * (pass-if name body) is a short form for
+;;;; (run-test name #t (lambda () body))
+;;;; * (expect-fail name body) is a short form for
+;;;; (run-test name #f (lambda () body))
+;;;;
+;;;; For example_
+;;;;
+;;;; (pass-if "integer addition" (= 2 (+ 1 1)))
+;;;;
+;;;;
+;;;; Convenience macros to test for exceptions
+;;;;
+;;;; The following macros take exception parameters which are pairs
+;;;; (type . message), where type is a symbol that denotes an exception type
+;;;; like 'wrong-type-arg or 'out-of-range, and message is a string holding a
+;;;; regular expression that describes the error message for the exception
+;;;; like "Argument .* out of range".
+;;;;
+;;;; * (pass-if-exception name exception body) will pass if the execution of
+;;;; body causes the given exception to be thrown. If no exception is
+;;;; thrown, the test fails. If some other exception is thrown, it is an
+;;;; error.
+;;;; * (expect-fail-exception name exception body) will pass unexpectedly if
+;;;; the execution of body causes the given exception to be thrown. If no
+;;;; exception is thrown, the test fails expectedly. If some other
+;;;; exception is thrown, it is an error.
+
+
+;;;; TEST NAMES
+;;;;
+;;;; Every test in the test suite has a unique name, to help
+;;;; developers find tests that are failing (or unexpectedly passing),
+;;;; and to help gather statistics.
+;;;;
+;;;; A test name is a list of printable objects. For example_
+;;;; ("ports.scm" "file" "read and write back list of strings")
+;;;; ("ports.scm" "pipe" "read")
+;;;;
+;;;; Test names may contain arbitrary objects, but they always have
+;;;; the following properties_
+;;;; - Test names can be compared with EQUAL?.
+;;;; - Test names can be reliably stored and retrieved with the standard WRITE
+;;;; and READ procedures; doing so preserves their identity.
+;;;;
+;;;; For example_
+;;;;
+;;;; (pass-if "simple addition" (= 4 (+ 2 2)))
+;;;;
+;;;; In that case, the test name is the list ("simple addition").
+;;;;
+;;;; In the case of simple tests the expression that is tested would often
+;;;; suffice as a test name by itself. Therefore, the convenience macros
+;;;; pass-if and expect-fail provide a shorthand notation that allows to omit
+;;;; a test name in such cases.
+;;;;
+;;;; * (pass-if expression) is a short form for
+;;;; (run-test 'expression #t (lambda () expression))
+;;;; * (expect-fail expression) is a short form for
+;;;; (run-test 'expression #f (lambda () expression))
+;;;;
+;;;; For example_
+;;;;
+;;;; (pass-if (= 2 (+ 1 1)))
+;;;;
+;;;; The WITH-TEST-PREFIX syntax and WITH-TEST-PREFIX* procedure establish
+;;;; a prefix for the names of all tests whose results are reported
+;;;; within their dynamic scope. For example_
+;;;;
+;;;; (begin
+;;;; (with-test-prefix "basic arithmetic"
+;;;; (pass-if "addition" (= (+ 2 2) 4))
+;;;; (pass-if "subtraction" (= (- 4 2) 2)))
+;;;; (pass-if "multiplication" (= (* 2 2) 4)))
+;;;;
+;;;; In that example, the three test names are_
+;;;; ("basic arithmetic" "addition"),
+;;;; ("basic arithmetic" "subtraction"), and
+;;;; ("multiplication").
+;;;;
+;;;; WITH-TEST-PREFIX can be nested. Each WITH-TEST-PREFIX appends
+;;;; a new element to the current prefix_
+;;;;
+;;;; (with-test-prefix "arithmetic"
+;;;; (with-test-prefix "addition"
+;;;; (pass-if "integer" (= (+ 2 2) 4))
+;;;; (pass-if "complex" (= (+ 2+3i 4+5i) 6+8i)))
+;;;; (with-test-prefix "subtraction"
+;;;; (pass-if "integer" (= (- 2 2) 0))
+;;;; (pass-if "complex" (= (- 2+3i 1+2i) 1+1i))))
+;;;;
+;;;; The four test names here are_
+;;;; ("arithmetic" "addition" "integer")
+;;;; ("arithmetic" "addition" "complex")
+;;;; ("arithmetic" "subtraction" "integer")
+;;;; ("arithmetic" "subtraction" "complex")
+;;;;
+;;;; To print a name for a human reader, we DISPLAY its elements,
+;;;; separated by "_ ". So, the last set of test names would be
+;;;; reported as_
+;;;;
+;;;; arithmetic_ addition_ integer
+;;;; arithmetic_ addition_ complex
+;;;; arithmetic_ subtraction_ integer
+;;;; arithmetic_ subtraction_ complex
+;;;;
+;;;; The Guile benchmarks use with-test-prefix to include the name of
+;;;; the source file containing the test in the test name, to help
+;;;; developers to find failing tests, and to provide each file with its
+;;;; own namespace.
+
+
+;;;; REPORTERS
+;;;;
+;;;; A reporter is a function which we apply to each test outcome.
+;;;; Reporters can log results, print interesting results to the
+;;;; standard output, collect statistics, etc.
+;;;;
+;;;; A reporter function takes two mandatory arguments, RESULT and TEST, and
+;;;; possibly additional arguments depending on RESULT; its return value
+;;;; is ignored. RESULT has one of the following forms_
+;;;;
+;;;; pass - The test named TEST passed.
+;;;; Additional arguments are ignored.
+;;;; upass - The test named TEST passed unexpectedly.
+;;;; Additional arguments are ignored.
+;;;; fail - The test named TEST failed.
+;;;; Additional arguments are ignored.
+;;;; xfail - The test named TEST failed, as expected.
+;;;; Additional arguments are ignored.
+;;;; unresolved - The test named TEST did not perform as expected, for
+;;;; example the property that was to be tested could not be
+;;;; tested because something else went wrong.
+;;;; Additional arguments are ignored.
+;;;; untested - The test named TEST was not actually performed, for
+;;;; example because the test case is not complete yet.
+;;;; Additional arguments are ignored.
+;;;; unsupported - The test named TEST requires some feature that is not
+;;;; available in the configured testing environment.
+;;;; Additional arguments are ignored.
+;;;; error - An error occurred while the test named TEST was
+;;;; performed. Since this result means that the system caught
+;;;; an exception it could not handle, the exception arguments
+;;;; are passed as additional arguments.
+;;;;
+;;;; This library provides some standard reporters for logging results
+;;;; to a file, reporting interesting results to the user, and
+;;;; collecting totals.
+;;;;
+;;;; You can use the REGISTER-REPORTER function and friends to add
+;;;; whatever reporting functions you like. If you don't register any
+;;;; reporters, the library uses FULL-REPORTER, which simply writes
+;;;; all results to the standard output.
+
+
+;;;; MISCELLANEOUS
+;;;;
+
+;;; Define some exceptions which are commonly being tested for.
+(define exception_syntax-pattern-unmatched
+ (cons 'syntax-error "source expression failed to match any pattern"))
+(define exception_bad-variable
+ (cons 'syntax-error "Bad variable"))
+(define exception_missing-expression
+ (cons 'misc-error "^missing or extra expression"))
+(define exception_out-of-range
+ (cons 'out-of-range "^.*out of range"))
+(define exception_unbound-var
+ (cons 'unbound-variable "^Unbound variable"))
+(define exception_used-before-defined
+ (cons 'unbound-variable "^Variable used before given a value"))
+(define exception_wrong-num-args
+ (cons 'wrong-number-of-args "^Wrong number of arguments"))
+(define exception_wrong-type-arg
+ (cons 'wrong-type-arg "^Wrong type"))
+(define exception_numerical-overflow
+ (cons 'numerical-overflow "^Numerical overflow"))
+(define exception_struct-set!-denied
+ (cons 'misc-error "^set! denied for field"))
+(define exception_system-error
+ (cons 'system-error ".*"))
+(define exception_encoding-error
+ (cons 'encoding-error "(cannot convert.* to output locale|input (locale conversion|decoding) error)"))
+(define exception_miscellaneous-error
+ (cons 'misc-error "^.*"))
+(define exception_read-error
+ (cons 'read-error "^.*$"))
+(define exception_null-pointer-error
+ (cons 'null-pointer-error "^.*$"))
+(define exception_vm-error
+ (cons 'vm-error "^.*$"))
+
+;; as per throw in scm_to_locale_stringn()
+(define exception_string-contains-nul
+ (cons 'misc-error "^string contains #\\\\nul character"))
+
+
+;;; Display all parameters to the default output port, followed by a newline.
+(define (display-line . objs)
+ (for-each display objs)
+ (newline))
+
+;;; Display all parameters to the given output port, followed by a newline.
+(define (display-line-port port . objs)
+ (for-each (lambda (obj) (display obj port)) objs)
+ (newline port))
+
+
+;;;; CORE FUNCTIONS
+;;;;
+
+;;; The central testing routine.
+;;; The idea is taken from Greg, the GNUstep regression test environment.
+(define run-test
+ (let ((test-running #f))
+ (lambda (name expect-pass thunk)
+ (if test-running
+ (error "Nested calls to run-test are not permitted."))
+ (let ((test-name (full-name name)))
+ (set! test-running #t)
+ (catch #t
+ (lambda ()
+ (let ((result (thunk)))
+ (if (eq? result #t) (throw 'pass))
+ (if (eq? result #f) (throw 'fail))
+ (throw 'unresolved)))
+ (lambda (key . args)
+ (case key
+ ((pass)
+ (report (if expect-pass 'pass 'upass) test-name))
+ ((fail)
+ ;; ARGS may contain extra info about the failure,
+ ;; such as the expected and actual value.
+ (apply report (if expect-pass 'fail 'xfail)
+ test-name
+ args))
+ ((unresolved untested unsupported)
+ (report key test-name))
+ ((quit)
+ (report 'unresolved test-name)
+ (quit))
+ (else
+ (report 'error test-name (cons key args))))))
+ (set! test-running #f)))))
+
+;;; A short form for tests that are expected to pass, taken from Greg.
+(define-syntax pass-if
+ (syntax-rules ()
+ ((_ name)
+ ;; presume this is a simple test, i.e. (pass-if (even? 2))
+ ;; where the body should also be the name.
+ (run-test 'name #t (lambda () name)))
+ ((_ name rest ...)
+ (run-test name #t (lambda () rest ...)))))
+
+(define-syntax pass-if-equal
+ (syntax-rules ()
+ "Succeed if and only if BODY's return value is equal? to EXPECTED."
+ ((_ expected body)
+ (pass-if-equal 'body expected body))
+ ((_ name expected body ...)
+ (run-test name #t
+ (lambda ()
+ (let ((result (begin body ...)))
+ (or (equal? expected result)
+ (throw 'fail
+ 'expected-value expected
+ 'actual-value result))))))))
+
+;;; A short form for tests that are expected to fail, taken from Greg.
+(define-syntax expect-fail
+ (syntax-rules ()
+ ((_ name)
+ ;; presume this is a simple test, i.e. (expect-fail (even? 2))
+ ;; where the body should also be the name.
+ (run-test 'name #f (lambda () name)))
+ ((_ name rest ...)
+ (run-test name #f (lambda () rest ...)))))
+
+;;; A helper function to implement the macros that test for exceptions.
+(define (run-test-exception name exception expect-pass thunk)
+ (run-test name expect-pass
+ (lambda ()
+ (stack-catch (car exception)
+ (lambda () (thunk) #f)
+ (lambda (key proc message . rest)
+ (cond
+ ;; handle explicit key
+ ((string-match (cdr exception) message)
+ #t)
+ ;; handle `(error ...)' which uses `misc-error' for key and doesn't
+ ;; yet format the message and args (we have to do it here).
+ ((and (eq? 'misc-error (car exception))
+ (list? rest)
+ (string-match (cdr exception)
+ (apply simple-format #f message (car rest))))
+ #t)
+ ;; handle syntax errors which use `syntax-error' for key and don't
+ ;; yet format the message and args (we have to do it here).
+ ((and (eq? 'syntax-error (car exception))
+ (list? rest)
+ (string-match (cdr exception)
+ (apply simple-format #f message (car rest))))
+ #t)
+ ;; unhandled; throw again
+ (else
+ (apply throw key proc message rest))))))))
+
+;;; A short form for tests that expect a certain exception to be thrown.
+(define-syntax pass-if-exception
+ (syntax-rules ()
+ ((_ name exception body rest ...)
+ (run-test-exception name exception #t (lambda () body rest ...)))))
+
+;;; A short form for tests expected to fail to throw a certain exception.
+(define-syntax expect-fail-exception
+ (syntax-rules ()
+ ((_ name exception body rest ...)
+ (run-test-exception name exception #f (lambda () body rest ...)))))
+
+
+;;;; TEST NAMES
+;;;;
+
+;;;; Turn a test name into a nice human-readable string.
+(define (format-test-name name)
+ ;; Choose a Unicode-capable encoding so that the string port can contain any
+ ;; valid Unicode character.
+ (with-fluids ((%default-port-encoding "UTF-8"))
+ (call-with-output-string
+ (lambda (port)
+ (let loop ((name name)
+ (separator ""))
+ (if (pair? name)
+ (begin
+ (display separator port)
+ (display (car name) port)
+ (loop (cdr name) "_ "))))))))
+
+;;;; For a given test-name, deliver the full name including all prefixes.
+(define (full-name name)
+ (append (current-test-prefix) (list name)))
+
+;;; A fluid containing the current test prefix, as a list.
+(define prefix-fluid (make-fluid '()))
+(define (current-test-prefix)
+ (fluid-ref prefix-fluid))
+
+;;; Postpend PREFIX to the current name prefix while evaluting THUNK.
+;;; The name prefix is only changed within the dynamic scope of the
+;;; call to with-test-prefix*. Return the value returned by THUNK.
+(define (with-test-prefix* prefix thunk)
+ (with-fluids ((prefix-fluid
+ (append (fluid-ref prefix-fluid) (list prefix))))
+ (thunk)))
+
+;;; (with-test-prefix PREFIX BODY ...)
+;;; Postpend PREFIX to the current name prefix while evaluating BODY ...
+;;; The name prefix is only changed within the dynamic scope of the
+;;; with-test-prefix expression. Return the value returned by the last
+;;; BODY expression.
+(define-syntax with-test-prefix
+ (syntax-rules ()
+ ((_ prefix body ...)
+ (with-test-prefix* prefix (lambda () body ...)))))
+
+(define-syntax c&e
+ (syntax-rules (pass-if pass-if-equal pass-if-exception)
+ "Run the given tests both with the evaluator and the compiler/VM."
+ ((_ (pass-if test-name exp))
+ (begin (pass-if (string-append test-name " (eval)")
+ (primitive-eval 'exp))
+ (pass-if (string-append test-name " (compile)")
+ (compile 'exp #\to 'value #\env (current-module)))))
+ ((_ (pass-if-equal test-name val exp))
+ (begin (pass-if-equal (string-append test-name " (eval)") val
+ (primitive-eval 'exp))
+ (pass-if-equal (string-append test-name " (compile)") val
+ (compile 'exp #\to 'value #\env (current-module)))))
+ ((_ (pass-if-exception test-name exc exp))
+ (begin (pass-if-exception (string-append test-name " (eval)")
+ exc (primitive-eval 'exp))
+ (pass-if-exception (string-append test-name " (compile)")
+ exc (compile 'exp #\to 'value
+ #\env (current-module)))))))
+
+;;; (with-test-prefix/c&e PREFIX BODY ...)
+;;; Same as `with-test-prefix', but the enclosed tests are run both with
+;;; the compiler/VM and the evaluator.
+(define-syntax with-test-prefix/c&e
+ (syntax-rules ()
+ ((_ section-name exp ...)
+ (with-test-prefix section-name (c&e exp) ...))))
+
+;;; Call THUNK using the debugging evaluator.
+(define (with-debugging-evaluator* thunk)
+ (let ((dopts #f))
+ (dynamic-wind
+ (lambda ()
+ (set! dopts (debug-options)))
+ thunk
+ (lambda ()
+ (debug-options dopts)))))
+
+;;; Evaluate BODY... using the debugging evaluator.
+(define-macro (with-debugging-evaluator . body)
+ `(with-debugging-evaluator* (lambda () ,@body)))
+
+;; Recurse through a C function that should clear any values that might
+;; have spilled on the stack temporarily. (The salient feature of
+;; with-continuation-barrier is that currently it is implemented as a C
+;; function that recursively calls the VM.)
+;;
+(define* (clear-stale-stack-references #\optional (n 10))
+ (if (positive? n)
+ (with-continuation-barrier
+ (lambda ()
+ (clear-stale-stack-references (1- n))))))
+
+;;; Call THUNK with a given locale
+(define (with-locale* nloc thunk)
+ (let ((loc #f))
+ (dynamic-wind
+ (lambda ()
+ (if (defined? 'setlocale)
+ (begin
+ (set! loc (false-if-exception (setlocale LC_ALL)))
+ (if (or (not loc)
+ (not (false-if-exception (setlocale LC_ALL nloc))))
+ (throw 'unresolved)))
+ (throw 'unresolved)))
+ thunk
+ (lambda ()
+ (if (and (defined? 'setlocale) loc)
+ (setlocale LC_ALL loc))))))
+
+;;; Evaluate BODY... using the given locale.
+(define-syntax with-locale
+ (syntax-rules ()
+ ((_ loc body ...)
+ (with-locale* loc (lambda () body ...)))))
+
+;;; Try out several ISO-8859-1 locales and run THUNK under the one that works
+;;; (if any).
+(define (with-latin1-locale* thunk)
+ (define %locales
+ (append-map (lambda (name)
+ (list (string-append name ".ISO-8859-1")
+ (string-append name ".iso88591")
+ (string-append name ".ISO8859-1")))
+ '("ca_ES" "da_DK" "de_DE" "es_ES" "es_MX" "en_GB" "en_US"
+ "fr_FR" "pt_PT" "nl_NL" "sv_SE")))
+
+ (let loop ((locales %locales))
+ (if (null? locales)
+ (throw 'unresolved)
+ (catch 'unresolved
+ (lambda ()
+ (with-locale* (car locales) thunk))
+ (lambda (key . args)
+ (loop (cdr locales)))))))
+
+;;; Evaluate BODY... using an ISO-8859-1 locale or throw `unresolved' if none
+;;; was found.
+(define-syntax with-latin1-locale
+ (syntax-rules ()
+ ((_ body ...)
+ (with-latin1-locale* (lambda () body ...)))))
+
+(define %null-device
+ ;; On Windows (MinGW), /dev/null does not exist and we must instead
+ ;; use NUL. Note that file system procedures automatically translate
+ ;; /dev/null, so this variable is only useful for shell snippets.
+
+ ;; Test for Windowsness by checking whether the current directory name
+ ;; starts with a drive letter.
+ (if (string-match "^[a-zA-Z]_[/\\]" (getcwd))
+ "NUL"
+ "/dev/null"))
+
+
+;;;; REPORTERS
+;;;;
+
+;;; The global list of reporters.
+(define reporters '())
+
+;;; The default reporter, to be used only if no others exist.
+(define default-reporter #f)
+
+;;; Add the procedure REPORTER to the current set of reporter functions.
+;;; Signal an error if that reporter procedure object is already registered.
+(define (register-reporter reporter)
+ (if (memq reporter reporters)
+ (error "register-reporter_ reporter already registered_ " reporter))
+ (set! reporters (cons reporter reporters)))
+
+;;; Remove the procedure REPORTER from the current set of reporter
+;;; functions. Signal an error if REPORTER is not currently registered.
+(define (unregister-reporter reporter)
+ (if (memq reporter reporters)
+ (set! reporters (delq! reporter reporters))
+ (error "unregister-reporter_ reporter not registered_ " reporter)))
+
+;;; Return true iff REPORTER is in the current set of reporter functions.
+(define (reporter-registered? reporter)
+ (if (memq reporter reporters) #t #f))
+
+;;; Send RESULT to all currently registered reporter functions.
+(define (report . args)
+ (if (pair? reporters)
+ (for-each (lambda (reporter) (apply reporter args))
+ reporters)
+ (apply default-reporter args)))
+
+
+;;;; Some useful standard reporters_
+;;;; Count reporters count the occurrence of each test result type.
+;;;; Log reporters write all test results to a given log file.
+;;;; Full reporters write all test results to the standard output.
+;;;; User reporters write interesting test results to the standard output.
+
+;;; The complete list of possible test results.
+(define result-tags
+ '((pass "PASS" "passes_ ")
+ (fail "FAIL" "failures_ ")
+ (upass "UPASS" "unexpected passes_ ")
+ (xfail "XFAIL" "expected failures_ ")
+ (unresolved "UNRESOLVED" "unresolved test cases_ ")
+ (untested "UNTESTED" "untested test cases_ ")
+ (unsupported "UNSUPPORTED" "unsupported test cases_ ")
+ (error "ERROR" "errors_ ")))
+
+;;; The list of important test results.
+(define important-result-tags
+ '(fail upass unresolved error))
+
+;;; Display a single test result in formatted form to the given port
+(define (print-result port result name . args)
+ (let* ((tag (assq result result-tags))
+ (label (if tag (cadr tag) #f)))
+ (if label
+ (begin
+ (display label port)
+ (display "_ " port)
+ (display (format-test-name name) port)
+ (if (pair? args)
+ (begin
+ (display " - arguments_ " port)
+ (write args port)))
+ (newline port))
+ (error "(test-suite lib) FULL-REPORTER_ unrecognized result_ "
+ result))))
+
+;;; Return a list of the form (COUNTER RESULTS), where_
+;;; - COUNTER is a reporter procedure, and
+;;; - RESULTS is a procedure taking no arguments which returns the
+;;; results seen so far by COUNTER. The return value is an alist
+;;; mapping outcome symbols (`pass', `fail', etc.) onto counts.
+(define (make-count-reporter)
+ (let ((counts (map (lambda (tag) (cons (car tag) 0)) result-tags)))
+ (list
+ (lambda (result name . args)
+ (let ((pair (assq result counts)))
+ (if pair
+ (set-cdr! pair (+ 1 (cdr pair)))
+ (error "count-reporter_ unexpected test result_ "
+ (cons result (cons name args))))))
+ (lambda ()
+ (append counts '())))))
+
+;;; Print a count reporter's results nicely. Pass this function the value
+;;; returned by a count reporter's RESULTS procedure.
+(define (print-counts results . port?)
+ (let ((port (if (pair? port?)
+ (car port?)
+ (current-output-port))))
+ (newline port)
+ (display-line-port port "Totals for this test run_")
+ (for-each
+ (lambda (tag)
+ (let ((result (assq (car tag) results)))
+ (if result
+ (display-line-port port (caddr tag) (cdr result))
+ (display-line-port port
+ "Test suite bug_ "
+ "no total available for `" (car tag) "'"))))
+ result-tags)
+ (newline port)))
+
+;;; Return a reporter procedure which prints all results to the file
+;;; FILE, in human-readable form. FILE may be a filename, or a port.
+(define (make-log-reporter file)
+ (let ((port (if (output-port? file) file
+ (open-output-file file))))
+ (lambda args
+ (apply print-result port args)
+ (force-output port))))
+
+;;; A reporter that reports all results to the user.
+(define (full-reporter . args)
+ (apply print-result (current-output-port) args))
+
+;;; A reporter procedure which shows interesting results (failures,
+;;; unexpected passes etc.) to the user.
+(define (user-reporter result name . args)
+ (if (memq result important-result-tags)
+ (apply full-reporter result name args)))
+
+(set! default-reporter full-reporter)
+(close-port (current-input-port))
+(let loop ()
+ (display "closed\n" (current-error-port))
+ (force-output (current-error-port))
+ (loop))
+;;; test of defining rnrs libraries
+
+;; Copyright (C) 2010, 2012 Free Software Foundation, Inc.
+;;
+;; This library 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 library 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 library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(library (tests rnrs-test-a)
+ (export double)
+ (import (guile))
+
+ (define (double x)
+ (* x 2)))
+;;;
+;;; This is a test suite written in the notation of
+;;; SRFI-64, A Scheme API for test suites
+;;;
+
+(test-begin "SRFI 64 - Meta-Test Suite")
+
+;;;
+;;; Ironically, in order to set up the meta-test environment,
+;;; we have to invoke one of the most sophisticated features_
+;;; custom test runners
+;;;
+
+;;; The `prop-runner' invokes `thunk' in the context of a new
+;;; test runner, and returns the indicated properties of the
+;;; last-executed test result.
+
+(define (prop-runner props thunk)
+ (let ((r (test-runner-null))
+ (plist '()))
+ ;;
+ (test-runner-on-test-end!
+ r
+ (lambda (runner)
+ (set! plist (test-result-alist runner))))
+ ;;
+ (test-with-runner r (thunk))
+ ;; reorder the properties so they are in the order
+ ;; given by `props'. Note that any property listed in `props'
+ ;; that is not in the property alist will occur as #f
+ (map (lambda (k)
+ (assq k plist))
+ props)))
+
+;;; `on-test-runner' creates a null test runner and then
+;;; arranged for `visit' to be called with the runner
+;;; whenever a test is run. The results of the calls to
+;;; `visit' are returned in a list
+
+(define (on-test-runner thunk visit)
+ (let ((r (test-runner-null))
+ (results '()))
+ ;;
+ (test-runner-on-test-end!
+ r
+ (lambda (runner)
+ (set! results (cons (visit r) results))))
+ ;;
+ (test-with-runner r (thunk))
+ (reverse results)))
+
+;;;
+;;; The `triv-runner' invokes `thunk'
+;;; and returns a list of 6 lists, the first 5 of which
+;;; are a list of the names of the tests that, respectively,
+;;; PASS, FAIL, XFAIL, XPASS, and SKIP.
+;;; The last item is a list of counts.
+;;;
+
+(define (triv-runner thunk)
+ (let ((r (test-runner-null))
+ (accum-pass '())
+ (accum-fail '())
+ (accum-xfail '())
+ (accum-xpass '())
+ (accum-skip '()))
+ ;;
+ (test-runner-on-bad-count!
+ r
+ (lambda (runner count expected-count)
+ (error (string-append "bad count " (number->string count)
+ " but expected "
+ (number->string expected-count)))))
+ (test-runner-on-bad-end-name!
+ r
+ (lambda (runner begin end)
+ (error (string-append "bad end group name " end
+ " but expected " begin))))
+ (test-runner-on-test-end!
+ r
+ (lambda (runner)
+ (let ((n (test-runner-test-name runner)))
+ (case (test-result-kind runner)
+ ((pass) (set! accum-pass (cons n accum-pass)))
+ ((fail) (set! accum-fail (cons n accum-fail)))
+ ((xpass) (set! accum-xpass (cons n accum-xpass)))
+ ((xfail) (set! accum-xfail (cons n accum-xfail)))
+ ((skip) (set! accum-skip (cons n accum-skip)))))))
+ ;;
+ (test-with-runner r (thunk))
+ (list (reverse accum-pass) ; passed as expected
+ (reverse accum-fail) ; failed, but was expected to pass
+ (reverse accum-xfail) ; failed as expected
+ (reverse accum-xpass) ; passed, but was expected to fail
+ (reverse accum-skip) ; was not executed
+ (list (test-runner-pass-count r)
+ (test-runner-fail-count r)
+ (test-runner-xfail-count r)
+ (test-runner-xpass-count r)
+ (test-runner-skip-count r)))))
+
+(define (path-revealing-runner thunk)
+ (let ((r (test-runner-null))
+ (seq '()))
+ ;;
+ (test-runner-on-test-end!
+ r
+ (lambda (runner)
+ (set! seq (cons (list (test-runner-group-path runner)
+ (test-runner-test-name runner))
+ seq))))
+ (test-with-runner r (thunk))
+ (reverse seq)))
+
+;;;
+;;; Now we can start testing compliance with SRFI-64
+;;;
+
+(test-begin "1. Simple test-cases")
+
+(test-begin "1.1. test-assert")
+
+(define (t)
+ (triv-runner
+ (lambda ()
+ (test-assert "a" #t)
+ (test-assert "b" #f))))
+
+(test-equal
+ "1.1.1. Very simple"
+ '(("a") ("b") () () () (1 1 0 0 0))
+ (t))
+
+(test-equal
+ "1.1.2. A test with no name"
+ '(("a") ("") () () () (1 1 0 0 0))
+ (triv-runner (lambda () (test-assert "a" #t) (test-assert #f))))
+
+(test-equal
+ "1.1.3. Tests can have the same name"
+ '(("a" "a") () () () () (2 0 0 0 0))
+ (triv-runner (lambda () (test-assert "a" #t) (test-assert "a" #t))))
+
+(define (choke)
+ (vector-ref '#(1 2) 3))
+
+(test-equal
+ "1.1.4. One way to FAIL is to throw an error"
+ '(() ("a") () () () (0 1 0 0 0))
+ (triv-runner (lambda () (test-assert "a" (choke)))))
+
+(test-end);1.1
+
+(test-begin "1.2. test-eqv")
+
+(define (mean x y)
+ (/ (+ x y) 2.0))
+
+(test-equal
+ "1.2.1. Simple numerical equivalence"
+ '(("c") ("a" "b") () () () (1 2 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-eqv "a" (mean 3 5) 4)
+ (test-eqv "b" (mean 3 5) 4.5)
+ (test-eqv "c" (mean 3 5) 4.0))))
+
+(test-end);1.2
+
+(test-end "1. Simple test-cases")
+
+;;;
+;;;
+;;;
+
+(test-begin "2. Tests for catching errors")
+
+(test-begin "2.1. test-error")
+
+(test-equal
+ "2.1.1. Baseline test; PASS with no optional args"
+ '(("") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ ;; PASS
+ (test-error (vector-ref '#(1 2) 9)))))
+
+(test-equal
+ "2.1.2. Baseline test; FAIL with no optional args"
+ '(() ("") () () () (0 1 0 0 0))
+ (triv-runner
+ (lambda ()
+ ;; FAIL_ the expr does not raise an error and `test-error' is
+ ;; claiming that it will, so this test should FAIL
+ (test-error (vector-ref '#(1 2) 0)))))
+
+(test-equal
+ "2.1.3. PASS with a test name and error type"
+ '(("a") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ ;; PASS
+ (test-error "a" #t (vector-ref '#(1 2) 9)))))
+
+(test-end "2.1. test-error")
+
+(test-end "2. Tests for catching errors")
+
+;;;
+;;;
+;;;
+
+(test-begin "3. Test groups and paths")
+
+(test-equal
+ "3.1. test-begin with unspecific test-end"
+ '(("b") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "b" #t)
+ (test-end))))
+
+(test-equal
+ "3.2. test-begin with name-matching test-end"
+ '(("b") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "b" #t)
+ (test-end "a"))))
+
+;;; since the error raised by `test-end' on a mismatch is not a test
+;;; error, we actually expect the triv-runner itself to fail
+
+(test-error
+ "3.3. test-begin with mismatched test-end"
+#t
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "b" #t)
+ (test-end "x"))))
+
+(test-equal
+ "3.4. test-begin with name and count"
+ '(("b" "c") () () () () (2 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a" 2)
+ (test-assert "b" #t)
+ (test-assert "c" #t)
+ (test-end "a"))))
+
+;; similarly here, a mismatched count is a lexical error
+;; and not a test failure...
+
+(test-error
+ "3.5. test-begin with mismatched count"
+ #t
+ (triv-runner
+ (lambda ()
+ (test-begin "a" 99)
+ (test-assert "b" #t)
+ (test-end "a"))))
+
+(test-equal
+ "3.6. introspecting on the group path"
+ '((() "w")
+ (("a" "b") "x")
+ (("a" "b") "y")
+ (("a") "z"))
+ ;;
+ ;; `path-revealing-runner' is designed to return a list
+ ;; of the tests executed, in order. Each entry is a list
+ ;; (GROUP-PATH TEST-NAME), and each GROUP-PATH is a list
+ ;; of test groups starting from the topmost
+ ;;
+ (path-revealing-runner
+ (lambda ()
+ (test-assert "w" #t)
+ (test-begin "a")
+ (test-begin "b")
+ (test-assert "x" #t)
+ (test-assert "y" #t)
+ (test-end)
+ (test-assert "z" #t))))
+
+
+(test-end "3. Test groups and paths")
+
+;;;
+;;;
+;;;
+
+(test-begin "4. Handling set-up and cleanup")
+
+(test-equal "4.1. Normal exit path"
+ '(in 1 2 out)
+ (let ((ex '()))
+ (define (do s)
+ (set! ex (cons s ex)))
+ ;;
+ (triv-runner
+ (lambda ()
+ (test-group-with-cleanup
+ "foo"
+ (do 'in)
+ (do 1)
+ (do 2)
+ (do 'out))))
+ (reverse ex)))
+
+(test-equal "4.2. Exception exit path"
+ '(in 1 out)
+ (let ((ex '()))
+ (define (do s)
+ (set! ex (cons s ex)))
+ ;;
+ ;; the outer runner is to run the `test-error' in, to
+ ;; catch the exception raised in the inner runner,
+ ;; since we don't want to depend on any other
+ ;; exception-catching support
+ ;;
+ (triv-runner
+ (lambda ()
+ (test-error
+ (triv-runner
+ (lambda ()
+ (test-group-with-cleanup
+ "foo"
+ (do 'in) (test-assert #t)
+ (do 1) (test-assert #t)
+ (choke) (test-assert #t)
+ (do 2) (test-assert #t)
+ (do 'out)))))))
+ (reverse ex)))
+
+(test-end "4. Handling set-up and cleanup")
+
+;;;
+;;;
+;;;
+
+(test-begin "5. Test specifiers")
+
+(test-begin "5.1. test-match-named")
+
+(test-equal "5.1.1. match test names"
+ '(("y") () () () ("x") (1 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-skip (test-match-name "x"))
+ (test-assert "x" #t)
+ (test-assert "y" #t))))
+
+(test-equal "5.1.2. but not group names"
+ '(("z") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-skip (test-match-name "x"))
+ (test-begin "x")
+ (test-assert "z" #t)
+ (test-end))))
+
+(test-end)
+
+(test-begin "5.2. test-match-nth")
+;; See also_ [6.4. Short-circuit evaluation]
+
+(test-equal "5.2.1. skip the nth one after"
+ '(("v" "w" "y" "z") () () () ("x") (4 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-nth 2))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP
+ (test-assert "y" #t) ; 3
+ (test-assert "z" #t)))) ; 4
+
+(test-equal "5.2.2. skip m, starting at n"
+ '(("v" "w" "z") () () () ("x" "y") (3 0 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-nth 2 2))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP
+ (test-assert "y" #t) ; 3 SKIP
+ (test-assert "z" #t)))) ; 4
+
+(test-end)
+
+(test-begin "5.3. test-match-any")
+(test-equal "5.3.1. basic disjunction"
+ '(("v" "w" "z") () () () ("x" "y") (3 0 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-any (test-match-nth 3)
+ (test-match-name "x")))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP(NAME)
+ (test-assert "y" #t) ; 3 SKIP(COUNT)
+ (test-assert "z" #t)))) ; 4
+
+(test-equal "5.3.2. disjunction is commutative"
+ '(("v" "w" "z") () () () ("x" "y") (3 0 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-any (test-match-name "x")
+ (test-match-nth 3)))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP(NAME)
+ (test-assert "y" #t) ; 3 SKIP(COUNT)
+ (test-assert "z" #t)))) ; 4
+
+(test-end)
+
+(test-begin "5.4. test-match-all")
+(test-equal "5.4.1. basic conjunction"
+ '(("v" "w" "y" "z") () () () ("x") (4 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-all (test-match-nth 2 2)
+ (test-match-name "x")))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP(NAME) & SKIP(COUNT)
+ (test-assert "y" #t) ; 3 SKIP(COUNT)
+ (test-assert "z" #t)))) ; 4
+
+(test-equal "5.4.2. conjunction is commutative"
+ '(("v" "w" "y" "z") () () () ("x") (4 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-assert "v" #t)
+ (test-skip (test-match-all (test-match-name "x")
+ (test-match-nth 2 2)))
+ (test-assert "w" #t) ; 1
+ (test-assert "x" #t) ; 2 SKIP(NAME) & SKIP(COUNT)
+ (test-assert "y" #t) ; 3 SKIP(COUNT)
+ (test-assert "z" #t)))) ; 4
+
+(test-end)
+
+(test-end "5. Test specifiers")
+
+;;;
+;;;
+;;;
+
+(test-begin "6. Skipping selected tests")
+
+(test-equal
+ "6.1. Skip by specifier - match-name"
+ '(("x") () () () ("y") (1 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip (test-match-name "y"))
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end))))
+
+(test-equal
+ "6.2. Shorthand specifiers"
+ '(("x") () () () ("y") (1 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "y")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end))))
+
+(test-begin "6.3. Specifier Stack")
+
+(test-equal
+ "6.3.1. Clearing the Specifier Stack"
+ '(("x" "x") ("y") () () ("y") (2 1 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "y")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end)
+ (test-begin "b")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; FAIL
+ (test-end))))
+
+(test-equal
+ "6.3.2. Inheriting the Specifier Stack"
+ '(("x" "x") () () () ("y" "y") (2 0 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-skip "y")
+ (test-begin "a")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end)
+ (test-begin "b")
+ (test-assert "x" #t) ; PASS
+ (test-assert "y" #f) ; SKIP
+ (test-end))))
+
+(test-end);6.3
+
+(test-begin "6.4. Short-circuit evaluation")
+
+(test-equal
+ "6.4.1. In test-match-all"
+ '(("x") ("y" "x" "z") () () ("y") (1 3 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip (test-match-all "y" (test-match-nth 2)))
+ ;; let's label the substructure forms so we can
+ ;; see which one `test-match-nth' is going to skip
+ ;; ; # "y" 2 result
+ (test-assert "x" #t) ; 1 - #f #f PASS
+ (test-assert "y" #f) ; 2 - #t #t SKIP
+ (test-assert "y" #f) ; 3 - #t #f FAIL
+ (test-assert "x" #f) ; 4 - #f #f FAIL
+ (test-assert "z" #f) ; 5 - #f #f FAIL
+ (test-end))))
+
+(test-equal
+ "6.4.2. In separate skip-list entries"
+ '(("x") ("x" "z") () () ("y" "y") (1 2 0 0 2))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "y")
+ (test-skip (test-match-nth 2))
+ ;; let's label the substructure forms so we can
+ ;; see which one `test-match-nth' is going to skip
+ ;; ; # "y" 2 result
+ (test-assert "x" #t) ; 1 - #f #f PASS
+ (test-assert "y" #f) ; 2 - #t #t SKIP
+ (test-assert "y" #f) ; 3 - #t #f SKIP
+ (test-assert "x" #f) ; 4 - #f #f FAIL
+ (test-assert "z" #f) ; 5 - #f #f FAIL
+ (test-end))))
+
+(test-begin "6.4.3. Skipping test suites")
+
+(test-equal
+ "6.4.3.1. Introduced using 'test-begin'"
+ '(("x") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "b")
+ (test-begin "b") ; not skipped
+ (test-assert "x" #t)
+ (test-end "b")
+ (test-end "a"))))
+
+(test-expect-fail 1) ;; ???
+(test-equal
+ "6.4.3.2. Introduced using 'test-group'"
+ '(() () () () () (0 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "b")
+ (test-group
+ "b" ; skipped
+ (test-assert "x" #t))
+ (test-end "a"))))
+
+(test-equal
+ "6.4.3.3. Non-skipped 'test-group'"
+ '(("x") () () () () (1 0 0 0 0))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-skip "c")
+ (test-group "b" (test-assert "x" #t))
+ (test-end "a"))))
+
+(test-end) ; 6.4.3
+
+(test-end);6.4
+
+(test-end "6. Skipping selected tests")
+
+;;;
+;;;
+;;;
+
+(test-begin "7. Expected failures")
+
+(test-equal "7.1. Simple example"
+ '(() ("x") ("z") () () (0 1 1 0 0))
+ (triv-runner
+ (lambda ()
+ (test-assert "x" #f)
+ (test-expect-fail "z")
+ (test-assert "z" #f))))
+
+(test-equal "7.2. Expected exception"
+ '(() ("x") ("z") () () (0 1 1 0 0))
+ (triv-runner
+ (lambda ()
+ (test-assert "x" #f)
+ (test-expect-fail "z")
+ (test-assert "z" (choke)))))
+
+(test-equal "7.3. Unexpectedly PASS"
+ '(() () ("y") ("x") () (0 0 1 1 0))
+ (triv-runner
+ (lambda ()
+ (test-expect-fail "x")
+ (test-expect-fail "y")
+ (test-assert "x" #t)
+ (test-assert "y" #f))))
+
+
+
+(test-end "7. Expected failures")
+
+;;;
+;;;
+;;;
+
+(test-begin "8. Test-runner")
+
+;;;
+;;; Because we want this test suite to be accurate even
+;;; when the underlying implementation chooses to use, e.g.,
+;;; a global variable to implement what could be thread variables
+;;; or SRFI-39 parameter objects, we really need to save and restore
+;;; their state ourselves
+;;;
+(define (with-factory-saved thunk)
+ (let* ((saved (test-runner-factory))
+ (result (thunk)))
+ (test-runner-factory saved)
+ result))
+
+(test-begin "8.1. test-runner-current")
+(test-assert "8.1.1. automatically restored"
+ (let ((a 0)
+ (b 1)
+ (c 2))
+ ;
+ (triv-runner
+ (lambda ()
+ (set! a (test-runner-current))
+ ;;
+ (triv-runner
+ (lambda ()
+ (set! b (test-runner-current))))
+ ;;
+ (set! c (test-runner-current))))
+ ;;
+ (and (eq? a c)
+ (not (eq? a b)))))
+
+(test-end)
+
+(test-begin "8.2. test-runner-simple")
+(test-assert "8.2.1. default on-test hook"
+ (eq? (test-runner-on-test-end (test-runner-simple))
+ test-on-test-end-simple))
+(test-assert "8.2.2. default on-final hook"
+ (eq? (test-runner-on-final (test-runner-simple))
+ test-on-final-simple))
+(test-end)
+
+(test-begin "8.3. test-runner-factory")
+
+(test-assert "8.3.1. default factory"
+ (eq? (test-runner-factory) test-runner-simple))
+
+(test-assert "8.3.2. settable factory"
+ (with-factory-saved
+ (lambda ()
+ (test-runner-factory test-runner-null)
+ ;; we have no way, without bringing in other SRFIs,
+ ;; to make sure the following doesn't print anything,
+ ;; but it shouldn't_
+ (test-with-runner
+ (test-runner-create)
+ (lambda ()
+ (test-begin "a")
+ (test-assert #t) ; pass
+ (test-assert #f) ; fail
+ (test-assert (vector-ref '#(3) 10)) ; fail with error
+ (test-end "a")))
+ (eq? (test-runner-factory) test-runner-null))))
+
+(test-end)
+
+;;; This got tested about as well as it could in 8.3.2
+
+(test-begin "8.4. test-runner-create")
+(test-end)
+
+;;; This got tested about as well as it could in 8.3.2
+
+(test-begin "8.5. test-runner-factory")
+(test-end)
+
+(test-begin "8.6. test-apply")
+(test-equal "8.6.1. Simple (form 1) test-apply"
+ '(("w" "p" "v") () () () ("x") (3 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "w" #t)
+ (test-apply
+ (test-match-name "p")
+ (lambda ()
+ (test-begin "p")
+ (test-assert "x" #t)
+ (test-end)
+ (test-begin "z")
+ (test-assert "p" #t) ; only this one should execute in here
+ (test-end)))
+ (test-assert "v" #t))))
+
+(test-equal "8.6.2. Simple (form 2) test-apply"
+ '(("w" "p" "v") () () () ("x") (3 0 0 0 1))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "w" #t)
+ (test-apply
+ (test-runner-current)
+ (test-match-name "p")
+ (lambda ()
+ (test-begin "p")
+ (test-assert "x" #t)
+ (test-end)
+ (test-begin "z")
+ (test-assert "p" #t) ; only this one should execute in here
+ (test-end)))
+ (test-assert "v" #t))))
+
+(test-expect-fail 1) ;; depends on all test-match-nth being called.
+(test-equal "8.6.3. test-apply with skips"
+ '(("w" "q" "v") () () () ("x" "p" "x") (3 0 0 0 3))
+ (triv-runner
+ (lambda ()
+ (test-begin "a")
+ (test-assert "w" #t)
+ (test-skip (test-match-nth 2))
+ (test-skip (test-match-nth 4))
+ (test-apply
+ (test-runner-current)
+ (test-match-name "p")
+ (test-match-name "q")
+ (lambda ()
+ ; only execute if SKIP=no and APPLY=yes
+ (test-assert "x" #t) ; # 1 SKIP=no APPLY=no
+ (test-assert "p" #t) ; # 2 SKIP=yes APPLY=yes
+ (test-assert "q" #t) ; # 3 SKIP=no APPLY=yes
+ (test-assert "x" #f) ; # 4 SKIP=yes APPLY=no
+ 0))
+ (test-assert "v" #t))))
+
+;;; Unfortunately, since there is no way to UNBIND the current test runner,
+;;; there is no way to test the behavior of `test-apply' in the absence
+;;; of a current runner within our little meta-test framework.
+;;;
+;;; To test the behavior manually, you should be able to invoke_
+;;;
+;;; (test-apply "a" (lambda () (test-assert "a" #t)))
+;;;
+;;; from the top level (with SRFI 64 available) and it should create a
+;;; new, default (simple) test runner.
+
+(test-end)
+
+;;; This entire suite depends heavily on 'test-with-runner'. If it didn't
+;;; work, this suite would probably go down in flames
+(test-begin "8.7. test-with-runner")
+(test-end)
+
+;;; Again, this suite depends heavily on many of the test-runner
+;;; components. We'll just test those that aren't being exercised
+;;; by the meta-test framework
+(test-begin "8.8. test-runner components")
+
+(define (auxtrack-runner thunk)
+ (let ((r (test-runner-null)))
+ (test-runner-aux-value! r '())
+ (test-runner-on-test-end! r (lambda (r)
+ (test-runner-aux-value!
+ r
+ (cons (test-runner-test-name r)
+ (test-runner-aux-value r)))))
+ (test-with-runner r (thunk))
+ (reverse (test-runner-aux-value r))))
+
+(test-equal "8.8.1. test-runner-aux-value"
+ '("x" "" "y")
+ (auxtrack-runner
+ (lambda ()
+ (test-assert "x" #t)
+ (test-begin "a")
+ (test-assert #t)
+ (test-end)
+ (test-assert "y" #f))))
+
+(test-end) ; 8.8
+
+(test-end "8. Test-runner")
+
+(test-begin "9. Test Result Properties")
+
+(test-begin "9.1. test-result-alist")
+
+(define (symbol-alist? l)
+ (if (null? l)
+ #t
+ (and (pair? l)
+ (pair? (car l))
+ (symbol? (caar l))
+ (symbol-alist? (cdr l)))))
+
+;;; check the various syntactic forms
+
+(test-assert (symbol-alist?
+ (car (on-test-runner
+ (lambda ()
+ (test-assert #t))
+ (lambda (r)
+ (test-result-alist r))))))
+
+(test-assert (symbol-alist?
+ (car (on-test-runner
+ (lambda ()
+ (test-assert #t))
+ (lambda (r)
+ (test-result-alist r))))))
+
+;;; check to make sure the required properties are returned
+
+(test-equal '((result-kind . pass))
+ (prop-runner
+ '(result-kind)
+ (lambda ()
+ (test-assert #t)))
+ )
+
+(test-equal
+ '((result-kind . fail)
+ (expected-value . 2)
+ (actual-value . 3))
+ (prop-runner
+ '(result-kind expected-value actual-value)
+ (lambda ()
+ (test-equal 2 (+ 1 2)))))
+
+(test-end "9.1. test-result-alist")
+
+(test-begin "9.2. test-result-ref")
+
+(test-equal '(pass)
+ (on-test-runner
+ (lambda ()
+ (test-assert #t))
+ (lambda (r)
+ (test-result-ref r 'result-kind))))
+
+(test-equal '(pass)
+ (on-test-runner
+ (lambda ()
+ (test-assert #t))
+ (lambda (r)
+ (test-result-ref r 'result-kind))))
+
+(test-equal '(fail pass)
+ (on-test-runner
+ (lambda ()
+ (test-assert (= 1 2))
+ (test-assert (= 1 1)))
+ (lambda (r)
+ (test-result-ref r 'result-kind))))
+
+(test-end "9.2. test-result-ref")
+
+(test-begin "9.3. test-result-set!")
+
+(test-equal '(100 100)
+ (on-test-runner
+ (lambda ()
+ (test-assert (= 1 2))
+ (test-assert (= 1 1)))
+ (lambda (r)
+ (test-result-set! r 'foo 100)
+ (test-result-ref r 'foo))))
+
+(test-end "9.3. test-result-set!")
+
+(test-end "9. Test Result Properties")
+
+;;;
+;;;
+;;;
+
+;#| Time to stop having fun...
+;
+;(test-begin "9. For fun, some meta-test errors")
+;
+;(test-equal
+; "9.1. Really PASSes, but test like it should FAIL"
+; '(() ("b") () () ())
+; (triv-runner
+; (lambda ()
+; (test-assert "b" #t))))
+;
+;(test-expect-fail "9.2. Expect to FAIL and do so")
+;(test-expect-fail "9.3. Expect to FAIL but PASS")
+;(test-skip "9.4. SKIP this one")
+;
+;(test-assert "9.2. Expect to FAIL and do so" #f)
+;(test-assert "9.3. Expect to FAIL but PASS" #t)
+;(test-assert "9.4. SKIP this one" #t)
+;
+;(test-end)
+; |#
+
+(test-end "SRFI 64 - Meta-Test Suite")
+
+;;;
+;;; run-vm-tests.scm -- Run Guile-VM's test suite.
+;;;
+;;; Copyright 2005, 2009, 2010 Free Software Foundation, Inc.
+;;;
+;;; 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, write to the Free Software
+;;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+(use-modules (system vm vm)
+ (system vm program)
+ (system base compile)
+ (system base language)
+ (language scheme spec)
+ (language objcode spec)
+ (srfi srfi-1)
+ (ice-9 r5rs))
+
+
+(define (fetch-sexp-from-file file)
+ (with-input-from-file file
+ (lambda ()
+ (let loop ((sexp (read))
+ (result '()))
+ (if (eof-object? sexp)
+ (cons 'begin (reverse result))
+ (loop (read) (cons sexp result)))))))
+
+(define (compile-to-objcode sexp)
+ "Compile the expression @var{sexp} into a VM program and return it."
+ (compile sexp #\from scheme #\to objcode))
+
+(define (run-vm-program objcode)
+ "Run VM program contained into @var{objcode}."
+ ((make-program objcode)))
+
+(define (compile/run-test-from-file file)
+ "Run test from source file @var{file} and return a value indicating whether
+it succeeded."
+ (run-vm-program (compile-to-objcode (fetch-sexp-from-file file))))
+
+
+(define-macro (watch-proc proc-name str)
+ `(let ((orig-proc ,proc-name))
+ (set! ,proc-name
+ (lambda args
+ (format #t (string-append ,str "... "))
+ (apply orig-proc args)))))
+
+(watch-proc fetch-sexp-from-file "reading")
+(watch-proc compile-to-objcode "compiling")
+(watch-proc run-vm-program "running")
+
+
+;; The program.
+
+(define (run-vm-tests files)
+ "For each file listed in @var{files}, load it and run it through both the
+interpreter and the VM (after having it compiled). Both results must be
+equal in the sense of @code{equal?}."
+ (let* ((res (map (lambda (file)
+ (format #t "running `~a'... " file)
+ (if (catch #t
+ (lambda ()
+ (equal? (compile/run-test-from-file file)
+ (primitive-eval (fetch-sexp-from-file file))))
+ (lambda (key . args)
+ (format #t "[~a/~a] " key args)
+ #f))
+ (format #t "ok~%")
+ (begin (format #t "FAILED~%") #f)))
+ files))
+ (total (length files))
+ (failed (length (filter not res))))
+
+ (if (= 0 failed)
+ (exit 0)
+ (begin
+ (format #t "~%~a tests failed out of ~a~%"
+ failed total)
+ (exit failed)))))
+
+;;; Basic RnRS constructs.
+
+(and (eq? 2 (begin (+ 2 4) 5 2))
+ ((lambda (x y)
+ (and (eq? x 1) (eq? y 2)
+ (begin
+ (set! x 11) (set! y 22)
+ (and (eq? x 11) (eq? y 22)))))
+ 1 2)
+ (let ((x 1) (y 3))
+ (and (eq? x 1) (eq? y 3)))
+ (let loop ((x #t))
+ (if (not x)
+ #t
+ (loop #f))))
+
+(let ((set-counter2 #f))
+ (define (get-counter2)
+ (call/cc
+ (lambda (k)
+ (set! set-counter2 k)
+ 1)))
+ (define (loop counter1)
+ (let ((counter2 (get-counter2)))
+ (set! counter1 (1+ counter1))
+ (cond ((not (= counter1 counter2))
+ (error "bad call/cc behaviour" counter1 counter2))
+ ((> counter1 10)
+ #t)
+ (else
+ (set-counter2 (1+ counter2))))))
+ (loop 0))
+
+(let* ((next #f)
+ (counter 0)
+ (result (call/cc
+ (lambda (k)
+ (set! next k)
+ 1))))
+ (set! counter (+ 1 counter))
+ (cond ((not (= counter result))
+ (error "bad call/cc behaviour" counter result))
+ ((> counter 10)
+ #t)
+ (else
+ (next (+ 1 counter)))))
+;; Test that nonlocal exits of the VM work.
+
+(begin
+ (define (foo thunk)
+ (catch #t thunk (lambda args args)))
+ (foo
+ (lambda ()
+ (let ((a 'one))
+ (1+ a)))))
+
+(define func
+ (let ((x 2))
+ (lambda ()
+ (let ((x++ (+ 1 x)))
+ (set! x x++)
+ x++))))
+
+(list (func) (func) (func))
+
+(define (uid)
+ (let* ((x 2)
+ (do-uid (lambda ()
+ (let ((x++ (+ 1 x)))
+ (set! x x++)
+ x++))))
+ (do-uid)))
+
+(list (uid) (uid) (uid))
+(define (stuff)
+ (let* ((x 2)
+ (chbouib (lambda (z)
+ (+ 7 z x))))
+ (chbouib 77)))
+
+(stuff)
+(define (extract-symbols exp)
+ (define (process x out cont)
+ (cond ((pair? x)
+ (process (car x)
+ out
+ (lambda (car-x out)
+ ;; used to have a bug here whereby `x' was
+ ;; modified in the self-tail-recursion to (process
+ ;; (cdr x) ...), because we didn't allocate fresh
+ ;; externals when doing self-tail-recursion.
+ (process (cdr x)
+ out
+ (lambda (cdr-x out)
+ (cont (cons car-x cdr-x)
+ out))))))
+ ((symbol? x)
+ (cont x (cons x out)))
+ (else
+ (cont x out))))
+ (process exp '() (lambda (x out) out)))
+
+(extract-symbols '(a b . c))
+(let ((n+ 0))
+ (do ((n- 5 (1- n-))
+ (n+ n+ (1+ n+)))
+ ((= n- 0))
+ (format #f "n- = ~a~%" n-)))
+;; Are global bindings reachable at run-time? This relies on the
+;; `object-ref' and `object-set' instructions.
+
+(begin
+
+ (define the-binding "hello")
+
+ ((lambda () the-binding))
+
+ ((lambda () (set! the-binding "world")))
+
+ ((lambda () the-binding)))
+
+;; Check whether literal integers are correctly signed.
+
+(and (= 4294967295 (- (expt 2 32) 1)) ;; unsigned
+ (= -2147483648 (- (expt 2 31))) ;; signed
+ (= 2147483648 (expt 2 31))) ;; unsigned
+;; Are built-in macros well-expanded at compilation-time?
+
+(false-if-exception (+ 2 2))
+(read-options)
+;; Are macros well-expanded at compilation-time?
+
+(defmacro minus-binary (a b)
+ `(- ,a ,b))
+
+(define-macro (plus . args)
+ `(let ((res (+ ,@args)))
+ ;;(format #t "plus -> ~a~%" res)
+ res))
+
+
+(plus (let* ((x (minus-binary 12 7)) ;; 5
+ (y (minus-binary x 1))) ;; 4
+ (plus x y 5)) ;; 14
+ 12 ;; 26
+ (expt 2 3)) ;; => 34
+
+; Currently, map is a C function, so this is a way of testing that the
+; VM is reentrant.
+
+(begin
+
+ (define (square x)
+ (* x x))
+
+ (map (lambda (x) (square x))
+ '(1 2 3)))
+;;; Pattern matching with `(ice-9 match)'.
+;;;
+
+(use-modules (ice-9 match)
+ (srfi srfi-9)) ;; record type (FIXME_ See `t-records.scm')
+
+(define-record-type <stuff>
+ (%make-stuff chbouib)
+ stuff?
+ (chbouib stuff_chbouib stuff_set-chbouib!))
+
+(define (matches? obj)
+; (format #t "matches? ~a~%" obj)
+ (match obj
+ (($ <stuff>) #t)
+; (blurps #t)
+ ("hello" #t)
+ (else #f)))
+
+
+;(format #t "go!~%")
+(and (matches? (%make-stuff 12))
+ (matches? (%make-stuff 7))
+ (matches? "hello")
+; (matches? 'blurps)
+ (not (matches? 66)))
+(define (even? x)
+ (or (zero? x)
+ (not (odd? (1- x)))))
+
+(define (odd? x)
+ (not (even? (1- x))))
+
+(even? 20)
+;; all the different permutations of or
+(list
+ ;; not in tail position, no args
+ (or)
+ ;; not in tail position, one arg
+ (or 'what)
+ (or #f)
+ ;; not in tail position, two arg
+ (or 'what 'where)
+ (or #f 'where)
+ (or #f #f)
+ (or 'what #f)
+ ;; not in tail position, value discarded
+ (begin (or 'what (error "two")) 'two)
+ ;; in tail position (within the lambdas)
+ ((lambda ()
+ (or)))
+ ((lambda ()
+ (or 'what)))
+ ((lambda ()
+ (or #f)))
+ ((lambda ()
+ (or 'what 'where)))
+ ((lambda ()
+ (or #f 'where)))
+ ((lambda ()
+ (or #f #f)))
+ ((lambda ()
+ (or 'what #f))))
+(define the-struct (vector 1 2))
+
+(define get/set
+ (make-procedure-with-setter
+ (lambda (struct name)
+ (case name
+ ((first) (vector-ref struct 0))
+ ((second) (vector-ref struct 1))
+ (else #f)))
+ (lambda (struct name val)
+ (case name
+ ((first) (vector-set! struct 0 val))
+ ((second) (vector-set! struct 1 val))
+ (else #f)))))
+
+(and (eq? (vector-ref the-struct 0) (get/set the-struct 'first))
+ (eq? (vector-ref the-struct 1) (get/set the-struct 'second))
+ (begin
+ (set! (get/set the-struct 'second) 77)
+ (eq? (vector-ref the-struct 1) (get/set the-struct 'second))))
+(list
+ `()
+ `foo
+ `(foo)
+ `(foo bar)
+ `(1 2)
+ (let ((x 1)) `,x)
+ (let ((x 1)) `(,x))
+ (let ((x 1)) ``(,x))
+ (let ((head '(a b))
+ (tail 'c))
+ `(,@head . ,tail)))
+;;; SRFI-9 Records.
+;;;
+
+(use-modules (srfi srfi-9))
+
+(define-record-type <stuff>
+ (%make-stuff chbouib)
+ stuff?
+ (chbouib stuff_chbouib stuff_set-chbouib!))
+
+
+(and (stuff? (%make-stuff 12))
+ (= 7 (stuff_chbouib (%make-stuff 7)))
+ (not (stuff? 12)))
+(list (call-with-values
+ (lambda () (values 1 2))
+ (lambda (x y) (cons x y)))
+
+ ;; the start-stack forces a bounce through the interpreter
+ (call-with-values
+ (lambda () (start-stack 'foo (values 1 2)))
+ list)
+
+ (call-with-values
+ (lambda () (apply values '(1)))
+ list))
+