view core/src/luan/modules/parsers/Json.java @ 758:c29d11d675fd

added Json.toString() and rpc now sends tables as json
author Franklin Schmidt <fschmidt@gmail.com>
date Tue, 19 Jul 2016 00:57:37 -0600
parents bdd766df1c17
children ae612dfc57cb
line wrap: on
line source

package luan.modules.parsers;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Iterator;
import luan.LuanTable;
import luan.LuanException;


public final class Json {

	public static Object parse(String text) throws ParseException {
		return new Json(text).parse();
	}

	private final Parser parser;

	private Json(String text) {
		this.parser = new Parser(text);
	}

	private ParseException exception(String msg) {
		return new ParseException(parser,msg);
	}

	private Object parse() throws ParseException {
		spaces();
		Object value = value();
		spaces();
		if( !parser.endOfInput() )
			throw exception("unexpected text");
		return value;
	}

	private Object value() throws ParseException {
		if( parser.match("null") )
			return null;
		if( parser.match("true") )
			return Boolean.TRUE;
		if( parser.match("false") )
			return Boolean.FALSE;
		String s = string();
		if( s != null )
			return s;
		Number n = number();
		if( n != null )
			return n;
		LuanTable a = array();
		if( a != null )
			return a;
		LuanTable o = object();
		if( o != null )
			return o;
		throw exception("invalid value");
	}

	private String string() throws ParseException {
		parser.begin();
		if( !parser.match('"') )
			return parser.failure(null);
		StringBuilder sb = new StringBuilder();
		while( parser.anyChar() ) {
			char c = parser.lastChar();
			switch(c) {
			case '"':
				return parser.success(sb.toString());
			case '\\':
				if( parser.anyChar() ) {
					c = parser.lastChar();
					switch(c) {
					case '"':
					case '\\':
					case '/':
						sb.append(c);
						continue;
					case 'b':
						sb.append('\b');
						continue;
					case 'f':
						sb.append('\f');
						continue;
					case 'n':
						sb.append('\n');
						continue;
					case 'r':
						sb.append('\r');
						continue;
					case 't':
						sb.append('\t');
						continue;
					case 'u':
						int n = 0;
						for( int i=0; i<4; i++ ) {
							int d;
							if( parser.inCharRange('0','9') ) {
								d = parser.lastChar() - '0';
							} else if( parser.inCharRange('a','f') ) {
								d = parser.lastChar() - 'a' + 10;
							} else if( parser.inCharRange('A','F') ) {
								d = parser.lastChar() - 'A' + 10;
							} else {
								throw exception("invalid hex digit");
							}
							n = 16*n + d;
						}
						sb.append((char)n);
						continue;
					}
				}
				throw exception("invalid escape char");
			default:
				sb.append(c);
			}
		}
		parser.failure();
		throw exception("unclosed string");
	}

	private Number number() {
		int start = parser.begin();
		parser.match('-');
		if( !parser.match('0') ) {
			if( !parser.inCharRange('1','9') )
				return parser.failure(null);
			while( parser.inCharRange('0','9') );
		}
		if( parser.match('.') ) {
			if( !parser.inCharRange('0','9') )
				return parser.failure(null);
			while( parser.inCharRange('0','9') );
		}
		if( parser.anyOf("eE") ) {
			parser.anyOf("+-");
			if( !parser.inCharRange('0','9') )
				return parser.failure(null);
			while( parser.inCharRange('0','9') );
		}
		String s = parser.textFrom(start);
		return parser.success(Double.valueOf(s));
	}

	private LuanTable array() throws ParseException {
		parser.begin();
		if( !parser.match('[') )
			return parser.failure(null);
		spaces();
		if( parser.match(']') )
			return parser.success(new LuanTable());
		List list = new ArrayList();
		list.add( value() );
		spaces();
		while( parser.match(',') ) {
			spaces();
			list.add( value() );
			spaces();
		}
		if( parser.match(']') )
			return parser.success(new LuanTable(list));
		if( parser.endOfInput() ) {
			parser.failure();
			throw exception("unclosed array");
		}
		throw exception("unexpected text in array");
	}

	private LuanTable object() throws ParseException {
		parser.begin();
		if( !parser.match('{') )
			return parser.failure(null);
		spaces();
		if( parser.match('}') )
			return parser.success(new LuanTable());
		Map map = new LinkedHashMap();
		addEntry(map);
		while( parser.match(',') ) {
			spaces();
			addEntry(map);
		}
		if( parser.match('}') )
			return parser.success(new LuanTable(map));
		if( parser.endOfInput() ) {
			parser.failure();
			throw exception("unclosed object");
		}
		throw exception("unexpected text in object");
	}

	private void addEntry(Map map) throws ParseException {
		String key = string();
		if( key==null )
			throw exception("invalid object key");
		spaces();
		if( !parser.match(':') )
			throw exception("':' expected");
		spaces();
		Object value = value();
		spaces();
		map.put(key,value);
	}

	private void spaces() {
		while( parser.anyOf(" \t\r\n") );
	}









	public static String toString(Object obj) throws LuanException {
		StringBuilder sb = new StringBuilder();
		toString(obj,sb);
		return sb.toString();
	}

	private static void toString(Object obj,StringBuilder sb) throws LuanException {
		if( obj == null || obj instanceof Boolean || obj instanceof Number ) {
			sb.append(obj);
			return;
		}
		if( obj instanceof String ) {
			toString((String)obj,sb);
			return;
		}
		if( obj instanceof LuanTable ) {
			toString((LuanTable)obj,sb);
			return;
		}
		throw new LuanException("can't handle type "+obj.getClass().getName());
	}

	private static void toString(final String s,StringBuilder sb) {
		sb.append('"');
		for( int i=0; i<s.length(); i++ ) {
			char c = s.charAt(i);
			switch(c) {
			case '"':
				sb.append("\\\"");
				break;
			case '\\':
				sb.append("\\\\");
				break;
			case '\b':
				sb.append("\\b");
				break;
			case '\f':
				sb.append("\\f");
				break;
			case '\n':
				sb.append("\\n");
				break;
			case '\r':
				sb.append("\\r");
				break;
			case '\t':
				sb.append("\\t");
				break;
			default:
				sb.append(c);
			}
		}
		sb.append('"');
	}

	private static void toString(LuanTable t,StringBuilder sb) throws LuanException {
		if( t.isList() ) {
			final List list = t.asList();
			if( list.isEmpty() ) {
				sb.append("{}");
				return;
			}
			sb.append('[');
			toString(list.get(0),sb);
			for( int i=1; i<list.size(); i++ ) {
				sb.append(',');
				toString(list.get(i),sb);
			}
			sb.append(']');
			return;
		}
		sb.append('{');
		Iterator<Map.Entry<Object,Object>> i = t.rawIterator();
		toString(i.next(),sb);
		while( i.hasNext() ) {
			sb.append(',');
			toString(i.next(),sb);
		}
		sb.append('}');
	}

	private static void toString(Map.Entry<Object,Object> entry,StringBuilder sb) throws LuanException {
		Object key = entry.getKey();
		if( !(key instanceof String) )
			throw new LuanException("table keys must be strings");
		toString((String)key,sb);
		sb.append(':');
		toString(entry.getValue(),sb);
	}

}