;; © 2021 Idiomdrottning, BSD 1-Clause License ;; contributions from Chris Brannon (module brev-separate (define-ir-syntax define-syntax-rules define-ir-syntax* define-closure match-define match-define-closure call-table call-table* call-vector call-string call-list call-table? call-table*? call-vector? call-string? call-list? for-each-line with-result fn over c_r like? is? define-curry c 🍛 empty? eif econd define-some slice as-list) (import scheme (chicken base) (chicken io) (chicken syntax) matchable miscmacros srfi-1 srfi-69) (import-for-syntax matchable miscmacros srfi-1) (define-syntax define-ir-syntax (ir-macro-transformer (lambda (e i c) `(define-syntax ,(cadr e) (ir-macro-transformer (lambda (,(i 'exp) ,(i 'inject) ,(i 'compare)) ,@(cddr e))))))) (define-syntax define-syntax-rules (syntax-rules () ((_ name arg ...) (define-syntax name (syntax-rules arg ...))))) (define-syntax define-ir-syntax* (ir-macro-transformer (lambda (e i c) `(define-syntax ,(cadr e) (ir-macro-transformer (lambda (exp ,(i 'inject) ,(i 'compare)) (match exp ,@(cddr e)))))))) (define-ir-syntax* define-closure ((define-closure bindings head body ...) (match (chicken.syntax#expand-curried-define head body '()) ((define name body) `(define ,name (let ,(let desc ((lis bindings)) (cons (list (car lis) (cadr lis)) (if (null? (cddr lis)) '() (desc (cddr lis))))) ,body)))))) (define-ir-syntax match-define `(define ,(caaadr exp) (match-lambda* ,@(map (lambda (el) (cons (cdar el) (cdr el))) (cdr exp))))) (define-for-syntax caaaddr (o caaar cddr)) (define-ir-syntax match-define-closure `(define ,(caaaddr exp) (let ,(let desc ((lis (cadr exp))) (cons (list (car lis) (cadr lis)) (if (null? (cddr lis)) '() (desc (cddr lis))))) (match-lambda* ,@(map (lambda (el) (cons (cdar el) (cdr el))) (cddr exp)))))) (define (call-table #!optional st #!key (default #f)) (let* ((ht (if st (if (hash-table? st) st (alist->hash-table st)) (make-hash-table))) (proc (match-lambda* ((key) (hash-table-ref/default ht key default)) (() ht) ((#:update (? hash-table? new-ht)) (set! ht new-ht)) ((key val) (hash-table-set! ht key val))))) (getter-with-setter proc (lambda args (if (= 1 (length args)) (apply proc update: args) (apply proc args))) "call-table"))) (define (call-table* #!optional st #!key (initial '()) (proc cons) (unary #f)) (let* ((ht (if st (if (hash-table? st) st (alist->hash-table st)) (make-hash-table))) (proc (match-lambda* ((key) (if unary (hash-table-update! ht key proc (constantly initial)) (hash-table-ref/default ht key initial))) (() ht) ((#:update (? hash-table? new-ht)) (set! ht new-ht)) ((key val) (hash-table-update! ht key (cut proc val <>) (constantly initial)))))) (getter-with-setter proc (lambda args (if (= 1 (length args)) (apply proc update: args) (apply proc args))) "call-table*"))) (define-ir-syntax* for-each-line ((for-each-line filename . body) `(let ((in-port (open-input-file ,filename))) (dynamic-wind void (lambda () (let loop ((,(inject 'line) (read-line in-port))) (unless (eof-object? ,(inject 'line)) ,@body (loop (read-line in-port))))) (lambda () (close-input-port in-port)))))) (define-ir-syntax with-result `(let* ((ret #f) (,( inject 'save) (lambda (val) (set! ret val) val))) ,@(cdr exp) ret)) (define-ir-syntax fn (let* ((body (cdr exp)) (friends (let desc ((friends '()) (body body)) (cond ((null? body) friends) ((pair? body) (append (desc friends (car body)) (desc friends (cdr body)))) ((any (cut compare body <>) '(x y z rest args)) (cons (strip-syntax body) friends)) (else '()))))) `(lambda ,(if (memq 'x friends) (if (memq 'y friends) (if (memq 'z friends) (if (memq 'rest friends) (cons* (inject 'x) (inject 'y) (inject 'z) (inject 'rest)) (list (inject 'x) (inject 'y) (inject 'z))) (if (memq 'rest friends) (cons* (inject 'x) (inject 'y) (inject 'rest)) (list (inject 'x) (inject 'y)))) (if (memq 'rest friends) (cons (inject 'x) (inject 'rest)) (list (inject 'x)))) (if (memq 'args friends) (inject 'args) '())) ,@body))) (define-ir-syntax over `'(fn ,@(cdr exp))) (over (+ x y)) (define-syntax-rule (over bindings . body) (lambda lis (apply map (lambda bindings . body) lis))) (define-ir-syntax c_r `(define (,(inject (cadr exp)) x) ,(fold list 'x (unfold-right null? (lambda (n) (case (car n) ((#\a) 'car) ((#\d) 'cdr))) cdr (cdr (butlast (string->list (symbol->string (strip-syntax (cadr exp)))))))))) ;; Pre-curried versions of popular procedures ;; only cuts out a few letters (pun intended) but still (define ((like? a) b) (equal? a b)) (define ((is? a) b) (eq? a b)) (define-syntax-rule (define-curry (name . args) . body) (define (name . friends) (if (= (length friends) (length 'args)) (apply (lambda args . body) friends) (lambda stragglers (apply name (append friends stragglers)))))) (define (🍛 name . args) (lambda stragglers (apply name (append args stragglers)))) (define c 🍛) (match-define ((empty? (? boolean? x)) (not x)) ((empty? (? number? x)) (zero? x)) ((empty? (? integer? x)) (zero? x)) ((empty? (? string? x)) (zero? (string-length x))) ((empty? (? vector? x)) (zero? (vector-length x))) ((empty? (? hash-table? x)) (zero? (hash-table-size x))) ((empty? (? list? x)) (null? x))) (define-ir-syntax* eif ((eif test yes no) `(let ((,(inject 'it) ,test)) (if (empty? ,(inject 'it)) ,no ,yes)))) (define-syntax econd (syntax-rules (else =>) ((econd (else result1 result2 ...)) (begin result1 result2 ...)) ((econd (test => result)) (eif test (result it))) ((econd (test => result) clause1 clause2 ...) (eif test (result it) (econd clause1 clause2 ...))) ((econd (test)) test) ((econd (test) clause1 clause2 ...) (eif test it (econd clause1 clause2 ...))) ((econd (test result1 result2 ...)) (eif test (begin result1 result2 ...))) ((econd (test result1 result2 ...) clause1 clause2 ...) (eif test (begin result1 result2 ...) (econd clause1 clause2 ...))))) (define-syntax-rule (define-some (name arg args ...) body ...) (define (name arg args ...) (if (empty? arg) '() (begin body ...)))) (match-define ((list-> (? integer? x)) (o string->number list->string (c map (o (cut string-ref <> 0) number->string)))) ((list-> (? string? x)) list->string) ((list-> (? vector? x)) list->vector) ((list-> (? hash-table? x)) alist->hash-table) ((list-> (? list? x)) identity)) (match-define ((->list (? integer? x)) (map (o string->number string) (string->list (number->string x)))) ((->list (? string? x)) (string->list x)) ((->list (? vector? x)) (vector->list x)) ((->list (? hash-table? x)) (hash-table->alist x)) ((->list (? list? x)) x)) (define ((as-list . procs) . xs) (let ((ret (apply (apply compose procs) (map ->list xs)))) (if (list? ret) ((list-> (car xs)) ret) ret))) (define (mancala-skele me-set! me-length) (rec (desc vals vec index) (cond ((null? vals) vec) ((< index (me-length vec)) (me-set! vec index (car vals)) (desc (cdr vals) vec (add1 index))) (else (warning "Attempting to sow past boundary, truncating") vec)))) (define (call-skele make-me me? opposite->me list->me me-ref me-set! me-err me-str me-length) (lambda (seed) (let* ((vec (cond ((integer? seed) (make-me seed)) ((me? seed) seed) ((list? seed) (list->me seed)) ((string? seed) (list->me (string->list seed))) (else (error me-err)))) (flip (lambda (start) (if (negative? start) (+ (me-length vec) start) start))) (me-mancala! (mancala-skele me-set! me-length))) (let ((proc (match-lambda* (() vec) ((#:update (? me? new-vec)) (set! vec new-vec) vec) (((? integer? index)) (me-ref vec (flip index))) (((? integer? index) val) (me-set! vec (flip index) val) vec) ((#:slice start stop) (unfold (cut >= <> (if (positive? stop) stop (+ stop (me-length vec)))) (cut me-ref vec <>) add1 (flip start))) (((? integer? index) #:sow vals) (me-mancala! (->list vals) vec (flip index))) ((#:sow vals) (me-mancala! (->list vals) vec 0)) (((? integer? index) #:sow val . vals) (me-mancala! (cons val vals) vec (flip index))) ((#:sow val . vals) (me-mancala! (cons val vals) vec 0))))) (getter-with-setter proc (lambda args (cond ((= 1 (length args)) (proc update: (car args))) ((eq? #:slice (car args)) (let* ((la (->list (last args))) (leo (- (flip (third args)) (flip (second args)))) (len (length la))) (unless (= leo len) (warning "Attempting to set! to mismatched slice, truncating")) (proc (second args) sow: (take la (min leo len))))) (else (apply proc args)))) me-str))))) (define call-list (call-skele make-list list? ->list identity list-ref (lambda (lis index val) (set-car! (drop lis index) val)) "No seed value provided for call-list" "call-list" length)) (define call-string (call-skele make-string string? (o list->string vector->list) list->string string-ref string-set! "No seed value provided for call-string" "call-string" string-length)) (define call-vector (call-skele make-vector vector? (o list->vector string->list) list->vector vector-ref vector-set! "No seed value provided for call-vector" "call-vector" vector-length)) ;; This one owes a debt to Mario's callable-data-structures! (define ((pred-skele sym) obj) (and (procedure? obj) (eq? (procedure-information obj) sym))) (define call-table? (pred-skele 'call-table)) (define call-table*? (pred-skele 'call-table*)) (define call-string? (pred-skele 'call-string)) (define call-vector? (pred-skele 'call-vector)) (define call-list? (pred-skele 'call-list)) (match-define ((->sliceable (? list? x)) (call-list x)) ((->sliceable (? vector? x)) (call-vector x)) ((->sliceable (? string? x)) (call-string x)) ((->sliceable x) (call-list (->list x)))) (define slice (getter-with-setter (lambda (anything start stop) ((->sliceable anything) slice: start stop)) (lambda (anything start stop new) (set! ((->sliceable anything) slice: start stop) new)) "slice")) )