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

remove SliceBuilder and Transaction #315

Merged
merged 1 commit into from
Sep 12, 2023
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
274 changes: 3 additions & 271 deletions source/dyaml/reader.d
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ class ReaderException : YAMLException
}
}

/// Provides an API to read characters from a UTF-8 buffer and build slices into that
/// buffer to avoid allocations (see SliceBuilder).
/// Provides an API to read characters from a UTF-8 buffer.
final class Reader
{
private:
Expand Down Expand Up @@ -123,7 +122,6 @@ final class Reader
enforce(isPrintableValidUTF8(buffer_),
new ReaderException("Special unicode characters are not allowed"));

this.sliceBuilder = SliceBuilder(this);
checkASCII();
}

Expand Down Expand Up @@ -212,8 +210,7 @@ final class Reader
/// Get specified number of characters starting at current position.
///
/// Note: This gets only a "view" into the internal buffer, which will be
/// invalidated after other Reader calls. Use SliceBuilder to build slices
/// for permanent use.
/// invalidated after other Reader calls.
///
/// Params: length = Number of characters (code points, not bytes) to get. May
/// reach past the end of the buffer; in that case the returned
Expand All @@ -228,8 +225,7 @@ final class Reader
/// Get specified number of bytes, not code points, starting at current position.
///
/// Note: This gets only a "view" into the internal buffer, which will be
/// invalidated after other Reader calls. Use SliceBuilder to build slices
/// for permanent use.
/// invalidated after other Reader calls.
///
/// Params: length = Number bytes (not code points) to get. May NOT reach past
/// the end of the buffer; should be used with peek() to avoid
Expand Down Expand Up @@ -396,9 +392,6 @@ final class Reader
checkASCII();
}

/// Used to build slices of read data in Reader; to avoid allocations.
SliceBuilder sliceBuilder;

/// Get a string describing current buffer position, used for error messages.
Mark mark() const pure nothrow @nogc @safe { return Mark(name_, line_, column_); }

Expand Down Expand Up @@ -448,267 +441,6 @@ private:
}
}

