view src/luan/modules/parsers/LuanToString.java @ 1802:ca98dee04e08 default tip

add Parsers.json_null
author Franklin Schmidt <fschmidt@gmail.com>
date Sun, 21 Apr 2024 21:25:15 -0600
parents f8f5c51f5b36
children
line wrap: on
line source

package luan.modules.parsers;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
import luan.Luan;
import luan.LuanTable;
import luan.LuanFunction;
import luan.LuanException;
import luan.LuanRuntimeException;


public final class LuanToString {
	private static final Set<String> settingsKeys = new HashSet<String>();
	static {
		Collections.addAll(settingsKeys
			,"strict"
			,"number_types"
			,"compressed"
			,"long_strings"
			,"inline"
			,"no_name_keys"
		);
	}

	private static void checkOptions(LuanTable options) throws LuanException {
		for( Map.Entry entry : options.rawIterable() ) {
			if( !settingsKeys.contains(entry.getKey()) )
				throw new LuanException("invalid option: "+entry.getKey());
			if( !(entry.getValue() instanceof Boolean) )
				throw new LuanException("options values must be boolean");
		}
	}

	public static class Settings implements Cloneable {
		public boolean strict = false;
		public boolean numberTypes = false;
		public boolean compressed = false;
		public boolean longStrings = false;
		public boolean inline = false;
		public boolean noNameKeys = false;

		void applyOptions(LuanTable options) throws LuanException {
			checkOptions(options);
			Boolean b;
			b = (Boolean)options.rawGet("strict");
			if( b != null )
				strict = b;
			b = (Boolean)options.rawGet("number_types");
			if( b != null )
				numberTypes = b;
			b = (Boolean)options.rawGet("compressed");
			if( b != null )
				compressed = b;
			b = (Boolean)options.rawGet("long_strings");
			if( b != null )
				longStrings = b;
			b = (Boolean)options.rawGet("inline");
			if( b != null )
				inline = b;
			b = (Boolean)options.rawGet("no_name_keys");
			if( b != null )
				noNameKeys = b;
		}

		public Settings cloneSettings() {
			try {
				return (Settings)clone();
			} catch(CloneNotSupportedException e) {
				throw new RuntimeException(e);
			}
		}
	}
	private static final Settings keySettings = new Settings();

	public final Settings settingsInit = new Settings();
	private final Luan luan;
	private final LuanFunction fnOptions;
	private final LuanTable stack = new LuanTable();
	private final Set<LuanTable> seen = new HashSet<LuanTable>();

	public LuanToString() {
		this.luan = null;
		this.fnOptions = null;
	}

	public LuanToString(Luan luan) throws LuanException {
		this.luan = luan;
		this.fnOptions = null;
	}

	public LuanToString(Luan luan,LuanTable options) throws LuanException {
		this.luan = luan;
		this.fnOptions = null;
		settingsInit.applyOptions(options);
	}

	public LuanToString(Luan luan,LuanFunction fnOptions) throws LuanException {
		this.luan = luan;
		this.fnOptions = fnOptions;
		LuanTable options = getOptions();
		if( options != null )
			settingsInit.applyOptions(options);
	}

	private LuanTable getOptions() throws LuanException {
		if( fnOptions == null )
			return null;
		Object rtn = fnOptions.call(luan,stack);
		if( !(rtn==null || rtn instanceof LuanTable) )
			throw new LuanException("options-function must return table or nil");
		return (LuanTable)rtn;
	}

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

	private void objectToString(Object obj,StringBuilder sb,int indented,Settings settings) throws LuanException {
		if( obj == null ) {
			sb.append( "nil" );
			return;
		}
		if( obj instanceof Boolean ) {
			sb.append( obj );
			return;
		}
		if( obj instanceof Number ) {
			toString((Number)obj,sb,settings);
			return;
		}
		if( obj instanceof String ) {
			toString((String)obj,sb,settings);
			return;
		}
		if( obj instanceof LuanTable ) {
			toString((LuanTable)obj,sb,indented,settings);
			return;
		}
		if( settings.strict )
			throw new LuanException("can't handle type "+Luan.type(obj));
		sb.append( '<' );
		sb.append( obj );
		sb.append( '>' );
	}

