In the beginning there was darkness
This commit is contained in:
commit
04e8a0d5ae
7 changed files with 471 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*~
|
||||
*.c
|
23
LICENSE
Normal file
23
LICENSE
Normal file
|
@ -0,0 +1,23 @@
|
|||
Copyright (c) 2013, Daniel Ziltener
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
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.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
|
51
README.md
Normal file
51
README.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
[![License](//img.shields.io/badge/license-BSD-blue.svg?style=flat)]()
|
||||
[![Boot](//img.shields.io/badge/Chicken-Scheme-ECC42F.svg?style=flat)](http://www.call-cc.org/)
|
||||
[![Clojars](//img.shields.io/badge/Eggversion-0.5.1-blue.svg?style=flat)](http://wiki.call-cc.org/eggref/4/edn)
|
||||
[![Gratipay](//img.shields.io/gratipay/zilti.svg?style=flat)](//gratipay.com/zilti)
|
||||
[![Flattr this](//api.flattr.com/button/flattr-badge-small.png)](https://flattr.com/submit/auto?user_id=zilti&url=https%3A%2F%2Fbitbucket.org%2Fzilti%2Fedn)
|
||||
|
||||
chicken-edn
|
||||
===========
|
||||
|
||||
An [EDN](https://github.com/edn-format/edn) reader and writer for chicken scheme.
|
||||
|
||||
Installation: `chicken-install edn`
|
||||
|
||||
Data type conversions
|
||||
---------------------
|
||||
|
||||
* All kinds of numbers get converted to Scheme numbers, precision suffixes (N and M) get ignored.
|
||||
* Keywords :keyword get converted to chicken scheme keywords keyword:.
|
||||
* Maps get converted to SRFI 69 hashtables.
|
||||
* Vectors are srfi-4 vectors.
|
||||
* true = #t, false = #f, nil = '()
|
||||
|
||||
Missing reader functionality
|
||||
----------------------------
|
||||
Should you notice missing functionality of the reader, plesase use [the issues page](https://bitbucket.org/zilti/edn/issues?status=new&status=open) to report
|
||||
it and, if possible, provide a minimal test case.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
* Transforming EDN into Chicken: `(with-input-from-port <port> read-edn)`
|
||||
* Transforming Chicken into EDN: `(with-output-to-port <port> (write-edn <datastructure>))`
|
||||
* Using reader tags: the library contains a public a-list `tag-handlers`. To register a handler, add an a-list entry where the key is the tag without `\#` and as a keyword, and the value a one-argument procedure.
|
||||
|
||||
Releases
|
||||
--------
|
||||
|
||||
* **0.5.1**: Small compatibility improvements: "/" now starts a symbol as well, and "," is treated as whitespace.
|
||||
* **0.5**: Reader tag support.
|
||||
* **0.4**: Complete rewrite. Only relies on R7RS, and SRFI 1, 4, 69 and 88. Uses ports. Reads and writes EDN.
|
||||
* **0.3**: EDN tags, including special forms, work. #inst and #uuid both get read as strings. Add nil. Add number prefixes. Add no-space-required to #_ tag.
|
||||
* **0.2.1**: Can read EDN-files and -strings. EDN tags are not working yet.
|
||||
* **0.2**: Can read EDN-strings with one top-level data structure.
|
||||
|
||||
Roadmap
|
||||
-------
|
||||
|
||||
|
||||
About
|
||||
-----
|
||||
Written by Daniel Ziltener. EDN written by Rich Hickey. The EDN specification is available at [https://github.com/edn-format/edn](https://github.com/edn-format/edn).
|
322
edn-impl.scm
Normal file
322
edn-impl.scm
Normal file
|
@ -0,0 +1,322 @@
|
|||
(import scheme
|
||||
srfi-69
|
||||
srfi-1
|
||||
(chicken keyword)
|
||||
(chicken port))
|
||||
;; EDN Reading
|
||||
;; ===========
|
||||
|
||||
(define (is-char? a)
|
||||
(lambda (b)
|
||||
(and (char? b)
|
||||
(char=? a b))))
|
||||
|
||||
(define (is-number? c)
|
||||
(or (char-numeric? c)
|
||||
(char=? #\+ c)
|
||||
(char=? #\- c)))
|
||||
|
||||
(define (is-whitespace? c)
|
||||
(or (char-whitespace? c)
|
||||
(char=? #\, c)))
|
||||
|
||||
(define (is-endingchar? c)
|
||||
(or (char=? #\# c)
|
||||
(char=? #\) c)
|
||||
(char=? #\] c)
|
||||
(char=? #\} c)))
|
||||
|
||||
(define (is-symbolstarter? c)
|
||||
(or (char-alphabetic? c)
|
||||
(char=? #\/ c)))
|
||||
|
||||
(define edn->atom
|
||||
(case-lambda
|
||||
((skip-fn end-fn finalizer) (lambda (subparser input)
|
||||
(edn->atom subparser skip-fn end-fn finalizer '() '() input)))
|
||||
((subparser skip-fn end-fn finalizer result pile input)
|
||||
(cond ((or (eq? #!eof (peek-char input))
|
||||
(end-fn result pile input))
|
||||
(cons (finalizer (reverse result))
|
||||
(if (or (not (char-ready? input))
|
||||
(is-endingchar? (peek-char input)))
|
||||
input
|
||||
(begin (read-char input) input))))
|
||||
((skip-fn result pile input)
|
||||
(edn->atom subparser skip-fn end-fn finalizer result (cons (read-char input) pile) input))
|
||||
(else (edn->atom subparser skip-fn end-fn finalizer (cons (peek-char input) result) (cons (peek-char input) pile)
|
||||
(if (null? input) input (begin (read-char input) input))))))))
|
||||
|
||||
(define edn->string
|
||||
(edn->atom (lambda (result pile input)
|
||||
(or (char=? #\\ (peek-char input))
|
||||
(and (null? result)
|
||||
(char=? #\" (peek-char input)))))
|
||||
(lambda (result pile input)
|
||||
(and (char=? #\" (peek-char input))
|
||||
(not (null? pile))
|
||||
(or (not (char=? #\\ (car pile)))
|
||||
(char=? #\" (car pile)))))
|
||||
list->string))
|
||||
|
||||
(define edn->keyword
|
||||
(edn->atom (lambda (result pile input)
|
||||
(char=? #\: (peek-char input)))
|
||||
(lambda (result pile input)
|
||||
(or (is-whitespace? (peek-char input))
|
||||
(is-endingchar? (peek-char input))))
|
||||
(lambda (in) (string->keyword (list->string in)))))
|
||||
|
||||
(define edn->symbol
|
||||
(edn->atom (lambda (result pile input) #f)
|
||||
(lambda (result pile input)
|
||||
(or (is-whitespace? (peek-char input))
|
||||
(is-endingchar? (peek-char input))))
|
||||
(lambda (in) (let ((res-string (list->string in)))
|
||||
(cond
|
||||
((equal? "true" res-string) #t)
|
||||
((equal? "false" res-string) #f)
|
||||
((equal? "nil" res-string) '())
|
||||
(else (string->symbol res-string)))))))
|
||||
|
||||
(define edn->number
|
||||
(edn->atom (lambda (result pile input) #f)
|
||||
(lambda (result pile input)
|
||||
(or (is-whitespace? (peek-char input))
|
||||
(is-endingchar? (peek-char input))
|
||||
(char=? #\M (peek-char input))
|
||||
(char=? #\N (peek-char input))))
|
||||
(lambda (in) (string->number (list->string in)))))
|
||||
|
||||
(define edn->rtag
|
||||
(edn->atom (lambda (result pile input)
|
||||
(char=? #\# (peek-char input)))
|
||||
(lambda (result pile input)
|
||||
(or (is-whitespace? (peek-char input))
|
||||
(char=? #\( (peek-char input))
|
||||
(char=? #\[ (peek-char input))
|
||||
(and (not (null? pile))
|
||||
(char=? #\{ (car pile)))))
|
||||
(lambda (in) (cons edn/tag: (string->keyword (list->string in))))))
|
||||
|
||||
(define edn->coll
|
||||
(case-lambda
|
||||
((ld rd finalize) (lambda (subparser input) (edn->coll subparser ld rd finalize '() input #t)))
|
||||
((subparser ld rd finalize result input fresh?)
|
||||
(cond
|
||||
;; End of sequence
|
||||
((or (eq? #!eof (peek-char input))
|
||||
(char=? rd (peek-char input)))
|
||||
(cons (finalize (reverse result)) (begin (read-char input) input)))
|
||||
;; First character of sequence
|
||||
((and (char=? ld (peek-char input))
|
||||
fresh?)
|
||||
(edn->coll subparser ld rd finalize result (begin (read-char input) input) #f))
|
||||
;; Sub-sequence of same type
|
||||
((char=? ld (peek-char input))
|
||||
(let ((sub-result (subparser input)))
|
||||
(edn->coll subparser ld rd finalize (cons (cadr sub-result) result) (caddr sub-result) #f)))
|
||||
;; Stuff in the data!
|
||||
(else (let ((compiled (subparser input)))
|
||||
(edn->coll (first compiled)
|
||||
ld rd finalize
|
||||
(if (equal? (second compiled) edn/omit:)
|
||||
result
|
||||
(cons (second compiled) result))
|
||||
(third compiled) #f)))))))
|
||||
|
||||
(define edn->list (edn->coll #\( #\) (lambda (x) x)))
|
||||
(define edn->vector (edn->coll #\[ #\] (lambda (x) (list->vector x))))
|
||||
|
||||
(define edn->htable
|
||||
(case-lambda
|
||||
((subparser input) (edn->htable subparser (make-hash-table) '() input #t))
|
||||
((subparser result key input fresh?)
|
||||
(cond ((or (eq? #!eof (peek-char input))
|
||||
(char=? #\} (peek-char input)))
|
||||
(cons result (begin (read-char input) input)))
|
||||
((and (char=? #\{ (peek-char input))
|
||||
fresh?)
|
||||
(edn->htable subparser result key (begin (read-char input) input) #f))
|
||||
(else (let ((compiled (subparser input)))
|
||||
(cond
|
||||
((eq? edn/omit: (second compiled))
|
||||
(edn->htable (first compiled) result key (third compiled) #f))
|
||||
((null? key)
|
||||
(edn->htable (first compiled) result (second compiled) (third compiled) #f))
|
||||
(else
|
||||
(edn->htable (first compiled) (begin (hash-table-set! result key (second compiled)) result)
|
||||
'() (third compiled) #f)))))))))
|
||||
|
||||
(define (edn->whitespace subparser input)
|
||||
(if (char-whitespace? (peek-char input))
|
||||
(cons edn/omit: (begin (read-char input) input))
|
||||
(cons (read-char input) input)))
|
||||
|
||||
(define (guard-charcheck fun)
|
||||
(lambda (x)
|
||||
(and (char? x)
|
||||
(fun x))))
|
||||
|
||||
@(heading "Reading EDN")
|
||||
|
||||
(define tag-handlers @("An a-list containing the handlers for reader tags. You can register your own reader tags by simply adding a new a-list entry.
|
||||
|
||||
Example for a tag \"#keywordify\": add the entry `(cons keywordify: keywordify-procedure)`.")
|
||||
(list (cons _: (lambda (input) edn/omit:))))
|
||||
|
||||
(define reader-handlers
|
||||
(list (cons (is-char? #\() edn->list)
|
||||
(cons (is-char? #\)) edn->list)
|
||||
(cons (is-char? #\[) edn->vector)
|
||||
(cons (is-char? #\]) edn->vector)
|
||||
(cons (is-char? #\{) edn->htable)
|
||||
(cons (is-char? #\}) edn->htable)
|
||||
(cons (is-char? #\#) edn->rtag)
|
||||
(cons (is-char? #\:) edn->keyword)
|
||||
(cons (is-char? #\") edn->string)
|
||||
(cons (guard-charcheck is-symbolstarter?) edn->symbol)
|
||||
(cons (guard-charcheck is-number?) edn->number)
|
||||
(cons (guard-charcheck is-whitespace?) edn->whitespace)))
|
||||
|
||||
(define (is-tag? in)
|
||||
(and (pair? in)
|
||||
(pair? (car in))
|
||||
(equal? (caar in) edn/tag:)
|
||||
(contains-tag-handler? (car in))))
|
||||
|
||||
(define (contains-tag-handler? tag)
|
||||
(assoc (cdr tag) tag-handlers))
|
||||
|
||||
(define (call-tag tag data)
|
||||
((cdr (assoc (cdr tag) tag-handlers)) data))
|
||||
|
||||
(define (parse-edn state)
|
||||
(lambda (in-port)
|
||||
(let* ((struct-handler (cdr
|
||||
(find (lambda (item) ((car item) (peek-char in-port)))
|
||||
reader-handlers)))
|
||||
(result (struct-handler (parse-edn state) in-port)))
|
||||
(list (if (is-tag? result)
|
||||
(parse-edn result)
|
||||
(parse-edn '()))
|
||||
(cond ((is-tag? state)
|
||||
(call-tag (car state) (car result)))
|
||||
((is-tag? result)
|
||||
edn/omit:)
|
||||
(else (car result)))
|
||||
(cdr result)))))
|
||||
|
||||
(define (read-edn)
|
||||
@("Reads EDN data from the `current-input-port`, converts it to Chicken data and returns it. Precision suffixes for numbers get ignored, maps get converted to SRFI-69 hashtables, vectors to SRFI-4 vectors.")
|
||||
(second ((parse-edn '()) (current-input-port))))
|
||||
|
||||
;; EDN writing
|
||||
;; ===========
|
||||
|
||||
(define (pair->reader-tag subparser in)
|
||||
(string-append "#" (keyword->string (cdr in))))
|
||||
|
||||
(define (scm-kw->edn-kw subparser in)
|
||||
(string-append ":" (keyword->string in)))
|
||||
|
||||
(define (boolean->edn subparser in)
|
||||
(case in
|
||||
((#t) "true")
|
||||
((#f) "false")
|
||||
(else "nil")))
|
||||
|
||||
(define (char->edn subparser in)
|
||||
(string #\\ in))
|
||||
|
||||
(define (string->edn subparser in)
|
||||
(string-append "\"" in "\""))
|
||||
|
||||
(define (number->edn subparser in)
|
||||
(number->string in))
|
||||
|
||||
(define (sequential->edn subparser ld rd in)
|
||||
(string-append ld
|
||||
(foldr (lambda (elem init)
|
||||
(string-append (subparser elem)
|
||||
(if (equal? "" init) "" " ")
|
||||
init))
|
||||
"" in)
|
||||
rd))
|
||||
|
||||
(define (list->edn subparser in)
|
||||
(sequential->edn subparser "(" ")" in))
|
||||
|
||||
(define (vector->edn subparser in)
|
||||
(sequential->edn subparser "[" "]" (vector->list in)))
|
||||
|
||||
(define (map->edn subparser in)
|
||||
(string-append "{"
|
||||
(foldr (lambda (elem init)
|
||||
(string-append (subparser (car elem))
|
||||
" "
|
||||
(subparser (cdr elem))
|
||||
(if (equal? "" init) "" " ")
|
||||
init))
|
||||
"" in)
|
||||
"}"))
|
||||
|
||||
(define (htable->edn subparser in)
|
||||
(string-append "{"
|
||||
(hash-table-fold in
|
||||
(lambda (hkey hval folded)
|
||||
(string-append (subparser hkey)
|
||||
" "
|
||||
(subparser hval)
|
||||
(if (equal? "" folded) "" " ")
|
||||
folded))
|
||||
"")
|
||||
"}"))
|
||||
|
||||
(define (nil->edn subparser in)
|
||||
"nil")
|
||||
|
||||
(define (symbol->edn subparser in)
|
||||
(symbol->string in))
|
||||
|
||||
(define (edn-readertag? in)
|
||||
(and
|
||||
(not (list? in))
|
||||
(pair? in)
|
||||
(equal? edn/reader-tag: (car in))))
|
||||
|
||||
(define (edn-alist? in)
|
||||
(and (list? in)
|
||||
(any (lambda (item) (and (not (list? item)) (pair? item)))
|
||||
in)))
|
||||
|
||||
(define (edn-htable? in)
|
||||
(hash-table? in))
|
||||
|
||||
(define writer-handlers
|
||||
(list (cons null? nil->edn)
|
||||
(cons string? string->edn)
|
||||
(cons char? char->edn)
|
||||
(cons boolean? boolean->edn)
|
||||
(cons number? number->edn)
|
||||
(cons keyword? scm-kw->edn-kw)
|
||||
(cons symbol? symbol->edn)
|
||||
(cons vector? vector->edn)
|
||||
(cons edn-alist? map->edn)
|
||||
(cons edn-htable? htable->edn)
|
||||
(cons edn-readertag? pair->reader-tag)
|
||||
(cons list? list->edn)))
|
||||
|
||||
(define (parse-entry in)
|
||||
((cdr
|
||||
(find (lambda (item) ((car item) in))
|
||||
writer-handlers))
|
||||
parse-entry in))
|
||||
|
||||
@(heading "Writing EDN")
|
||||
|
||||
(define (write-edn struct)
|
||||
@("Converts Chicken data structures to EDN and writes it to the `current-output-port`."
|
||||
(struct "A Chicken data structure consisting of atoms, lists, vectors and hashtables."))
|
||||
(lambda ()
|
||||
(display (parse-entry struct) (current-output-port))))
|
11
edn.egg
Normal file
11
edn.egg
Normal file
|
@ -0,0 +1,11 @@
|
|||
;;; -*- scheme -*-
|
||||
|
||||
((author "Daniel Ziltener")
|
||||
(synopsis "EDN data reader/writer.")
|
||||
(category parsing)
|
||||
(license "BSD")
|
||||
(dependencies hahn srfi-69 srfi-1)
|
||||
(test-dependencies srfi-64)
|
||||
(components (extension edn
|
||||
(modules edn)
|
||||
(csc-options "-X" "hahn"))))
|
10
edn.scm
Normal file
10
edn.scm
Normal file
|
@ -0,0 +1,10 @@
|
|||
@(heading "EDN")
|
||||
|
||||
@(text "This egg provides a parser and a writer for the [[https://github.com/edn-format/edn|Extensible Data Notation]].")
|
||||
|
||||
@(heading "Documentation")
|
||||
@(noop)
|
||||
|
||||
(use srfi-1 srfi-69 srfi-88)
|
||||
(module edn (parse-entry tag-handlers write-edn read-edn)
|
||||
"edn-impl.scm")
|
52
tests/run.scm
Normal file
52
tests/run.scm
Normal file
|
@ -0,0 +1,52 @@
|
|||
(require-extension r7rs srfi-69 srfi-64 srfi-88 srfi-1 hahn)
|
||||
(include "../edn-impl.scm")
|
||||
;; (run-hahn -o edn.wiki edn.scm edn-impl.scm)
|
||||
|
||||
(define s->k string->keyword)
|
||||
|
||||
(test-begin "EDN writing")
|
||||
|
||||
(test-equal (parse-entry keyword:) ":keyword")
|
||||
(test-equal (parse-entry #t) "true")
|
||||
(test-equal (parse-entry #f) "false")
|
||||
(test-equal (parse-entry '()) "nil")
|
||||
(test-equal (parse-entry #\a) "\\a")
|
||||
(test-equal (parse-entry "String") "\"String\"")
|
||||
(test-equal (parse-entry (cons edn/reader-tag: neat:)) "#neat")
|
||||
|
||||
|
||||
(test-equal (list->edn parse-entry '(1 2 3 4)) "(1 2 3 4)")
|
||||
(test-equal (vector->edn parse-entry #(a: b: c: d:)) "[:a :b :c :d]")
|
||||
(test-equal (parse-entry '((a: . "Hi")
|
||||
(b: . i-am:)
|
||||
(c: . (a list)))) "{:a \"Hi\" :b :i-am :c (a list)}")
|
||||
(test-end "EDN writing")
|
||||
|
||||
(test-begin "EDN reading")
|
||||
(define wifs with-input-from-string)
|
||||
|
||||
(test-equal (wifs "(:keyword)" read-edn) '(keyword:))
|
||||
(test-equal (wifs "(123)" read-edn) '(123))
|
||||
(test-equal (wifs "(\"Hello World!\")" read-edn) '("Hello World!"))
|
||||
(test-equal (wifs "(false)" read-edn) '(#f))
|
||||
(test-equal (wifs "(true)" read-edn) '(#t))
|
||||
(test-equal (wifs "(:Hello \"World\" 1)" read-edn) '(Hello: "World" 1))
|
||||
(test-equal (wifs "[:a :b :c :d]" read-edn) #(a: b: c: d:))
|
||||
(test-assert
|
||||
((lambda (a b)
|
||||
(and (equal? (hash-table-ref b a:) "Hi")
|
||||
(equal? (hash-table-ref b b:) i-am:)
|
||||
(equal? (hash-table-ref b c:) `(a list))))
|
||||
(alist->hash-table '((a: . "Hi") (b: . i-am:) (c: . (a list))))
|
||||
(wifs "{:a \"Hi\" :b :i-am :c (a list)}" read-edn)))
|
||||
(test-end "EDN reading")
|
||||
|
||||
(test-begin "Tag handling")
|
||||
(test-equal (wifs "(1 2 #_ 3 4)" read-edn) '(1 2 4))
|
||||
|
||||
(set! tag-handlers (cons (cons keywordify:
|
||||
(lambda (input)
|
||||
(string->keyword (symbol->string input))))
|
||||
tag-handlers))
|
||||
(test-equal (wifs "(asdf #keywordify qwertz)" read-edn) '(asdf qwertz:))
|
||||
(test-end "Tag handling")
|
Loading…
Reference in a new issue