Skip to content

Commit

Permalink
Merge pull request #273 from usethesource/func/readable-istring
Browse files Browse the repository at this point in the history
Implemented asReader extension on IString
  • Loading branch information
DavyLandman authored Sep 22, 2024
2 parents 38556b4 + f7d591e commit 8621d34
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 22 deletions.
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

0 comments on commit 8621d34

Please sign in to comment.