Implement range interface for Loader, remove old opApply interface (#196)

* Implement range interface for Loader, remove old opApply interface

* remove dead Composer.getSingleNode

* add Returns: lines to range primitives

* a ddoc fixup
This commit is contained in:
Cameron Ross 2018-09-04 12:31:56 -02:30 committed by BBasile
parent 9c2ae02792
commit 1f5eb76996
6 changed files with 70 additions and 94 deletions

View file

@ -45,6 +45,7 @@ void extract(ref Node document) @safe
void main(string[] args) //@safe void main(string[] args) //@safe
{ {
import std.array : array;
bool get = false; bool get = false;
bool dump = false; bool dump = false;
bool reload = false; bool reload = false;
@ -113,7 +114,7 @@ void main(string[] args) //@safe
loader.resolver = resolver; loader.resolver = resolver;
loader.constructor = constructor; loader.constructor = constructor;
nodes = loader.loadAll(); nodes = loader.array;
} }
void runDumpBenchmark() @safe void runDumpBenchmark() @safe
{ {

View file

@ -114,27 +114,6 @@ final class Composer
return composeDocument(); return composeDocument();
} }
///Get single YAML document, throwing if there is more than one document.
Node getSingleNode() @safe
{
assert(parser_.front.id != EventID.streamEnd,
"Trying to get a node from Composer when there is no node to " ~
"get. use checkNode() to determine if there is a node.");
Node document = composeDocument();
//Ensure that the stream contains no more documents.
enforce(parser_.front.id == EventID.streamEnd,
new ComposerException("Expected single document in the stream, " ~
"but found another document.",
parser_.front.startMark));
skipExpected(EventID.streamEnd);
assert(parser_.empty, "Found event after stream end");
return document;
}
private: private:
void skipExpected(const EventID id) @safe void skipExpected(const EventID id) @safe

View file

@ -47,6 +47,10 @@ struct Loader
string name_ = "<unknown>"; string name_ = "<unknown>";
// Are we done loading? // Are we done loading?
bool done_; bool done_;
// Last node read from stream
Node currentNode;
// Has the range interface been initialized yet?
bool rangeInitialized;
public: public:
@disable this(); @disable this();
@ -191,25 +195,12 @@ struct Loader
* or on a YAML parsing error. * or on a YAML parsing error.
*/ */
Node load() @safe Node load() @safe
in
{ {
assert(!done_, "Loader: Trying to load YAML twice"); enforce!YAMLException(!empty, "Zero documents in stream");
} auto output = front;
do popFront();
{ enforce!YAMLException(empty, "More than one document in stream");
try return output;
{
lazyInitConstructorResolver();
scope(exit) { done_ = true; }
auto composer = new Composer(parser_, resolver_, constructor_);
enforce(composer.checkNode(), new YAMLException("No YAML document to load"));
return composer.getSingleNode();
}
catch(YAMLException e)
{
throw new YAMLException("Unable to load YAML from %s : %s"
.format(name_, e.msg), e.file, e.line);
}
} }
/** Load all YAML documents. /** Load all YAML documents.
@ -224,63 +215,63 @@ struct Loader
* *
* Throws: YAMLException on a parsing error. * Throws: YAMLException on a parsing error.
*/ */
Node[] loadAll() @safe deprecated("Redundant, loader is now an InputRange") Node[] loadAll() @safe
{ {
Node[] nodes; import std.array: array;
foreach(ref node; this) return this.array;
}
/** Implements the empty range primitive.
*
* If there's no more documents left in the stream, this will be true.
*
* Returns: `true` if no more documents left, `false` otherwise.
*/
bool empty() @safe
{
// currentNode and done_ are both invalid until popFront is called once
if (!rangeInitialized)
{ {
nodes ~= node; popFront();
} }
return nodes; return done_;
} }
/** Implements the popFront range primitive.
/** Foreach over YAML documents. *
* * Reads the next document from the stream, if possible.
* Parses documents lazily, when they are needed. */
* void popFront() @safe
* Foreach over a Loader can only be used once; this is enforced by contract.
*
* Throws: YAMLException on a parsing error.
*/
int opApply(int delegate(ref Node) @safe dg) @safe
{ {
return opApplyImpl(dg); // Composer initialization is done here in case the constructor is
} // modified, which is a pretty common case.
/// Ditto static Composer composer;
int opApply(int delegate(ref Node) @system dg) @system if (!rangeInitialized)
{
return opApplyImpl(dg);
}
package:
int opApplyImpl(T)(T dg)
in
{
assert(!done_, "Loader: Trying to load YAML twice");
}
do
{
scope(exit) { done_ = true; }
try
{ {
lazyInitConstructorResolver(); lazyInitConstructorResolver();
auto composer = new Composer(parser_, resolver_, constructor_); composer = new Composer(parser_, resolver_, constructor_);
rangeInitialized = true;
int result;
while(composer.checkNode())
{
auto node = composer.getNode();
result = dg(node);
if(result) { break; }
}
return result;
} }
catch(YAMLException e) assert(!done_, "Loader.popFront called on empty range");
if (composer.checkNode())
{ {
throw new YAMLException("Unable to load YAML from %s : %s " currentNode = composer.getNode();
.format(name_, e.msg), e.file, e.line);
} }
else
{
done_ = true;
}
}
/** Implements the front range primitive.
*
* Returns: the current document as a Node.
*/
Node front() @safe
{
// currentNode and done_ are both invalid until popFront is called once
if (!rangeInitialized)
{
popFront();
}
return currentNode;
} }
// Scan and return all tokens. Used for debugging. // Scan and return all tokens. Used for debugging.
Token[] scan() @safe Token[] scan() @safe
@ -353,6 +344,7 @@ struct Loader
/// Load all YAML documents from a file: /// Load all YAML documents from a file:
@safe unittest @safe unittest
{ {
import std.array : array;
import std.file : write; import std.file : write;
write("example.yaml", write("example.yaml",
"---\n"~ "---\n"~
@ -362,7 +354,7 @@ struct Loader
"Hello world 2!\n"~ "Hello world 2!\n"~
"...\n" "...\n"
); );
auto nodes = Loader.fromFile("example.yaml").loadAll(); auto nodes = Loader.fromFile("example.yaml").array;
assert(nodes.length == 2); assert(nodes.length == 2);
} }
/// Iterate over YAML documents in a file, lazily loading them: /// Iterate over YAML documents in a file, lazily loading them:

View file

@ -34,8 +34,9 @@ void testParser(string dataFilename, string canonicalFilename) @safe
/// canonicalFilename = Another file to load, in canonical YAML format. /// canonicalFilename = Another file to load, in canonical YAML format.
void testLoader(string dataFilename, string canonicalFilename) @safe void testLoader(string dataFilename, string canonicalFilename) @safe
{ {
auto data = Loader.fromFile(dataFilename).loadAll(); import std.array : array;
auto canonical = Loader.fromFile(canonicalFilename).loadAll(); auto data = Loader.fromFile(dataFilename).array;
auto canonical = Loader.fromFile(canonicalFilename).array;
assert(data.length == canonical.length, "Unequal node count"); assert(data.length == canonical.length, "Unequal node count");
foreach(n; 0 .. data.length) foreach(n; 0 .. data.length)

View file

@ -20,8 +20,9 @@ import dyaml.test.common;
/// Params: errorFilename = File name to read from. /// Params: errorFilename = File name to read from.
void testLoaderError(string errorFilename) @safe void testLoaderError(string errorFilename) @safe
{ {
import std.array : array;
Node[] nodes; Node[] nodes;
try { nodes = Loader.fromFile(errorFilename).loadAll(); } try { nodes = Loader.fromFile(errorFilename).array; }
catch(YAMLException e) catch(YAMLException e)
{ {
printException(e); printException(e);
@ -35,9 +36,10 @@ void testLoaderError(string errorFilename) @safe
/// Params: errorFilename = File name to read from. /// Params: errorFilename = File name to read from.
void testLoaderErrorString(string errorFilename) @safe void testLoaderErrorString(string errorFilename) @safe
{ {
import std.array : array;
try try
{ {
auto nodes = Loader.fromFile(errorFilename).loadAll(); auto nodes = Loader.fromFile(errorFilename).array;
} }
catch(YAMLException e) catch(YAMLException e)
{ {
@ -52,7 +54,8 @@ void testLoaderErrorString(string errorFilename) @safe
/// Params: errorFilename = File name to read from. /// Params: errorFilename = File name to read from.
void testLoaderErrorFilename(string errorFilename) @safe void testLoaderErrorFilename(string errorFilename) @safe
{ {
try { auto nodes = Loader.fromFile(errorFilename).loadAll(); } import std.array : array;
try { auto nodes = Loader.fromFile(errorFilename).array; }
catch(YAMLException e) catch(YAMLException e)
{ {
printException(e); printException(e);

View file

@ -68,7 +68,7 @@ void testRepresenterTypes(string codeFilename) @safe
auto loader = Loader.fromString(emitStream.data.toUTF8); auto loader = Loader.fromString(emitStream.data.toUTF8);
loader.name = "TEST"; loader.name = "TEST";
loader.constructor = constructor; loader.constructor = constructor;
readNodes = loader.loadAll(); readNodes = loader.array;
assert(expectedNodes.length == readNodes.length); assert(expectedNodes.length == readNodes.length);
foreach(n; 0 .. expectedNodes.length) foreach(n; 0 .. expectedNodes.length)