Compare commits

...

14 Commits

5 changed files with 272 additions and 82 deletions

View File

@@ -1,4 +1,4 @@
SOURCES = d-.scm d-/test.scm SOURCES = d-.scm d-/test.scm d-/json.scm
GOBJECTS = $(SOURCES:%.scm=%.go) GOBJECTS = $(SOURCES:%.scm=%.go)
GUILD = @GUILD@ GUILD = @GUILD@

86
d-.scm
View File

@@ -6,14 +6,16 @@
~>> ~>>
as~> as~>
if-not if-not
when-not
if-let if-let
when-let
for for
partial partial
argmin argmin
iterate iterate
upply upply
conjoin
distinct? distinct?
alambda
generator generator
macro-expand macro-expand
amb amb
@@ -39,17 +41,20 @@
(let [(as v)] (let [(as v)]
(as~> as (fn args ...) more ...))])) (as~> as (fn args ...) more ...))]))
(define-syntax-rule (if-not pred body ...) (define-syntax-rule (if-not pred true-arm false-arm)
(if (not pred) body ...)) (if (not pred) true-arm false-arm))
(define-syntax-rule (when-not pred body ...) (define-syntax-rule (if-let ([ident test]) true-arm false-arm)
(when (not pred) body ...)) (let ([t test])
(if t
(let ([ident t])
true-arm)
false-arm)))
(define-syntax-rule (if-let ([ident test]) expr ...) (define-syntax-rule (when-let ([ident test]) expr ...)
(let ([ident test]) (let ([ident test])
(if ident (when ident
(begin expr ...) expr ...)))
#f)))
(define-syntax for (define-syntax for
(syntax-rules () (syntax-rules ()
@@ -57,35 +62,55 @@
[(for ([ident lst] bindings ...) expr ...) [(for ([ident lst] bindings ...) expr ...)
(let iter ([rest lst]) (let iter ([rest lst])
(if (pair? rest) (if (pair? rest)
(let ([ident (car rest)]) (let ([ident (car rest)])
(append (for (bindings ...) expr ...) (iter (cdr rest)))) (append (for (bindings ...) expr ...) (iter (cdr rest))))
'()))])) '()))]))
(define (partial fn . args) (define (partial fn . args)
(lambda x (apply fn (append args x)))) (lambda x (apply fn (append args x))))
(define (argmin arg lt? . vals) (define (argmin arg lt? . vals)
(reduce (lambda (val min) (reduce (lambda (val min)
(if (lt? (arg val) (arg min)) (if (lt? (arg val) (arg min))
val val
min)) min))
#f #f
vals)) vals))
(define (iterate n f v) (define (iterate n f v)
"Repeatedly call f on values returned from (f v)"
(if (zero? n) (if (zero? n)
v v
(iterate (1- n) f (f v)))) (iterate (1- n) f (f v))))
(define (upply a b cmp . fs) (define (upply a b cmp . fs)
"U-shapped apply, apply fs to a and b as in compose, then apply cmp to both results"
(let ([arm-f (apply compose fs)]) (let ([arm-f (apply compose fs)])
(cmp (arm-f a) (arm-f b)))) (cmp (arm-f a) (arm-f b))))
(define (conjoin . preds)
"Returns a procedure that applies each pred to a single value, and returns #t if all return a truthy value. With no preds returns a one arg function that always returns true"
(if (null? preds)
(lambda (x) #t)
(lambda (x)
(if ((car preds) x)
((apply conjoin (cdr preds)) x)
#f))))
(define (distinct? . a) (define (distinct? . a)
"Are all values distinct, as in equal?"
(if (or (null? a) (null? (cdr a))) (if (or (null? a) (null? (cdr a)))
#t #t
(and (not (any (lambda (x) (eq? (car a) x)) (cdr a))) (and (not (any (lambda (x) (equal? (car a) x)) (cdr a)))
(apply distinct? (cdr a))))) (apply distinct? (cdr a)))))
(define-syntax alambda
(syntax-rules ()
[(alambda (kar kdr) body ...)
(lambda (kons)
(let ([kar (car kons)]
[kdr (cdr kons)])
body ...))]))
;; Shamelessly ripped from https://wingolog.org/archives/2013/02/25/on-generators ;; Shamelessly ripped from https://wingolog.org/archives/2013/02/25/on-generators
(define (make-generator f) (define (make-generator f)
@@ -96,17 +121,18 @@
(call-with-prompt tag (call-with-prompt tag
thunk thunk
(lambda (k value) (lambda (k value)
(set! thunk k) (set! thunk k)
value)))) value))))
(define-syntax generator (define-syntax generator
;; generator with an anaphoric yield
(lambda (x) (lambda (x)
(syntax-case x () (syntax-case x ()
[(generator expr ...) [(generator expr ...)
(with-syntax ([yield (datum->syntax x 'yield)]) (with-syntax ([yield (datum->syntax x 'yield)])
#'(make-generator #'(make-generator
(lambda (yield) (lambda (yield)
expr ...)))]))) expr ...)))])))
;; Why wasn't this included? ;; Why wasn't this included?
(define macro-expand (compose tree-il->scheme macroexpand)) (define macro-expand (compose tree-il->scheme macroexpand))
@@ -121,12 +147,12 @@
[(amb a b ...) [(amb a b ...)
(let ([fail0 *fail*]) (let ([fail0 *fail*])
(call/cc (call/cc
(lambda (cc) (lambda (cc)
(set! *fail* (set! *fail*
(lambda () (lambda ()
(set! *fail* fail0) (set! *fail* fail0)
(cc (amb b ...)))) (cc (amb b ...))))
(cc a))))])) (cc a))))]))
(define (amb-reset) (define (amb-reset)
;; Shouldn't be necessary, but prompts are hard and I'm dumb dumb dummy ;; Shouldn't be necessary, but prompts are hard and I'm dumb dumb dummy
(set! *fail* (lambda () (error "Could not satisfy amb")))) (set! *fail* (lambda () (error "Could not satisfy amb"))))

95
d-/json.scm Normal file
View File

@@ -0,0 +1,95 @@
(define-module (d- json)
#:use-module (srfi srfi-1)
#:use-module (ice-9 exceptions)
#:use-module (ice-9 peg)
#:export (json))
(define-peg-pattern ++ none (* (or "\t" "\n" "\r" " ")))
(define-peg-pattern comma none (and "," ++))
(define-peg-pattern open-sq none (and "[" ++))
(define-peg-pattern close-sq none (and "]" ++))
(define-peg-pattern open-cu none (and "{" ++))
(define-peg-pattern close-cu none (and "}" ++))
(define-peg-pattern boolean all (and (or "true" "false")
++))
(define-peg-pattern number all (and (or (and (+ (range #\0 #\9)) "." (* (range #\0 #\9)))
(and (?".") (+ (range #\0 #\9))))
++))
(define-peg-pattern string all (and (ignore "\"")
(* (or "\\\""
(and (not-followed-by "\"") peg-any)))
(ignore "\"") ++))
(define-peg-pattern earr all (and open-sq close-sq))
(define-peg-pattern array all (or earr
(and open-sq
(* (and value comma))
value
close-sq)))
(define-peg-pattern eobj all (and open-cu close-cu))
(define-peg-pattern object all (or eobj
(and open-cu
(* (and string (ignore ":") ++ value comma))
(and string (ignore ":") ++ value)
close-cu)))
(define-peg-pattern value body (or number boolean string array object))
(define default-arr-cons cons)
(define default-obj-acons
(lambda (key value rest)
(acons (string->symbol key) value rest)))
(define *arr-cons* (make-parameter default-arr-cons))
(define *obj-acons* (make-parameter default-obj-acons))
(define (parse-boolean s)
(if (string= s "true") #t #f))
(define (parse-number s)
(string->number s))
(define (parse-string s)
s)
(define (parse-array ss)
(if (symbol? (car ss))
'()
(fold-right (*arr-cons*) '() (map parse-value ss))))
(define (parse-object ss)
(if (or (null? ss) (null? (cdr ss)))
'()
(let ([key (parse-value (car ss))]
[value (parse-value (cadr ss))]
[rest (parse-object (cddr ss))])
((*obj-acons*) key value rest))))
(define flatwords '(boolean number string array object))
(define (parse-value s)
(if (pair? s)
(case (car s)
[(boolean) (parse-boolean (cadr s))]
[(number) (parse-number (cadr s))]
[(string) (parse-string (cadr s))]
[(array) (parse-array (keyword-flatten flatwords (cdr s)))]
[(object) (parse-object (keyword-flatten flatwords (cdr s)))])
(raise-exception
(make-exception
(make-programming-error)
(make-exception-with-irritants s)))))
(define* (json string #:key
(arr-cons default-arr-cons)
(obj-acons default-obj-acons))
"Parses a json string into a sexp
`arr-cons' sets how arrays are constructed
`obj-acons` sets how objects are constructed"
(define match (match-pattern value string))
(if match
(parameterize ([*arr-cons* arr-cons]
[*obj-acons* obj-acons])
(parse-value (peg:tree match)))
(raise-exception (make-exception
(make-external-error)
(make-exception-with-irritants string)))))

View File

@@ -1,37 +1,49 @@
(define-module (d- test) (define-module (d- test)
#:use-module (srfi srfi-1)
#:use-module (ice-9 format) #:use-module (ice-9 format)
#:use-module (ice-9 control) #:use-module (ice-9 control)
#:export (define-test-suite)) #:export (define-test-suite anything))
(define *anysym* (gensym))
(define (anything) *anysym*)
(define (assert expected actual eq? fail) (define (assert expected actual eq? fail)
(when (not (eq? expected actual)) (when (and (not (eq? expected actual)))
(format #t "fail - expected ~a but got ~a" expected actual) (format #t "fail - expected ~a but got ~a" expected actual)
(newline) (newline)
(fail))) (fail)))
(define (equal-or-any? expected actual)
(cond
[(eq? *anysym* expected) #t]
[(or (not (list? expected))
(not (list? actual)))
(equal? expected actual)]
[else (every equal-or-any? expected actual)]))
(define-syntax define-test-suite (define-syntax define-test-suite
(lambda (stx) (lambda (stx)
(with-syntax ([fail (datum->syntax stx 'fail)]) (with-syntax ([fail (datum->syntax stx 'fail)])
(define (assert-stx expr) (define (assert-stx expr)
(syntax-case expr (assert-equal assert-not-equal) (syntax-case expr (assert-equal assert-not-equal assert-unspecified)
[(assert-equal e a) #'(assert e a equal? fail)] [(assert-equal e a) #'(assert e a equal-or-any? fail)]
[(assert-not-equal e a) #'(assert e a (compose not equal?) fail)] [(assert-not-equal e a) #'(assert e a (compose not equal-or-any?) fail)]
[(assert-unspecified v) #'(assert (when #f 'a) v eq? fail)] [(assert-unspecified v) #'(assert (when #f 'a) v eq? fail)]
[else #'else])) [else #'else]))
(define (define-test-stx test) (define (define-test-stx test)
(syntax-case test (define-test) (syntax-case test (define-test)
[(define-test test-name e ...) [(define-test test-name e ...)
#`(call/ec (lambda (fail) #`(call/ec (lambda (fail)
(format #t "~a: " test-name) (format #t "~a: " test-name)
(begin (begin
#,@(map assert-stx #'(e ...))) #,@(map assert-stx #'(e ...)))
(display "ok") (display "ok")
(newline)))])) (newline)))]))
(syntax-case stx () (syntax-case stx ()
[(define-test-suite name test ...) [(define-test-suite name test ...)
#`(begin #`(begin
(format #t "Suite ~a:" name) (format #t "Suite ~a:" name)
(newline) (newline)
#,@(map define-test-stx #,@(map define-test-stx
#'(test ...)) #'(test ...))
(newline))])))) (newline))]))))

115
test.scm
View File

@@ -1,6 +1,7 @@
(use-modules (d-) (use-modules (d-)
(d- test) (d- json)
(srfi srfi-1)) (srfi srfi-1)
(d- test))
(define-test-suite "~>" (define-test-suite "~>"
(define-test "unwrapped" (define-test "unwrapped"
@@ -30,25 +31,28 @@
(define-test "test" (define-test "test"
(assert-equal 'a (if-not #f 'a 'b)))) (assert-equal 'a (if-not #f 'a 'b))))
(define-test-suite "when-not" (define-test-suite "if-let"
(define-test "positive-case" (define-test "positive case"
(assert-equal 'return (when-not #f 'do-some-stuff 'return))) (assert-equal 2 (if-let ([v (or #f 1)]) (+ v 1) 3)))
(define-test "negative-case" (define-test "negative case"
(assert-unspecified (when-not #t 'do-some-stuff 'return)))) (assert-equal 3 (if-let ([v (and #f 1)]) (+ v 1) 3)))
(define-test "variable not shadowed in false arm"
(define v 3)
(assert-equal 3 (if-let ([v #f]) (+ v 1) v))))
(define-test-suite "when-let"
(define-test "positive case"
(assert-equal 2 (when-let ([v (or #f 1)]) (+ v 1))))
(define-test "negative case"
(assert-equal (when #f #t) (when-let ([v (and #f 1)]) (+ v 1)))))
(define-test-suite "for" (define-test-suite "for"
(define-test "permutation" (define-test "permutation"
(define value (for ([i (iota 2)] (define value (for ([i (iota 2)]
[j (iota 2)]) [j (iota 2)])
(cons i j))) (cons i j)))
(assert-equal '((0 . 0) (0 . 1) (1 . 0) (1 . 1)) value))) (assert-equal '((0 . 0) (0 . 1) (1 . 0) (1 . 1)) value)))
(define-test-suite "if-let"
(define-test "positive-case"
(assert-equal 2 (if-let ([v (or #f 1)]) (+ v 1))))
(define-test "negative-case"
(assert-equal #f (if-let ([v (and #f 1)]) (+ v 1)))))
(define-test-suite "partial" (define-test-suite "partial"
(define-test "test" (define-test "test"
(define value (partial / 2)) (define value (partial / 2))
@@ -69,13 +73,25 @@
(define value (upply -3 5 = abs 1-)) (define value (upply -3 5 = abs 1-))
(assert-equal #t value))) (assert-equal #t value)))
(define-test-suite "conjoin"
(define-test "test"
(assert-equal #t ((conjoin negative? odd? rational?) -3))
(assert-equal #f ((conjoin negative? odd? rational?) -4)))
(define-test "vacuous"
(assert-equal #t ((conjoin) '(some donkus)))))
(define-test-suite "alambda"
(define-test "map"
(assert-equal '(4 4 4)
(map (alambda (i v) (+ i v)) '((3 . 1) (2 . 2) (1 . 3))))))
(define-test-suite "generator" (define-test-suite "generator"
(define-test "test" (define-test "test"
(define number-generator (define number-generator
(generator (generator
(let loop ([i 1]) (let loop ([i 1])
(yield i) (yield i)
(loop (1+ i))))) (loop (1+ i)))))
(assert-equal 1 (number-generator)) (assert-equal 1 (number-generator))
(assert-equal 2 (number-generator)) (assert-equal 2 (number-generator))
(assert-equal 3 (number-generator)) (assert-equal 3 (number-generator))
@@ -86,16 +102,57 @@
(define (liars) (define (liars)
(amb-reset) (amb-reset)
(let ([betty (amb 1 2 3 4 5)] (let ([betty (amb 1 2 3 4 5)]
[ethel (amb 1 2 3 4 5)] [ethel (amb 1 2 3 4 5)]
[joan (amb 1 2 3 4 5)] [joan (amb 1 2 3 4 5)]
[kitty (amb 1 2 3 4 5)] [kitty (amb 1 2 3 4 5)]
[mary (amb 1 2 3 4 5)]) [mary (amb 1 2 3 4 5)])
;; The "Liars" problem ;; The "Liars" problem
(amb-require (distinct? betty ethel joan kitty mary)) (amb-require (distinct? betty ethel joan kitty mary))
(amb-require (or (= kitty 1) (= betty 3))) (amb-require (or (= kitty 1) (= betty 3)))
(amb-require (or (= ethel 1) (= joan 2))) (amb-require (or (= ethel 1) (= joan 2)))
(amb-require (or (= joan 3) (= ethel 5))) (amb-require (or (= joan 3) (= ethel 5)))
(amb-require (or (= kitty 2) (= mary 4))) (amb-require (or (= kitty 2) (= mary 4)))
(amb-require (or (= mary 4) (= betty 1))) (amb-require (or (= mary 4) (= betty 1)))
(list betty ethel joan kitty mary))) (list betty ethel joan kitty mary)))
(assert-equal '(3 5 2 1 4) (liars)))) (assert-equal '(3 5 2 1 4) (liars))))
(define-test-suite "test"
(define-test "anything"
(assert-equal `(dog ,(anything)) '(dog cat))))
(define-test-suite "json"
(define-test "boolean"
(assert-equal #t (json "true"))
(assert-equal #f (json "false")))
(define-test "number"
(assert-equal 1.1 (json "1.1"))
(assert-equal 0.1 (json ".1"))
(assert-equal 1.0 (json "1.")))
(define-test "string"
(assert-equal "hello" (json "\"hello\""))
(assert-equal "\\\"hello\\\"" (json "\"\\\"hello\\\"\"")))
(define-test "array"
(assert-equal '() (json "[]"))
(assert-equal '(1 "two" #f) (json "[1, \"two\", false]"))
(assert-equal '(() (())) (json "[[], [[]]]"))
(assert-equal "catdogsnail"
(json "[\"cat\", \"dog\", \"snail\"]"
#:arr-cons
(lambda (a d)
(if (null? d)
a
(string-append a d))))))
(define-test "object"
(assert-equal '() (json "{}"))
(assert-equal '((cat . 1)
(bat . #t)
(rat . "yessir"))
(json "{\"cat\": 1, \"bat\": true, \"rat\": \"yessir\"}"))
(assert-equal '((butter . 1)
(brownie))
(json "{\"butter\": 1, \"brownie\": {}}"))
(assert-equal '(1 2)
(json "{\"hello\": 1, \"world\": 2}"
#:obj-acons
(lambda (_key value rest)
(cons value rest))))))