package freeboogie.ast.gen;
import java.io.IOException;
import java.util.logging.Logger;
/**
* Provides a convenient interface for reading a stream of {@code T}s.
*
* Elements are read one by one using the method {@code next}. The current
* position in the stream can be {@code mark}ed. The method {@code rewind}
* goes to the closest previous marked position or to the beginning of
* the stream. The elements from the beginning of the stream to the current
* position can be {@code eat}en. In general, elements should be eaten
* whenever you know you will not need to rewind and read them again,
* so that memory is not wasted.
*
* The user of this class should implement the method {@code read}
* which returns element in order or {@code null} if the end-of-file
* is reached.
*
* @author rgrig
* @author reviewed by TODO
* @param <T> the type of the stream elements
*/
public abstract class PeekStream<T> {
/*
* We keep a |buffer| of elements that have been used and of locations.
* An element is paired with the location of the previous element.
* The field |nextElement| points into the |buffer| to a node that
* contains the element to be returned by the subsequent call to |next|
* and the location returned by a call to |getLoc|.
*
* Some of the nodes in |buffer| are marked by being added to the stack
* |markedStack|. This stack is used to |rewind|.
*
* NOTE Even if the time and space complexity of this implementation is
* reasonable it should be noted that the constant factors (especially for
* space) are quite big.
*/
private class Node<S> {
/** data */ public S data;
/** next */ public Node<S> next;
/**
* {@code next} is set to {@code null}.
* @param data the data to put in the node
*/
public Node(S data) {
this.data = data;
this.next = null;
}
/**
* @param data the data to put in the note
* @param next the next node
*/
public Node(S data, Node<S> next) {
this.data = data;
this.next = next;
}
}
private class ElLocPair {
/** Element */
public T elem;
/** Location of _previous_ element. */
public Location<T> loc;
/**
* Initialization
* @param elem the element
* @param loc the location of the previous element
*/
public ElLocPair(T elem, Location<T> loc) {
this.elem = elem; this.loc = loc;
}
}
private static final Logger log = Logger.getLogger("freeboogie.ast.gen");
private Node<ElLocPair> buffer;
private Node<ElLocPair> nextElement;
private Node<Node<ElLocPair>> markedStack;
private Location<T> initLoc;
/**
* Creates a {@code PeekStream} and sets a location tracking object.
* @param loc the location tracking object
*/
public PeekStream(Location<T> loc) {
initLoc = loc;
markedStack = null;
// The constructor cannnot call read() because the subclass is not init
buffer = nextElement = null;
}
/**
* Returns the next element in the stream, or {@code null} if beyond its end.
* @return the next element in the stream
* @throws IOException if thrown by underlying stream
*/
public T next() throws IOException {
if (buffer == null) {
ElLocPair x = new ElLocPair(read(), initLoc);
buffer = nextElement = new Node<ElLocPair>(x);
}
if (nextElement.data.elem == null) return null;
T result = nextElement.data.elem;
if (nextElement.next == null) {
Location<T> l = nextElement.data.loc.advance(result);
ElLocPair x = new ElLocPair(read(), l);
nextElement.next = new Node<ElLocPair>(x);
}
nextElement = nextElement.next;
return result;
}
/**
* Marks the current position (if not already marked).
* @see freeboogie.ast.gen.PeekStream#rewind()
*/
public void mark() {
if (markedStack != null && markedStack.data == nextElement) return;
markedStack = new Node<Node<ElLocPair>>(nextElement, markedStack);
}
/**
* Go back to the previously marked element or to the beginning of
* the (not-yet-eaten) stream if no element is marked.
* @see freeboogie.ast.gen.PeekStream#mark()
*/
public void rewind() {
if (markedStack == null) nextElement = buffer;
else {
nextElement = markedStack.data;
markedStack = markedStack.next;
}
}
/**
* Eats the elements from the beginning up to, and including, the
* last element read by {@code next}.
*/
public void eat() {
log.entering("PeekStream", "eat");
while (buffer != nextElement) {
if (markedStack != null && markedStack.data == buffer)
markedStack = markedStack.next;
buffer = buffer.next;
}
}
/**
* Returns the location of the current element (that is, the one before
* what {@code next} would return). If {@code next} was not called then
* return the location object given to the constructor.
* @return the location in the stream
*/
public Location<T> getLoc() {
if (buffer == null) return initLoc;
return nextElement.data.loc;
}
/**
* Returns a the name of this {@code PeekStream}. It is meant to be
* used for reporting errors, debugging, and so on.
*
* @return the name of this {@code PeekStream}
*/
public String getName() {
return "";
}
/**
* Reads one element from the underlying stream. Must return {@code null}
* if the end was reached.
* @return the next element in the underlying stream
* @throws IOException if thrown by the underlying stream
*/
protected abstract T read() throws IOException;
}