Skip to content

Commit 692817a

Browse files
committed
tagged literals for edn reader, new sigs for read-edn, read-edn-string
1 parent 19f1e49 commit 692817a

File tree

3 files changed

+114
-47
lines changed

3 files changed

+114
-47
lines changed

src/clj/clojure/core.clj

+28-19
Original file line numberDiff line numberDiff line change
@@ -3406,23 +3406,6 @@
34063406
([stream eof-error? eof-value recursive?]
34073407
(. clojure.lang.LispReader (read stream (boolean eof-error?) eof-value recursive?))))
34083408

3409-
(defn read-edn
3410-
"Reads the next object from stream, which must be an instance of
3411-
java.io.PushbackReader or some derivee. stream defaults to the
3412-
current value of *in*.
3413-
3414-
Reads data in the edn format (subset of Clojure data):
3415-
http://edn-format.org"
3416-
{:added "1.5"}
3417-
([]
3418-
(read-edn *in*))
3419-
([stream]
3420-
(read-edn stream true nil))
3421-
([stream eof-error? eof-value]
3422-
(read-edn stream eof-error? eof-value false))
3423-
([stream eof-error? eof-value recursive?]
3424-
(. clojure.lang.EdnReader (read stream (boolean eof-error?) eof-value recursive?))))
3425-
34263409
(defn read-line
34273410
"Reads the next line from stream that is the current value of *in* ."
34283411
{:added "1.0"
@@ -3443,13 +3426,39 @@
34433426
:static true}
34443427
[s] (clojure.lang.RT/readString s))
34453428

3429+
(defn read-edn
3430+
"Reads the next object from stream, which must be an instance of
3431+
java.io.PushbackReader or some derivee. stream defaults to the
3432+
current value of *in*.
3433+
3434+
Reads data in the edn format (subset of Clojure data):
3435+
http://edn-format.org
3436+
3437+
opts is a map that can include the following keys:
3438+
:eof - value to return on end-of-file. When not supplied, eof throws an exception.
3439+
:readers - a map of tag symbols to data-reader functions to be considered before *default-data-readers*.
3440+
When not supplied, only the *default-data-readers* will be used.
3441+
:default - A function of two args, that will, if present and no reader is found for a tag,
3442+
be called with the tag and the value."
3443+
3444+
{:added "1.5"}
3445+
([]
3446+
(read-edn *in*))
3447+
([stream]
3448+
(read-edn {} stream))
3449+
([opts stream]
3450+
(clojure.lang.EdnReader/read stream opts)))
3451+
34463452
(defn read-edn-string
34473453
"Reads one object from the string s. Returns nil when s is nil or empty.
34483454
34493455
Reads data in the edn format (subset of Clojure data):
3450-
http://edn-format.org"
3456+
http://edn-format.org
3457+
3458+
opts is a map as per read-edn"
34513459
{:added "1.5"}
3452-
[s] (when (pos? (count s)) (clojure.lang.EdnReader/readString s)))
3460+
([s] (read-edn-string {:eof nil} s))
3461+
([opts s] (when s (clojure.lang.EdnReader/readString s opts))))
34533462

