diff options
| -rw-r--r-- | scripts/HeaderOrder.txt | 6 | ||||
| -rw-r--r-- | src/SparseVector.h | 186 | ||||
| -rw-r--r-- | test/unit/testSparseVector.cxx | 185 | 
3 files changed, 377 insertions, 0 deletions
| diff --git a/scripts/HeaderOrder.txt b/scripts/HeaderOrder.txt index df4f0f4a2..cbfdf6f3f 100644 --- a/scripts/HeaderOrder.txt +++ b/scripts/HeaderOrder.txt @@ -21,6 +21,7 @@  #include <cstring>  #include <cctype>  #include <cstdio> +#include <cstdarg>  #include <cmath>  // C++ standard library @@ -98,6 +99,7 @@  #include "SplitVector.h"  #include "Partitioning.h"  #include "RunStyles.h" +#include "SparseVector.h"  #include "ContractionState.h"  #include "CellBuffer.h"  #include "PerLine.h" @@ -148,3 +150,7 @@  #import "ScintillaView.h"  #import "ScintillaCocoa.h"  #import "PlatCocoa.h" + +// Catch testing framework +#include "catch.hpp" + diff --git a/src/SparseVector.h b/src/SparseVector.h new file mode 100644 index 000000000..f96b36b8b --- /dev/null +++ b/src/SparseVector.h @@ -0,0 +1,186 @@ +// Scintilla source code edit control +/** @file SparseVector.h + ** Hold data sparsely associated with elements in a range. + **/ +// Copyright 2016 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#ifndef SPARSEVECTOR_H +#define SPARSEVECTOR_H + +#ifdef SCI_NAMESPACE +namespace Scintilla { +#endif + +// SparseVector is similar to RunStyles but is more efficient for cases where values occur +// for one position instead of over a range of positions. +template <typename T> +class SparseVector { +private: +	Partitioning *starts; +	SplitVector<T> *values; +	// Private so SparseVector objects can not be copied +	SparseVector(const SparseVector &); +	void ClearValue(int partition) { +		values->SetValueAt(partition, T()); +	} +	void CommonSetValueAt(int position, T value) { +		// Do the work of setting the value to allow for specialization of SetValueAt. +		assert(position < Length()); +		const int partition = starts->PartitionFromPosition(position); +		const int startPartition = starts->PositionFromPartition(partition); +		if (value == T()) { +			// Setting the empty value is equivalent to deleting the position +			if (position == 0) { +				ClearValue(partition); +			} else if (position == startPartition) { +				// Currently an element at this position, so remove +				ClearValue(partition); +				starts->RemovePartition(partition); +				values->Delete(partition); +			} +			// Else element remains empty +		} else { +			if (position == startPartition) { +				// Already a value at this position, so replace +				ClearValue(partition); +				values->SetValueAt(partition, value); +			} else { +				// Insert a new element +				starts->InsertPartition(partition + 1, position); +				values->InsertValue(partition + 1, 1, value); +			} +		} +	} +public: +	SparseVector() { +		starts = new Partitioning(8); +		values = new SplitVector<T>(); +		values->InsertValue(0, 2, T()); +	} +	~SparseVector() { +		delete starts; +		starts = NULL; +		// starts dead here but not used by ClearValue. +		for (int part = 0; part < values->Length(); part++) { +			ClearValue(part); +		} +		delete values; +		values = NULL; +	} +	int Length() const { +		return starts->PositionFromPartition(starts->Partitions()); +	} +	int Elements() const { +		return starts->Partitions(); +	} +	int PositionOfElement(int element) const { +		return starts->PositionFromPartition(element); +	} +	T ValueAt(int position) const { +		assert(position < Length()); +		const int partition = starts->PartitionFromPosition(position); +		const int startPartition = starts->PositionFromPartition(partition); +		if (startPartition == position) { +			return values->ValueAt(partition); +		} else { +			return T(); +		} +	} +	void SetValueAt(int position, T value) { +		CommonSetValueAt(position, value); +	} +	void InsertSpace(int position, int insertLength) { +		assert(position <= Length());	// Only operation that works at end. +		const int partition = starts->PartitionFromPosition(position); +		const int startPartition = starts->PositionFromPartition(partition); +		if (startPartition == position) { +			T valueCurrent = values->ValueAt(partition); +			// Inserting at start of run so make previous longer +			if (partition == 0) { +				// Inserting at start of document so ensure 0 +				if (valueCurrent != T()) { +					ClearValue(0); +					starts->InsertPartition(1, 0); +					values->InsertValue(1, 1, valueCurrent); +					starts->InsertText(0, insertLength); +				} else { +					starts->InsertText(partition, insertLength); +				} +			} else { +				if (valueCurrent != T()) { +					starts->InsertText(partition - 1, insertLength); +				} else { +					// Insert at end of run so do not extend style +					starts->InsertText(partition, insertLength); +				} +			} +		} else { +			starts->InsertText(partition, insertLength); +		} +	} +	void DeletePosition(int position) { +		assert(position < Length()); +		int partition = starts->PartitionFromPosition(position); +		const int startPartition = starts->PositionFromPartition(partition); +		if (startPartition == position) { +			if (partition == 0) { +				ClearValue(0); +			} else if (partition == starts->Partitions()) { +				// This should not be possible +				ClearValue(partition); +				throw std::runtime_error("SparseVector: deleting end partition."); +			} else { +				ClearValue(partition); +				starts->RemovePartition(partition); +				values->Delete(partition); +				// Its the previous partition now that gets smaller  +				partition--; +			} +		} +		starts->InsertText(partition, -1); +	} +	void Check() const { +		if (Length() < 0) { +			throw std::runtime_error("SparseVector: Length can not be negative."); +		} +		if (starts->Partitions() < 1) { +			throw std::runtime_error("SparseVector: Must always have 1 or more partitions."); +		} +		if (starts->Partitions() != values->Length() - 1) { +			throw std::runtime_error("SparseVector: Partitions and values different lengths."); +		} +		// The final element can not be set +		if (values->ValueAt(values->Length() - 1) != T()) { +			throw std::runtime_error("SparseVector: Unused style at end changed."); +		} +	} +}; + +// The specialization for const char * makes copies and deletes them as needed. + +template<> +inline void SparseVector<const char *>::ClearValue(int partition) { +	const char *value = values->ValueAt(partition); +	delete []value; +	values->SetValueAt(partition, NULL); +} + +template<> +inline void SparseVector<const char *>::SetValueAt(int position, const char *value) { +	// Make a copy of the string +	if (value) { +		const size_t len = strlen(value); +		char *valueCopy = new char[len + 1](); +		std::copy(value, value + len, valueCopy); +		CommonSetValueAt(position, valueCopy); +	} else { +		CommonSetValueAt(position, NULL); +	} +} + +#ifdef SCI_NAMESPACE +} +#endif + +#endif diff --git a/test/unit/testSparseVector.cxx b/test/unit/testSparseVector.cxx new file mode 100644 index 000000000..11960163e --- /dev/null +++ b/test/unit/testSparseVector.cxx @@ -0,0 +1,185 @@ +// Unit Tests for Scintilla internal data structures + +#include <string.h> + +#include <cassert> + +#include <stdexcept> +#include <algorithm> + +#include "Platform.h" + +#include "Position.h" +#include "SplitVector.h" +#include "Partitioning.h" +#include "SparseVector.h" + +#include "catch.hpp" + +// Test SparseVector. + +// Helper to produce a string representation of a SparseVector<const char *> +// to simplify checks. +static std::string Representation(const SparseVector<const char *> &st) { +	std::string ret; +	for (int i = 0;i < st.Length();i++) { +		const char *value = st.ValueAt(i); +		if (value) +			ret += value; +		else +			ret += "-"; +	} +	return ret; +} + +TEST_CASE("SparseVector") { + +	SparseVector<const char *> st; + +	SECTION("IsEmptyInitially") { +		REQUIRE(1 == st.Elements()); +		REQUIRE(0 == st.Length()); +		st.Check(); +	} + +	SECTION("InsertSpace") { +		st.InsertSpace(0, 5); +		REQUIRE(1 == st.Elements()); +		REQUIRE(static_cast<const char *>(NULL) == st.ValueAt(0)); +		REQUIRE(static_cast<const char *>(NULL) == st.ValueAt(1)); +		REQUIRE(static_cast<const char *>(NULL) == st.ValueAt(4)); +		st.Check(); +	} + +	SECTION("InsertValue") { +		st.InsertSpace(0, 5); +		st.SetValueAt(3, "3"); +		REQUIRE(2 == st.Elements()); +		REQUIRE("---3-" == Representation(st)); +		st.Check(); +	} + +	SECTION("InsertAndDeleteValue") { +		st.InsertSpace(0, 5); +		REQUIRE(5 == st.Length()); +		st.SetValueAt(3, "3"); +		REQUIRE(2 == st.Elements()); +		st.DeletePosition(3); +		REQUIRE(1 == st.Elements()); +		REQUIRE(4 == st.Length()); +		REQUIRE("----" == Representation(st)); +		st.Check(); +	} + +	SECTION("InsertAndDeleteAtStart") { +		REQUIRE(1 == st.Elements()); +		st.InsertSpace(0, 5); +		st.SetValueAt(0, "3"); +		REQUIRE(1 == st.Elements()); +		REQUIRE("3----" == Representation(st)); +		st.DeletePosition(0); +		REQUIRE(1 == st.Elements()); +		REQUIRE("----" == Representation(st)); +		st.SetValueAt(0, "4"); +		REQUIRE(1 == st.Elements()); +		REQUIRE("4---" == Representation(st)); +		st.DeletePosition(0); +		REQUIRE(1 == st.Elements()); +		REQUIRE("---" == Representation(st)); +		st.Check(); +	} + +	SECTION("InsertAndDeleteAtEnd") { +		REQUIRE(1 == st.Elements()); +		st.InsertSpace(0, 5); +		st.SetValueAt(4, "5"); +		REQUIRE(2 == st.Elements()); +		REQUIRE("----5" == Representation(st)); +		st.DeletePosition(4); +		REQUIRE(1 == st.Elements()); +		REQUIRE("----" == Representation(st)); +		st.Check(); +	} + +	SECTION("SetNULL") { +		REQUIRE(1 == st.Elements()); +		st.InsertSpace(0, 5); +		st.SetValueAt(4, "5"); +		REQUIRE(2 == st.Elements()); +		REQUIRE("----5" == Representation(st)); +		st.SetValueAt(4, NULL); +		REQUIRE(1 == st.Elements()); +		REQUIRE("-----" == Representation(st)); +		st.Check(); +	} + +	SECTION("DeleteAll") { +		REQUIRE(1 == st.Elements()); +		st.InsertSpace(0, 10); +		st.SetValueAt(9, "9"); +		st.SetValueAt(7, "7"); +		st.SetValueAt(4, "4"); +		st.SetValueAt(3, "3"); +		REQUIRE(5 == st.Elements()); +		REQUIRE("---34--7-9" == Representation(st)); +		st.Check(); +	} + +} + +TEST_CASE("SparseTextInt") { + +	SparseVector<int> st; + +	SECTION("InsertAndDeleteValue") { +		st.InsertSpace(0, 5); +		st.SetValueAt(3, 3); +		REQUIRE(2 == st.Elements()); +		REQUIRE(0 == st.ValueAt(0)); +		REQUIRE(0 == st.ValueAt(1)); +		REQUIRE(0 == st.ValueAt(2)); +		REQUIRE(3 == st.ValueAt(3)); +		REQUIRE(0 == st.ValueAt(4)); +		st.SetValueAt(3, -3); +		REQUIRE(2 == st.Elements()); +		REQUIRE(0 == st.ValueAt(0)); +		REQUIRE(0 == st.ValueAt(1)); +		REQUIRE(0 == st.ValueAt(2)); +		REQUIRE(-3 == st.ValueAt(3)); +		REQUIRE(0 == st.ValueAt(4)); +		st.SetValueAt(3, 0); +		REQUIRE(1 == st.Elements()); +		REQUIRE(0 == st.ValueAt(0)); +		REQUIRE(0 == st.ValueAt(1)); +		REQUIRE(0 == st.ValueAt(2)); +		REQUIRE(0 == st.ValueAt(3)); +		REQUIRE(0 == st.ValueAt(4)); +		st.Check(); +	} +} + +TEST_CASE("SparseTextString") { + +	SparseVector<std::string> st; + +	SECTION("InsertAndDeleteValue") { +		st.InsertSpace(0, 5); +		REQUIRE(5 == st.Length()); +		st.SetValueAt(3, "3"); +		REQUIRE(2 == st.Elements()); +		REQUIRE("" == st.ValueAt(0)); +		REQUIRE("" == st.ValueAt(2)); +		REQUIRE("3" == st.ValueAt(3)); +		REQUIRE("" == st.ValueAt(4)); +		st.DeletePosition(0); +		REQUIRE(4 == st.Length()); +		REQUIRE("3" == st.ValueAt(2)); +		st.DeletePosition(2); +		REQUIRE(1 == st.Elements()); +		REQUIRE(3 == st.Length()); +		REQUIRE("" == st.ValueAt(0)); +		REQUIRE("" == st.ValueAt(1)); +		REQUIRE("" == st.ValueAt(2)); +		st.Check(); +	} +} | 
