Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented first version of the asReader interface on IString #273

Merged
merged 14 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/main/java/io/usethesource/vallang/IString.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package io.usethesource.vallang;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.PrimitiveIterator.OfInt;

Expand Down Expand Up @@ -99,6 +100,13 @@ default int getMatchFingerprint() {
*/
public void write(Writer w) throws IOException;

/**
* Generates a reader that can be used to stream the contents of the string
* Note, this will generate java characters, users are responsible for dealing with surrogate-pairs.
* See {@link #iterator()} for a more unicode compatible approach to iterate over the characters of an IString.
*/
public Reader asReader();

/**
* Build an iterator which generates the Unicode UTF-32 codepoints of the IString one-by-one.
* @see Character for more information on Unicode UTF-32 codepoints.
Expand Down
203 changes: 187 additions & 16 deletions src/main/java/io/usethesource/vallang/impl/primitive/StringValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
package io.usethesource.vallang.impl.primitive;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.management.ManagementFactory;
Expand All @@ -25,15 +27,15 @@
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.PrimitiveIterator;
import java.util.PrimitiveIterator.OfInt;

import java.util.function.Function;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

import io.usethesource.vallang.IString;
import io.usethesource.vallang.IValueFactory;
import io.usethesource.vallang.impl.persistent.ValueFactory;
Expand Down Expand Up @@ -279,6 +281,16 @@ public boolean isNewlineTerminated() {
public IString concat(IString other) {
return other;
}

@Override
public Reader asReader() {
return Reader.nullReader();
}

@Override
public Iterator<CharBuffer> iterateParts() {
return Collections.emptyIterator();
}
}

private static class FullUnicodeString extends AbstractString {
Expand Down Expand Up @@ -501,6 +513,11 @@ public void write(Writer w) throws IOException {
w.write(value);
}

@Override
public Reader asReader() {
return new StringReader(value);
}

@Override
public void indentedWrite(Writer w, Deque<IString> whitespace, boolean indentFirstLine) throws IOException {
if (value.isEmpty()) {
Expand Down Expand Up @@ -579,6 +596,11 @@ public int nextInt() {
}
};
}

@Override
public Iterator<CharBuffer> iterateParts() {
return Collections.singleton(CharBuffer.wrap(value)).iterator();
}
}

/**
Expand Down Expand Up @@ -633,6 +655,16 @@ public int nextInt() {
}
};
}

@Override
public Reader asReader() {
return new StringReader(value);
}

@Override
public Iterator<CharBuffer> iterateParts() {
return Collections.singleton(CharBuffer.wrap(value)).iterator();
}
}

/**
Expand Down Expand Up @@ -808,6 +840,8 @@ default AbstractString rotateRightLeft() {
default AbstractString rotateLeftRight() {
return (AbstractString) this;
}

Iterator<CharBuffer> iterateParts();
}

private abstract static class AbstractString implements IString, IStringTreeNode, IIndentableString {
Expand Down Expand Up @@ -963,6 +997,47 @@ protected final int hashCode(int prefixCode) {
}

abstract boolean hasNonBMPCodePoints();

public abstract Iterator<CharBuffer> iterateParts();

@Override
public Reader asReader() {
return new Reader() {
final Iterator<CharBuffer> parts = iterateParts();
CharBuffer currentBuffer = CharBuffer.allocate(0);

private CharBuffer getBuffer() {
var actualBuffer = currentBuffer;
while (!actualBuffer.hasRemaining()) {
if (!parts.hasNext()) {
return actualBuffer;
}
actualBuffer = currentBuffer = parts.next();
}
return actualBuffer;
}

@Override
public int read(char[] cbuf, int off, int len) throws IOException {
if (off < 0 || len < 0 || len > cbuf.length + off) {
throw new IndexOutOfBoundsException();
}
var target = CharBuffer.wrap(cbuf, off, len);
while (target.hasRemaining()) {
var actualBuffer = getBuffer();
if (!actualBuffer.hasRemaining()) {
break;
}
actualBuffer.read(target);
}
return target.position() == off ? -1 : (len - target.remaining());
}

@Override
public void close() throws IOException {
}
};
}
}

private static class LazyConcatString extends AbstractString {
Expand Down Expand Up @@ -1146,6 +1221,8 @@ public void write(Writer w) throws IOException {
right.write(w);
}



@Override
public void indentedWrite(Writer w, Deque<IString> whitespace, boolean indentFirstLine) throws IOException {
left.indentedWrite(w, whitespace, indentFirstLine);
Expand Down Expand Up @@ -1180,47 +1257,78 @@ public AbstractString rotateLeftRight() {
@Override
public OfInt iterator() {
return new OfInt() {
final Deque<AbstractString> todo = new ArrayDeque<>(depth);
OfInt currentLeaf = leftmostLeafIterator(todo, LazyConcatString.this);
final InOrderIterator<OfInt> it = new InOrderIterator<>(IStringTreeNode::iterator);

@Override
public boolean hasNext() {
return currentLeaf.hasNext(); /* || !todo.isEmpty() is unnecessary due to post-condition of nextInt() */
return it.getActive().hasNext();
}

