unofficial mirror of guile-devel@gnu.org 
 help / color / mirror / Atom feed
* Add SRFI-123 support.
@ 2015-08-23 15:17 Taylan Ulrich Bayırlı/Kammer
  2015-08-24 12:38 ` Taylan Ulrich Bayırlı/Kammer
  0 siblings, 1 reply; 3+ messages in thread
From: Taylan Ulrich Bayırlı/Kammer @ 2015-08-23 15:17 UTC (permalink / raw)
  To: guile-devel

[-- Attachment #1: Type: text/plain, Size: 761 bytes --]

Here's a patch for adding SRFI-123 support.  It applies on stable-2.0.

Please tell me if the changes to (srfi srfi-9) and (rnrs bytevectors)
are OK.  The test suite passes.  I also added a rudimentary test-suite
for SRFI-123.

I kept the documentation brief, since the full documentation can be
found in the SRFI document itself.


To clarify: this SRFI caters to programmers writing code that is 1) not
performance-critical, 2) rich in calls to verbose procedures like
hashtable-ref which become annoying.  "Scripty" code if you will.  It's
also a matter of taste.  It doesn't have much foot-shooting potential so
having it shouldn't hurt.  (Making this disclaimer because some Guilers
didn't seem to like it.)


Thanks in advance for the review. :-)
Taylan


[-- Attachment #2: 0001-Add-SRFI-123-support.patch --]
[-- Type: text/x-diff, Size: 19219 bytes --]

From 3f3a1225d76340d283c53830f990b18a485d3416 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Taylan=20Ulrich=20Bay=C4=B1rl=C4=B1/Kammer?=
 <taylanbayirli@gmail.com>
Date: Sun, 23 Aug 2015 15:21:49 +0200
Subject: [PATCH] Add SRFI-123 support.

* module/srfi/srfi-123.scm: New file.
* module/Makefile.am (SRFI_SOURCES): Add it.
* doc/ref/srfi-modules.texi (SRFI Support): Document it.

* module/rnrs/hashtables.scm: Register hashtable-ref and hashtable-set!
  with SRFI-123.

* module/srfi/srfi-9.scm (%define-record-type): Register an SRFI-123
  getter/setter for the defined record type.

* test-suite/tests/srfi-123.test: New file.
* test-suite/Makefile.am (SCM_TESTS): Add it.
---
 doc/ref/srfi-modules.texi      |  54 ++++++++++++
 module/Makefile.am             |   3 +-
 module/rnrs/hashtables.scm     |  12 ++-
 module/srfi/srfi-123.scm       | 183 +++++++++++++++++++++++++++++++++++++++++
 module/srfi/srfi-9.scm         |  42 +++++++++-
 test-suite/Makefile.am         |   1 +
 test-suite/tests/srfi-123.test |  79 ++++++++++++++++++
 7 files changed, 370 insertions(+), 4 deletions(-)
 create mode 100644 module/srfi/srfi-123.scm
 create mode 100644 test-suite/tests/srfi-123.test

diff --git a/doc/ref/srfi-modules.texi b/doc/ref/srfi-modules.texi
index d8ed8e1..9963526 100644
--- a/doc/ref/srfi-modules.texi
+++ b/doc/ref/srfi-modules.texi
@@ -63,6 +63,7 @@ get the relevant SRFI documents from the SRFI home page
 * SRFI-98::                     Accessing environment variables.
 * SRFI-105::                    Curly-infix expressions.
 * SRFI-111::                    Boxes.
+* SRFI-123::                    Generic accessor and modifier operators.
 @end menu
 
 
@@ -5603,6 +5604,59 @@ Return the current contents of @var{box}.
 Set the contents of @var{box} to @var{value}.
 @end deffn
 
+@node SRFI-123
+@subsection SRFI-123 Generic accessor and modifier operators.
+@cindex SRFI-123
+
+@uref{http://srfi.schemers.org/srfi-123/srfi-123.html, SRFI-123}
+provides a generic @var{ref} procedure that works on a variety of
+compound data types through dynamic dispatch, and a related
+@uref{http://srfi.schemers.org/srfi-17/srfi-17.html, SRFI-17} setter for
+it.  It also provides @var{ref*} which can walk through nested data
+structures via a given sequence of indices/keys, and again a related
+SRFI-17 setter.  There is a synonym to @var{ref*}, called @var{~}.  New
+types can be registered via @var{register-getter-with-setter!}, but this
+is generally unnecessary because record types are registered
+automatically.
+
+@deffn {Scheme Procedure} ref object field [default]
+Returns the value for @var{field} in @var{object}, e.g. the value for a
+given index in a list or vector, the value associated with a given key
+in a hashtable, etc.  The @var{default} argument is accepted for
+hashtables and returned when the hashtable look-up fails; an exception
+is raised if it isn't provided in that case.
+
+If @var{object} is a record, @var{field} may be a symbol denoting a
+field of the relevant record type.
+
+Guile's native hashtables aren't supported because there is no single
+referencing or setting procedure for them.  Alists also aren't
+supported, because they can't be easily distinguished from lists.
+
+There is an SRFI-17 setter for this procedure, which does the expected
+thing.
+@end deffn
+
+@deffn {Scheme Procedure} ref* object field1 field2 @dots{}
+@deffnx {Scheme Procedure} ~ object field1 field2 @dots{}
+Calls @var{ref} on @var{object} with @var{field1}, then with
+@var{field2} on the result, and so on, and returns the ultimate result.
+(The @var{ref} calls are all done without the @var{default} argument.)
+
+This procedure also has an SRFI-17 setter.
+@end deffn
+
+@deffn {Scheme Procedure} register-getter-with-setter! type getter sparse?
+Registers a new type for the dynamic dispatch.  The type predicate
+@var{type} should be for a disjoint type; it should not overlap with any
+other types as for instance @var{pair?} and @var{list?} do.  If you want
+to dispatch to different actions on a single type, register a
+getter/setter which does this sub-dispatch itself.
+
+The Boolean @var{sparse?} indicates whether the type is a ``sparse'' one
+like hashtables are.
+@end deffn
+
 @c srfi-modules.texi ends here
 
 @c Local Variables:
diff --git a/module/Makefile.am b/module/Makefile.am
index 7e96de7..13e5000 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -295,7 +295,8 @@ SRFI_SOURCES = \
   srfi/srfi-69.scm \
   srfi/srfi-88.scm \
   srfi/srfi-98.scm \
-  srfi/srfi-111.scm
+  srfi/srfi-111.scm \
+  srfi/srfi-123.scm
 
 RNRS_SOURCES =					\
   rnrs/base.scm					\
diff --git a/module/rnrs/hashtables.scm b/module/rnrs/hashtables.scm
index 98d2d76..871f841 100644
--- a/module/rnrs/hashtables.scm
+++ b/module/rnrs/hashtables.scm
@@ -68,7 +68,9 @@
 		  (hash equal-hash)
 		  (hash-by-identity symbol-hash))
 	  (rnrs base (6))
-	  (rnrs records procedural (6)))
+	  (rnrs records procedural (6))
+          (srfi :17)
+          (srfi :123))
   
   (define r6rs:hashtable 
     (make-record-type-descriptor 
@@ -178,4 +180,10 @@
     (hash-table-equivalence-function (r6rs:hashtable-wrapped-table hashtable)))
 
   (define (hashtable-hash-function hashtable)
-    (r6rs:hashtable-orig-hash-function hashtable)))
+    (r6rs:hashtable-orig-hash-function hashtable))
+
+  (register-getter-with-setter!
+   hashtable?
+   (getter-with-setter hashtable-ref
+                       hashtable-set!)
+   #t))
diff --git a/module/srfi/srfi-123.scm b/module/srfi/srfi-123.scm
new file mode 100644
index 0000000..41c377d
--- /dev/null
+++ b/module/srfi/srfi-123.scm
@@ -0,0 +1,183 @@
+;;; srfi-123.scm -- Generic accessor and modifier operators
+
+;; Copyright (C) 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.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
+
+;;; To solve a circular import problem with SRFI-9, we don't import
+;;; (rnrs hashtables) here; instead (rnrs hashtables) hooks itself into
+;;; this SRFI via register-getter-with-setter!.
+
+(define-module (srfi srfi-123)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-4)
+  #:use-module (srfi srfi-17)
+  #:use-module (srfi srfi-31)
+  #:use-module (ice-9 hash-table)
+  #:use-module (rnrs bytevectors)
+  #:export (ref ref* ~ register-getter-with-setter! $bracket-apply$))
+
+(cond-expand-provide (current-module) '(srfi-123))
+
+;;; Helpers
+
+(define-syntax push!
+  (syntax-rules ()
+    ((_ <list-var> <x>)
+     (set! <list-var> (cons <x> <list-var>)))))
+
+(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))))
+
+;;; SRFI-4 support
+
+(define srfi-4-types
+  (list s8vector? u8vector? s16vector? u16vector? s32vector? u32vector?
+        s64vector? u64vector?))
+
+(define bytevector-ref
+  (let ((getters (alist->hashq-table
+                  (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)))))
+    (lambda (bytevector index)
+      (let ((type (find (lambda (pred) (pred bytevector)) srfi-4-types)))
+        (if type
+            ((hashq-ref getters type) bytevector index)
+            (error "Wrong type argument." bytevector))))))
+
+(define bytevector-set!
+  (let ((setters (alist->hashq-table
+                  (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!)))))
+    (lambda (bytevector index value)
+      (let ((type (find (lambda (pred) (pred bytevector)) srfi-4-types)))
+        (if type
+            ((hashq-ref setters type) bytevector index value)
+            (error "Wrong type argument." bytevector))))))
+
+;;; 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 (hashq-ref getter-table (type-of object) #f)
+      (error "No generic getter for object's type." object)))
+
+(define (lookup-setter object)
+  (or (hashq-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->hashq-table
+   (list (cons bytevector? bytevector-ref)
+         (cons pair? pair-ref)
+         (cons string? string-ref)
+         (cons vector? vector-ref))))
+
+(define setter-table
+  (alist->hashq-table
+   (list (cons bytevector? bytevector-set!)
+         (cons pair? pair-set!)
+         (cons string? string-set!)
+         (cons vector? vector-set!))))
+
+(define sparse-types '())
+
+(define type-list
+  (list boolean? bytevector? char? eof-object? null? number? pair?
+        port? procedure? string? symbol? vector?))
+
+(define (register-getter-with-setter! type getter sparse?)
+  (push! type-list type)
+  (hashq-set! getter-table type getter)
+  (hashq-set! setter-table type (setter getter))
+  (when sparse?
+    (push! sparse-types type)))
+
+;;; srfi-123.scm ends here
diff --git a/module/srfi/srfi-9.scm b/module/srfi/srfi-9.scm
index 324fe9c..eeebd25 100644
--- a/module/srfi/srfi-9.scm
+++ b/module/srfi/srfi-9.scm
@@ -61,6 +61,9 @@
 
 (define-module (srfi srfi-9)
   #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-17)
+  #:use-module (srfi srfi-123)
+  #:use-module (ice-9 hash-table)
   #:use-module (system base ck)
   #:export (define-record-type))
 
@@ -273,6 +276,35 @@
       (let ((desc (if immutable? "pr" "pw")))
         (string-concatenate (make-list count desc))))
 
+    (define (generic-getter field-specs)
+      (syntax-case field-specs ()
+        (((name getter-name . rest) ...)
+         #`(let ((getters (alist->hashq-table
+                           (list (cons 'name getter-name)
+                                 ...))))
+             (lambda (record field-name)
+               (let ((getter (or (hashq-ref getters field-name)
+                                 (error "No such field of record."
+                                        record field-name))))
+                 (getter record)))))))
+
+    (define (generic-setter field-specs)
+      (let ((specs-with-setter (filter-map (lambda (spec)
+                                             (syntax-case spec ()
+                                               ((name getter) #f)
+                                               ((name getter setter) spec)))
+                                           field-specs)))
+        (syntax-case specs-with-setter ()
+          (((name getter-name setter-name) ...)
+           #`(let ((setters (alist->hashq-table
+                             (list (cons 'name setter-name)
+                                   ...))))
+               (lambda (record field-name value)
+                 (let ((setter (or (hashq-ref setters field-name)
+                                   (error "No such assignable field of record."
+                                          record field-name))))
+                   (setter record value))))))))
+
     (syntax-case x ()
       ((_ immutable? form type-name constructor-spec predicate-name
           field-spec ...)
@@ -329,7 +361,15 @@
              #,(copier #'type-name getter-ids copier-id)
              #,@(if immutable?
                     (functional-setters copier-id #'(field-spec ...))
-                    (setters #'type-name #'(field-spec ...))))))
+                    (setters #'type-name #'(field-spec ...)))
+             ;; Throw-away definition so as not to disturb a sequence of
+             ;; internal definitions.
+             (define __throwaway
+               (register-getter-with-setter!
+                predicate-name
+                (getter-with-setter #,(generic-getter #'(field-spec ...))
+                                    #,(generic-setter #'(field-spec ...)))
+                #f)))))
       ((_ immutable? form . rest)
        (syntax-violation
         (syntax-case #'form ()
diff --git a/test-suite/Makefile.am b/test-suite/Makefile.am
index 3b10353..4726086 100644
--- a/test-suite/Makefile.am
+++ b/test-suite/Makefile.am
@@ -153,6 +153,7 @@ SCM_TESTS = tests/00-initial-env.test		\
 	    tests/srfi-98.test			\
 	    tests/srfi-105.test			\
 	    tests/srfi-111.test			\
+	    tests/srfi-123.test			\
 	    tests/srfi-4.test			\
 	    tests/srfi-9.test			\
 	    tests/statprof.test			\
diff --git a/test-suite/tests/srfi-123.test b/test-suite/tests/srfi-123.test
new file mode 100644
index 0000000..38cc5e0
--- /dev/null
+++ b/test-suite/tests/srfi-123.test
@@ -0,0 +1,79 @@
+;;;; srfi-123.test --- SRFI-123.          -*- mode: scheme; coding: utf-8; -*-
+;;;;
+;;;; Copyright (C) 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.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
+
+(define-module (test-srfi-123)
+  #:use-module (test-suite lib)
+  #:use-module (srfi srfi-4)
+  #:use-module (srfi srfi-9)
+  #:use-module (srfi srfi-123)
+  #:use-module (rnrs bytevectors)
+  #:use-module (rnrs hashtables))
+
+(define-record-type <foo> (make-foo a b) foo?
+  (a foo-a set-foo-a!)
+  (b foo-b))
+
+(with-test-prefix "ref"
+  (pass-if "bytevector" (= 1 (ref #u8(0 1 2) 1)))
+  (pass-if "hashtable" (let ((table (make-eqv-hashtable)))
+                         (hashtable-set! table 'foo 0)
+                         (= 0 (ref table 'foo))))
+  (pass-if "hashtable default" (let ((table (make-eqv-hashtable)))
+                                 (= 1 (ref table 0 1))))
+  (pass-if "pair" (= 1 (ref (cons 0 1) 'cdr)))
+  (pass-if "list" (= 1 (ref (list 0 1 2) 1)))
+  (pass-if "string" (char=? #\b (ref "abc" 1)))
+  (pass-if "vector" (= 1 (ref (vector 0 1 2) 1)))
+  (pass-if "record" (= 1 (ref (make-foo 0 1) 'b)))
+  (pass-if "srfi-4" (= 1 (ref (s16vector 0 1 2) 1))))
+
+(with-test-prefix "ref*"
+  (pass-if (= 1 (ref* '(_ #(_ (0 . 1) _) _) 1 1 'cdr))))
+
+(with-test-prefix "ref setter"
+  (pass-if "bytevector" (let ((bv #u8(0 1 2)))
+                          (set! (ref bv 1) 3)
+                          (= 3 (ref bv 1))))
+  (pass-if "hashtable" (let ((ht (make-eqv-hashtable)))
+                         (set! (ref ht 'foo) 0)
+                         (= 0 (ref ht 'foo))))
+  (pass-if "pair" (let ((p (cons 0 1)))
+                    (set! (ref p 'cdr) 2)
+                    (= 2 (ref p 'cdr))))
+  (pass-if "list" (let ((l (list 0 1 2)))
+                    (set! (ref l 1) 3)
+                    (= 3 (ref l 1))))
+  (pass-if "string" (let ((s (string #\a #\b #\c)))
+                      (set! (ref s 1) #\d)
+                      (char=? #\d (ref s 1))))
+  (pass-if "vector" (let ((v (vector 0 1 2)))
+                      (set! (ref v 1) 3)
+                      (= 3 (ref v 1))))
+  (pass-if "record" (let ((r (make-foo 0 1)))
+                      (set! (ref r 'a) 2)
+                      (= 2 (ref r 'a))))
+  (pass-if "bad record assignment"
+    (not (false-if-exception (set! (ref (make-foo 0 1) 'b) 2))))
+  (pass-if "srfi-4" (let ((s16v (s16vector 0 1 2)))
+                      (set! (ref s16v 1) 3)
+                      (= 3 (ref s16v 1)))))
+
+(with-test-prefix "ref* setter"
+  (pass-if (let ((obj (list '_ (vector '_ (cons 0 1) '_) '_)))
+             (set! (ref* obj 1 1 'cdr) 2)
+             (= 2 (ref* obj 1 1 'cdr)))))
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: Add SRFI-123 support.
  2015-08-23 15:17 Add SRFI-123 support Taylan Ulrich Bayırlı/Kammer
@ 2015-08-24 12:38 ` Taylan Ulrich Bayırlı/Kammer
  2015-09-01 22:15   ` Taylan Ulrich Bayırlı/Kammer
  0 siblings, 1 reply; 3+ messages in thread
From: Taylan Ulrich Bayırlı/Kammer @ 2015-08-24 12:38 UTC (permalink / raw)
  To: guile-devel

[-- Attachment #1: Type: text/plain, Size: 132 bytes --]

I was mutating a bytevector literal in the test suite.  Here's a fixed
patch.  (It was passing, but isn't future-safe as we know.)


[-- Attachment #2: 0001-Add-SRFI-123-support.patch --]
[-- Type: text/x-diff, Size: 19228 bytes --]

From 9a8cc0727b34a4015cf3a0839bb354df979bfe65 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Taylan=20Ulrich=20Bay=C4=B1rl=C4=B1/Kammer?=
 <taylanbayirli@gmail.com>
Date: Sun, 23 Aug 2015 15:21:49 +0200
Subject: [PATCH] Add SRFI-123 support.

* module/srfi/srfi-123.scm: New file.
* module/Makefile.am (SRFI_SOURCES): Add it.
* doc/ref/srfi-modules.texi (SRFI Support): Document it.

* module/rnrs/hashtables.scm: Register hashtable-ref and hashtable-set!
  with SRFI-123.

* module/srfi/srfi-9.scm (%define-record-type): Register an SRFI-123
  getter/setter for the defined record type.

* test-suite/tests/srfi-123.test: New file.
* test-suite/Makefile.am (SCM_TESTS): Add it.
---
 doc/ref/srfi-modules.texi      |  54 ++++++++++++
 module/Makefile.am             |   3 +-
 module/rnrs/hashtables.scm     |  12 ++-
 module/srfi/srfi-123.scm       | 183 +++++++++++++++++++++++++++++++++++++++++
 module/srfi/srfi-9.scm         |  42 +++++++++-
 test-suite/Makefile.am         |   1 +
 test-suite/tests/srfi-123.test |  79 ++++++++++++++++++
 7 files changed, 370 insertions(+), 4 deletions(-)
 create mode 100644 module/srfi/srfi-123.scm
 create mode 100644 test-suite/tests/srfi-123.test

diff --git a/doc/ref/srfi-modules.texi b/doc/ref/srfi-modules.texi
index d8ed8e1..9963526 100644
--- a/doc/ref/srfi-modules.texi
+++ b/doc/ref/srfi-modules.texi
@@ -63,6 +63,7 @@ get the relevant SRFI documents from the SRFI home page
 * SRFI-98::                     Accessing environment variables.
 * SRFI-105::                    Curly-infix expressions.
 * SRFI-111::                    Boxes.
+* SRFI-123::                    Generic accessor and modifier operators.
 @end menu
 
 
@@ -5603,6 +5604,59 @@ Return the current contents of @var{box}.
 Set the contents of @var{box} to @var{value}.
 @end deffn
 
+@node SRFI-123
+@subsection SRFI-123 Generic accessor and modifier operators.
+@cindex SRFI-123
+
+@uref{http://srfi.schemers.org/srfi-123/srfi-123.html, SRFI-123}
+provides a generic @var{ref} procedure that works on a variety of
+compound data types through dynamic dispatch, and a related
+@uref{http://srfi.schemers.org/srfi-17/srfi-17.html, SRFI-17} setter for
+it.  It also provides @var{ref*} which can walk through nested data
+structures via a given sequence of indices/keys, and again a related
+SRFI-17 setter.  There is a synonym to @var{ref*}, called @var{~}.  New
+types can be registered via @var{register-getter-with-setter!}, but this
+is generally unnecessary because record types are registered
+automatically.
+
+@deffn {Scheme Procedure} ref object field [default]
+Returns the value for @var{field} in @var{object}, e.g. the value for a
+given index in a list or vector, the value associated with a given key
+in a hashtable, etc.  The @var{default} argument is accepted for
+hashtables and returned when the hashtable look-up fails; an exception
+is raised if it isn't provided in that case.
+
+If @var{object} is a record, @var{field} may be a symbol denoting a
+field of the relevant record type.
+
+Guile's native hashtables aren't supported because there is no single
+referencing or setting procedure for them.  Alists also aren't
+supported, because they can't be easily distinguished from lists.
+
+There is an SRFI-17 setter for this procedure, which does the expected
+thing.
+@end deffn
+
+@deffn {Scheme Procedure} ref* object field1 field2 @dots{}
+@deffnx {Scheme Procedure} ~ object field1 field2 @dots{}
+Calls @var{ref} on @var{object} with @var{field1}, then with
+@var{field2} on the result, and so on, and returns the ultimate result.
+(The @var{ref} calls are all done without the @var{default} argument.)
+
+This procedure also has an SRFI-17 setter.
+@end deffn
+
+@deffn {Scheme Procedure} register-getter-with-setter! type getter sparse?
+Registers a new type for the dynamic dispatch.  The type predicate
+@var{type} should be for a disjoint type; it should not overlap with any
+other types as for instance @var{pair?} and @var{list?} do.  If you want
+to dispatch to different actions on a single type, register a
+getter/setter which does this sub-dispatch itself.
+
+The Boolean @var{sparse?} indicates whether the type is a ``sparse'' one
+like hashtables are.
+@end deffn
+
 @c srfi-modules.texi ends here
 
 @c Local Variables:
diff --git a/module/Makefile.am b/module/Makefile.am
index 7e96de7..13e5000 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -295,7 +295,8 @@ SRFI_SOURCES = \
   srfi/srfi-69.scm \
   srfi/srfi-88.scm \
   srfi/srfi-98.scm \
-  srfi/srfi-111.scm
+  srfi/srfi-111.scm \
+  srfi/srfi-123.scm
 
 RNRS_SOURCES =					\
   rnrs/base.scm					\
diff --git a/module/rnrs/hashtables.scm b/module/rnrs/hashtables.scm
index 98d2d76..871f841 100644
--- a/module/rnrs/hashtables.scm
+++ b/module/rnrs/hashtables.scm
@@ -68,7 +68,9 @@
 		  (hash equal-hash)
 		  (hash-by-identity symbol-hash))
 	  (rnrs base (6))
-	  (rnrs records procedural (6)))
+	  (rnrs records procedural (6))
+          (srfi :17)
+          (srfi :123))
   
   (define r6rs:hashtable 
     (make-record-type-descriptor 
@@ -178,4 +180,10 @@
     (hash-table-equivalence-function (r6rs:hashtable-wrapped-table hashtable)))
 
   (define (hashtable-hash-function hashtable)
-    (r6rs:hashtable-orig-hash-function hashtable)))
+    (r6rs:hashtable-orig-hash-function hashtable))
+
+  (register-getter-with-setter!
+   hashtable?
+   (getter-with-setter hashtable-ref
+                       hashtable-set!)
+   #t))
diff --git a/module/srfi/srfi-123.scm b/module/srfi/srfi-123.scm
new file mode 100644
index 0000000..41c377d
--- /dev/null
+++ b/module/srfi/srfi-123.scm
@@ -0,0 +1,183 @@
+;;; srfi-123.scm -- Generic accessor and modifier operators
+
+;; Copyright (C) 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.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
+
+;;; To solve a circular import problem with SRFI-9, we don't import
+;;; (rnrs hashtables) here; instead (rnrs hashtables) hooks itself into
+;;; this SRFI via register-getter-with-setter!.
+
+(define-module (srfi srfi-123)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-4)
+  #:use-module (srfi srfi-17)
+  #:use-module (srfi srfi-31)
+  #:use-module (ice-9 hash-table)
+  #:use-module (rnrs bytevectors)
+  #:export (ref ref* ~ register-getter-with-setter! $bracket-apply$))
+
+(cond-expand-provide (current-module) '(srfi-123))
+
+;;; Helpers
+
+(define-syntax push!
+  (syntax-rules ()
+    ((_ <list-var> <x>)
+     (set! <list-var> (cons <x> <list-var>)))))
+
+(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))))
+
+;;; SRFI-4 support
+
+(define srfi-4-types
+  (list s8vector? u8vector? s16vector? u16vector? s32vector? u32vector?
+        s64vector? u64vector?))
+
+(define bytevector-ref
+  (let ((getters (alist->hashq-table
+                  (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)))))
+    (lambda (bytevector index)
+      (let ((type (find (lambda (pred) (pred bytevector)) srfi-4-types)))
+        (if type
+            ((hashq-ref getters type) bytevector index)
+            (error "Wrong type argument." bytevector))))))
+
+(define bytevector-set!
+  (let ((setters (alist->hashq-table
+                  (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!)))))
+    (lambda (bytevector index value)
+      (let ((type (find (lambda (pred) (pred bytevector)) srfi-4-types)))
+        (if type
+            ((hashq-ref setters type) bytevector index value)
+            (error "Wrong type argument." bytevector))))))
+
+;;; 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 (hashq-ref getter-table (type-of object) #f)
+      (error "No generic getter for object's type." object)))
+
+(define (lookup-setter object)
+  (or (hashq-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->hashq-table
+   (list (cons bytevector? bytevector-ref)
+         (cons pair? pair-ref)
+         (cons string? string-ref)
+         (cons vector? vector-ref))))
+
+(define setter-table
+  (alist->hashq-table
+   (list (cons bytevector? bytevector-set!)
+         (cons pair? pair-set!)
+         (cons string? string-set!)
+         (cons vector? vector-set!))))
+
+(define sparse-types '())
+
+(define type-list
+  (list boolean? bytevector? char? eof-object? null? number? pair?
+        port? procedure? string? symbol? vector?))
+
+(define (register-getter-with-setter! type getter sparse?)
+  (push! type-list type)
+  (hashq-set! getter-table type getter)
+  (hashq-set! setter-table type (setter getter))
+  (when sparse?
+    (push! sparse-types type)))
+
+;;; srfi-123.scm ends here
diff --git a/module/srfi/srfi-9.scm b/module/srfi/srfi-9.scm
index 324fe9c..eeebd25 100644
--- a/module/srfi/srfi-9.scm
+++ b/module/srfi/srfi-9.scm
@@ -61,6 +61,9 @@
 
 (define-module (srfi srfi-9)
   #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-17)
+  #:use-module (srfi srfi-123)
+  #:use-module (ice-9 hash-table)
   #:use-module (system base ck)
   #:export (define-record-type))
 
@@ -273,6 +276,35 @@
       (let ((desc (if immutable? "pr" "pw")))
         (string-concatenate (make-list count desc))))
 
+    (define (generic-getter field-specs)
+      (syntax-case field-specs ()
+        (((name getter-name . rest) ...)
+         #`(let ((getters (alist->hashq-table
+                           (list (cons 'name getter-name)
+                                 ...))))
+             (lambda (record field-name)
+               (let ((getter (or (hashq-ref getters field-name)
+                                 (error "No such field of record."
+                                        record field-name))))
+                 (getter record)))))))
+
+    (define (generic-setter field-specs)
+      (let ((specs-with-setter (filter-map (lambda (spec)
+                                             (syntax-case spec ()
+                                               ((name getter) #f)
+                                               ((name getter setter) spec)))
+                                           field-specs)))
+        (syntax-case specs-with-setter ()
+          (((name getter-name setter-name) ...)
+           #`(let ((setters (alist->hashq-table
+                             (list (cons 'name setter-name)
+                                   ...))))
+               (lambda (record field-name value)
+                 (let ((setter (or (hashq-ref setters field-name)
+                                   (error "No such assignable field of record."
+                                          record field-name))))
+                   (setter record value))))))))
+
     (syntax-case x ()
       ((_ immutable? form type-name constructor-spec predicate-name
           field-spec ...)
@@ -329,7 +361,15 @@
              #,(copier #'type-name getter-ids copier-id)
              #,@(if immutable?
                     (functional-setters copier-id #'(field-spec ...))
-                    (setters #'type-name #'(field-spec ...))))))
+                    (setters #'type-name #'(field-spec ...)))
+             ;; Throw-away definition so as not to disturb a sequence of
+             ;; internal definitions.
+             (define __throwaway
+               (register-getter-with-setter!
+                predicate-name
+                (getter-with-setter #,(generic-getter #'(field-spec ...))
+                                    #,(generic-setter #'(field-spec ...)))
+                #f)))))
       ((_ immutable? form . rest)
        (syntax-violation
         (syntax-case #'form ()
diff --git a/test-suite/Makefile.am b/test-suite/Makefile.am
index 3b10353..4726086 100644
--- a/test-suite/Makefile.am
+++ b/test-suite/Makefile.am
@@ -153,6 +153,7 @@ SCM_TESTS = tests/00-initial-env.test		\
 	    tests/srfi-98.test			\
 	    tests/srfi-105.test			\
 	    tests/srfi-111.test			\
+	    tests/srfi-123.test			\
 	    tests/srfi-4.test			\
 	    tests/srfi-9.test			\
 	    tests/statprof.test			\
diff --git a/test-suite/tests/srfi-123.test b/test-suite/tests/srfi-123.test
new file mode 100644
index 0000000..a33e234
--- /dev/null
+++ b/test-suite/tests/srfi-123.test
@@ -0,0 +1,79 @@
+;;;; srfi-123.test --- SRFI-123.          -*- mode: scheme; coding: utf-8; -*-
+;;;;
+;;;; Copyright (C) 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.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
+
+(define-module (test-srfi-123)
+  #:use-module (test-suite lib)
+  #:use-module (srfi srfi-4)
+  #:use-module (srfi srfi-9)
+  #:use-module (srfi srfi-123)
+  #:use-module (rnrs bytevectors)
+  #:use-module (rnrs hashtables))
+
+(define-record-type <foo> (make-foo a b) foo?
+  (a foo-a set-foo-a!)
+  (b foo-b))
+
+(with-test-prefix "ref"
+  (pass-if "bytevector" (= 1 (ref #u8(0 1 2) 1)))
+  (pass-if "hashtable" (let ((table (make-eqv-hashtable)))
+                         (hashtable-set! table 'foo 0)
+                         (= 0 (ref table 'foo))))
+  (pass-if "hashtable default" (let ((table (make-eqv-hashtable)))
+                                 (= 1 (ref table 0 1))))
+  (pass-if "pair" (= 1 (ref (cons 0 1) 'cdr)))
+  (pass-if "list" (= 1 (ref (list 0 1 2) 1)))
+  (pass-if "string" (char=? #\b (ref "abc" 1)))
+  (pass-if "vector" (= 1 (ref (vector 0 1 2) 1)))
+  (pass-if "record" (= 1 (ref (make-foo 0 1) 'b)))
+  (pass-if "srfi-4" (= 1 (ref (s16vector 0 1 2) 1))))
+
+(with-test-prefix "ref*"
+  (pass-if (= 1 (ref* '(_ #(_ (0 . 1) _) _) 1 1 'cdr))))
+
+(with-test-prefix "ref setter"
+  (pass-if "bytevector" (let ((bv (make-bytevector 3)))
+                          (set! (ref bv 1) 3)
+                          (= 3 (ref bv 1))))
+  (pass-if "hashtable" (let ((ht (make-eqv-hashtable)))
+                         (set! (ref ht 'foo) 0)
+                         (= 0 (ref ht 'foo))))
+  (pass-if "pair" (let ((p (cons 0 1)))
+                    (set! (ref p 'cdr) 2)
+                    (= 2 (ref p 'cdr))))
+  (pass-if "list" (let ((l (list 0 1 2)))
+                    (set! (ref l 1) 3)
+                    (= 3 (ref l 1))))
+  (pass-if "string" (let ((s (string #\a #\b #\c)))
+                      (set! (ref s 1) #\d)
+                      (char=? #\d (ref s 1))))
+  (pass-if "vector" (let ((v (vector 0 1 2)))
+                      (set! (ref v 1) 3)
+                      (= 3 (ref v 1))))
+  (pass-if "record" (let ((r (make-foo 0 1)))
+                      (set! (ref r 'a) 2)
+                      (= 2 (ref r 'a))))
+  (pass-if "bad record assignment"
+    (not (false-if-exception (set! (ref (make-foo 0 1) 'b) 2))))
+  (pass-if "srfi-4" (let ((s16v (s16vector 0 1 2)))
+                      (set! (ref s16v 1) 3)
+                      (= 3 (ref s16v 1)))))
+
+(with-test-prefix "ref* setter"
+  (pass-if (let ((obj (list '_ (vector '_ (cons 0 1) '_) '_)))
+             (set! (ref* obj 1 1 'cdr) 2)
+             (= 2 (ref* obj 1 1 'cdr)))))
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: Add SRFI-123 support.
  2015-08-24 12:38 ` Taylan Ulrich Bayırlı/Kammer
@ 2015-09-01 22:15   ` Taylan Ulrich Bayırlı/Kammer
  0 siblings, 0 replies; 3+ messages in thread
From: Taylan Ulrich Bayırlı/Kammer @ 2015-09-01 22:15 UTC (permalink / raw)
  To: guile-devel

[-- Attachment #1: Type: text/plain, Size: 453 bytes --]

Oops, it looks like the test suite doesn't pass anymore after I fixed
the problem where I was mutating a literal bytevector.  It should have
been the other way around! :-)

So a #u8() and #vu8() aren't the same thing after all.

(u8vector? #vu8(0))              => #f
(u8vector? (make-bytevector 1))  => #f

Maybe it's a bug?  Anyway, updated patch that works around the issue
cleanly is attached.  I verified that the test suite passes again.

Taylan


[-- Attachment #2: 0001-Add-SRFI-123-support.patch --]
[-- Type: text/x-diff, Size: 19297 bytes --]

From 7030862fd6e62ae772c92dc7c66b0f41eef61452 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Taylan=20Ulrich=20Bay=C4=B1rl=C4=B1/Kammer?=
 <taylanbayirli@gmail.com>
Date: Sun, 23 Aug 2015 15:21:49 +0200
Subject: [PATCH] Add SRFI-123 support.

* module/srfi/srfi-123.scm: New file.
* module/Makefile.am (SRFI_SOURCES): Add it.
* doc/ref/srfi-modules.texi (SRFI Support): Document it.

* module/rnrs/hashtables.scm: Register hashtable-ref and hashtable-set!
  with SRFI-123.

* module/srfi/srfi-9.scm (%define-record-type): Register an SRFI-123
  getter/setter for the defined record type.

* test-suite/tests/srfi-123.test: New file.
* test-suite/Makefile.am (SCM_TESTS): Add it.
---
 doc/ref/srfi-modules.texi      |  54 ++++++++++++
 module/Makefile.am             |   3 +-
 module/rnrs/hashtables.scm     |  12 ++-
 module/srfi/srfi-123.scm       | 185 +++++++++++++++++++++++++++++++++++++++++
 module/srfi/srfi-9.scm         |  42 +++++++++-
 test-suite/Makefile.am         |   1 +
 test-suite/tests/srfi-123.test |  79 ++++++++++++++++++
 7 files changed, 372 insertions(+), 4 deletions(-)
 create mode 100644 module/srfi/srfi-123.scm
 create mode 100644 test-suite/tests/srfi-123.test

diff --git a/doc/ref/srfi-modules.texi b/doc/ref/srfi-modules.texi
index d8ed8e1..9963526 100644
--- a/doc/ref/srfi-modules.texi
+++ b/doc/ref/srfi-modules.texi
@@ -63,6 +63,7 @@ get the relevant SRFI documents from the SRFI home page
 * SRFI-98::                     Accessing environment variables.
 * SRFI-105::                    Curly-infix expressions.
 * SRFI-111::                    Boxes.
+* SRFI-123::                    Generic accessor and modifier operators.
 @end menu
 
 
@@ -5603,6 +5604,59 @@ Return the current contents of @var{box}.
 Set the contents of @var{box} to @var{value}.
 @end deffn
 
+@node SRFI-123
+@subsection SRFI-123 Generic accessor and modifier operators.
+@cindex SRFI-123
+
+@uref{http://srfi.schemers.org/srfi-123/srfi-123.html, SRFI-123}
+provides a generic @var{ref} procedure that works on a variety of
+compound data types through dynamic dispatch, and a related
+@uref{http://srfi.schemers.org/srfi-17/srfi-17.html, SRFI-17} setter for
+it.  It also provides @var{ref*} which can walk through nested data
+structures via a given sequence of indices/keys, and again a related
+SRFI-17 setter.  There is a synonym to @var{ref*}, called @var{~}.  New
+types can be registered via @var{register-getter-with-setter!}, but this
+is generally unnecessary because record types are registered
+automatically.
+
+@deffn {Scheme Procedure} ref object field [default]
+Returns the value for @var{field} in @var{object}, e.g. the value for a
+given index in a list or vector, the value associated with a given key
+in a hashtable, etc.  The @var{default} argument is accepted for
+hashtables and returned when the hashtable look-up fails; an exception
+is raised if it isn't provided in that case.
+
+If @var{object} is a record, @var{field} may be a symbol denoting a
+field of the relevant record type.
+
+Guile's native hashtables aren't supported because there is no single
+referencing or setting procedure for them.  Alists also aren't
+supported, because they can't be easily distinguished from lists.
+
+There is an SRFI-17 setter for this procedure, which does the expected
+thing.
+@end deffn
+
+@deffn {Scheme Procedure} ref* object field1 field2 @dots{}
+@deffnx {Scheme Procedure} ~ object field1 field2 @dots{}
+Calls @var{ref} on @var{object} with @var{field1}, then with
+@var{field2} on the result, and so on, and returns the ultimate result.
+(The @var{ref} calls are all done without the @var{default} argument.)
+
+This procedure also has an SRFI-17 setter.
+@end deffn
+
+@deffn {Scheme Procedure} register-getter-with-setter! type getter sparse?
+Registers a new type for the dynamic dispatch.  The type predicate
+@var{type} should be for a disjoint type; it should not overlap with any
+other types as for instance @var{pair?} and @var{list?} do.  If you want
+to dispatch to different actions on a single type, register a
+getter/setter which does this sub-dispatch itself.
+
+The Boolean @var{sparse?} indicates whether the type is a ``sparse'' one
+like hashtables are.
+@end deffn
+
 @c srfi-modules.texi ends here
 
 @c Local Variables:
diff --git a/module/Makefile.am b/module/Makefile.am
index 7e96de7..13e5000 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -295,7 +295,8 @@ SRFI_SOURCES = \
   srfi/srfi-69.scm \
   srfi/srfi-88.scm \
   srfi/srfi-98.scm \
-  srfi/srfi-111.scm
+  srfi/srfi-111.scm \
+  srfi/srfi-123.scm
 
 RNRS_SOURCES =					\
   rnrs/base.scm					\
diff --git a/module/rnrs/hashtables.scm b/module/rnrs/hashtables.scm
index 98d2d76..871f841 100644
--- a/module/rnrs/hashtables.scm
+++ b/module/rnrs/hashtables.scm
@@ -68,7 +68,9 @@
 		  (hash equal-hash)
 		  (hash-by-identity symbol-hash))
 	  (rnrs base (6))
-	  (rnrs records procedural (6)))
+	  (rnrs records procedural (6))
+          (srfi :17)
+          (srfi :123))
   
   (define r6rs:hashtable 
     (make-record-type-descriptor 
@@ -178,4 +180,10 @@
     (hash-table-equivalence-function (r6rs:hashtable-wrapped-table hashtable)))
 
   (define (hashtable-hash-function hashtable)
-    (r6rs:hashtable-orig-hash-function hashtable)))
+    (r6rs:hashtable-orig-hash-function hashtable))
+
+  (register-getter-with-setter!
+   hashtable?
+   (getter-with-setter hashtable-ref
+                       hashtable-set!)
+   #t))
diff --git a/module/srfi/srfi-123.scm b/module/srfi/srfi-123.scm
new file mode 100644
index 0000000..5a25e6d
--- /dev/null
+++ b/module/srfi/srfi-123.scm
@@ -0,0 +1,185 @@
+;;; srfi-123.scm -- Generic accessor and modifier operators
+
+;; Copyright (C) 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.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
+
+;;; To solve a circular import problem with SRFI-9, we don't import
+;;; (rnrs hashtables) here; instead (rnrs hashtables) hooks itself into
+;;; this SRFI via register-getter-with-setter!.
+
+(define-module (srfi srfi-123)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-4)
+  #:use-module (srfi srfi-17)
+  #:use-module (srfi srfi-31)
+  #:use-module (ice-9 hash-table)
+  #:use-module (rnrs bytevectors)
+  #:export (ref ref* ~ register-getter-with-setter! $bracket-apply$))
+
+(cond-expand-provide (current-module) '(srfi-123))
+
+;;; Helpers
+
+(define-syntax push!
+  (syntax-rules ()
+    ((_ <list-var> <x>)
+     (set! <list-var> (cons <x> <list-var>)))))
+
+(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))))
+
+;;; SRFI-4 support
+
+(define srfi-4-types
+  (list s8vector? u8vector? s16vector? u16vector? s32vector? u32vector?
+        s64vector? u64vector?))
+
+(define bytevector-ref
+  (let ((getters (alist->hashq-table
+                  (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)))))
+    (lambda (bytevector index)
+      (let* ((type (find (lambda (pred) (pred bytevector)) srfi-4-types))
+             (getter (if type
+                         (hashq-ref getters type)
+                         bytevector-u8-ref)))
+        (getter bytevector index)))))
+
+(define bytevector-set!
+  (let ((setters (alist->hashq-table
+                  (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!)))))
+    (lambda (bytevector index value)
+      (let* ((type (find (lambda (pred) (pred bytevector)) srfi-4-types))
+             (setter (if type
+                         (hashq-ref setters type)
+                         bytevector-u8-set!)))
+        (setter bytevector index value)))))
+
+;;; 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 (hashq-ref getter-table (type-of object) #f)
+      (error "No generic getter for object's type." object)))
+
+(define (lookup-setter object)
+  (or (hashq-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->hashq-table
+   (list (cons bytevector? bytevector-ref)
+         (cons pair? pair-ref)
+         (cons string? string-ref)
+         (cons vector? vector-ref))))
+
+(define setter-table
+  (alist->hashq-table
+   (list (cons bytevector? bytevector-set!)
+         (cons pair? pair-set!)
+         (cons string? string-set!)
+         (cons vector? vector-set!))))
+
+(define sparse-types '())
+
+(define type-list
+  (list boolean? bytevector? char? eof-object? null? number? pair?
+        port? procedure? string? symbol? vector?))
+
+(define (register-getter-with-setter! type getter sparse?)
+  (push! type-list type)
+  (hashq-set! getter-table type getter)
+  (hashq-set! setter-table type (setter getter))
+  (when sparse?
+    (push! sparse-types type)))
+
+;;; srfi-123.scm ends here
diff --git a/module/srfi/srfi-9.scm b/module/srfi/srfi-9.scm
index 324fe9c..eeebd25 100644
--- a/module/srfi/srfi-9.scm
+++ b/module/srfi/srfi-9.scm
@@ -61,6 +61,9 @@
 
 (define-module (srfi srfi-9)
   #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-17)
+  #:use-module (srfi srfi-123)
+  #:use-module (ice-9 hash-table)
   #:use-module (system base ck)
   #:export (define-record-type))
 
@@ -273,6 +276,35 @@
       (let ((desc (if immutable? "pr" "pw")))
         (string-concatenate (make-list count desc))))
 
+    (define (generic-getter field-specs)
+      (syntax-case field-specs ()
+        (((name getter-name . rest) ...)
+         #`(let ((getters (alist->hashq-table
+                           (list (cons 'name getter-name)
+                                 ...))))
+             (lambda (record field-name)
+               (let ((getter (or (hashq-ref getters field-name)
+                                 (error "No such field of record."
+                                        record field-name))))
+                 (getter record)))))))
+
+    (define (generic-setter field-specs)
+      (let ((specs-with-setter (filter-map (lambda (spec)
+                                             (syntax-case spec ()
+                                               ((name getter) #f)
+                                               ((name getter setter) spec)))
+                                           field-specs)))
+        (syntax-case specs-with-setter ()
+          (((name getter-name setter-name) ...)
+           #`(let ((setters (alist->hashq-table
+                             (list (cons 'name setter-name)
+                                   ...))))
+               (lambda (record field-name value)
+                 (let ((setter (or (hashq-ref setters field-name)
+                                   (error "No such assignable field of record."
+                                          record field-name))))
+                   (setter record value))))))))
+
     (syntax-case x ()
       ((_ immutable? form type-name constructor-spec predicate-name
           field-spec ...)
@@ -329,7 +361,15 @@
              #,(copier #'type-name getter-ids copier-id)
              #,@(if immutable?
                     (functional-setters copier-id #'(field-spec ...))
-                    (setters #'type-name #'(field-spec ...))))))
+                    (setters #'type-name #'(field-spec ...)))
+             ;; Throw-away definition so as not to disturb a sequence of
+             ;; internal definitions.
+             (define __throwaway
+               (register-getter-with-setter!
+                predicate-name
+                (getter-with-setter #,(generic-getter #'(field-spec ...))
+                                    #,(generic-setter #'(field-spec ...)))
+                #f)))))
       ((_ immutable? form . rest)
        (syntax-violation
         (syntax-case #'form ()
diff --git a/test-suite/Makefile.am b/test-suite/Makefile.am
index 3b10353..4726086 100644
--- a/test-suite/Makefile.am
+++ b/test-suite/Makefile.am
@@ -153,6 +153,7 @@ SCM_TESTS = tests/00-initial-env.test		\
 	    tests/srfi-98.test			\
 	    tests/srfi-105.test			\
 	    tests/srfi-111.test			\
+	    tests/srfi-123.test			\
 	    tests/srfi-4.test			\
 	    tests/srfi-9.test			\
 	    tests/statprof.test			\
diff --git a/test-suite/tests/srfi-123.test b/test-suite/tests/srfi-123.test
new file mode 100644
index 0000000..a33e234
--- /dev/null
+++ b/test-suite/tests/srfi-123.test
@@ -0,0 +1,79 @@
+;;;; srfi-123.test --- SRFI-123.          -*- mode: scheme; coding: utf-8; -*-
+;;;;
+;;;; Copyright (C) 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.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
+
+(define-module (test-srfi-123)
+  #:use-module (test-suite lib)
+  #:use-module (srfi srfi-4)
+  #:use-module (srfi srfi-9)
+  #:use-module (srfi srfi-123)
+  #:use-module (rnrs bytevectors)
+  #:use-module (rnrs hashtables))
+
+(define-record-type <foo> (make-foo a b) foo?
+  (a foo-a set-foo-a!)
+  (b foo-b))
+
+(with-test-prefix "ref"
+  (pass-if "bytevector" (= 1 (ref #u8(0 1 2) 1)))
+  (pass-if "hashtable" (let ((table (make-eqv-hashtable)))
+                         (hashtable-set! table 'foo 0)
+                         (= 0 (ref table 'foo))))
+  (pass-if "hashtable default" (let ((table (make-eqv-hashtable)))
+                                 (= 1 (ref table 0 1))))
+  (pass-if "pair" (= 1 (ref (cons 0 1) 'cdr)))
+  (pass-if "list" (= 1 (ref (list 0 1 2) 1)))
+  (pass-if "string" (char=? #\b (ref "abc" 1)))
+  (pass-if "vector" (= 1 (ref (vector 0 1 2) 1)))
+  (pass-if "record" (= 1 (ref (make-foo 0 1) 'b)))
+  (pass-if "srfi-4" (= 1 (ref (s16vector 0 1 2) 1))))
+
+(with-test-prefix "ref*"
+  (pass-if (= 1 (ref* '(_ #(_ (0 . 1) _) _) 1 1 'cdr))))
+
+(with-test-prefix "ref setter"
+  (pass-if "bytevector" (let ((bv (make-bytevector 3)))
+                          (set! (ref bv 1) 3)
+                          (= 3 (ref bv 1))))
+  (pass-if "hashtable" (let ((ht (make-eqv-hashtable)))
+                         (set! (ref ht 'foo) 0)
+                         (= 0 (ref ht 'foo))))
+  (pass-if "pair" (let ((p (cons 0 1)))
+                    (set! (ref p 'cdr) 2)
+                    (= 2 (ref p 'cdr))))
+  (pass-if "list" (let ((l (list 0 1 2)))
+                    (set! (ref l 1) 3)
+                    (= 3 (ref l 1))))
+  (pass-if "string" (let ((s (string #\a #\b #\c)))
+                      (set! (ref s 1) #\d)
+                      (char=? #\d (ref s 1))))
+  (pass-if "vector" (let ((v (vector 0 1 2)))
+                      (set! (ref v 1) 3)
+                      (= 3 (ref v 1))))
+  (pass-if "record" (let ((r (make-foo 0 1)))
+                      (set! (ref r 'a) 2)
+                      (= 2 (ref r 'a))))
+  (pass-if "bad record assignment"
+    (not (false-if-exception (set! (ref (make-foo 0 1) 'b) 2))))
+  (pass-if "srfi-4" (let ((s16v (s16vector 0 1 2)))
+                      (set! (ref s16v 1) 3)
+                      (= 3 (ref s16v 1)))))
+
+(with-test-prefix "ref* setter"
+  (pass-if (let ((obj (list '_ (vector '_ (cons 0 1) '_) '_)))
+             (set! (ref* obj 1 1 'cdr) 2)
+             (= 2 (ref* obj 1 1 'cdr)))))
-- 
2.5.0


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2015-09-01 22:15 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-08-23 15:17 Add SRFI-123 support Taylan Ulrich Bayırlı/Kammer
2015-08-24 12:38 ` Taylan Ulrich Bayırlı/Kammer
2015-09-01 22:15   ` Taylan Ulrich Bayırlı/Kammer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).