aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--scripts/HeaderOrder.txt6
-rw-r--r--src/SparseVector.h186
-rw-r--r--test/unit/testSparseVector.cxx185
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();
+ }
+}