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
- Always call hasMore() before peek()
- Restructure the parsing loop to terminate when !hasMore() instead of catching the exception
- 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
- Gate every peek() behind hasMore()
- Prefer match(needle, consume) for conditional lookahead
- Terminate parse loops on !hasMore() rather than on exceptions
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
- Start must be <= end.
- Start outside of string.
- End outside of string.
- Start must be >= 0.
- Start must be <= end.
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)