	private void toString(LuanTable tbl,StringBuilder sb,int indented,Settings settings) throws LuanException {
		if( !seen.add(tbl) ) {
			if( settings.strict )
				throw new LuanException("loop in "+tbl.rawToString());
			sb.append("<loop>");
			return;
		}
		if( tbl.getMetatable()!=null ) {
			if( settings.strict )
				throw new LuanException("can't handle metatables when strict");
			if( luan==null )
				throw new LuanException("can't handle metatables when luan isn't set");
			LuanFunction pairs = luan.getHandlerFunction("__pairs",tbl);
			if( pairs != null ) {
				sb.append( '{' );
				boolean first = true;
				for( Object obj : tbl.iterable(luan) ) {
					Map.Entry entry = (Map.Entry)obj;
					if( settings.compressed ) {
						if( first )
							first = false;
						else
							sb.append( ',' );
					} else if( settings.inline ) {
						if( first ) {
							first = false;
							sb.append( ' ' );
						} else
							sb.append( ", " );
					} else {
						first = false;
						indent(sb,indented+1);
					}
					toString(entry,sb,indented+1,settings);
				}
				if( !first ) {
					if( settings.compressed ) {
					} else if( settings.inline ) {
						sb.append( ' ' );
					} else {
						indent(sb,indented);
					}
				}
				sb.append( '}' );
				return;
			}
		}
		List list = tbl.asList();
		Map map = tbl.rawMap();
		sb.append( '{' );
		boolean first = true;
		for( Object obj : list ) {
			if( settings.compressed ) {
				if( first )
					first = false;
				else
					sb.append( ',' );
			} else if( settings.inline ) {
				if( first ) {
					first = false;
					sb.append( ' ' );
				} else
					sb.append( ", " );
			} else {
				indent(sb,indented+1);
			}
			objectToString(obj,sb,indented+1,settings);
		}
		for( Object obj : map.entrySet() ) {
			Map.Entry entry = (Map.Entry)obj;
			if( settings.compressed ) {
				if( first )
					first = false;
				else
					sb.append( ',' );
			} else if( settings.inline ) {
				if( first ) {
					first = false;
					sb.append( ' ' );
				} else
					sb.append( ", " );
			} else {
				indent(sb,indented+1);
			}
			toString(entry,sb,indented+1,settings);
		}
		if( !list.isEmpty() || !map.isEmpty() ) {
			if( settings.compressed ) {
			} else if( settings.inline ) {
				sb.append( ' ' );
			} else {
				indent(sb,indented);
			}
		}
		sb.append( '}' );
		return;
	}

	private void toString(Map.Entry entry,StringBuilder sb,int indented,Settings settings)
		throws LuanException
	{
		Object key = entry.getKey();
		if( key instanceof String && !settings.noNameKeys && ((String)key).matches("[a-zA-Z_][a-zA-Z_0-9]*") ) {
			sb.append( (String)key );
		} else {
			sb.append( '[' );
			objectToString( key, sb, indented, keySettings );
			sb.append( ']' );
		}
		sb.append( settings.compressed ? "=" : " = " );
		stack.rawAdd(key);  // push
		LuanTable options = getOptions();
		if( options != null ) {
			settings = settings.cloneSettings();
			settings.applyOptions(options);
		}
		objectToString( entry.getValue(), sb, indented, settings );
		stack.removeFromList(stack.rawLength());  // pop
	}

	private void indent(StringBuilder sb,int indented) {
		sb.append( '\n' );
		for( int i=0; i<indented; i++ ) {
			sb.append( '\t' );
		}
	}

	private void toString(Number n,StringBuilder sb,Settings settings) throws LuanException {
		if( settings.numberTypes ) {
			sb.append( n.getClass().getSimpleName().toLowerCase() );
			sb.append( '(' );
		}
		sb.append( Luan.toString(n) );
		if( settings.numberTypes )
			sb.append( ')' );
	}

	public static void addNumberTypes(Luan luan,LuanTable env) {
		try {
			LuanTable module = (LuanTable)luan.require("luan:Number.luan");
			env.rawPut( "double", module.fn(luan,"double") );
			env.rawPut( "float", module.fn(luan,"float") );
			env.rawPut( "integer", module.fn(luan,"integer") );
			env.rawPut( "long", module.fn(luan,"long") );
		} catch(LuanException e) {
			throw new LuanRuntimeException(e);
		}
	}

	private void toString(String s,StringBuilder sb,Settings settings) {
		if( settings.longStrings ) {
			StringBuilder start = new StringBuilder("[[");
			if( s.indexOf('\n') != -1 )
				start.append('\n');
			StringBuilder end = new StringBuilder("]]");
			while( s.contains(end) ) {
				start.insert(1,'=');
				end.insert(1,'=');
			}
			sb.append(start);
			sb.append(s);
			sb.append(end);
			return;
		}
		sb.append( '"' );
		sb.append( Luan.stringEncode(s) );
		sb.append( '"' );
	}

}