mirror of https://github.com/adamdruppe/arsd.git
Remove destruciveness footguns and add further documentation
This commit is contained in:
parent
5c7538421f
commit
a2fe6f1fb4
150
ini.d
150
ini.d
|
|
@ -17,6 +17,51 @@
|
||||||
return parseIniDocument(readText(filePath));
|
return parseIniDocument(readText(filePath));
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
### On destructiveness and GC usage
|
||||||
|
|
||||||
|
Depending on the dialect and string type,
|
||||||
|
[IniParser] can operate in one of these three modes:
|
||||||
|
|
||||||
|
$(LIST
|
||||||
|
* Non-destructive with no heap alloc (incl. `@nogc`)
|
||||||
|
* Non-destructive (uses the GC)
|
||||||
|
* Destructive with no heap alloc (incl. `@nogc`)
|
||||||
|
)
|
||||||
|
|
||||||
|
a) If a given dialect requests no mutation of the input data
|
||||||
|
(i.e. no escape sequences, no concaternation of substrings etc.)
|
||||||
|
and is therefore possible to implement with slicing operations only,
|
||||||
|
the parser will be non-destructive and not do any heap allocations.
|
||||||
|
Such a parser is verifiably `@nogc`, too.
|
||||||
|
|
||||||
|
b) In cases where a dialect requires data-mutating operations,
|
||||||
|
there are two ways for a parser to implement them:
|
||||||
|
|
||||||
|
b.0) Either perform those mutations on the input data itself
|
||||||
|
and alter the contents of that buffer.
|
||||||
|
Because of the destructive nature of this operation,
|
||||||
|
it can be performed only once safely.
|
||||||
|
(Such an implementation could optionally fix up the modified data
|
||||||
|
to become valid and parsable again.
|
||||||
|
Though doing so would come with a performance overhead.)
|
||||||
|
|
||||||
|
b.1) Or allocate a new buffer for the result of the operation.
|
||||||
|
This also has the advantage that it works with `immutable` and `const`
|
||||||
|
input data.
|
||||||
|
For convenience reasons the GC is used to perform such allocations.
|
||||||
|
|
||||||
|
Use [IniParser.isDestructive] to check for the operating mode.
|
||||||
|
|
||||||
|
The construct a non-destructive parser despite a mutable input data,
|
||||||
|
specify `const(char)[]` as the value of the `string` template parameter.
|
||||||
|
|
||||||
|
---
|
||||||
|
char[] mutableInput = [ /* … */ ];
|
||||||
|
auto parser = makeIniParser!(dialect, const(char)[])(mutableInput);
|
||||||
|
assert(parser.isDestructive == false);
|
||||||
|
---
|
||||||
+/
|
+/
|
||||||
module arsd.ini;
|
module arsd.ini;
|
||||||
|
|
||||||
|
|
@ -336,6 +381,18 @@ struct IniParser(
|
||||||
public {
|
public {
|
||||||
///
|
///
|
||||||
alias Token = IniToken!string;
|
alias Token = IniToken!string;
|
||||||
|
|
||||||
|
// dfmt off
|
||||||
|
///
|
||||||
|
enum isDestructive = (
|
||||||
|
(operatingMode!string == OperatingMode.destructive)
|
||||||
|
&& (
|
||||||
|
dialect.hasFeature(Dialect.concatSubstrings)
|
||||||
|
|| dialect.hasFeature(Dialect.escapeSequences)
|
||||||
|
|| dialect.hasFeature(Dialect.lineFolding)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// dfmt on
|
||||||
}
|
}
|
||||||
|
|
||||||
private {
|
private {
|
||||||
|
|
@ -364,17 +421,16 @@ struct IniParser(
|
||||||
public {
|
public {
|
||||||
|
|
||||||
///
|
///
|
||||||
bool empty() const {
|
bool empty() const @nogc {
|
||||||
return _empty;
|
return _empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
inout(Token) front() inout {
|
inout(Token) front() inout @nogc {
|
||||||
return _front;
|
return _front;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
private void popFrontImpl() {
|
||||||
void popFront() {
|
|
||||||
if (_source.length == 0) {
|
if (_source.length == 0) {
|
||||||
_empty = true;
|
_empty = true;
|
||||||
return;
|
return;
|
||||||
|
|
@ -383,9 +439,35 @@ struct IniParser(
|
||||||
_front = this.fetchFront();
|
_front = this.fetchFront();
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
/*
|
||||||
inout(typeof(this)) save() inout {
|
This is a workaround.
|
||||||
return this;
|
The compiler doesn’t feel like inferring `@nogc` properly otherwise.
|
||||||
|
|
||||||
|
→ cannot call non-@nogc function
|
||||||
|
`arsd.ini.makeIniParser!(IniDialect.concatSubstrings, char[]).makeIniParser`
|
||||||
|
→ which calls
|
||||||
|
`arsd.ini.IniParser!(IniDialect.concatSubstrings, char[]).IniParser.this`
|
||||||
|
→ which calls
|
||||||
|
`arsd.ini.IniParser!(IniDialect.concatSubstrings, char[]).IniParser.popFront`
|
||||||
|
*/
|
||||||
|
static if (isDestructive) {
|
||||||
|
///
|
||||||
|
void popFront() @nogc {
|
||||||
|
popFrontImpl();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
///
|
||||||
|
void popFront() {
|
||||||
|
popFrontImpl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destructive parsers make very poor Forward Ranges.
|
||||||
|
static if (!isDestructive) {
|
||||||
|
///
|
||||||
|
inout(typeof(this)) save() inout @nogc {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -705,7 +787,7 @@ struct IniParser(
|
||||||
|
|
||||||
Token token = this.lexSubstringImpl!tokenType();
|
Token token = this.lexSubstringImpl!tokenType();
|
||||||
|
|
||||||
auto next = this.save();
|
auto next = this; // copy
|
||||||
next._bypassConcatSubstrings = true;
|
next._bypassConcatSubstrings = true;
|
||||||
next.popFront();
|
next.popFront();
|
||||||
|
|
||||||
|
|
@ -885,6 +967,9 @@ struct IniFilteredParser(
|
||||||
///
|
///
|
||||||
public alias Token = IniToken!string;
|
public alias Token = IniToken!string;
|
||||||
|
|
||||||
|
///
|
||||||
|
public enum isDestructive = IniParser!(dialect, string).isDestructive;
|
||||||
|
|
||||||
private IniParser!(dialect, string) _parser;
|
private IniParser!(dialect, string) _parser;
|
||||||
|
|
||||||
public @safe pure nothrow:
|
public @safe pure nothrow:
|
||||||
|
|
@ -901,10 +986,10 @@ public @safe pure nothrow:
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
bool empty() const => _parser.empty;
|
bool empty() const @nogc => _parser.empty;
|
||||||
|
|
||||||
///
|
///
|
||||||
inout(Token) front() inout => _parser.front;
|
inout(Token) front() inout @nogc => _parser.front;
|
||||||
|
|
||||||
///
|
///
|
||||||
void popFront() {
|
void popFront() {
|
||||||
|
|
@ -912,14 +997,16 @@ public @safe pure nothrow:
|
||||||
_parser.skipIrrelevant(true);
|
_parser.skipIrrelevant(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
static if (!isDestructive) {
|
||||||
inout(typeof(this)) save() inout {
|
///
|
||||||
return this;
|
inout(typeof(this)) save() inout @nogc {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@safe unittest {
|
@safe @nogc unittest {
|
||||||
// INI document (demo data)
|
// INI document (demo data)
|
||||||
static immutable string rawIniDocument = `; This is a comment.
|
static immutable string rawIniDocument = `; This is a comment.
|
||||||
[section1]
|
[section1]
|
||||||
|
|
@ -960,7 +1047,7 @@ oachkatzl = schwoaf ;try pronouncing that
|
||||||
assert(values == 2);
|
assert(values == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@safe unittest {
|
@safe @nogc unittest {
|
||||||
static immutable string rawIniDocument = `; This is a comment.
|
static immutable string rawIniDocument = `; This is a comment.
|
||||||
[section1]
|
[section1]
|
||||||
s1key1 = value1
|
s1key1 = value1
|
||||||
|
|
@ -1079,7 +1166,7 @@ s2key2 = value no.4
|
||||||
assert(parser.empty());
|
assert(parser.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@safe unittest {
|
@safe @nogc unittest {
|
||||||
static immutable rawIni = "#not-a = comment";
|
static immutable rawIni = "#not-a = comment";
|
||||||
auto parser = makeIniParser(rawIni);
|
auto parser = makeIniParser(rawIni);
|
||||||
|
|
||||||
|
|
@ -1094,7 +1181,7 @@ s2key2 = value no.4
|
||||||
assert(parser.empty);
|
assert(parser.empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@safe unittest {
|
@safe @nogc unittest {
|
||||||
static immutable rawIni = "#actually_a = comment\r\n\t#another one\r\n\t\t ; oh, and a third one";
|
static immutable rawIni = "#actually_a = comment\r\n\t#another one\r\n\t\t ; oh, and a third one";
|
||||||
enum dialect = (Dialect.hashLineComments | Dialect.lineComments);
|
enum dialect = (Dialect.hashLineComments | Dialect.lineComments);
|
||||||
auto parser = makeIniParser!dialect(rawIni);
|
auto parser = makeIniParser!dialect(rawIni);
|
||||||
|
|
@ -1114,7 +1201,7 @@ s2key2 = value no.4
|
||||||
assert(parser.empty);
|
assert(parser.empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@safe unittest {
|
@safe @nogc unittest {
|
||||||
static immutable rawIni = ";not a = line comment\nkey = value ;not-a-comment \nfoo = bar # not a comment\t";
|
static immutable rawIni = ";not a = line comment\nkey = value ;not-a-comment \nfoo = bar # not a comment\t";
|
||||||
enum dialect = Dialect.lite;
|
enum dialect = Dialect.lite;
|
||||||
auto parser = makeIniParser!dialect(rawIni);
|
auto parser = makeIniParser!dialect(rawIni);
|
||||||
|
|
@ -1149,7 +1236,7 @@ s2key2 = value no.4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@safe unittest {
|
@safe @nogc unittest {
|
||||||
static immutable rawIni = "; line comment 0\t\n\nkey = value ; comment-1\nfoo = bar #comment 2\n";
|
static immutable rawIni = "; line comment 0\t\n\nkey = value ; comment-1\nfoo = bar #comment 2\n";
|
||||||
enum dialect = (Dialect.inlineComments | Dialect.hashInlineComments);
|
enum dialect = (Dialect.inlineComments | Dialect.hashInlineComments);
|
||||||
auto parser = makeIniParser!dialect(rawIni);
|
auto parser = makeIniParser!dialect(rawIni);
|
||||||
|
|
@ -1191,7 +1278,7 @@ s2key2 = value no.4
|
||||||
assert(parser.skipIrrelevant(false));
|
assert(parser.skipIrrelevant(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@safe unittest {
|
@safe @nogc unittest {
|
||||||
static immutable rawIni = "key = value;inline";
|
static immutable rawIni = "key = value;inline";
|
||||||
enum dialect = Dialect.inlineComments;
|
enum dialect = Dialect.inlineComments;
|
||||||
auto parser = makeIniParser!dialect(rawIni);
|
auto parser = makeIniParser!dialect(rawIni);
|
||||||
|
|
@ -1211,7 +1298,7 @@ s2key2 = value no.4
|
||||||
assert(parser.empty);
|
assert(parser.empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@safe unittest {
|
@safe @nogc unittest {
|
||||||
static immutable rawIni = "key: value\n"
|
static immutable rawIni = "key: value\n"
|
||||||
~ "foo= bar\n"
|
~ "foo= bar\n"
|
||||||
~ "lol :rofl\n"
|
~ "lol :rofl\n"
|
||||||
|
|
@ -1274,7 +1361,7 @@ s2key2 = value no.4
|
||||||
assert(parser.skipIrrelevant());
|
assert(parser.skipIrrelevant());
|
||||||
}
|
}
|
||||||
|
|
||||||
@safe unittest {
|
@safe @nogc unittest {
|
||||||
static immutable rawIni =
|
static immutable rawIni =
|
||||||
"\"foo=bar\"=foobar\n"
|
"\"foo=bar\"=foobar\n"
|
||||||
~ "'foo = bar' = foo_bar\n"
|
~ "'foo = bar' = foo_bar\n"
|
||||||
|
|
@ -1392,7 +1479,7 @@ IniParser!(dialect, string) makeIniParser(
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@safe unittest {
|
@safe @nogc unittest {
|
||||||
string regular;
|
string regular;
|
||||||
auto parser1 = makeIniParser(regular);
|
auto parser1 = makeIniParser(regular);
|
||||||
assert(parser1.empty); // exclude from docs
|
assert(parser1.empty); // exclude from docs
|
||||||
|
|
@ -1404,6 +1491,21 @@ IniParser!(dialect, string) makeIniParser(
|
||||||
const(char)[] constChars;
|
const(char)[] constChars;
|
||||||
auto parser3 = makeIniParser(constChars);
|
auto parser3 = makeIniParser(constChars);
|
||||||
assert(parser3.empty); // exclude from docs
|
assert(parser3.empty); // exclude from docs
|
||||||
|
|
||||||
|
assert(!parser1.isDestructive); // exclude from docs
|
||||||
|
assert(!parser2.isDestructive); // exclude from docs
|
||||||
|
assert(!parser3.isDestructive); // exclude from docs
|
||||||
|
}
|
||||||
|
|
||||||
|
@safe unittest {
|
||||||
|
char[] mutableInput;
|
||||||
|
enum dialect = Dialect.concatSubstrings;
|
||||||
|
|
||||||
|
auto parser1 = makeIniParser!(dialect, const(char)[])(mutableInput);
|
||||||
|
auto parser2 = (() @nogc => makeIniParser!(dialect)(mutableInput))();
|
||||||
|
|
||||||
|
assert(!parser1.isDestructive);
|
||||||
|
assert(parser2.isDestructive);
|
||||||
}
|
}
|
||||||
|
|
||||||
/++
|
/++
|
||||||
|
|
@ -1427,7 +1529,7 @@ IniFilteredParser!(dialect, string) makeIniFilteredParser(
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@safe unittest {
|
@safe @nogc unittest {
|
||||||
string regular;
|
string regular;
|
||||||
auto parser1 = makeIniFilteredParser(regular);
|
auto parser1 = makeIniFilteredParser(regular);
|
||||||
assert(parser1.empty); // exclude from docs
|
assert(parser1.empty); // exclude from docs
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue