ssssssss-team/spider-flow · error · RuntimeException

No more characters in stream.

Error message

No more characters in stream.

What it means

CharacterStream.peek() returns the next character without advancing (despite the post-increment in the shown code, the Javadoc contract is non-advancing lookahead). If the stream is exhausted (index >= end), it throws RuntimeException("No more characters in stream.").

Solutions

  1. Always call hasMore() before peek()
  2. Restructure the parsing loop to terminate when !hasMore() instead of catching the exception
  3. Use match(needle, consume) for lookahead tests, which handles the end of stream safely

Example fix

// before
char c = stream.peek();
// after
if (stream.hasMore()) {
    char c = stream.peek();
} else {
    // end of input: handle gracefully
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!stream.hasMore()) { /* handle end of input */ }

Type guard

boolean canPeek(CharacterStream s) { return s.hasMore(); }

Try / catch

try {
    char c = stream.peek();
} catch (RuntimeException e) {
    if ("No more characters in stream.".equals(e.getMessage())) {
        // handle EOF gracefully
    } else throw e;
}

Prevention

When it happens

Trigger: Calling peek() when hasMore() is false — i.e. after consuming all characters of the expression source.

Common situations: Tokenizers peeking at the next character inside loops without checking hasMore() first, especially at end-of-input for expressions ending exactly at a token boundary.

Related errors


AI-assisted analysis of ssssssss-team/spider-flow@c799cca99c (2026-09-08). Data as JSON: /api/errors/d764a9d6525b8191. Report an issue: GitHub.

Appendix: source

Thrown at spider-flow-core/src/main/java/org/spiderflow/core/expression/parsing/CharacterStream.java:37

	public CharacterStream (String source, int start, int end) {
		if (start > end) throw new IllegalArgumentException("Start must be <= end.");
		if (start < 0) throw new IndexOutOfBoundsException("Start must be >= 0.");
		if (start > Math.max(0, source.length() - 1)) throw new IndexOutOfBoundsException("Start outside of string.");
		if (end > source.length()) throw new IndexOutOfBoundsException("End outside of string.");

		this.source = source;
		this.index = start;
		this.end = end;
	}

	/** Returns whether there are more characters in the stream **/
	public boolean hasMore () {
		return index < end;
	}

	/** Returns the next character without advancing the stream **/
	public char peek () {
		if (!hasMore()) throw new RuntimeException("No more characters in stream.");
		return source.charAt(index++);
	}

	/** Returns the next character and advance the stream **/
	public char consume () {
		if (!hasMore()) throw new RuntimeException("No more characters in stream.");
		return source.charAt(index++);
	}

	/** Matches the given needle with the next characters. Returns true if the needle is matched, false otherwise. If there's a
	 * match and consume is true, the stream is advanced by the needle's length. */
	public boolean match (String needle, boolean consume) {
		int needleLength = needle.length();
		if(needleLength + index >end){
			return false;
		}
		for (int i = 0, j = index; i < needleLength; i++, j++) {
			if (index >= end) return false;

View on GitHub (pinned to c799cca99c)