PsyString.java

package coneforest.psylla.core;

import coneforest.psylla.runtime.*;

/**
*	The representation of {@code string}, an immutable string.
*/
@Type("string")
public class PsyString
	implements PsyTextual, PsyValue, PsyConcatenable<PsyString>
{
	protected final String string;

	/**
	*	Instantiate a new {@code string} object from the given value.
	*
	*	@param cs a given value.
	*/
	public PsyString(final CharSequence cs)
	{
		string=cs.toString();
	}

	/**
	*	Instantiate a new {@code string} object from the value given as {@code textual} object.
	*
	*	@param oTextual a {@code textual} object.
	*/
	public PsyString(final PsyTextual oTextual)
	{
		this(oTextual.stringValue());
	}

	/**
	*	{@return a string value of this object}
	*/
	@Override
	public String stringValue()
	{
		return string;
	}

	@Override
	public PsyStringBuffer psyToStringBuffer()
	{
		return new PsyStringBuffer(string);
	}

	/**
	*	@return an {@code integer} length (in characters) of this {@code string}.
	*/
	@Override
	public PsyInteger psyLength()
	{
		return PsyInteger.of(string.length());
	}

	@Override
	public PsyString psyConcat(final PsyString oString)
	{
		return new PsyString(string+oString.string);
	}

	/**
	*	Converts all of the characters in this object to upper case according to default locale and
	*	returns a new {@code string} object representing the result of the conversion.
	*
	*	@return a {@code string} result of upper-casing.
	*/
	@Override
	public PsyString psyUpperCase()
	{
		return new PsyString(string.toUpperCase());
	}

	/**
	*	Converts all of the characters in this object to lower case according to default locale and
	*	returns a new {@code string} object representing the result of the conversion.
	*
	*	@return a {@code string} result of lower-casing.
	*/
	@Override
	public PsyString psyLowerCase()
	{
		return new PsyString(string.toLowerCase());
	}

	/**
	*	{@return a syntactic representation of this object’s value} Returns a value string prepended
	*	with {@code /}.
	*/
	@Override
	public String toSyntaxString()
	{
		if(string.length()==0)
			return "''";
		if(string.length()==1)
		{
			final char c=string.charAt(0);
			return (c>='A' && c<='Z'
					|| c>='a' && c<='z'
					|| c=='['
					|| c==']'
					|| c=='<'
					|| c=='>'
					|| c=='('
					|| c==')'
					|| c=='?'
					|| c=='=')? "/"+string: "'"+string+"'";
		}
		boolean slashed=true;
		if(string.charAt(0)>='0' && string.charAt(0)<='9')
			slashed=false;
		else
		{
			for(int i=0; i<string.length(); i++)
			{
				final char c=string.charAt(i);
				slashed=c>='0' && c<='9'
						|| c>='A' && c<='Z'
						|| c>='a' && c<='z'
						|| c=='_'
						|| c=='.'
						|| c=='-'
						|| c=='+'
						|| c=='='
						|| c=='$';
				if(!slashed)
					break;
			}
		}
		return slashed? "/"+string: "'"+string+"'";
	}

	/**
	*	{@return a {@code boolean} object indicating whether some other object is “equal to” this
	*	one} Return value is {@code true} if and only if other object has {@code string} type and
	*	its value is equal to this one’s.
	*/
	@Override
	public boolean equals(final Object object)
	{
		return object instanceof PsyString
				&& psyEq((PsyString)object).booleanValue();
	}

	@Override
	public int hashCode()
	{
		return string.hashCode();
	}

	public static PsyString parseLiteral(final String image)
		throws PsySyntaxErrorException
	{
		if(image.charAt(0)=='/')
			return new PsyString(image.substring(1).intern());
		final var sb=new StringBuilder();
		for(int i=1; i<image.length()-1; i++)
		{
			final var c=image.charAt(i);
			switch(c)
			{
				case '\\':
					switch(image.charAt(++i))
					{
						case '0'->sb.append('\u0000');
						case 'a'->sb.append('\u0007');
						case 'n'->sb.append('\n');
						case 'r'->sb.append('\r');
						case 't'->sb.append('\t');
						case 'v'->sb.append('\u000B');
						case 'f'->sb.append('\f');
						case 'e'->sb.append('\u001B');
						case '\''->sb.append('\'');
						case '\\'->sb.append('\\');
						case '\n'->{}
						case 'u'->
							{
								sb.append(Character.toChars(Integer.valueOf(image.substring(i+1, i+5), 16)));
								i+=4;
							}
						case 'c'->
							{
								final var ch=image.charAt(++i);
								sb.append(Character.toChars(ch+(ch<64? 64: -64)));
							}
						case 'x'->
							{
								try
								{
									final var j=image.indexOf('}', i+2);
									sb.append(Character.toChars(Integer.valueOf(image.substring(i+2, j), 16)));
									i=j;
								}
								catch(final IllegalArgumentException ex)
								{
									throw new PsySyntaxErrorException();
								}
							}
						case 'o'->
							{
								try
								{
									final var j=image.indexOf('}', i+2);
									sb.append(Character.toChars(Integer.valueOf(image.substring(i+2, j), 8)));
									i=j;
								}
								catch(final IllegalArgumentException ex)
								{
									throw new PsySyntaxErrorException();
								}
							}
						case 'N'->
							{
								try
								{
									final var j=image.indexOf('}', i+2);
									final var cp=Character.codePointOf(image.substring(i+2, j));
									// TODO
									sb.append((char)cp);
									i=j;
								}
								catch(final IllegalArgumentException ex)
								{
									throw new PsySyntaxErrorException();
								}
							}
					}
					break;
				default:
					sb.append(c);
					break;
			}
		}
		return new PsyString(sb.toString().intern());
	}
}