34543463
(defn subvec
34553464
"Returns a persistent vector of the items in vector from

src/jvm/clojure/lang/EdnReader.java

+85-27
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public class EdnReader{
3232
static Pattern floatPat = Pattern.compile("([-+]?[0-9]+(\\.[0-9]*)?([eE][-+]?[0-9]+)?)(M)?");
3333
static final Symbol SLASH = Symbol.intern("/");
3434

35+
static IFn taggedReader = new TaggedReader();
36+
3537
static
3638
{
3739
macros['"'] = new StringReader();
@@ -54,10 +56,14 @@ public class EdnReader{
5456
dispatchMacros['_'] = new DiscardReader();
5557
}
5658

57-
static public Object readString(String s){
59+
static boolean nonConstituent(int ch){
60+
return ch == '@' || ch == '`' || ch == '~';
61+
}
62+
63+
static public Object readString(String s, IPersistentMap opts){
5864
PushbackReader r = new PushbackReader(new java.io.StringReader(s));
5965
try {
60-
return EdnReader.read(r, true, null, false);
66+
return read(r, opts);
6167
}
6268
catch(Exception e) {
6369
throw Util.sneakyThrow(e);
@@ -102,7 +108,14 @@ static public int read1(Reader r){
102108
}
103109
}
104110

105-
static public Object read(PushbackReader r, boolean eofIsError, Object eofValue, boolean isRecursive)
111+
static final Keyword EOF = Keyword.intern(null,"eof");
112+
113+
static public Object read(PushbackReader r, IPersistentMap opts){
114+
return read(r,!opts.containsKey(EOF),opts.valAt(EOF),false,opts);
115+
}
116+
117+
static public Object read(PushbackReader r, boolean eofIsError, Object eofValue, boolean isRecursive,
118+
Object opts)
106119
{
107120

108121
try
@@ -132,7 +145,7 @@ static public Object read(PushbackReader r, boolean eofIsError, Object eofValue,
132145
IFn macroFn = getMacro(ch);
133146
if(macroFn != null)
134147
{
135-
Object ret = macroFn.invoke(r, (char) ch);
148+
Object ret = macroFn.invoke(r, (char) ch, opts);
136149
if(RT.suppressRead())
137150
return null;
138151
//no op macros return the reader
@@ -173,16 +186,22 @@ static public Object read(PushbackReader r, boolean eofIsError, Object eofValue,
173186

174187
static private String readToken(PushbackReader r, char initch) {
175188
StringBuilder sb = new StringBuilder();
189+
if(nonConstituent(initch))
190+
throw Util.runtimeException("Invalid leading character: " + (char)initch);
191+
176192
sb.append(initch);
177193

178194
for(; ;)
179195
{
180196
int ch = read1(r);
197+
181198
if(ch == -1 || isWhitespace(ch) || isTerminatingMacro(ch))
182199
{
183200
unread(r, ch);
184201
return sb.toString();
185202
}
203+
else if(nonConstituent(ch))
204+
throw Util.runtimeException("Invalid constituent character: " + (char)ch);
186205
sb.append((char) ch);
187206
}
188207
}
@@ -391,7 +410,7 @@ public Object invoke(Object reader, Object doublequote) {
391410
*/
392411

393412
public static class StringReader extends AFn{
394-
public Object invoke(Object reader, Object doublequote) {
413+
public Object invoke(Object reader, Object doublequote, Object opts) {
395414
StringBuilder sb = new StringBuilder();
396415
Reader r = (Reader) reader;
397416

@@ -453,7 +472,7 @@ public Object invoke(Object reader, Object doublequote) {
453472
}
454473

455474
public static class CommentReader extends AFn{
456-
public Object invoke(Object reader, Object semicolon) {
475+
public Object invoke(Object reader, Object semicolon, Object opts) {
457476
Reader r = (Reader) reader;
458477
int ch;
459478
do
@@ -466,29 +485,36 @@ public Object invoke(Object reader, Object semicolon) {
466485
}
467486

468487
public static class DiscardReader extends AFn{
469-
public Object invoke(Object reader, Object underscore) {
488+
public Object invoke(Object reader, Object underscore, Object opts) {
470489
PushbackReader r = (PushbackReader) reader;
471-
read(r, true, null, true);
490+
read(r, true, null, true, opts);
472491
return r;
473492
}
474493
}
475494

476495
public static class DispatchReader extends AFn{
477-
public Object invoke(Object reader, Object hash) {
496+
public Object invoke(Object reader, Object hash, Object opts) {
478497
int ch = read1((Reader) reader);
479498
if(ch == -1)
480499
throw Util.runtimeException("EOF while reading character");
481500
IFn fn = dispatchMacros[ch];
482501

483502
if(fn == null) {
503+
//try tagged reader
504+
if(Character.isLetter(ch))
505+
{
506+
unread((PushbackReader) reader, ch);
507+
return taggedReader.invoke(reader, ch, opts);
508+
}
509+
484510
throw Util.runtimeException(String.format("No dispatch macro for: %c", (char) ch));
485511
}
486-
return fn.invoke(reader, ch);
512+
return fn.invoke(reader, ch, opts);
487513
}
488514
}
489515

490516
public static class MetaReader extends AFn{
491-
public Object invoke(Object reader, Object caret) {
517+
public Object invoke(Object reader, Object caret, Object opts) {
492518
PushbackReader r = (PushbackReader) reader;
493519
int line = -1;
494520
int column = -1;
@@ -497,15 +523,15 @@ public Object invoke(Object reader, Object caret) {
497523
line = ((LineNumberingPushbackReader) r).getLineNumber();
498524
column = ((LineNumberingPushbackReader) r).getColumnNumber()-1;
499525
}
500-
Object meta = read(r, true, null, true);
526+
Object meta = read(r, true, null, true, opts);
501527
if(meta instanceof Symbol || meta instanceof String)
502528
meta = RT.map(RT.TAG_KEY, meta);
503529
else if (meta instanceof Keyword)
504530
meta = RT.map(meta, RT.T);
505531
else if(!(meta instanceof IPersistentMap))
506532
throw new IllegalArgumentException("Metadata must be Symbol,Keyword,String or Map");
507533

508-
Object o = read(r, true, null, true);
534+
Object o = read(r, true, null, true, opts);
509535
if(o instanceof IMeta)
510536
{
511537
if(line != -1 && o instanceof ISeq)
@@ -531,7 +557,7 @@ else if(!(meta instanceof IPersistentMap))
531557
}
532558

533559
public static class CharacterReader extends AFn{
534-
public Object invoke(Object reader, Object backslash) {
560+
public Object invoke(Object reader, Object backslash, Object opts) {
535561
PushbackReader r = (PushbackReader) reader;
536562
int ch = read1(r);
537563
if(ch == -1)
@@ -574,7 +600,7 @@ else if(token.startsWith("o"))
574600
}
575601

576602
public static class ListReader extends AFn{
577-
public Object invoke(Object reader, Object leftparen) {
603+
public Object invoke(Object reader, Object leftparen, Object opts) {
578604
PushbackReader r = (PushbackReader) reader;
579605
int line = -1;
580606
int column = -1;
@@ -583,7 +609,7 @@ public Object invoke(Object reader, Object leftparen) {
583609
line = ((LineNumberingPushbackReader) r).getLineNumber();
584610
column = ((LineNumberingPushbackReader) r).getColumnNumber()-1;
585611
}
586-
List list = readDelimitedList(')', r, true);
612+
List list = readDelimitedList(')', r, true, opts);
587613
if(list.isEmpty())
588614
return PersistentList.EMPTY;
589615
IObj s = (IObj) PersistentList.create(list);
@@ -599,17 +625,17 @@ public Object invoke(Object reader, Object leftparen) {
599625
}
600626

601627
public static class VectorReader extends AFn{
602-
public Object invoke(Object reader, Object leftparen) {
628+
public Object invoke(Object reader, Object leftparen, Object opts) {
603629
PushbackReader r = (PushbackReader) reader;
604-
return LazilyPersistentVector.create(readDelimitedList(']', r, true));
630+
return LazilyPersistentVector.create(readDelimitedList(']', r, true, opts));
605631
}
606632

607633
}
608634

609635
public static class MapReader extends AFn{
610-
public Object invoke(Object reader, Object leftparen) {
636+
public Object invoke(Object reader, Object leftparen, Object opts) {
611637
PushbackReader r = (PushbackReader) reader;
612-
Object[] a = readDelimitedList('}', r, true).toArray();
638+
Object[] a = readDelimitedList('}', r, true, opts).toArray();
613639
if((a.length & 1) == 1)
614640
throw Util.runtimeException("Map literal must contain an even number of forms");
615641
return RT.map(a);
@@ -618,27 +644,27 @@ public Object invoke(Object reader, Object leftparen) {
618644
}
619645

620646
public static class SetReader extends AFn{
621-
public Object invoke(Object reader, Object leftbracket) {
647+
public Object invoke(Object reader, Object leftbracket, Object opts) {
622648
PushbackReader r = (PushbackReader) reader;
623-
return PersistentHashSet.createWithCheck(readDelimitedList('}', r, true));
649+
return PersistentHashSet.createWithCheck(readDelimitedList('}', r, true, opts));
624650
}
625651

626652
}
627653

628654
public static class UnmatchedDelimiterReader extends AFn{
629-
public Object invoke(Object reader, Object rightdelim) {
655+
public Object invoke(Object reader, Object rightdelim, Object opts) {
630656
throw Util.runtimeException("Unmatched delimiter: " + rightdelim);
631657
}
632658

633659
}
634660

635661
public static class UnreadableReader extends AFn{
636-
public Object invoke(Object reader, Object leftangle) {
662+
public Object invoke(Object reader, Object leftangle, Object opts) {
637663
throw Util.runtimeException("Unreadable form");
638664
}
639665
}
640666

641-
public static List readDelimitedList(char delim, PushbackReader r, boolean isRecursive) {
667+
public static List readDelimitedList(char delim, PushbackReader r, boolean isRecursive, Object opts) {
642668
final int firstline =
643669
(r instanceof LineNumberingPushbackReader) ?
644670
((LineNumberingPushbackReader) r).getLineNumber() : -1;
@@ -666,7 +692,7 @@ public static List readDelimitedList(char delim, PushbackReader r, boolean isRec
666692
IFn macroFn = getMacro(ch);
667693
if(macroFn != null)
668694
{
669-
Object mret = macroFn.invoke(r, (char) ch);
695+
Object mret = macroFn.invoke(r, (char) ch, opts);
670696
//no op macros return the reader
671697
if(mret != r)
672698
a.add(mret);
@@ -675,7 +701,7 @@ public static List readDelimitedList(char delim, PushbackReader r, boolean isRec
675701
{
676702
unread(r, ch);
677703

678-
Object o = read(r, true, null, isRecursive);
704+
Object o = read(r, true, null, isRecursive, opts);
679705
if(o != r)
680706
a.add(o);
681707
}
@@ -685,5 +711,37 @@ public static List readDelimitedList(char delim, PushbackReader r, boolean isRec
685711
return a;
686712
}
687713

714+
public static class TaggedReader extends AFn{
715+
public Object invoke(Object reader, Object firstChar, Object opts){
716+
PushbackReader r = (PushbackReader) reader;
717+
Object name = read(r, true, null, false, opts);
718+
if (!(name instanceof Symbol))
719+
throw new RuntimeException("Reader tag must be a symbol");
720+
Symbol sym = (Symbol)name;
721+
return readTagged(r, sym, (IPersistentMap) opts);
722+
}
723+
724+
static Keyword READERS = Keyword.intern(null,"readers");
725+
static Keyword DEFAULT = Keyword.intern(null,"default");
726+
727+
private Object readTagged(PushbackReader reader, Symbol tag, IPersistentMap opts){
728+
Object o = read(reader, true, null, true, opts);
729+
730+
ILookup readers = (ILookup)RT.get(opts, READERS);
731+
IFn dataReader = (IFn)RT.get(readers, tag);
732+
if(dataReader == null)
733+
dataReader = (IFn)RT.get(RT.DEFAULT_DATA_READERS.deref(),tag);
734+
if(dataReader == null){
735+
IFn defaultReader = (IFn)RT.get(opts, DEFAULT);
736+
if(defaultReader != null)
737+
return defaultReader.invoke(tag, o);
738+
else
739+
throw new RuntimeException("No reader function for tag " + tag.toString());
740+
}
741+
else
742+
return dataReader.invoke(o);
743+
}
744+
745+
}
688746
}
689747

src/jvm/clojure/lang/LispReader.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ static private boolean isMacro(int ch){
424424
}
425425

426426
static private boolean isTerminatingMacro(int ch){
427-
return (ch != '#' && ch != '\'' && isMacro(ch));
427+
return (ch != '#' && ch != '\'' && ch != '%' && isMacro(ch));
428428
}
429429

430430
public static class RegexReader extends AFn{

0 commit comments

Comments
 (0)