blob: 504cd2a17603fc325a39cacbae154a70c39577c2 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2007, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.jcommons.text.core.input;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.string.CharArrayString;
import org.eclipse.statet.jcommons.string.StringFactory;
import org.eclipse.statet.jcommons.text.core.BasicTextRegion;
import org.eclipse.statet.jcommons.text.core.TextRegion;
/**
* Generic API for input of lexers etc.
*
* <p>Subclasses have to fill and update the buffer.</p>
*
* <p>Naming Convention:</p><ul>
* <li>'index' refers to absolute position in the original source.</li>
* <li>'offset' refers to relative char offset from the current position (0= current char, 1= next char, ...).</li>
* <li>'idx' refers to index in buffer (not public).</li>
* </ul>
**/
@NonNullByDefault
public abstract class TextParserInput {
public static final int EOF= -1;
protected static final int DEFAULT_BUFFER_SIZE= 0x800;
protected static final char[] NO_INPUT= new char[0];
private final CharArrayString tmpCharString= new CharArrayString();
private int startIndex;
private int stopIndex;
private final int defaultBufferLength;
private char[] buffer;
private int currentIdx;
private int endIdx;
private int currentIndex;
protected TextParserInput(final int defaultBufferLength) {
this.defaultBufferLength= defaultBufferLength;
this.buffer= NO_INPUT;
this.stopIndex= Integer.MIN_VALUE;
this.currentIndex= Integer.MIN_VALUE;
}
protected void reset() {
this.startIndex= 0;
this.stopIndex= Integer.MIN_VALUE;
this.currentIndex= Integer.MIN_VALUE;
this.currentIdx= 0;
this.endIdx= 0;
}
public TextParserInput init() {
init(0, Integer.MIN_VALUE);
return this;
}
public TextParserInput init(final int startIndex, int stopIndex) {
final int length= getSourceLength();
if (length > 0) {
final int sourceStartIndex= getSourceStartIndex();
final int sourceStopIndex= sourceStartIndex + length;
if (stopIndex > sourceStopIndex) {
throw new IndexOutOfBoundsException("stopIndex= " + stopIndex); //$NON-NLS-1$
}
else if (stopIndex == Integer.MIN_VALUE) {
stopIndex= sourceStopIndex;
}
if (startIndex < sourceStartIndex | startIndex > sourceStopIndex) {
throw new IndexOutOfBoundsException("startIndex= " + startIndex); //$NON-NLS-1$
}
if (startIndex > stopIndex) {
throw new IllegalArgumentException("startIndex > stopIndex: " +
"startIndex= " + startIndex + ", stopIndex= " + stopIndex); //$NON-NLS-1$
}
}
this.startIndex= startIndex;
this.stopIndex= stopIndex;
this.currentIndex= startIndex;
this.currentIdx= 0;
this.endIdx= 0;
updateBuffer(0);
return this;
}
/**
* Returns the start index of the source text (by default <code>0</code>).
*
* @return the start index
*/
protected int getSourceStartIndex() {
return 0;
}
/**
* Returns the length of the source text.
*
* @return the length or <code>-1</code> for unknown
*/
protected int getSourceLength() {
return -1;
}
/**
* Returns the source text as string, if possible.
*
* @return the underlying text
*/
protected @Nullable String getSourceString() {
return null;
}
/**
* Returns the index of the {@link #getSourceString() source string} in the source text.
*
* @return the index of the source string
*/
protected int getSourceStringIndex() {
return 0;
}
/**
* Returns the start index of this input in the source.
*
* @return the start index
*/
public final int getStartIndex() {
return this.startIndex;
}
/**
* Returns the stop index of this input in the source (exclusive).
*
* @return the stop index
*/
public final int getStopIndex() {
return this.stopIndex;
}
/**
* Returns the current index in the source text.
*
* @return the index in the source
*/
public final int getIndex() {
return this.currentIndex;
}
/**
* Returns the index in the source text for the character at the specified offset.
*
* @return the index in the source
*/
public int getIndex(final int offset) {
return this.currentIndex + offset;
}
/**
* Returns the length in source text of the content from the current index to the specified
* offset (exclusive).
*
* @param offset the end offset in the content
* @return the length in the source
*/
public int getLengthInSource(final int offset) {
return offset;
}
/**
* Returns the region in the source text of the context within the specified offsets.
* @param startOffset the start offset in the content (inclusive)
* @param endOffset the end offset in the content (exclusive)
* @return the region
*/
public TextRegion getRegionInSource(final int startOffset, final int endOffset) {
final int startIndex= getIndex(startOffset);
return new BasicTextRegion(startIndex, getIndex() + getLengthInSource(endOffset));
}
/**
* Returns the character at the specified offset in the content.
*
* @param offset the offset in the content
* @return the content character, or {@link #EOF} if outside of the content
*/
public final int get(final int offset) {
int idx= this.currentIdx + offset;
if (idx >= this.endIdx) {
if (updateBuffer(offset + 1)) {
idx= this.currentIdx + offset;
}
else {
return EOF;
}
}
return this.buffer[idx];
}
public final boolean matches(final int offset, final char c1) {
int idx= this.currentIdx + offset;
if (idx >= this.endIdx) {
if (updateBuffer(offset + 1)) {
idx= this.currentIdx + offset;
}
else {
return false;
}
}
return (this.buffer[idx] == c1);
}
public final boolean matches(final int offset, final char c1, final char c2) {
int idx= this.currentIdx + offset;
if (idx + 1 >= this.endIdx) {
if (updateBuffer(offset + 2)) {
idx= this.currentIdx + offset;
}
else {
return false;
}
}
return (this.buffer[idx] == c1 && this.buffer[++idx] == c2);
}
public final boolean matches(final int offset, final char c1, final char c2, final char c3) {
int idx= this.currentIdx + offset;
if (idx + 2 >= this.endIdx) {
if (updateBuffer(offset + 3)) {
idx= this.currentIdx + offset;
}
else {
return false;
}
}
return (this.buffer[idx] == c1 && this.buffer[++idx] == c2 && this.buffer[++idx] == c3);
}
public final boolean matches(int offset, final char[] sequence) {
final int l= sequence.length;
int idx= this.currentIdx + offset;
if (idx + l > this.endIdx) {
if (updateBuffer(offset + l)) {
idx= this.currentIdx + offset;
}
else {
return false;
}
}
for (offset= 0; offset < l; offset++) {
if (this.buffer[idx++] != sequence[offset]) {
return false;
}
}
return true;
}
public final boolean matchesN(int offset, final char c, final int n) {
int idx= this.currentIdx + offset;
if (idx + n > this.endIdx) {
if (updateBuffer(offset + n)) {
idx= this.currentIdx + offset;
}
else {
return false;
}
}
for (offset= 0; offset < n; offset++) {
if (this.buffer[idx++] != c) {
return false;
}
}
return true;
}
/**
* Forwards the current index to the specified offset.
*
* @param offset the offset in content
*/
public void consume(final int offset) {
setConsume(offset, this.currentIndex + offset);
}
protected final void setConsume(final int offset, final int index) {
this.currentIdx+= offset;
this.currentIndex= index;
}
protected boolean updateBuffer(final int requiredLength) {
final int index= this.currentIndex;
if (index + requiredLength > this.stopIndex) {
return false;
}
char[] buffer= this.buffer;
int recommendLength= this.defaultBufferLength;
if (requiredLength > recommendLength) {
recommendLength= ((requiredLength + 0x410) / 0x400) * 0x400;
}
if (buffer.length < recommendLength) {
buffer= new char[recommendLength];
}
doUpdateBuffer(index, buffer, requiredLength, recommendLength);
return (this.currentIdx + requiredLength <= this.endIdx);
}
protected void doUpdateBuffer(final int index, final char[] buffer,
final int requiredLength, final int recommendLength) {
setBuffer(NO_INPUT, 0, 0);
}
/**
* Copies valid region of current char buffer to specified char buffer.
* The {@link #getIndex() current index} is copied to index 0 in the char buffer.
*
* @param buffer char buffer to copy to.
*
* @return length of valid region copied
*/
protected final int copyBuffer0(final char[] buffer) {
final int l= this.endIdx - this.currentIdx;
if (l > 0) {
System.arraycopy(this.buffer, this.currentIdx, buffer, 0, l);
}
return l;
}
protected final char[] getBuffer() {
return this.buffer;
}
/**
* Sets the char buffer.
*
* @param buffer the char buffer
* @param currentIdx index in char buffer of {@link #getIndex() current index}
* @param length the length of valid region in char buffer
*/
protected final void setBuffer(final char[] buffer, final int currentIdx, final int length) {
this.buffer= buffer;
this.currentIdx= currentIdx;
this.endIdx= checkEndIdx(currentIdx + length);
this.tmpCharString.clear();
}
private int checkEndIdx(int idx) {
if (this.stopIndex >= 0) {
final int stopIdx= this.currentIdx - this.currentIndex + this.stopIndex;
if (stopIdx < idx) {
idx= stopIdx;
}
}
return (idx > 0) ? idx : 0;
}
/**
* Returns index in char buffer of {@link #getIndex() current index}.
*
* @return index in char buffer
*/
protected final int getIndexIdx() {
return this.currentIdx;
}
protected final int getEndIdx() {
return this.endIdx;
}
protected final CharArrayString getTmpString(final int startOffset, final int endOffset) {
this.tmpCharString.set(this.buffer, this.currentIdx + startOffset, endOffset - startOffset);
return this.tmpCharString;
}
/**
* Return the text as string.
*
* @param startOffset the start offset in the content (inclusive)
* @param endOffset the end offset in the content (exclusive)
* @return the text
*/
public final String getString(final int startOffset, final int endOffset) {
return new String(this.buffer, this.currentIdx + startOffset, endOffset - startOffset);
}
/**
* Return the text as string created by using the specified factory.
*
* @param startOffset the start offset in the content (inclusive)
* @param endOffset the end offset in the content (exclusive)
* @return the text
*/
public final @Nullable String getString(final int startOffset, final int endOffset,
final StringFactory factory) {
this.tmpCharString.set(this.buffer, this.currentIdx + startOffset, endOffset - startOffset);
return factory.get(this.tmpCharString);
}
/**
* Appends the text to the specified StringBuilder.
*
* @param startOffset the start offset in the content (inclusive)
* @param endOffset the end offset in the content (exclusive)
*/
public final void appendTo(final int startOffset, final int endOffset,
final StringBuilder dst) {
final int length= endOffset - startOffset;
switch (length) {
case 0:
return;
case 1:
dst.append(this.buffer[this.currentIdx + startOffset]);
return;
default:
dst.append(this.buffer, this.currentIdx + startOffset, length);
return;
}
}
/**
* Inserts the text in the specified StringBuilder.
*
* @param startOffset the start offset in the content (inclusive)
* @param endOffset the end offset in the content (exclusive)
*/
public final void insertInto(final int startOffset, final int endOffset,
final StringBuilder dst, final int dstOffset) {
final int length= endOffset - startOffset;
switch (endOffset - startOffset) {
case 0:
return;
case 1:
dst.insert(dstOffset, this.buffer[this.currentIdx + startOffset]);
return;
default:
dst.insert(dstOffset, this.buffer, this.currentIdx + startOffset, length);
return;
}
}
protected final void checkOffset(final int offset) {
if (this.currentIdx + offset > this.endIdx) {
throw new IndexOutOfBoundsException("offset= " + offset);
}
}
@Override
public String toString() {
final StringBuilder sb= new StringBuilder(getClass().getName());
final String s= getSourceString();
if (s != null) {
final int index= getSourceStringIndex();
sb.append("\n~~~ [").append(index).append(", ").append(index + s.length()).append("] ~~~");
sb.append(getSourceStringIndex());
sb.append("\n~~~\n");
}
return super.toString();
}
}