@Override
public int nextInt() {
int next = currentLeaf.nextInt();
return it.getActive().nextInt();
}
};
}

if (!currentLeaf.hasNext() && !todo.isEmpty()) {
// now we back track to the previous node we went left from,
// take the right branch and continue with its first leaf:
currentLeaf = leftmostLeafIterator(todo, todo.pop());
}
@Override
public Iterator<CharBuffer> iterateParts() {
return new Iterator<> () {
final InOrderIterator<Iterator<CharBuffer>> it = new InOrderIterator<>(IStringTreeNode::iterateParts);

assert currentLeaf.hasNext() || todo.isEmpty();
return next;
@Override
public boolean hasNext() {
return it.getActive().hasNext();
}

@Override
public CharBuffer next() {
return it.getActive().next();
}
};
}

/**
* Static helper function for the iterator() method.
* An in order traversel of the leafs of the concat tree.
* We then for every leaf call the desired iterator, and replace it when the next when it's consumed
*/
private class InOrderIterator<T extends Iterator<?>> {
private final Deque<AbstractString> todo;
private final Function<IStringTreeNode, T> getActualIterator;
private T activeIterator;

InOrderIterator( Function<IStringTreeNode, T> getActualIterator) {
this.getActualIterator = getActualIterator;
todo = new ArrayDeque<>(depth);
activeIterator = getActualIterator.apply(leftmostLeaf(todo, LazyConcatString.this));
}

T getActive() {
while (!activeIterator.hasNext() && !todo.isEmpty()) {
activeIterator = getActualIterator.apply(leftmostLeaf(todo, todo.pop()));
}
return activeIterator;
}

}
/**
* helper function for the iterator() method.
*
* It finds the left-most leaf of the tree, and collects
* the path of nodes to this leaf as a side-effect in the todo
* stack.
*/
private static OfInt leftmostLeafIterator(Deque<AbstractString> todo, IStringTreeNode start) {
private static IStringTreeNode leftmostLeaf(Deque<AbstractString> todo, IStringTreeNode start) {
IStringTreeNode cur = start;

while (cur.depth() > 1) {
todo.push(cur.right());
cur = cur.left();
}

return cur.iterator();
return cur;
}

}

private static class IndentedString extends AbstractString {
Expand Down Expand Up @@ -1332,6 +1440,69 @@ public int nextInt() {
};
}

@Override
public Iterator<CharBuffer> iterateParts() {
if (flattened != null) {
return flattened.iterateParts();
}
var indentBuffer = CharBuffer.wrap(indent.getValue());
return new Iterator<>() {
final Iterator<CharBuffer> content = wrapped.iterateParts();
CharBuffer active = CharBuffer.allocate(0);
boolean indentNext = indentFirstLine;

@Override
public boolean hasNext() {
return indentNext || content.hasNext() || active.hasRemaining();
}

private CharBuffer nextTillNewlineOrEndOfBuffer() {
int start = active.position();
int end = start + active.remaining();
int cur = start;
while (cur < end) {
if (active.get(cur) == NEWLINE) {
cur++;
indentNext = true;
break;
}
cur++;
}
if (cur != end) {
var result = active.duplicate();
result.limit(cur);
active.position(cur);
return result;
}
else {
// end of the buffer
var result = active;
if (content.hasNext()) {
active = content.next();
}
else {
// end of the stream
indentNext = false;
active = CharBuffer.allocate(0);
}
return result;
}
}

@Override
public CharBuffer next() {
if (indentNext) {
indentNext = false;
return indentBuffer.asReadOnlyBuffer();
}
// okay so no indent to send
// now we should give the next char-buffer till the next newline
return nextTillNewlineOrEndOfBuffer();
}

};
}

@Override
public IString reverse() {
return applyIndentation().reverse();
Expand Down
Loading
Loading