code: /**
   *   Copyright (c) Rich Hickey. All rights reserved.
   *   The use and distribution terms for this software are covered by the
   *   Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
   *   which can be found in the file epl-v10.html at the root of this distribution.
   *   By using this software in any fashion, you are agreeing to be bound by
   * 	 the terms of this license.
   *   You must not remove this notice, or any other, from this software.
   **/
  
  package clojure.lang;
  
  import java.io.IOException;
  import java.io.PushbackReader;
  import java.io.Reader;
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  public class EdnReader{
  
  static IFn[] macros = new IFn[256];
  static IFn[] dispatchMacros = new IFn[256];
  static Pattern symbolPat = Pattern.compile("[:]?([\\D&&[^/]].*/)?(/|[\\D&&[^/]][^/]*)");
  static Pattern intPat =
  		Pattern.compile(
  				"([-+]?)(?:(0)|([1-9][0-9]*)|0[xX]([0-9A-Fa-f]+)|0([0-7]+)|([1-9][0-9]?)[rR]([0-9A-Za-z]+)|0[0-9]+)(N)?");
  static Pattern ratioPat = Pattern.compile("([-+]?[0-9]+)/([0-9]+)");
  static Pattern floatPat = Pattern.compile("([-+]?[0-9]+(\\.[0-9]*)?([eE][-+]?[0-9]+)?)(M)?");
  
  static IFn taggedReader = new TaggedReader();
  
  static
  	{
  	macros['"'] = new StringReader();
  	macros[';'] = new CommentReader();
  	macros['^'] = new MetaReader();
  	macros['('] = new ListReader();
  	macros[')'] = new UnmatchedDelimiterReader();
  	macros['['] = new VectorReader();
  	macros[']'] = new UnmatchedDelimiterReader();
  	macros['{'] = new MapReader();
  	macros['}'] = new UnmatchedDelimiterReader();
  	macros['\\'] = new CharacterReader();
  	macros['#'] = new DispatchReader();
  
  
  	dispatchMacros['#'] = new SymbolicValueReader();
  	dispatchMacros['^'] = new MetaReader();
  	//dispatchMacros['"'] = new RegexReader();
  	dispatchMacros['{'] = new SetReader();
  	dispatchMacros['<'] = new UnreadableReader();
  	dispatchMacros['_'] = new DiscardReader();
  	dispatchMacros[':'] = new NamespaceMapReader();
  	}
  
  static boolean nonConstituent(int ch){
  	return ch == '@' || ch == '`' || ch == '~';
  }
  
  static public Object readString(String s, IPersistentMap opts){
  	PushbackReader r = new PushbackReader(new java.io.StringReader(s));
  	return read(r, opts);
  }
  
  static boolean isWhitespace(int ch){
  	return Character.isWhitespace(ch) || ch == ',';
  }
  
  static void unread(PushbackReader r, int ch) {
  	if(ch != -1)
  		try
  			{
  			r.unread(ch);
  			}
  		catch(IOException e)
  			{
  			throw Util.sneakyThrow(e);
  			}
  }
  
  public static class ReaderException extends RuntimeException{
  	final int line;
  	final int column;
  
  	public ReaderException(int line, int column, Throwable cause){
  		super(cause);
  		this.line = line;
  		this.column = column;
  	}
  }
  
  static public int read1(Reader r){
  	try
  		{
  		return r.read();
  		}
  	catch(IOException e)
  		{
  		throw Util.sneakyThrow(e);
  		}
  }
  
  static final Keyword EOF = Keyword.intern(null,"eof");
  
  static public Object read(PushbackReader r, IPersistentMap opts){
  	return read(r,!opts.containsKey(EOF),opts.valAt(EOF),false,opts);
  }
  
  static public Object read(PushbackReader r, boolean eofIsError, Object eofValue, boolean isRecursive,
                            Object opts)
  {
  
  	try
  		{
  		for(; ;)
  			{
  			int ch = read1(r);
  
  			while(isWhitespace(ch))
  				ch = read1(r);
  
  			if(ch == -1)
  				{
  				if(eofIsError)
  					throw Util.runtimeException("EOF while reading");
  				return eofValue;
  				}
  
  			if(Character.isDigit(ch))
  				{
  				Object n = readNumber(r, (char) ch);
  				if(RT.suppressRead())
  					return null;
  				return n;
  				}
  
  			IFn macroFn = getMacro(ch);
  			if(macroFn != null)
  				{
  				Object ret = macroFn.invoke(r, (char) ch, opts);
  				if(RT.suppressRead())
  					return null;
  				//no op macros return the reader
  				if(ret == r)
  					continue;
  				return ret;
  				}
  
  			if(ch == '+' || ch == '-')
  				{
  				int ch2 = read1(r);
  				if(Character.isDigit(ch2))
  					{
  					unread(r, ch2);
  					Object n = readNumber(r, (char) ch);
  					if(RT.suppressRead())
  						return null;
  					return n;
  					}
  				unread(r, ch2);
  				}
  
  			String token = readToken(r, (char) ch, true);
  			if(RT.suppressRead())
  				return null;
  			return interpretToken(token);
  			}
  		}
  	catch(Exception e)
  		{
  		if(isRecursive || !(r instanceof LineNumberingPushbackReader))
  			throw Util.sneakyThrow(e);
  		LineNumberingPushbackReader rdr = (LineNumberingPushbackReader) r;
  		//throw Util.runtimeException(String.format("ReaderError:(%d,1) %s", rdr.getLineNumber(), e.getMessage()), e);
  		throw new ReaderException(rdr.getLineNumber(), rdr.getColumnNumber(), e);
  		}
  }
  
  static private String readToken(PushbackReader r, char initch, boolean leadConstituent) {
  	StringBuilder sb = new StringBuilder();
  	if(leadConstituent && nonConstituent(initch))
  		throw Util.runtimeException("Invalid leading character: " + (char)initch);
  
  	sb.append(initch);
  
  	for(; ;)
  		{
  		int ch = read1(r);
  
  		if(ch == -1 || isWhitespace(ch) || isTerminatingMacro(ch))
  			{
  			unread(r, ch);
  			return sb.toString();
  			}
  		else if(nonConstituent(ch))
  			throw Util.runtimeException("Invalid constituent character: " + (char)ch);
  		sb.append((char) ch);
  		}
  }
  
  static private Object readNumber(PushbackReader r, char initch) {
  	StringBuilder sb = new StringBuilder();
  	sb.append(initch);
  
  	for(; ;)
  		{
  		int ch = read1(r);
  		if(ch == -1 || isWhitespace(ch) || isMacro(ch))
  			{
  			unread(r, ch);
  			break;
  			}
  		sb.append((char) ch);
  		}
  
  	String s = sb.toString();
  	Object n = matchNumber(s);
  	if(n == null)
  		throw new NumberFormatException("Invalid number: " + s);
  	return n;
  }
  
  static private int readUnicodeChar(String token, int offset, int length, int base) {
  	if(token.length() != offset + length)
  		throw new IllegalArgumentException("Invalid unicode character: \\" + token);
  	int uc = 0;
  	for(int i = offset; i < offset + length; ++i)
  		{
  		int d = Character.digit(token.charAt(i), base);
  		if(d == -1)
  			throw new IllegalArgumentException("Invalid digit: " + token.charAt(i));
  		uc = uc * base + d;
  		}
  	return (char) uc;
  }
  
  static private int readUnicodeChar(PushbackReader r, int initch, int base, int length, boolean exact) {
  	int uc = Character.digit(initch, base);
  	if(uc == -1)
  		throw new IllegalArgumentException("Invalid digit: " + (char) initch);
  	int i = 1;
  	for(; i < length; ++i)
  		{
  		int ch = read1(r);
  		if(ch == -1 || isWhitespace(ch) || isMacro(ch))
  			{
  			unread(r, ch);
  			break;
  			}
  		int d = Character.digit(ch, base);
  		if(d == -1)
  			throw new IllegalArgumentException("Invalid digit: " + (char) ch);
  		uc = uc * base + d;
  		}
  	if(i != length && exact)
  		throw new IllegalArgumentException("Invalid character length: " + i + ", should be: " + length);
  	return uc;
  }
  
  static private Object interpretToken(String s) {
  	if(s.equals("nil"))
  		{
  		return null;
  		}
  	else if(s.equals("true"))
  		{
  		return RT.T;
  		}
  	else if(s.equals("false"))
  		{
  		return RT.F;
  		}
  
  	Object ret = null;
  
  	ret = matchSymbol(s);
  	if(ret != null)
  		return ret;
  
  	throw Util.runtimeException("Invalid token: " + s);
  }
  
  
  private static Object matchSymbol(String s){
  	Matcher m = symbolPat.matcher(s);
  	if(m.matches())
  		{
  		int gc = m.groupCount();
  		String ns = m.group(1);
  		String name = m.group(2);
  		if(ns != null && ns.endsWith(":/")
  		   || name.endsWith(":")
  		   || s.indexOf("::", 1) != -1)
  			return null;
  		if(s.startsWith("::"))
  			{
  			return null;
  			}
  		boolean isKeyword = s.charAt(0) == ':';
  		Symbol sym = Symbol.intern(s.substring(isKeyword ? 1 : 0));
  		if(isKeyword)
  			return Keyword.intern(sym);
  		return sym;
  		}
  	return null;
  }
  
  
  private static Object matchNumber(String s){
  	Matcher m = intPat.matcher(s);
  	if(m.matches())
  		{
  		if(m.group(2) != null)
  			{
  			if(m.group(8) != null)
  				return BigInt.ZERO;
  			return Numbers.num(0);
  			}
  		boolean negate = (m.group(1).equals("-"));
  		String n;
  		int radix = 10;
  		if((n = m.group(3)) != null)
  			radix = 10;
  		else if((n = m.group(4)) != null)
  			radix = 16;
  		else if((n = m.group(5)) != null)
  			radix = 8;
  		else if((n = m.group(7)) != null)
  			radix = Integer.parseInt(m.group(6));
  		if(n == null)
  			return null;
  		BigInteger bn = new BigInteger(n, radix);
  		if(negate)
  			bn = bn.negate();
  		if(m.group(8) != null)
  			return BigInt.fromBigInteger(bn);
  		return bn.bitLength() < 64 ?
  		       Numbers.num(bn.longValue())
  		                           : BigInt.fromBigInteger(bn);
  		}
  	m = floatPat.matcher(s);
  	if(m.matches())
  		{
  		if(m.group(4) != null)
  			return new BigDecimal(m.group(1));
  		return Double.parseDouble(s);
  		}
  	m = ratioPat.matcher(s);
  	if(m.matches())
  		{
  		String numerator = m.group(1);
  		if (numerator.startsWith("+")) numerator = numerator.substring(1);
  
  		return Numbers.divide(Numbers.reduceBigInt(BigInt.fromBigInteger(new BigInteger(numerator))),
  		                      Numbers.reduceBigInt(BigInt.fromBigInteger(new BigInteger(m.group(2)))));
  		}
  	return null;
  }
  
  static private IFn getMacro(int ch){
  	if(ch < macros.length)
  		return macros[ch];
  	return null;
  }
  
  static private boolean isMacro(int ch){
  	return (ch < macros.length && macros[ch] != null);
  }
  
  static private boolean isTerminatingMacro(int ch){
  	return (ch != '#' && ch != '\'' && isMacro(ch));
  }
  
  /*
  public static class RegexReader extends AFn{
  
  	static StringReader stringrdr = new StringReader();
  
  	public Object invoke(Object reader, Object doublequote) {
  		StringBuilder sb = new StringBuilder();
  		Reader r = (Reader) reader;
  		for(int ch = read1(r); ch != '"'; ch = read1(r))
  			{
  			if(ch == -1)
  				throw Util.runtimeException("EOF while reading regex");
  			sb.append( (char) ch );
  			if(ch == '\\')	//escape
  				{
  				ch = read1(r);
  				if(ch == -1)
  					throw Util.runtimeException("EOF while reading regex");
  				sb.append( (char) ch ) ;
  				}
  			}
  		return Pattern.compile(sb.toString());
  	}
  }
  */
  
  public static class StringReader extends AFn{
  	public Object invoke(Object reader, Object doublequote, Object opts) {
  		StringBuilder sb = new StringBuilder();
  		Reader r = (Reader) reader;
  
  		for(int ch = read1(r); ch != '"'; ch = read1(r))
  			{
  			if(ch == -1)
  				throw Util.runtimeException("EOF while reading string");
  			if(ch == '\\')	//escape
  				{
  				ch = read1(r);
  				if(ch == -1)
  					throw Util.runtimeException("EOF while reading string");
  				switch(ch)
  					{
  					case 't':
  						ch = '\t';
  						break;
  					case 'r':
  						ch = '\r';
  						break;
  					case 'n':
  						ch = '\n';
  						break;
  					case '\\':
  						break;
  					case '"':
  						break;
  					case 'b':
  						ch = '\b';
  						break;
  					case 'f':
  						ch = '\f';
  						break;
  					case 'u':
  					{
  					ch = read1(r);
  					if (Character.digit(ch, 16) == -1)
  						throw Util.runtimeException("Invalid unicode escape: \\u" + (char) ch);
  					ch = readUnicodeChar((PushbackReader) r, ch, 16, 4, true);
  					break;
  					}
  					default:
  					{
  					if(Character.isDigit(ch))
  						{
  						ch = readUnicodeChar((PushbackReader) r, ch, 8, 3, false);
  						if(ch > 0377)
  							throw Util.runtimeException("Octal escape sequence must be in range [0, 377].");
  						}
  					else
  						throw Util.runtimeException("Unsupported escape character: \\" + (char) ch);
  					}
  					}
  				}
  			sb.append((char) ch);
  			}
  		return sb.toString();
  	}
  }
  
  public static class CommentReader extends AFn{
  	public Object invoke(Object reader, Object semicolon, Object opts) {
  		Reader r = (Reader) reader;
  		int ch;
  		do
  			{
  			ch = read1(r);
  			} while(ch != -1 && ch != '\n' && ch != '\r');
  		return r;
  	}
  
  }
  
  public static class DiscardReader extends AFn{
  	public Object invoke(Object reader, Object underscore, Object opts) {
  		PushbackReader r = (PushbackReader) reader;
  		read(r, true, null, true, opts);
  		return r;
  	}
  }
  
  public static class NamespaceMapReader extends AFn{
  	public Object invoke(Object reader, Object colon, Object opts) {
  		PushbackReader r = (PushbackReader) reader;
  
  		// Read ns symbol
  		Object sym = read(r, true, null, false, opts);
  		if (!(sym instanceof Symbol) || ((Symbol)sym).getNamespace() != null)
  			throw new RuntimeException("Namespaced map must specify a valid namespace: " + sym);
  		String ns = ((Symbol)sym).getName();
  
  		// Read map
  		int nextChar = read1(r);
  		while(isWhitespace(nextChar))
  			nextChar = read1(r);
  		if('{' != nextChar)
  			throw new RuntimeException("Namespaced map must specify a map");
  		List kvs = readDelimitedList('}', r, true, opts);
  		if((kvs.size() & 1) == 1)
  			throw Util.runtimeException("Namespaced map literal must contain an even number of forms");
  
  		// Construct output map
  		Object[] a = new Object[kvs.size()];
  		Iterator iter = kvs.iterator();
  		for(int i = 0; iter.hasNext(); i += 2) {
  			Object key = iter.next();
  			Object val = iter.next();
  
  			if(key instanceof Keyword) {
  				Keyword kw = (Keyword) key;
  				if (kw.getNamespace() == null) {
  					key = Keyword.intern(ns, kw.getName());
  				} else if (kw.getNamespace().equals("_")) {
  					key = Keyword.intern(null, kw.getName());
  				}
  			} else if(key instanceof Symbol) {
  				Symbol s = (Symbol) key;
  				if (s.getNamespace() == null) {
  					key = Symbol.intern(ns, s.getName());
  				} else if (s.getNamespace().equals("_")) {
  					key = Symbol.intern(null, s.getName());
  				}
  			}
  			a[i] = key;
  			a[i+1] = val;
  		}
  		return RT.map(a);
  	}
  }
  
  public static class DispatchReader extends AFn{
  	public Object invoke(Object reader, Object hash, Object opts) {
  		int ch = read1((Reader) reader);
  		if(ch == -1)
  			throw Util.runtimeException("EOF while reading character");
  		IFn fn = dispatchMacros[ch];
  
  		if(fn == null) {
  			//try tagged reader
  		    if(Character.isLetter(ch))
  				{
  				unread((PushbackReader) reader, ch);
  		        return taggedReader.invoke(reader, ch, opts);
  				}
  
  			throw Util.runtimeException(String.format("No dispatch macro for: %c", (char) ch));
  		}
  		return fn.invoke(reader, ch, opts);
  	}
  }
  
  public static class MetaReader extends AFn{
  	public Object invoke(Object reader, Object caret, Object opts) {
  		PushbackReader r = (PushbackReader) reader;
  		int line = -1;
  		int column = -1;
  		if(r instanceof LineNumberingPushbackReader)
  			{
  			line = ((LineNumberingPushbackReader) r).getLineNumber();
  			column = ((LineNumberingPushbackReader) r).getColumnNumber()-1;
  			}
  		Object meta = read(r, true, null, true, opts);
  		if(meta instanceof Symbol || meta instanceof String)
  			meta = RT.map(RT.TAG_KEY, meta);
  		else if (meta instanceof Keyword)
  			meta = RT.map(meta, RT.T);
  		else if(!(meta instanceof IPersistentMap))
  			throw new IllegalArgumentException("Metadata must be Symbol,Keyword,String or Map");
  
  		Object o = read(r, true, null, true, opts);
  		if(o instanceof IMeta)
  			{
  			if(line != -1 && o instanceof ISeq)
  				{
  				meta = ((IPersistentMap) meta).assoc(RT.LINE_KEY, line).assoc(RT.COLUMN_KEY, column);
  				}
  			if(o instanceof IReference)
  				{
  				((IReference)o).resetMeta((IPersistentMap) meta);
  				return o;
  				}
  			Object ometa = RT.meta(o);
  			for(ISeq s = RT.seq(meta); s != null; s = s.next()) {
  			IMapEntry kv = (IMapEntry) s.first();
  			ometa = RT.assoc(ometa, kv.getKey(), kv.getValue());
  			}
  			return ((IObj) o).withMeta((IPersistentMap) ometa);
  			}
  		else
  			throw new IllegalArgumentException("Metadata can only be applied to IMetas");
  	}
  
  }
  
  public static class CharacterReader extends AFn{
  	public Object invoke(Object reader, Object backslash, Object opts) {
  		PushbackReader r = (PushbackReader) reader;
  		int ch = read1(r);
  		if(ch == -1)
  			throw Util.runtimeException("EOF while reading character");
  		String token = readToken(r, (char) ch, false);
  		if(token.length() == 1)
  			return Character.valueOf(token.charAt(0));
  		else if(token.equals("newline"))
  			return '\n';
  		else if(token.equals("space"))
  			return ' ';
  		else if(token.equals("tab"))
  			return '\t';
  		else if(token.equals("backspace"))
  			return '\b';
  		else if(token.equals("formfeed"))
  			return '\f';
  		else if(token.equals("return"))
  			return '\r';
  		else if(token.startsWith("u"))
  			{
  			char c = (char) readUnicodeChar(token, 1, 4, 16);
  			if(c >= '\uD800' && c <= '\uDFFF') // surrogate code unit?
  				throw Util.runtimeException("Invalid character constant: \\u" + Integer.toString(c, 16));
  			return c;
  			}
  		else if(token.startsWith("o"))
  			{
  			int len = token.length() - 1;
  			if(len > 3)
  				throw Util.runtimeException("Invalid octal escape sequence length: " + len);
  			int uc = readUnicodeChar(token, 1, len, 8);
  			if(uc > 0377)
  				throw Util.runtimeException("Octal escape sequence must be in range [0, 377].");
  			return (char) uc;
  			}
  		throw Util.runtimeException("Unsupported character: \\" + token);
  	}
  
  }
  
  public static class ListReader extends AFn{
  	public Object invoke(Object reader, Object leftparen, Object opts) {
  		PushbackReader r = (PushbackReader) reader;
  		int line = -1;
  		int column = -1;
  		if(r instanceof LineNumberingPushbackReader)
  			{
  			line = ((LineNumberingPushbackReader) r).getLineNumber();
  			column = ((LineNumberingPushbackReader) r).getColumnNumber()-1;
  			}
  		List list = readDelimitedList(')', r, true, opts);
  		if(list.isEmpty())
  			return PersistentList.EMPTY;
  		IObj s = (IObj) PersistentList.create(list);
  //		IObj s = (IObj) RT.seq(list);
  //		if(line != -1)
  //			{
  //			return s.withMeta(RT.map(RT.LINE_KEY, line, RT.COLUMN_KEY, column));
  //			}
  //		else
  			return s;
  	}
  
  }
  
  public static class VectorReader extends AFn{
  	public Object invoke(Object reader, Object leftparen, Object opts) {
  		PushbackReader r = (PushbackReader) reader;
  		return LazilyPersistentVector.create(readDelimitedList(']', r, true, opts));
  	}
  
  }
  
  public static class MapReader extends AFn{
  	public Object invoke(Object reader, Object leftparen, Object opts) {
  		PushbackReader r = (PushbackReader) reader;
  		Object[] a = readDelimitedList('}', r, true, opts).toArray();
  		if((a.length & 1) == 1)
  			throw Util.runtimeException("Map literal must contain an even number of forms");
  		return RT.map(a);
  	}
  
  }
  
  public static class SetReader extends AFn{
  	public Object invoke(Object reader, Object leftbracket, Object opts) {
  		PushbackReader r = (PushbackReader) reader;
  		return PersistentHashSet.createWithCheck(readDelimitedList('}', r, true, opts));
  	}
  
  }
  
  public static class UnmatchedDelimiterReader extends AFn{
  	public Object invoke(Object reader, Object rightdelim, Object opts) {
  		throw Util.runtimeException("Unmatched delimiter: " + rightdelim);
  	}
  
  }
  
  public static class UnreadableReader extends AFn{
  	public Object invoke(Object reader, Object leftangle, Object opts) {
  		throw Util.runtimeException("Unreadable form");
  	}
  }
  
  
  public static class SymbolicValueReader extends AFn{
  
      static IPersistentMap specials = PersistentHashMap.create(Symbol.intern("Inf"), Double.POSITIVE_INFINITY,
                                                                Symbol.intern("-Inf"), Double.NEGATIVE_INFINITY,
                                                                Symbol.intern("NaN"), Double.NaN);
  
  	public Object invoke(Object reader, Object quote, Object opts) {
  		PushbackReader r = (PushbackReader) reader;
  		Object o = read(r, true, null, true, opts);
  
  		if (!(o instanceof Symbol))
  			throw Util.runtimeException("Invalid token: ##" + o);
  		if (!(specials.containsKey(o)))
  			throw Util.runtimeException("Unknown symbolic value: ##" + o);
  
  		return specials.valAt(o);
  	}
  }
  
  public static List readDelimitedList(char delim, PushbackReader r, boolean isRecursive, Object opts) {
  	final int firstline =
  			(r instanceof LineNumberingPushbackReader) ?
  			((LineNumberingPushbackReader) r).getLineNumber() : -1;
  
  	ArrayList a = new ArrayList();
  
  	for(; ;)
  		{
  		int ch = read1(r);
  
  		while(isWhitespace(ch))
  			ch = read1(r);
  
  		if(ch == -1)
  			{
  			if(firstline < 0)
  				throw Util.runtimeException("EOF while reading");
  			else
  				throw Util.runtimeException("EOF while reading, starting at line " + firstline);
  			}
  
  		if(ch == delim)
  			break;
  
  		IFn macroFn = getMacro(ch);
  		if(macroFn != null)
  			{
  			Object mret = macroFn.invoke(r, (char) ch, opts);
  			//no op macros return the reader
  			if(mret != r)
  				a.add(mret);
  			}
  		else
  			{
  			unread(r, ch);
  
  			Object o = read(r, true, null, isRecursive, opts);
  			if(o != r)
  				a.add(o);
  			}
  		}
  
  
  	return a;
  }
  
  public static class TaggedReader extends AFn{
  	public Object invoke(Object reader, Object firstChar, Object opts){
  		PushbackReader r = (PushbackReader) reader;
  		Object name = read(r, true, null, false, opts);
  		if (!(name instanceof Symbol))
  			throw new RuntimeException("Reader tag must be a symbol");
  		Symbol sym = (Symbol)name;
  		return readTagged(r, sym, (IPersistentMap) opts);
  	}
  
  	static Keyword READERS = Keyword.intern(null,"readers");
  	static Keyword DEFAULT = Keyword.intern(null,"default");
  
  	private Object readTagged(PushbackReader reader, Symbol tag, IPersistentMap opts){
  		Object o = read(reader, true, null, true, opts);
  
  		ILookup readers = (ILookup)RT.get(opts, READERS);
  		IFn dataReader = (IFn)RT.get(readers, tag);
  		if(dataReader == null)
  			dataReader = (IFn)RT.get(RT.DEFAULT_DATA_READERS.deref(),tag);
  		if(dataReader == null){
  			IFn defaultReader = (IFn)RT.get(opts, DEFAULT);
  			if(defaultReader != null)
  				return defaultReader.invoke(tag, o);
  			else
  				throw new RuntimeException("No reader function for tag " + tag.toString());
  		}
  		else
  			return dataReader.invoke(o);
  	}
  
  }
  }
  