diff options
| author | Taylan Kammer <taylan.kammer@gmail.com> | 2025-04-05 17:11:29 +0200 |
|---|---|---|
| committer | Taylan Kammer <taylan.kammer@gmail.com> | 2025-04-05 17:11:29 +0200 |
| commit | 70089dacfa6bab5a1e1d0d5aa257e2d671493beb (patch) | |
| tree | 913b19c94792e2d41fdc800d728ad0bdabf0fada /src/test/data/parser-torture.scm | |
| parent | cf934006c650d3d008a4408bedbd95597f906e43 (diff) | |
uhhhh buncha changes
Diffstat (limited to 'src/test/data/parser-torture.scm')
| -rw-r--r-- | src/test/data/parser-torture.scm | 132358 |
1 files changed, 0 insertions, 132358 deletions
diff --git a/src/test/data/parser-torture.scm b/src/test/data/parser-torture.scm deleted file mode 100644 index d475379..0000000 --- a/src/test/data/parser-torture.scm +++ /dev/null @@ -1,132358 +0,0 @@ -;;; 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 - '((#\< . "<") (#\> . ">") (#\& . "&") (#\" . """)))) - -;;; 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. -; > is treated as an embedded #\> character -; Note, < and & 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 ; ">" 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]]>>&]]]>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&' _2= \"%r%n%t12 3\">" '() - `((_1 . "12&") (_2 . ,(unesc-string " 12%n3")))) - (test "%tAbc='<&>
'%nNext='12&ent;34' />" - '((ent . "<xx>")) - `((,(string->symbol "Abc") . ,(unesc-string "<&>%n")) - (,(string->symbol "Next") . "12<xx>34"))) - (test "%tAbc='<&>
'%nNext='12&ent;34' />" - '((ent . "<xx>")) - `((,(string->symbol "Abc") . ,(unesc-string "<&>%r")) - (,(string->symbol "Next") . "12<xx>34"))) - (test "%tAbc='<&>
'%nNext='12&en;34' />" - `((en . ,(lambda () (open-input-string ""xx'")))) - `((,(string->symbol "Abc") . ,(unesc-string "<&>%n")) - (,(string->symbol "Next") . "12\"xx'34"))) - (test "%tAbc='<&>
'%nNext='12&ent;34' />" - '((ent . "<&ent1;T;>") (ent1 . "&")) - `((,(string->symbol "Abc") . ,(unesc-string "<&>%n")) - (,(string->symbol "Next") . "12<&T;>34"))) - (test "%tAbc='<&>
'%nNext='12&ent;34' />" - `((*DEFAULT* . ,(lambda (port name) - (case name - ((ent) "<&ent1;T;>") - ((ent1) "&") - (else (error "unrecognized" name)))))) - `((,(string->symbol "Abc") . ,(unesc-string "<&>%n")) - (,(string->symbol "Next") . "12<&T;>34"))) - (assert (failed? - (test "%tAbc='<&>
'%nNext='12&ent;34' />" - '((ent . "<&ent1;T;>") (ent1 . "&")) '()))) - (assert (failed? - (test "%tAbc='<&>
'%nNext='12&ent;34' />" - '((ent . "<&ent;T;>") (ent1 . "&")) '()))) - (assert (failed? - (test "%tAbc='<&>
'%nNext='12&ent;34' />" - '((ent . "<&ent1;T;>") (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 " <" #f '(" ") a-ref) - (test " a<" #f '(" a") a-ref) - (test " a <" #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 "!<BR/>" #f '("" "!") a-tag) - (test "!%n<BR/>" #f '("" "!" "%n") a-tag) - (test "%t!%n<BR/>" #f '("%t" "!" "%n") a-tag) - (test "%t!%na a<BR/>" #f '("%t" "!" "%na a") a-tag) - (test "%t!%ra a<BR/>" #f '("%t" "!" "" "%n" "a a") a-tag) - (test "%t!%r%na a<BR/>" #f '("%t" "!" "" "%n" "a a") a-tag) - - (test " %ta ! b <BR/>" #f '(" %ta " "!" " b ") a-tag) - (test " %ta   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;</A>" - dummy-doctype-fn - '(('"A" (@ ('"HREF" "URL")) " link " ('"I" "itlink ") - " " "&" "amp;"))) - - (test - " <A HREF='URL' xml:space='preserve'> link <I>itlink </I> &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;</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>]]>]]></P>" - dummy-doctype-fn `(('"P" "<BR>" ,nl "<![CDATA[<BR>" "]]" "" ">"))) - - (test " <P><![CDATA[<BR>%r<![CDATA[<BR>]]>]]></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 (&) may be escaped numerically (&#38;) or with a general entity (&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>"e;!</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>"e;</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;</A>" '() - '(*TOP* (A (@ (HREF "URL")) " link " (I "itlink ") " &"))) - (test " <A HREF='URL' xml:space='preserve'> link <I>itlink </I> &amp;</A>" '() - '(*TOP* (A (@ (xml:space "preserve") (HREF "URL")) - " link " (I "itlink ") " &"))) - (test " <A HREF='URL' xml:space='preserve'> link <I xml:space='default'>itlink </I> &amp;</A>" '() - '(*TOP* (A (@ (xml:space "preserve") (HREF "URL")) - " link " (I (@ (xml:space "default")) - "itlink ") " &"))) - (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"<B>strong</B>"%r</P>" - '() - `(*TOP* (P ,(unesc-string "some text <1%n\"") - (B "strong") ,(unesc-string "\"%n")))) - (test " <P><![CDATA[<BR>%n<![CDATA[<BR>]]>]]></P>" '() - `(*TOP* (P ,(unesc-string "<BR>%n<![CDATA[<BR>]]>")))) -; (test "<T1><T2>it'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'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'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&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&product_id=912&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{\"&\"} 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)) - |
