merge bitcoin#16887: Abstract out some of the descriptor Span-parsing helpers

This commit is contained in:
Kittywhiskers Van Gogh 2019-07-30 14:53:05 -07:00
parent 8647ba1316
commit e534b48820
5 changed files with 252 additions and 57 deletions

View File

@ -278,6 +278,7 @@ BITCOIN_CORE_H = \
util/bytevectorhash.h \ util/bytevectorhash.h \
util/error.h \ util/error.h \
util/fees.h \ util/fees.h \
util/spanparsing.h \
util/system.h \ util/system.h \
util/asmap.h \ util/asmap.h \
util/getuniquepath.h \ util/getuniquepath.h \
@ -646,6 +647,7 @@ libdash_util_a_SOURCES = \
util/system.cpp \ util/system.cpp \
util/asmap.cpp \ util/asmap.cpp \
util/moneystr.cpp \ util/moneystr.cpp \
util/spanparsing.cpp \
util/strencodings.cpp \ util/strencodings.cpp \
util/time.cpp \ util/time.cpp \
util/serfloat.cpp \ util/serfloat.cpp \

View File

@ -10,6 +10,7 @@
#include <script/standard.h> #include <script/standard.h>
#include <span.h> #include <span.h>
#include <util/spanparsing.h>
#include <util/system.h> #include <util/system.h>
#include <util/memory.h> #include <util/memory.h>
#include <util/strencodings.h> #include <util/strencodings.h>
@ -582,63 +583,6 @@ enum class ParseScriptContext {
P2SH P2SH
}; };
/** Parse a constant. If succesful, sp is updated to skip the constant and return true. */
bool Const(const std::string& str, Span<const char>& sp)
{
if ((size_t)sp.size() >= str.size() && std::equal(str.begin(), str.end(), sp.begin())) {
sp = sp.subspan(str.size());
return true;
}
return false;
}
/** Parse a function call. If succesful, sp is updated to be the function's argument(s). */
bool Func(const std::string& str, Span<const char>& sp)
{
if ((size_t)sp.size() >= str.size() + 2 && sp[str.size()] == '(' && sp[sp.size() - 1] == ')' && std::equal(str.begin(), str.end(), sp.begin())) {
sp = sp.subspan(str.size() + 1, sp.size() - str.size() - 2);
return true;
}
return false;
}
/** Return the expression that sp begins with, and update sp to skip it. */
Span<const char> Expr(Span<const char>& sp)
{
int level = 0;
auto it = sp.begin();
while (it != sp.end()) {
if (*it == '(') {
++level;
} else if (level && *it == ')') {
--level;
} else if (level == 0 && (*it == ')' || *it == ',')) {
break;
}
++it;
}
Span<const char> ret = sp.first(it - sp.begin());
sp = sp.subspan(it - sp.begin());
return ret;
}
/** Split a string on every instance of sep, returning a vector. */
std::vector<Span<const char>> Split(const Span<const char>& sp, char sep)
{
std::vector<Span<const char>> ret;
auto it = sp.begin();
auto start = it;
while (it != sp.end()) {
if (*it == sep) {
ret.emplace_back(start, it);
start = it + 1;
}
++it;
}
ret.emplace_back(start, it);
return ret;
}
/** Parse a key path, being passed a split list of elements (the first element is ignored). */ /** Parse a key path, being passed a split list of elements (the first element is ignored). */
bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out) bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out)
{ {
@ -659,6 +603,8 @@ bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out)
/** Parse a public key that excludes origin information. */ /** Parse a public key that excludes origin information. */
std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out) std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out)
{ {
using namespace spanparsing;
auto split = Split(sp, '/'); auto split = Split(sp, '/');
std::string str(split[0].begin(), split[0].end()); std::string str(split[0].begin(), split[0].end());
if (split.size() == 1) { if (split.size() == 1) {
@ -697,6 +643,8 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char>& sp, boo
/** Parse a public key including origin information (if enabled). */ /** Parse a public key including origin information (if enabled). */
std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out) std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out)
{ {
using namespace spanparsing;
auto origin_split = Split(sp, ']'); auto origin_split = Split(sp, ']');
if (origin_split.size() > 2) return nullptr; if (origin_split.size() > 2) return nullptr;
if (origin_split.size() == 1) return ParsePubkeyInner(origin_split[0], permit_uncompressed, out); if (origin_split.size() == 1) return ParsePubkeyInner(origin_split[0], permit_uncompressed, out);
@ -719,6 +667,8 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool per
/** Parse a script in a particular context. */ /** Parse a script in a particular context. */
std::unique_ptr<DescriptorImpl> ParseScript(Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out) std::unique_ptr<DescriptorImpl> ParseScript(Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out)
{ {
using namespace spanparsing;
auto expr = Expr(sp); auto expr = Expr(sp);
if (Func("pk", expr)) { if (Func("pk", expr)) {
auto pubkey = ParsePubkey(expr, ctx != ParseScriptContext::P2SH, out); auto pubkey = ParsePubkey(expr, ctx != ParseScriptContext::P2SH, out);
@ -840,6 +790,8 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
/** Check a descriptor checksum, and update desc to be the checksum-less part. */ /** Check a descriptor checksum, and update desc to be the checksum-less part. */
bool CheckChecksum(Span<const char>& sp, bool require_checksum, std::string* out_checksum = nullptr) bool CheckChecksum(Span<const char>& sp, bool require_checksum, std::string* out_checksum = nullptr)
{ {
using namespace spanparsing;
auto check_split = Split(sp, '#'); auto check_split = Split(sp, '#');
if (check_split.size() > 2) return false; // Multiple '#' symbols if (check_split.size() > 2) return false; // Multiple '#' symbols
if (check_split.size() == 1 && require_checksum) return false; // Missing checksum if (check_split.size() == 1 && require_checksum) return false; // Missing checksum

View File

@ -13,6 +13,7 @@
#include <util/moneystr.h> #include <util/moneystr.h>
#include <test/test_dash.h> #include <test/test_dash.h>
#include <util/vector.h> #include <util/vector.h>
#include <util/spanparsing.h>
#include <stdint.h> #include <stdint.h>
#include <vector> #include <vector>
@ -1352,4 +1353,127 @@ BOOST_AUTO_TEST_CASE(test_Capitalize)
BOOST_CHECK_EQUAL(Capitalize("\x00\xfe\xff"), "\x00\xfe\xff"); BOOST_CHECK_EQUAL(Capitalize("\x00\xfe\xff"), "\x00\xfe\xff");
} }
static std::string SpanToStr(Span<const char>& span)
{
return std::string(span.begin(), span.end());
}
BOOST_AUTO_TEST_CASE(test_spanparsing)
{
using namespace spanparsing;
std::string input;
Span<const char> sp;
bool success;
// Const(...): parse a constant, update span to skip it if successful
input = "MilkToastHoney";
sp = MakeSpan(input);
success = Const("", sp); // empty
BOOST_CHECK(success);
BOOST_CHECK_EQUAL(SpanToStr(sp), "MilkToastHoney");
success = Const("Milk", sp);
BOOST_CHECK(success);
BOOST_CHECK_EQUAL(SpanToStr(sp), "ToastHoney");
success = Const("Bread", sp);
BOOST_CHECK(!success);
success = Const("Toast", sp);
BOOST_CHECK(success);
BOOST_CHECK_EQUAL(SpanToStr(sp), "Honey");
success = Const("Honeybadger", sp);
BOOST_CHECK(!success);
success = Const("Honey", sp);
BOOST_CHECK(success);
BOOST_CHECK_EQUAL(SpanToStr(sp), "");
// Func(...): parse a function call, update span to argument if successful
input = "Foo(Bar(xy,z()))";
sp = MakeSpan(input);
success = Func("FooBar", sp);
BOOST_CHECK(!success);
success = Func("Foo(", sp);
BOOST_CHECK(!success);
success = Func("Foo", sp);
BOOST_CHECK(success);
BOOST_CHECK_EQUAL(SpanToStr(sp), "Bar(xy,z())");
success = Func("Bar", sp);
BOOST_CHECK(success);
BOOST_CHECK_EQUAL(SpanToStr(sp), "xy,z()");
success = Func("xy", sp);
BOOST_CHECK(!success);
// Expr(...): return expression that span begins with, update span to skip it
Span<const char> result;
input = "(n*(n-1))/2";
sp = MakeSpan(input);
result = Expr(sp);
BOOST_CHECK_EQUAL(SpanToStr(result), "(n*(n-1))/2");
BOOST_CHECK_EQUAL(SpanToStr(sp), "");
input = "foo,bar";
sp = MakeSpan(input);
result = Expr(sp);
BOOST_CHECK_EQUAL(SpanToStr(result), "foo");
BOOST_CHECK_EQUAL(SpanToStr(sp), ",bar");
input = "(aaaaa,bbbbb()),c";
sp = MakeSpan(input);
result = Expr(sp);
BOOST_CHECK_EQUAL(SpanToStr(result), "(aaaaa,bbbbb())");
BOOST_CHECK_EQUAL(SpanToStr(sp), ",c");
input = "xyz)foo";
sp = MakeSpan(input);
result = Expr(sp);
BOOST_CHECK_EQUAL(SpanToStr(result), "xyz");
BOOST_CHECK_EQUAL(SpanToStr(sp), ")foo");
input = "((a),(b),(c)),xxx";
sp = MakeSpan(input);
result = Expr(sp);
BOOST_CHECK_EQUAL(SpanToStr(result), "((a),(b),(c))");
BOOST_CHECK_EQUAL(SpanToStr(sp), ",xxx");
// Split(...): split a string on every instance of sep, return vector
std::vector<Span<const char>> results;
input = "xxx";
results = Split(MakeSpan(input), 'x');
BOOST_CHECK_EQUAL(results.size(), 4);
BOOST_CHECK_EQUAL(SpanToStr(results[0]), "");
BOOST_CHECK_EQUAL(SpanToStr(results[1]), "");
BOOST_CHECK_EQUAL(SpanToStr(results[2]), "");
BOOST_CHECK_EQUAL(SpanToStr(results[3]), "");
input = "one#two#three";
results = Split(MakeSpan(input), '-');
BOOST_CHECK_EQUAL(results.size(), 1);
BOOST_CHECK_EQUAL(SpanToStr(results[0]), "one#two#three");
input = "one#two#three";
results = Split(MakeSpan(input), '#');
BOOST_CHECK_EQUAL(results.size(), 3);
BOOST_CHECK_EQUAL(SpanToStr(results[0]), "one");
BOOST_CHECK_EQUAL(SpanToStr(results[1]), "two");
BOOST_CHECK_EQUAL(SpanToStr(results[2]), "three");
input = "*foo*bar*";
results = Split(MakeSpan(input), '*');
BOOST_CHECK_EQUAL(results.size(), 4);
BOOST_CHECK_EQUAL(SpanToStr(results[0]), "");
BOOST_CHECK_EQUAL(SpanToStr(results[1]), "foo");
BOOST_CHECK_EQUAL(SpanToStr(results[2]), "bar");
BOOST_CHECK_EQUAL(SpanToStr(results[3]), "");
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

67
src/util/spanparsing.cpp Normal file
View File

@ -0,0 +1,67 @@
// Copyright (c) 2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <util/spanparsing.h>
#include <span.h>
#include <string>
#include <vector>
namespace spanparsing {
bool Const(const std::string& str, Span<const char>& sp)
{
if ((size_t)sp.size() >= str.size() && std::equal(str.begin(), str.end(), sp.begin())) {
sp = sp.subspan(str.size());
return true;
}
return false;
}
bool Func(const std::string& str, Span<const char>& sp)
{
if ((size_t)sp.size() >= str.size() + 2 && sp[str.size()] == '(' && sp[sp.size() - 1] == ')' && std::equal(str.begin(), str.end(), sp.begin())) {
sp = sp.subspan(str.size() + 1, sp.size() - str.size() - 2);
return true;
}
return false;
}
Span<const char> Expr(Span<const char>& sp)
{
int level = 0;
auto it = sp.begin();
while (it != sp.end()) {
if (*it == '(') {
++level;
} else if (level && *it == ')') {
--level;
} else if (level == 0 && (*it == ')' || *it == ',')) {
break;
}
++it;
}
Span<const char> ret = sp.first(it - sp.begin());
sp = sp.subspan(it - sp.begin());
return ret;
}
std::vector<Span<const char>> Split(const Span<const char>& sp, char sep)
{
std::vector<Span<const char>> ret;
auto it = sp.begin();
auto start = it;
while (it != sp.end()) {
if (*it == sep) {
ret.emplace_back(start, it);
start = it + 1;
}
++it;
}
ret.emplace_back(start, it);
return ret;
}
} // namespace spanparsing

50
src/util/spanparsing.h Normal file
View File

@ -0,0 +1,50 @@
// Copyright (c) 2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_UTIL_SPANPARSING_H
#define BITCOIN_UTIL_SPANPARSING_H
#include <span.h>
#include <string>
#include <vector>
namespace spanparsing {
/** Parse a constant.
*
* If sp's initial part matches str, sp is updated to skip that part, and true is returned.
* Otherwise sp is unmodified and false is returned.
*/
bool Const(const std::string& str, Span<const char>& sp);
/** Parse a function call.
*
* If sp's initial part matches str + "(", and sp ends with ")", sp is updated to be the
* section between the braces, and true is returned. Otherwise sp is unmodified and false
* is returned.
*/
bool Func(const std::string& str, Span<const char>& sp);
/** Extract the expression that sp begins with.
*
* This function will return the initial part of sp, up to (but not including) the first
* comma or closing brace, skipping ones that are surrounded by braces. So for example,
* for "foo(bar(1),2),3" the initial part "foo(bar(1),2)" will be returned. sp will be
* updated to skip the initial part that is returned.
*/
Span<const char> Expr(Span<const char>& sp);
/** Split a string on every instance of sep, returning a vector.
*
* If sep does not occur in sp, a singleton with the entirety of sp is returned.
*
* Note that this function does not care about braces, so splitting
* "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
*/
std::vector<Span<const char>> Split(const Span<const char>& sp, char sep);
} // namespace spanparsing
#endif // BITCOIN_UTIL_SPANPARSING_H