PsyStringBuffer.java

package coneforest.psylla.core;

import coneforest.psylla.runtime.*;

/**
*	An implementation of {@code stringbuffer}.
*/
@Type("stringbuffer")
public class PsyStringBuffer
	implements
		PsyTextual,
		PsyFormalArray<PsyInteger>
{
	/**
	*	Context action of the {@code stringbuffer} operator.
	*/
	@OperatorType("stringbuffer")
	public static final ContextAction PSY_STRING
		=ContextAction.ofSupplier(PsyStringBuffer::new);

	private final StringBuilder buffer;

	/**
	*	Creates a new empty {@code stringbuffer} object.
	*/
	public PsyStringBuffer()
	{
		this("");
	}

	/**
	*	Creates a new {@code stringbuffer} object whose buffer is initialized from string.
	*
	*	@param string a string.
	*/
	public PsyStringBuffer(final String string)
	{
		this(new StringBuilder(string));
	}

	/**
	*	Creates a new {@code string} object with the supplied buffer.
	*
	*	@param buffer a buffer.
	*/
	public PsyStringBuffer(final StringBuilder buffer)
	{
		this.buffer=buffer;
	}

	/**
	*	{@return the buffer}
	*/
	public StringBuilder getBuffer()
	{
		return buffer;
	}

	@Override
	public PsyStringBuffer psyToStringBuffer()
	{
		return this;
	}

	@Override
	public String stringValue()
	{
		return buffer.toString();
	}

	@Override
	public PsyStringBuffer psyClone()
	{
		return new PsyStringBuffer(stringValue());
	}

	@Override
	public PsyInteger get(final int index)
		throws PsyRangeCheckException
	{
		try
		{
			return PsyInteger.of(buffer.charAt(index));
		}
		catch(final IndexOutOfBoundsException ex)
		{
			throw new PsyRangeCheckException();
		}
	}

	@Override
	public PsyStringBuffer psyGetInterval(final PsyInteger oIndex, final PsyInteger oCount)
		throws PsyRangeCheckException
	{
		final int index=oIndex.intValue();
		final int count=oCount.intValue();
		try
		{
			return new PsyStringBuffer(buffer.substring(index, index+count));
		}
		catch(final IndexOutOfBoundsException ex)
		{
			throw new PsyRangeCheckException();
		}
	}

	@Override
	public void put(final int index, final PsyInteger oCharacter)
		throws PsyRangeCheckException
	{
		try
		{
			buffer.setCharAt(index, (char)oCharacter.intValue());
		}
		catch(final IndexOutOfBoundsException ex)
		{
			throw new PsyRangeCheckException();
		}
	}

	@Override
	public void psyPutInterval(final PsyInteger oIndex, final PsyIterable<? extends PsyInteger> oIterable)
		throws PsyRangeCheckException
	{
		int index=oIndex.intValue();
		if(index<0
				||
				oIterable instanceof PsyLengthy oLengthy
				&& index+oLengthy.length()>=length())
			throw new PsyRangeCheckException();
		for(final var oCharacter: oIterable)
		{
			buffer.setCharAt(index++, (char)oCharacter.intValue());
			if(index==length())
				break;
		}
	}

	@Override
	public void psyAppend(final PsyInteger oCharacter)
		throws PsyLimitCheckException
	{
		if(length()==Integer.MAX_VALUE)
			throw new PsyLimitCheckException();
		buffer.append((char)oCharacter.intValue());
	}

	@Override
	public void insert(final int index, final PsyInteger oCharacter)
		throws PsyRangeCheckException
	{
		try
		{
			buffer.insert(index, (char)oCharacter.intValue());
		}
		catch(final IndexOutOfBoundsException ex)
		{
			throw new PsyRangeCheckException();
		}
	}

	@Override
	public void psyInsertAll(final PsyInteger oIndex, final PsyIterable<? extends PsyInteger> oIterable)
		throws PsyRangeCheckException
	{
		int index=oIndex.intValue();
		try
		{
			if(oIterable instanceof PsyStringBuffer oString)
			{
				// Take care when attempting to insert this object into itself
				buffer.insert(index, this==oIterable? buffer.toString(): oString.buffer);
				return;
			}

			for(final var oCharacter: oIterable)
				// TODO
				buffer.insert(index++, (char)oCharacter.intValue());
		}
		catch(final IndexOutOfBoundsException ex)
		{
			throw new PsyRangeCheckException();
		}
	}

	@Override
	public void delete(final int index)
		throws PsyRangeCheckException
	{
		try
		{
			buffer.deleteCharAt(index);
		}
		catch(final StringIndexOutOfBoundsException ex)
		{
			throw new PsyRangeCheckException();
		}
	}

	@Override
	public PsyInteger extract(final int index)
		throws PsyRangeCheckException
	{
		try
		{
			final PsyInteger oResult=get(index);
			buffer.deleteCharAt(index);
			return oResult;
		}
		catch(final StringIndexOutOfBoundsException ex)
		{
			throw new PsyRangeCheckException();
		}
	}

	@Override
	public PsyStringBuffer psyExtractInterval(final PsyInteger oStart, final PsyInteger oLength)
		throws PsyRangeCheckException
	{
		final var oResult=psyGetInterval(oStart, oLength);
		buffer.replace(oStart.intValue(), oStart.intValue()+oLength.intValue(), "");
		return oResult;
	}

	@Override
	public PsyStringBuffer psySlice(final PsyIterable<PsyInteger> oIndices)
		throws PsyRangeCheckException, PsyLimitCheckException
	{
		final var oValues=new PsyStringBuffer();
		for(final var oIndex: oIndices)
			oValues.psyAppend(psyGet(oIndex));
		return oValues;
	}

	/*
	public PsyStringBuffer psyJoin(final PsyFormalArray<? extends PsyStringBuffer> oArray)
		throws PsyErrorException
	{
		if(oArray.isEmpty())
			return new PsyStringBuffer();
		PsyStringBuffer result=((PsyStringBuffer)oArray.get(0)).psyClone();
		for(int i=1; i<oArray.length(); i++)
		{
			result.psyAppendAll(this);
			result.psyAppendAll(oArray.get(i));
		}
		return result;
	}
	*/

	/*
	public PsyDict psySearch(PsyRegExp regexp)
	{
		PsyDict result=null;
		Pattern pattern=regexp.getPattern();
		Matcher matcher=pattern.matcher(buffer);
		matcher.find();
		return result;
	}
	*/

	/*
	public PsyMatcher PsyMatches(PsyRegExp regexp)
	{
	}
	*/

	@Override
	public void psySetLength(final PsyInteger oLength)
		throws PsyLimitCheckException, PsyRangeCheckException
	{
		final long length=oLength.longValue();
		if(length>Integer.MAX_VALUE)
			throw new PsyLimitCheckException();
		try
		{
			buffer.setLength((int)length);
		}
		catch(final IndexOutOfBoundsException ex)
		{
			throw new PsyRangeCheckException();
		}
	}

	@Override
	public void psyClear()
	{
		buffer.delete(0, buffer.length());
	}

	@Override
	public PsyStringBuffer psyReverse()
	{
		return new PsyStringBuffer((new StringBuilder(buffer)).reverse());
	}

	@Override
	public PsyStringBuffer psyUpperCase()
	{
		return new PsyStringBuffer(buffer.toString().toUpperCase());
	}

	@Override
	public PsyStringBuffer psyLowerCase()
	{
		return new PsyStringBuffer(buffer.toString().toLowerCase());
	}

	@Override
	public int length()
	{
		return buffer.length();
	}

	@Override
	public boolean equals(final Object object)
	{
		return getClass().isInstance(object)
				&& psyEq((PsyStringBuffer)object).booleanValue();
	}

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

	@Override
	public String toSyntaxString()
	{
		final var sb=new StringBuilder();
		for(int i=0; i<buffer.length(); i++)
		{
			final char c=buffer.charAt(i);
			sb.append(switch(c)
				{
					case '\u0000'->"\\0";
					case '\u0007'->"\\a";
					case '\n'->"\\n";
					case '\r'->"\\r";
					case '\t'->"\\t";
					case '\u000B'->"\\v";
					case '\f'->"\\f";
					case '\u001B'->"\\e";
					case '\"'->"\\\"";
					case '\\'->"\\\\";
					default->c;
				});
		}
		return "\""+sb.toString()+"\"";
	}

	/**
	*	{@return the {@code string} object obtained as a result of parsing the literal token image}
	*
	*	@param image the literal token image.
	*	@throws PsySyntaxErrorException when the literal image is syntactically incorrect.
	*/
	public static PsyStringBuffer parseLiteral(final String image)
		throws PsySyntaxErrorException
	{
		final var sb=new StringBuilder();
		for(int i=1; i<image.length()-1; i++)
		{
			final var c=image.charAt(i);
			switch(c)
			{
				case '\\'->
					{
						i++;
						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();
									}
								}
						}
					}
				default->sb.append(c);
			}
		}
		return new PsyStringBuffer(sb);
	}
}