diff options
-rw-r--r-- | src/android.cpp | 4 | ||||
-rw-r--r-- | src/benchmark.cpp | 6 | ||||
-rw-r--r-- | src/gl-visual-config.cpp | 4 | ||||
-rw-r--r-- | src/libmatrix/Makefile | 2 | ||||
-rw-r--r-- | src/libmatrix/shader-source.cc | 2 | ||||
-rw-r--r-- | src/libmatrix/test/libmatrix_test.cc | 4 | ||||
-rw-r--r-- | src/libmatrix/test/util_split_test.cc | 180 | ||||
-rw-r--r-- | src/libmatrix/test/util_split_test.h | 31 | ||||
-rw-r--r-- | src/libmatrix/util.cc | 169 | ||||
-rw-r--r-- | src/libmatrix/util.h | 24 | ||||
-rw-r--r-- | src/main-loop.cpp | 2 | ||||
-rw-r--r-- | src/options.cpp | 2 | ||||
-rw-r--r-- | src/scene-effect-2d.cpp | 4 | ||||
-rw-r--r-- | src/scene.cpp | 2 |
14 files changed, 403 insertions, 33 deletions
diff --git a/src/android.cpp b/src/android.cpp index d7e0830..7ec6565 100644 --- a/src/android.cpp +++ b/src/android.cpp @@ -101,7 +101,7 @@ get_args_from_file(const std::string &arguments_file, int &argc, char **&argv) std::string line; while (getline(ifs, line)) { if (!line.empty()) - Util::split(line, ' ', arguments); + Util::split(line, ' ', arguments, Util::SplitModeQuoted); } } @@ -118,7 +118,7 @@ static void get_args_from_string(const std::string &args_str, int &argc, char **&argv) { std::vector<std::string> arguments; - Util::split(args_str, ' ', arguments); + Util::split(args_str, ' ', arguments, Util::SplitModeQuoted); arg_vector_to_argv(arguments, argc, argv); } diff --git a/src/benchmark.cpp b/src/benchmark.cpp index fdfd582..1f39c39 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -35,7 +35,7 @@ get_scene_from_description(const string &s) { vector<string> elems; - Util::split(s, ':', elems); + Util::split(s, ':', elems, Util::SplitModeNormal); const string &name = !elems.empty() ? elems[0] : ""; @@ -48,7 +48,7 @@ get_options_from_description(const string &s) vector<Benchmark::OptionPair> options; vector<string> elems; - Util::split(s, ':', elems); + Util::split(s, ':', elems, Util::SplitModeNormal); for (vector<string>::const_iterator iter = elems.begin() + 1; iter != elems.end(); @@ -56,7 +56,7 @@ get_options_from_description(const string &s) { vector<string> opt; - Util::split(*iter, '=', opt); + Util::split(*iter, '=', opt, Util::SplitModeNormal); if (opt.size() == 2) options.push_back(Benchmark::OptionPair(opt[0], opt[1])); else diff --git a/src/gl-visual-config.cpp b/src/gl-visual-config.cpp index 2cbf164..4e9ab93 100644 --- a/src/gl-visual-config.cpp +++ b/src/gl-visual-config.cpp @@ -30,7 +30,7 @@ GLVisualConfig::GLVisualConfig(const std::string &s) : { std::vector<std::string> elems; - Util::split(s, ':', elems); + Util::split(s, ':', elems, Util::SplitModeNormal); for (std::vector<std::string>::const_iterator iter = elems.begin(); iter != elems.end(); @@ -38,7 +38,7 @@ GLVisualConfig::GLVisualConfig(const std::string &s) : { std::vector<std::string> opt; - Util::split(*iter, '=', opt); + Util::split(*iter, '=', opt, Util::SplitModeNormal); if (opt.size() == 2) { if (opt[0] == "r" || opt[0] == "red") red = Util::fromString<int>(opt[1]); diff --git a/src/libmatrix/Makefile b/src/libmatrix/Makefile index e56995f..418311f 100644 --- a/src/libmatrix/Makefile +++ b/src/libmatrix/Makefile @@ -9,6 +9,7 @@ TESTSRCS = $(TESTDIR)/options.cc \ $(TESTDIR)/inverse_test.cc \ $(TESTDIR)/transpose_test.cc \ $(TESTDIR)/shader_source_test.cc \ + $(TESTDIR)/util_split_test.cc \ $(TESTDIR)/libmatrix_test.cc TESTOBJS = $(TESTSRCS:.cc=.o) @@ -32,6 +33,7 @@ $(TESTDIR)/const_vec_test.o: $(TESTDIR)/const_vec_test.cc $(TESTDIR)/const_vec_t $(TESTDIR)/inverse_test.o: $(TESTDIR)/inverse_test.cc $(TESTDIR)/inverse_test.h $(TESTDIR)/libmatrix_test.h mat.h $(TESTDIR)/transpose_test.o: $(TESTDIR)/transpose_test.cc $(TESTDIR)/transpose_test.h $(TESTDIR)/libmatrix_test.h mat.h $(TESTDIR)/shader_source_test.o: $(TESTDIR)/shader_source_test.cc $(TESTDIR)/shader_source_test.h $(TESTDIR)/libmatrix_test.h shader-source.h +$(TESTDIR)/util_split_test.o: $(TESTDIR)/util_split_test.cc $(TESTDIR)/util_split_test.h $(TESTDIR)/libmatrix_test.h util.h $(TESTDIR)/libmatrix_test: $(TESTOBJS) libmatrix.a $(CXX) -o $@ $^ run_tests: $(LIBMATRIX_TESTS) diff --git a/src/libmatrix/shader-source.cc b/src/libmatrix/shader-source.cc index de22bf0..bf80d21 100644 --- a/src/libmatrix/shader-source.cc +++ b/src/libmatrix/shader-source.cc @@ -589,7 +589,7 @@ ShaderSource::Precision::Precision(const std::string& precision_values) : { std::vector<std::string> elems; - Util::split(precision_values, ',', elems); + Util::split(precision_values, ',', elems, Util::SplitModeNormal); for (size_t i = 0; i < elems.size() && i < 4; i++) { const std::string& pstr(elems[i]); diff --git a/src/libmatrix/test/libmatrix_test.cc b/src/libmatrix/test/libmatrix_test.cc index 93a303b..5e4ff12 100644 --- a/src/libmatrix/test/libmatrix_test.cc +++ b/src/libmatrix/test/libmatrix_test.cc @@ -8,6 +8,7 @@ // // Contributors: // Jesse Barker - original implementation. +// Alexandros Frantzis - Util::split tests // #include <iostream> #include <string> @@ -17,6 +18,7 @@ #include "transpose_test.h" #include "const_vec_test.h" #include "shader_source_test.h" +#include "util_split_test.h" using std::cerr; using std::cout; @@ -42,6 +44,8 @@ main(int argc, char** argv) testVec.push_back(new MatrixTest3x3Transpose()); testVec.push_back(new MatrixTest4x4Transpose()); testVec.push_back(new ShaderSourceBasic()); + testVec.push_back(new UtilSplitTestNormal()); + testVec.push_back(new UtilSplitTestQuoted()); for (vector<MatrixTest*>::iterator testIt = testVec.begin(); testIt != testVec.end(); diff --git a/src/libmatrix/test/util_split_test.cc b/src/libmatrix/test/util_split_test.cc new file mode 100644 index 0000000..4399f77 --- /dev/null +++ b/src/libmatrix/test/util_split_test.cc @@ -0,0 +1,180 @@ +// +// Copyright (c) 2012 Linaro Limited +// +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the MIT License which accompanies +// this distribution, and is available at +// http://www.opensource.org/licenses/mit-license.php +// +// Contributors: +// Alexandros Frantzis - original implementation. +// +#include <iostream> +#include <string> +#include <vector> +#include "libmatrix_test.h" +#include "util_split_test.h" +#include "../util.h" + +using std::cout; +using std::endl; +using std::string; +using std::vector; + +template <typename T> static bool +areVectorsEqual(vector<T>& vec1, vector<T>& vec2) +{ + if (vec1.size() != vec2.size()) + return false; + + for (unsigned int i = 0; i < vec1.size(); i++) + { + if (vec1[i] != vec2[i]) + return false; + } + + return true; +} + +template <typename T> static void +printVector(vector<T>& vec) +{ + cout << "["; + for (unsigned int i = 0; i < vec.size(); i++) + { + cout << '"' << vec[i] << '"'; + if (i < vec.size() - 1) + cout << ", "; + } + cout << "]"; +} + +void +UtilSplitTestNormal::run(const Options& options) +{ + const string test1("abc def ghi"); + const string test2(" abc: def :ghi "); + vector<string> expected1; + vector<string> expected2; + vector<string> results; + + expected1.push_back("abc"); + expected1.push_back("def"); + expected1.push_back("ghi"); + + expected2.push_back(" abc"); + expected2.push_back(" def "); + expected2.push_back("ghi "); + + if (options.beVerbose()) + { + cout << "Testing string \"" << test1 << "\"" << endl; + } + + Util::split(test1, ' ', results, Util::SplitModeNormal); + + if (options.beVerbose()) + { + cout << "Split result: "; + printVector(results); + cout << endl << "Expected: "; + printVector(expected1); + cout << endl; + } + + if (!areVectorsEqual(results, expected1)) + { + return; + } + + results.clear(); + + if (options.beVerbose()) + { + cout << "Testing string \"" << test2 << "\"" << endl; + } + + Util::split(test2, ':', results, Util::SplitModeNormal); + + if (options.beVerbose()) + { + cout << "Split result: "; + printVector(results); + cout << endl << "Expected: "; + printVector(expected2); + cout << endl; + } + + if (!areVectorsEqual(results, expected2)) + { + return; + } + + pass_ = true; +} + +void +UtilSplitTestQuoted::run(const Options& options) +{ + const string test1("abc \"def' ghi\" klm\\ nop -b qr:title='123 \"456'"); + const string test2("abc: def='1:2:3:'ghi : \":jk\""); + vector<string> expected1; + vector<string> expected2; + vector<string> results; + + expected1.push_back("abc"); + expected1.push_back("def' ghi"); + expected1.push_back("klm nop"); + expected1.push_back("-b"); + expected1.push_back("qr:title=123 \"456"); + + expected2.push_back("abc"); + expected2.push_back(" def=1:2:3:ghi "); + expected2.push_back(" :jk"); + + if (options.beVerbose()) + { + cout << "Testing string \"" << test1 << "\"" << endl; + } + + Util::split(test1, ' ', results, Util::SplitModeQuoted); + + if (options.beVerbose()) + { + cout << "Split result: "; + printVector(results); + cout << endl << "Expected: "; + printVector(expected1); + cout << endl; + } + + if (!areVectorsEqual(results, expected1)) + { + return; + } + + results.clear(); + + if (options.beVerbose()) + { + cout << "Testing string \"" << test2 << "\"" << endl; + } + + Util::split(test2, ':', results, Util::SplitModeQuoted); + + if (options.beVerbose()) + { + cout << "Split result: "; + printVector(results); + cout << endl << "Expected: "; + printVector(expected2); + cout << endl; + } + + if (!areVectorsEqual(results, expected2)) + { + return; + } + + pass_ = true; +} diff --git a/src/libmatrix/test/util_split_test.h b/src/libmatrix/test/util_split_test.h new file mode 100644 index 0000000..a8023ff --- /dev/null +++ b/src/libmatrix/test/util_split_test.h @@ -0,0 +1,31 @@ +// +// Copyright (c) 2012 Linaro Limited +// +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the MIT License which accompanies +// this distribution, and is available at +// http://www.opensource.org/licenses/mit-license.php +// +// Contributors: +// Alexandros Frantzis - original implementation. +// +#ifndef UTIL_SPLIT_TEST_H_ +#define UTIL_SPLIT_TEST_H_ + +class MatrixTest; +class Options; + +class UtilSplitTestNormal : public MatrixTest +{ +public: + UtilSplitTestNormal() : MatrixTest("Util::split::normal") {} + virtual void run(const Options& options); +}; + +class UtilSplitTestQuoted : public MatrixTest +{ +public: + UtilSplitTestQuoted() : MatrixTest("Util::split::quoted") {} + virtual void run(const Options& options); +}; +#endif // UTIL_SPLIT_TEST_H_ diff --git a/src/libmatrix/util.cc b/src/libmatrix/util.cc index e93308b..d96f393 100644 --- a/src/libmatrix/util.cc +++ b/src/libmatrix/util.cc @@ -25,25 +25,102 @@ using std::string; using std::vector; -void -Util::split(const string& src, char delim, vector<string>& elementVec, bool fuzzy) +/* + * State machine for bash-like quoted string escaping: + * + * \ + * -----------> +---------+ + * | ---------- | Escaped | + * | | *,ESC +---------+ + * | | + * | v ' + * +--------+ ---> +--------------+ ----- + * | Normal | <--- | SingleQuoted | | *, ESC + * +--------+ ' +--------------+ <---- + * | ^ + * | | + * | | " +--------------+ ---- + * | ---------- | DoubleQuoted | | *, ESC + * -----------> +--------------+ <--- + * " | ^ + * \ | | *, ESC + * v | + * +---------------------+ + * | DoubleQuotedEscaped | + * +---------------------+ + * + * ESC: Mark character as Escaped + */ +static void +fill_escape_vector(const string &str, vector<bool> &esc_vec) { - // Trivial rejection - if (src.empty()) - { - return; - } + enum State { + StateNormal, + StateEscaped, + StateDoubleQuoted, + StateDoubleQuotedEscaped, + StateSingleQuoted + }; - // Simple case: we want to enforce the value of 'delim' strictly - if (!fuzzy) + State state = StateNormal; + + for (string::const_iterator iter = str.begin(); + iter != str.end(); + iter++) { - std::stringstream ss(src); - string item; - while(std::getline(ss, item, delim)) - elementVec.push_back(item); - return; + const char c(*iter); + bool esc = false; + + switch (state) { + case StateNormal: + if (c == '"') + state = StateDoubleQuoted; + else if (c == '\\') + state = StateEscaped; + else if (c == '\'') + state = StateSingleQuoted; + break; + case StateEscaped: + esc = true; + state = StateNormal; + break; + case StateDoubleQuoted: + if (c == '"') + state = StateNormal; + else if (c == '\\') + state = StateDoubleQuotedEscaped; + else + esc = true; + break; + case StateDoubleQuotedEscaped: + esc = true; + state = StateDoubleQuoted; + break; + case StateSingleQuoted: + if (c == '\'') + state = StateNormal; + else + esc = true; + default: + break; + } + + esc_vec.push_back(esc); } +} +static void +split_normal(const string& src, char delim, vector<string>& elementVec) +{ + std::stringstream ss(src); + string item; + while(std::getline(ss, item, delim)) + elementVec.push_back(item); +} + +static void +split_fuzzy(const string& src, char delim, vector<string>& elementVec) +{ // Fuzzy case: Initialize our delimiter string based upon the caller's plus // a space to allow for more flexibility. string delimiter(" "); @@ -76,6 +153,70 @@ Util::split(const string& src, char delim, vector<string>& elementVec, bool fuzz elementVec.push_back(str); } +static void +split_quoted(const string& src, char delim, vector<string>& elementVec) +{ + std::stringstream ss; + vector<bool> escVec; + + /* Mark characters in the string as escaped or not */ + fill_escape_vector(src, escVec); + + /* Sanity check... */ + if (src.length() != escVec.size()) + return; + + for (vector<bool>::const_iterator iter = escVec.begin(); + iter != escVec.end(); + iter++) + { + bool escaped = static_cast<bool>(*iter); + char c = src[iter - escVec.begin()]; + + /* Output all characters, except unescaped ",\,' */ + if ((c != '"' && c != '\\' && c != '\'') || escaped) { + /* If we reach an unescaped delimiter character, do a split */ + if (c == delim && !escaped) { + elementVec.push_back(ss.str()); + ss.str(""); + ss.clear(); + } + else { + ss << c; + } + } + + } + + /* Handle final element, delimited by end of string */ + const string &finalElement(ss.str()); + if (!finalElement.empty()) + elementVec.push_back(finalElement); +} + +void +Util::split(const string& src, char delim, vector<string>& elementVec, + Util::SplitMode mode) +{ + // Trivial rejection + if (src.empty()) + { + return; + } + + switch (mode) + { + case Util::SplitModeNormal: + return split_normal(src, delim, elementVec); + case Util::SplitModeFuzzy: + return split_fuzzy(src, delim, elementVec); + case Util::SplitModeQuoted: + return split_quoted(src, delim, elementVec); + default: + break; + } +} + uint64_t Util::get_timestamp_us() { diff --git a/src/libmatrix/util.h b/src/libmatrix/util.h index d3698a2..2b0f0f0 100644 --- a/src/libmatrix/util.h +++ b/src/libmatrix/util.h @@ -25,21 +25,33 @@ struct Util { /** + * How to perform the split() operation + */ + enum SplitMode { + /** Normal split operation */ + SplitModeNormal, + /** Allow for spaces and multiple consecutive occurences of the delimiter */ + SplitModeFuzzy, + /** Take into account bash-like quoting and escaping rules */ + SplitModeQuoted + }; + + /** * split() - Splits a string into elements using a provided delimiter * * @s: the string to split * @delim: the delimiter to use * @elems: the string vector to populate - * @fuzzy: (optional) enable/disable strict handling of @delim + * @mode: the SplitMode to use * * Using @delim to determine field boundaries, splits @s into separate * string elements. These elements are returned in the string vector - * @elems. If @fuzzy is true, then the handling of @delim allows for - * spaces and multiple consecutive occurences of @delim in determining - * field boundaries. As long as @s is non-empty, there will be at least - * one element in @elems. + * @elems. As long as @s is non-empty, there will be at least one + * element in @elems. */ - static void split(const std::string &s, char delim, std::vector<std::string> &elems, bool fuzzy = false); + static void split(const std::string& src, char delim, + std::vector<std::string>& elems, + Util::SplitMode mode); /** * get_timestamp_us() - Returns the current time in microseconds */ diff --git a/src/main-loop.cpp b/src/main-loop.cpp index 3ccefbe..6e97a54 100644 --- a/src/main-loop.cpp +++ b/src/main-loop.cpp @@ -252,7 +252,7 @@ MainLoopDecoration::vec2_from_pos_string(const std::string &s) { LibMatrix::vec2 v(0.0, 0.0); std::vector<std::string> elems; - Util::split(s, ',', elems); + Util::split(s, ',', elems, Util::SplitModeNormal); if (elems.size() > 0) v.x(Util::fromString<float>(elems[0])); diff --git a/src/options.cpp b/src/options.cpp index 2303039..2ff1540 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -74,7 +74,7 @@ static void parse_size(const std::string &str, std::pair<int,int> &size) { std::vector<std::string> d; - Util::split(str, 'x', d); + Util::split(str, 'x', d, Util::SplitModeNormal); size.first = Util::fromString<int>(d[0]); diff --git a/src/scene-effect-2d.cpp b/src/scene-effect-2d.cpp index 1e08b7a..27275dc 100644 --- a/src/scene-effect-2d.cpp +++ b/src/scene-effect-2d.cpp @@ -194,7 +194,7 @@ parse_matrix(const std::string &str, std::vector<float> &matrix, std::vector<std::string> rows; unsigned int w = UINT_MAX; - Util::split(str, ';', rows); + Util::split(str, ';', rows, Util::SplitModeNormal); Log::debug("Parsing kernel matrix:\n"); static const std::string format("%f "); @@ -206,7 +206,7 @@ parse_matrix(const std::string &str, std::vector<float> &matrix, iter++) { std::vector<std::string> elems; - Util::split(*iter, ',', elems); + Util::split(*iter, ',', elems, Util::SplitModeNormal); if (w != UINT_MAX && elems.size() != w) { Log::error("Matrix row %u contains %u elements, whereas previous" diff --git a/src/scene.cpp b/src/scene.cpp index 9e01ec2..be6dd9a 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -38,7 +38,7 @@ Scene::Option::Option(const std::string &nam, const std::string &val, const std: const std::string &values) : name(nam), value(val), default_value(val), description(desc), set(false) { - Util::split(values, ',', acceptable_values); + Util::split(values, ',', acceptable_values, Util::SplitModeNormal); } Scene::Scene(Canvas &pCanvas, const string &name) : |