/// Used to build slices of already read data in Reader buffer, avoiding allocations.
///
/// Usually these slices point to unchanged Reader data, but sometimes the data is
/// changed due to how YAML interprets certain characters/strings.
///
/// See begin() documentation.
struct SliceBuilder
{
private:
// No copying by the user.
@disable this(this);
@disable void opAssign(ref SliceBuilder);

// Reader this builder works in.
Reader reader_;

// Start of the slice om reader_.buffer_ (size_t.max while no slice being build)
size_t start_ = size_t.max;
// End of the slice om reader_.buffer_ (size_t.max while no slice being build)
size_t end_ = size_t.max;

// Stack of slice ends to revert to (see Transaction)
//
// Very few levels as we don't want arbitrarily nested transactions.
size_t[4] endStack_;
// The number of elements currently in endStack_.
size_t endStackUsed_;

@safe const pure nothrow @nogc invariant()
{
if(!inProgress) { return; }
assert(end_ <= reader_.bufferOffset_, "Slice ends after buffer position");
assert(start_ <= end_, "Slice start after slice end");
}

// Is a slice currently being built?
bool inProgress() @safe const pure nothrow @nogc
in(start_ == size_t.max ? end_ == size_t.max : end_ != size_t.max, "start_/end_ are not consistent")
{
return start_ != size_t.max;
}

public:
/// Begin building a slice.
///
/// Only one slice can be built at any given time; before beginning a new slice,
/// finish the previous one (if any).
///
/// The slice starts at the current position in the Reader buffer. It can only be
/// extended up to the current position in the buffer; Reader methods get() and
/// forward() move the position. E.g. it is valid to extend a slice by write()-ing
/// a string just returned by get() - but not one returned by prefix() unless the
/// position has changed since the prefix() call.
void begin() @safe pure nothrow @nogc
in(!inProgress, "Beginning a slice while another slice is being built")
in(endStackUsed_ == 0, "Slice stack not empty at slice begin")
{

start_ = reader_.bufferOffset_;
end_ = reader_.bufferOffset_;
}

/// Finish building a slice and return it.
///
/// Any Transactions on the slice must be committed or destroyed before the slice
/// is finished.
///
/// Returns a string; once a slice is finished it is definitive that its contents
/// will not be changed.
char[] finish() @safe pure nothrow @nogc
in(inProgress, "finish called without begin")
in(endStackUsed_ == 0, "Finishing a slice with running transactions.")
{

auto result = reader_.buffer_[start_ .. end_];
start_ = end_ = size_t.max;
return result;
}

/// Write a string to the slice being built.
///
/// Data can only be written up to the current position in the Reader buffer.
///
/// If str is a string returned by a Reader method, and str starts right after the
/// end of the slice being built, the slice is extended (trivial operation).
///
/// See_Also: begin
void write(scope char[] str) @safe pure nothrow @nogc
{
assert(inProgress, "write called without begin");
assert(end_ <= reader_.bufferOffset_,
"AT START: Slice ends after buffer position");

// Nothing? Already done.
if (str.length == 0) { return; }
// If str starts at the end of the slice (is a string returned by a Reader
// method), just extend the slice to contain str.
if(&str[0] == &reader_.buffer_[end_])
{
end_ += str.length;
}
// Even if str does not start at the end of the slice, it still may be returned
// by a Reader method and point to buffer. So we need to memmove.
else
{
copy(str, reader_.buffer_[end_..end_ + str.length * char.sizeof]);
end_ += str.length;
}
}

/// Write a character to the slice being built.
///
/// Data can only be written up to the current position in the Reader buffer.
///
/// See_Also: begin
void write(dchar c) @safe pure
in(inProgress, "write called without begin")
{
if(c < 0x80)
{
reader_.buffer_[end_++] = cast(char)c;
return;
}

// We need to encode a non-ASCII dchar into UTF-8
char[4] encodeBuf;
const bytes = encode(encodeBuf, c);
reader_.buffer_[end_ .. end_ + bytes] = encodeBuf[0 .. bytes];
end_ += bytes;
}

/// Insert a character to a specified position in the slice.
///
/// Enlarges the slice by 1 char. Note that the slice can only extend up to the
/// current position in the Reader buffer.
///
/// Params:
///
/// c = The character to insert.
/// position = Position to insert the character at in code units, not code points.
/// Must be less than slice length(); a previously returned length()
/// can be used.
void insert(const dchar c, const size_t position) @safe pure
in(inProgress, "insert called without begin")
in(start_ + position <= end_, "Trying to insert after the end of the slice")
{

const point = start_ + position;
const movedLength = end_ - point;

// Encode c into UTF-8
char[4] encodeBuf;
if(c < 0x80) { encodeBuf[0] = cast(char)c; }
const size_t bytes = c < 0x80 ? 1 : encode(encodeBuf, c);

if(movedLength > 0)
{
copy(reader_.buffer_[point..point + movedLength * char.sizeof],
reader_.buffer_[point + bytes..point + bytes + movedLength * char.sizeof]);
}
reader_.buffer_[point .. point + bytes] = encodeBuf[0 .. bytes];
end_ += bytes;
}

/// Get the current length of the slice.
size_t length() @safe const pure nothrow @nogc
{
return end_ - start_;
}

/// A slice building transaction.
///
/// Can be used to save and revert back to slice state.
struct Transaction
{
private:
// The slice builder affected by the transaction.
SliceBuilder* builder_;
// Index of the return point of the transaction in StringBuilder.endStack_.
size_t stackLevel_;
// True after commit() has been called.
bool committed_;

public:
/// Begins a transaction on a SliceBuilder object.
///
/// The transaction must end $(B after) any transactions created within the
/// transaction but $(B before) the slice is finish()-ed. A transaction can be
/// ended either by commit()-ing or reverting through the destructor.
///
/// Saves the current state of a slice.
this(SliceBuilder* builder) @safe pure nothrow @nogc
{
builder_ = builder;
stackLevel_ = builder_.endStackUsed_;
builder_.push();
}

/// Commit changes to the slice.
///
/// Ends the transaction - can only be called once, and removes the possibility
/// to revert slice state.
///
/// Does nothing for a default-initialized transaction (the transaction has not
/// been started yet).
void commit() @safe pure nothrow @nogc
in(!committed_, "Can't commit a transaction more than once")
{

if(builder_ is null) { return; }
assert(builder_.endStackUsed_ == stackLevel_ + 1,
"Parent transactions don't fully contain child transactions");
builder_.apply();
committed_ = true;
}

/// Destroy the transaction and revert it if it hasn't been committed yet.
void end() @safe pure nothrow @nogc
in(builder_ && builder_.endStackUsed_ == stackLevel_ + 1, "Parent transactions don't fully contain child transactions")
{
builder_.pop();
builder_ = null;
}

}

private:
// Push the current end of the slice so we can revert to it if needed.
//
// Used by Transaction.
void push() @safe pure nothrow @nogc
in(inProgress, "push called without begin")
in(endStackUsed_ < endStack_.length, "Slice stack overflow")
{
endStack_[endStackUsed_++] = end_;
}

// Pop the current end of endStack_ and set the end of the slice to the popped
// value, reverting changes since the old end was pushed.
//
// Used by Transaction.
void pop() @safe pure nothrow @nogc
in(inProgress, "pop called without begin")
in(endStackUsed_ > 0, "Trying to pop an empty slice stack")
{
end_ = endStack_[--endStackUsed_];
}

// Pop the current end of endStack_, but keep the current end of the slice, applying
// changes made since pushing the old end.
//
// Used by Transaction.
void apply() @safe pure nothrow @nogc
in(inProgress, "apply called without begin")
in(endStackUsed_ > 0, "Trying to apply an empty slice stack")
{
--endStackUsed_;
}
}


private:

// Convert a UTF-8/16/32 buffer to UTF-8, in-place if possible.
Expand Down
Loading