diff --git a/examples/yaml_bench/yaml_bench.d b/examples/yaml_bench/yaml_bench.d index 83b4798..0540179 100644 --- a/examples/yaml_bench/yaml_bench.d +++ b/examples/yaml_bench/yaml_bench.d @@ -45,6 +45,7 @@ void extract(ref Node document) @safe void main(string[] args) //@safe { + import std.array : array; bool get = false; bool dump = false; bool reload = false; @@ -113,7 +114,7 @@ void main(string[] args) //@safe loader.resolver = resolver; loader.constructor = constructor; - nodes = loader.loadAll(); + nodes = loader.array; } void runDumpBenchmark() @safe { diff --git a/source/dyaml/composer.d b/source/dyaml/composer.d index 15e1f00..0a75262 100644 --- a/source/dyaml/composer.d +++ b/source/dyaml/composer.d @@ -114,27 +114,6 @@ final class Composer 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: void skipExpected(const EventID id) @safe diff --git a/source/dyaml/loader.d b/source/dyaml/loader.d index ceb7d21..7b7f82d 100644 --- a/source/dyaml/loader.d +++ b/source/dyaml/loader.d @@ -47,6 +47,10 @@ struct Loader string name_ = ""; // Are we done loading? bool done_; + // Last node read from stream + Node currentNode; + // Has the range interface been initialized yet? + bool rangeInitialized; public: @disable this(); @@ -191,25 +195,12 @@ struct Loader * or on a YAML parsing error. */ Node load() @safe - in { - assert(!done_, "Loader: Trying to load YAML twice"); - } - do - { - try - { - 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); - } + enforce!YAMLException(!empty, "Zero documents in stream"); + auto output = front; + popFront(); + enforce!YAMLException(empty, "More than one document in stream"); + return output; } /** Load all YAML documents. @@ -224,63 +215,63 @@ struct Loader * * Throws: YAMLException on a parsing error. */ - Node[] loadAll() @safe + deprecated("Redundant, loader is now an InputRange") Node[] loadAll() @safe { - Node[] nodes; - foreach(ref node; this) + import std.array: array; + 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_; } - - /** Foreach over YAML documents. - * - * Parses documents lazily, when they are needed. - * - * 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 + /** Implements the popFront range primitive. + * + * Reads the next document from the stream, if possible. + */ + void popFront() @safe { - return opApplyImpl(dg); - } - /// Ditto - int opApply(int delegate(ref Node) @system dg) @system - { - return opApplyImpl(dg); - } - - package: - int opApplyImpl(T)(T dg) - in - { - assert(!done_, "Loader: Trying to load YAML twice"); - } - do - { - scope(exit) { done_ = true; } - try + // Composer initialization is done here in case the constructor is + // modified, which is a pretty common case. + static Composer composer; + if (!rangeInitialized) { lazyInitConstructorResolver(); - auto composer = new Composer(parser_, resolver_, constructor_); - - int result; - while(composer.checkNode()) - { - auto node = composer.getNode(); - result = dg(node); - if(result) { break; } - } - - return result; + composer = new Composer(parser_, resolver_, constructor_); + rangeInitialized = true; } - catch(YAMLException e) + assert(!done_, "Loader.popFront called on empty range"); + if (composer.checkNode()) { - throw new YAMLException("Unable to load YAML from %s : %s " - .format(name_, e.msg), e.file, e.line); + currentNode = composer.getNode(); } + 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. Token[] scan() @safe @@ -353,6 +344,7 @@ struct Loader /// Load all YAML documents from a file: @safe unittest { + import std.array : array; import std.file : write; write("example.yaml", "---\n"~ @@ -362,7 +354,7 @@ struct Loader "Hello world 2!\n"~ "...\n" ); - auto nodes = Loader.fromFile("example.yaml").loadAll(); + auto nodes = Loader.fromFile("example.yaml").array; assert(nodes.length == 2); } /// Iterate over YAML documents in a file, lazily loading them: diff --git a/source/dyaml/test/compare.d b/source/dyaml/test/compare.d index 2a06d11..20284d4 100644 --- a/source/dyaml/test/compare.d +++ b/source/dyaml/test/compare.d @@ -34,8 +34,9 @@ void testParser(string dataFilename, string canonicalFilename) @safe /// canonicalFilename = Another file to load, in canonical YAML format. void testLoader(string dataFilename, string canonicalFilename) @safe { - auto data = Loader.fromFile(dataFilename).loadAll(); - auto canonical = Loader.fromFile(canonicalFilename).loadAll(); + import std.array : array; + auto data = Loader.fromFile(dataFilename).array; + auto canonical = Loader.fromFile(canonicalFilename).array; assert(data.length == canonical.length, "Unequal node count"); foreach(n; 0 .. data.length) diff --git a/source/dyaml/test/errors.d b/source/dyaml/test/errors.d index dfd9ae0..eb5efd7 100644 --- a/source/dyaml/test/errors.d +++ b/source/dyaml/test/errors.d @@ -20,8 +20,9 @@ import dyaml.test.common; /// Params: errorFilename = File name to read from. void testLoaderError(string errorFilename) @safe { + import std.array : array; Node[] nodes; - try { nodes = Loader.fromFile(errorFilename).loadAll(); } + try { nodes = Loader.fromFile(errorFilename).array; } catch(YAMLException e) { printException(e); @@ -35,9 +36,10 @@ void testLoaderError(string errorFilename) @safe /// Params: errorFilename = File name to read from. void testLoaderErrorString(string errorFilename) @safe { + import std.array : array; try { - auto nodes = Loader.fromFile(errorFilename).loadAll(); + auto nodes = Loader.fromFile(errorFilename).array; } catch(YAMLException e) { @@ -52,7 +54,8 @@ void testLoaderErrorString(string errorFilename) @safe /// Params: errorFilename = File name to read from. 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) { printException(e); diff --git a/source/dyaml/test/representer.d b/source/dyaml/test/representer.d index ac9f5c3..1600813 100644 --- a/source/dyaml/test/representer.d +++ b/source/dyaml/test/representer.d @@ -68,7 +68,7 @@ void testRepresenterTypes(string codeFilename) @safe auto loader = Loader.fromString(emitStream.data.toUTF8); loader.name = "TEST"; loader.constructor = constructor; - readNodes = loader.loadAll(); + readNodes = loader.array; assert(expectedNodes.length == readNodes.length); foreach(n; 0 .. expectedNodes.length)