Skip to content
Snippets Groups Projects
Commit 97de9df8 authored by Hugo Hörnquist's avatar Hugo Hörnquist
Browse files

Complete rewrite of define-record-type.

parent 9f1fb474
No related branches found
No related tags found
No related merge requests found
...@@ -36,68 +36,105 @@ ...@@ -36,68 +36,105 @@
(list key (serialize value))) (list key (serialize value)))
this)) this))
;; Helper procedure for define-record-type macro, which if @var{stx} is a syntax
;; object of a list takes the first argument of it, keeping it a syntax object
(define (unlist stx)
(if (list? (syntax->datum stx)) (define (handle-define*-clause stx)
(->> stx syntax->datum car (datum->syntax stx)) (syntax-case stx ()
stx)) ((name default: default args ...) #'(name default))
((name arg args ...) (handle-define*-clause #'(name args ...)))
(define (mlist->define-field stx) ((name) #'name)
(define l (syntax->datum stx)) (name #'name)))
(cond ((and (list? l)
(memv default: l)) (define-syntax build-validator
=> (lambda (field) (syntax-rules (and or)
#`(#,(datum->syntax stx (car l)) ((_ variable (and clauses ...)) (and (build-validator variable clauses) ...))
#,(datum->syntax stx (cadr field))))) ((_ variable (or clauses ...)) (or (build-validator variable clauses) ...))
((list? l) ((_ variable (proc args ...)) (proc variable args ...))
(datum->syntax stx (car l))) ((_ variable proc) (proc variable))))
(else stx)))
;; Given #'(<field-name> type: <validator-body>), expands validator-body to contain <field-name>
;; string x field-spec → <validator syntax>
(define (handle-validator constructor-name)
(define (inner field)
(syntax-case field ()
((name type: validator-body args ...)
#`(unless (build-validator name validator-body)
(scm-error 'wrong-type-arg #,constructor-name
"`~a: ~s' doesn't satisfy `~a'"
(list (quote name) name (quote validator-body))
(list (quote name) name))))
((name arg args ...)
(inner #'(name args ...)))
;; Case when no #:type annotation exists.
;; Should hopefully be optimized away by the compiler
(_ #'(if #f #f))))
inner)
;; Takes a field from the define-record-type macro, and returns the field name.
;; E.g.
;; (x args ...) ⇒ x
;; x ⇒ x
(define (field-name stx)
(syntax-case stx ()
((name args ...) #'name)
(name #'name)))
;; Helper function to use with with-syntax
(define (binding pattern fmt)
(->> pattern
syntax->datum
(format #f fmt)
string->symbol
(datum->syntax pattern)))
(define-syntax define-record-type (define-syntax define-record-type
(lambda (stx) (lambda (stx)
(syntax-case stx (fields) (syntax-case stx (fields)
((_ name (fields field ...)) ((_ type (fields field ...))
(with-syntax ((make-name (->> #'name syntax->datum (format #f "make-~a") (with-syntax ((make-<type> (binding #'type "make-~a"))
string->symbol (datum->syntax stx))) (<type>? (binding #'type "~a?")))
(name-predicate (->> #'name syntax->datum (format #f "~a?")
string->symbol (datum->syntax stx))))
#`(begin #`(begin
;; point ;; construct class
(define-class name () (define-class type ()
#,@(map (lambda (field-name) ;; needs a pre-expansion since define-class is a macro in itself
(cond ((list? (syntax->datum field-name)) #,@(map field-name #'(field ...)))
#`(#,(datum->syntax field-name (car (syntax->datum field-name)))
init-keyword: #,(-> field-name syntax->datum car symbol->keyword) ;; construct predicate
init-value: #,(cond ((memv default: (syntax->datum field-name)) (define (<type>? x)
=> (lambda (x) (is-a? x type))
(datum->syntax field-name (cadr x))))
(else #f)))) ;; construct public-facing constructor
(else (define* (make-<type> key: #,@(map handle-define*-clause #'(field ...)))
#`(#,field-name #,@(map (handle-validator (symbol->string (syntax->datum #'make-<type>)))
init-keyword: #,(-> field-name syntax->datum symbol->keyword) #'(field ...))
))))
#'(field ...))) ;; bind all values to object
;; point? (let ((return-value (make type)))
(define (name-predicate object) #,@(map (lambda (g)
(is-a? object name)) (with-syntax ((f (field-name g)))
;; make-point #`(slot-set! return-value (quote #,(field-name g)) f)))
(define* (make-name key: #,@(map mlist->define-field #'(field ...))) #'(field ...))
(make name return-value))
#,@(apply append
(map (lambda (name) ;; accessors
#`(#,(-> name syntax->datum symbol->keyword) #,name)) #,@(map (lambda (g)
(map unlist #'(field ...)))))) ;; Supplying the symbol instead of the identifier works here,
#,@(map (lambda (name) ;; due to how goops' define-method works.
#`(define-method (#,name this) #`(define-method (#,(field-name g) (this type))
(slot-ref this (quote #,name)))) (slot-ref this (quote #,(field-name g)))))
(map unlist #'(field ...))) #'(field ...))
(define-method (serialize (this name))
`(#,@(map (lambda (name) ;; pretty printing
#`(#,name ,(#,name this))) (define-method (serialize (this type))
(map unlist #'(field ...))))) (quasiquote
(define-method (write (this name) port) #,(map (lambda (g)
(with-syntax ((f (field-name g)))
#`(#,(field-name g) ,(serialize (f this)))))
#'(field ...))))
(define-method (write (this type) port)
((@ (ice-9 format) format) port "#<~a~:{ ~a=~s~}>" ((@ (ice-9 format) format) port "#<~a~:{ ~a=~s~}>"
(class-name name) (class-name type)
(serialize this))) (serialize this)))))))))
))))))
...@@ -34,3 +34,11 @@ ...@@ -34,3 +34,11 @@
;; This is however explicitly specified ;; This is however explicitly specified
(test-equal "Test default value for non-defaulted file with other kv-data" (test-equal "Test default value for non-defaulted file with other kv-data"
#f (x (make-r4))) #f (x (make-r4)))
(catch 'wrong-type-arg
(lambda () (make-r4 x: "Hello")
(test-assert "Type test didn't work" #f))
(lambda _ (test-assert "Type test failed correctly" #t)))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment