Move custom types to Node (#213)

Move custom types to Node
merged-on-behalf-of: BBasile <BBasile@users.noreply.github.com>
This commit is contained in:
Cameron Ross 2019-01-15 04:07:50 -03:30 committed by The Dlang Bot
parent beb160f1eb
commit 7f913246ea
20 changed files with 1152 additions and 1679 deletions

View file

@ -46,8 +46,6 @@ final class Composer
Parser parser_;
///Resolver resolving tags (data types).
Resolver resolver_;
///Constructor constructing YAML values.
Constructor constructor_;
///Nodes associated with anchors. Used by YAML aliases.
Node[string] anchors_;
@ -70,13 +68,11 @@ final class Composer
*
* Params: parser = Parser to provide YAML events.
* resolver = Resolver to resolve tags (data types).
* constructor = Constructor to construct nodes.
*/
this(Parser parser, Resolver resolver, Constructor constructor) @safe
this(Parser parser, Resolver resolver) @safe
{
parser_ = parser;
resolver_ = resolver;
constructor_ = constructor;
}
///Destroy the composer.
@ -84,7 +80,6 @@ final class Composer
{
parser_ = null;
resolver_ = null;
constructor_ = null;
anchors_.destroy();
anchors_ = null;
}
@ -222,8 +217,9 @@ final class Composer
const tag = resolver_.resolve(NodeID.scalar, event.tag, event.value,
event.implicit);
Node node = constructor_.node(event.startMark, event.endMark, tag,
event.value, event.scalarStyle);
Node node = constructNode(event.startMark, event.endMark, tag,
event.value);
node.scalarStyle = event.scalarStyle;
return node;
}
@ -248,8 +244,9 @@ final class Composer
nodeAppender.put(composeNode(pairAppenderLevel, nodeAppenderLevel + 1));
}
Node node = constructor_.node(startEvent.startMark, parser_.front.endMark,
tag, nodeAppender.data.dup, startEvent.collectionStyle);
Node node = constructNode(startEvent.startMark, parser_.front.endMark,
tag, nodeAppender.data.dup);
node.collectionStyle = startEvent.collectionStyle;
parser_.popFront();
nodeAppender.clear();
@ -370,8 +367,9 @@ final class Composer
enforce(numUnique == pairAppender.data.length,
new ComposerException("Duplicate key found in mapping", parser_.front.startMark));
Node node = constructor_.node(startEvent.startMark, parser_.front.endMark,
tag, pairAppender.data.dup, startEvent.collectionStyle);
Node node = constructNode(startEvent.startMark, parser_.front.endMark,
tag, pairAppender.data.dup);
node.collectionStyle = startEvent.collectionStyle;
parser_.popFront();
pairAppender.clear();

View file

@ -28,9 +28,10 @@ import dyaml.node;
import dyaml.exception;
import dyaml.style;
package:
// Exception thrown at constructor errors.
package class ConstructorException : YAMLException
class ConstructorException : YAMLException
{
/// Construct a ConstructorException.
///
@ -59,408 +60,148 @@ package class ConstructorException : YAMLException
*
* If a tag is detected with no known constructor function, it is considered an error.
*/
final class Constructor
/*
* Construct a node.
*
* Params: start = Start position of the node.
* end = End position of the node.
* tag = Tag (data type) of the node.
* value = Value to construct node from (string, nodes or pairs).
* style = Style of the node (scalar or collection style).
*
* Returns: Constructed node.
*/
Node constructNode(T)(const Mark start, const Mark end, const string tag,
T value) @safe
if((is(T : string) || is(T == Node[]) || is(T == Node.Pair[])))
{
private:
// Constructor functions from scalars.
Node delegate(ref Node) @safe[string] fromScalar_;
// Constructor functions from sequences.
Node delegate(ref Node) @safe[string] fromSequence_;
// Constructor functions from mappings.
Node delegate(ref Node) @safe[string] fromMapping_;
public:
/// Construct a Constructor.
///
/// If you don't want to support default YAML tags/data types, you can use
/// defaultConstructors to disable constructor functions for these.
///
/// Params: defaultConstructors = Use constructors for default YAML tags?
this(const Flag!"useDefaultConstructors" defaultConstructors = Yes.useDefaultConstructors)
@safe nothrow
Node newNode;
try
{
switch(tag)
{
if(!defaultConstructors){return;}
addConstructorScalar("tag:yaml.org,2002:null", &constructNull);
addConstructorScalar("tag:yaml.org,2002:bool", &constructBool);
addConstructorScalar("tag:yaml.org,2002:int", &constructLong);
addConstructorScalar("tag:yaml.org,2002:float", &constructReal);
addConstructorScalar("tag:yaml.org,2002:binary", &constructBinary);
addConstructorScalar("tag:yaml.org,2002:timestamp", &constructTimestamp);
addConstructorScalar("tag:yaml.org,2002:str", &constructString);
///In a mapping, the default value is kept as an entry with the '=' key.
addConstructorScalar("tag:yaml.org,2002:value", &constructString);
addConstructorSequence("tag:yaml.org,2002:omap", &constructOrderedMap);
addConstructorSequence("tag:yaml.org,2002:pairs", &constructPairs);
addConstructorMapping("tag:yaml.org,2002:set", &constructSet);
addConstructorSequence("tag:yaml.org,2002:seq", &constructSequence);
addConstructorMapping("tag:yaml.org,2002:map", &constructMap);
addConstructorScalar("tag:yaml.org,2002:merge", &constructMerge);
}
/** Add a constructor function from scalar.
*
* The function must take a reference to $(D Node) to construct from.
* The node contains a string for scalars, $(D Node[]) for sequences and
* $(D Node.Pair[]) for mappings.
*
* Any exception thrown by this function will be caught by D:YAML and
* its message will be added to a $(D YAMLException) that will also tell
* the user which type failed to construct, and position in the file.
*
*
* The value returned by this function will be stored in the resulting node.
*
* Only one constructor function can be set for one tag.
*
*
* Structs and classes must implement the $(D opCmp()) operator for D:YAML
* support. The signature of the operator that must be implemented
* is $(D const int opCmp(ref const MyStruct s)) for structs where
* $(I MyStruct) is the struct type, and $(D int opCmp(Object o)) for
* classes. Note that the class $(D opCmp()) should not alter the compared
* values - it is not const for compatibility reasons.
*
* Params: tag = Tag for the function to handle.
* ctor = Constructor function.
*/
void addConstructorScalar(T)(const string tag, T function(ref Node) @safe ctor)
{
const t = tag;
const deleg = addConstructor!T(t, ctor);
(*delegates!string)[t] = deleg;
}
///
@safe unittest
{
static struct MyStruct
{
int x, y, z;
//Any D:YAML type must have a custom opCmp operator.
//This is used for ordering in mappings.
int opCmp(ref const MyStruct s) const
case "tag:yaml.org,2002:null":
newNode = Node(YAMLNull(), tag);
break;
case "tag:yaml.org,2002:bool":
static if(is(T == string))
{
if(x != s.x){return x - s.x;}
if(y != s.y){return y - s.y;}
if(z != s.z){return z - s.z;}
return 0;
newNode = Node(constructBool(value), tag);
break;
}
}
static MyStruct constructMyStructScalar(ref Node node) @safe
{
//Guaranteed to be string as we construct from scalar.
//!mystruct x:y:z
auto parts = node.as!string().split(":");
// If this throws, the D:YAML will handle it and throw a YAMLException.
return MyStruct(to!int(parts[0]), to!int(parts[1]), to!int(parts[2]));
}
import dyaml.loader : Loader;
auto loader = Loader.fromString("!mystruct 12:34:56");
auto constructor = new Constructor;
constructor.addConstructorScalar("!mystruct", &constructMyStructScalar);
loader.constructor = constructor;
Node node = loader.load();
assert(node.get!MyStruct == MyStruct(12, 34, 56));
}
/** Add a constructor function from sequence.
*
* See_Also: addConstructorScalar
*/
void addConstructorSequence(T)(const string tag, T function(ref Node) @safe ctor)
{
const t = tag;
const deleg = addConstructor!T(t, ctor);
(*delegates!(Node[]))[t] = deleg;
}
///
@safe unittest
{
static struct MyStruct
{
int x, y, z;
//Any D:YAML type must have a custom opCmp operator.
//This is used for ordering in mappings.
int opCmp(ref const MyStruct s) const
else throw new Exception("Only scalars can be bools");
case "tag:yaml.org,2002:int":
static if(is(T == string))
{
if(x != s.x){return x - s.x;}
if(y != s.y){return y - s.y;}
if(z != s.z){return z - s.z;}
return 0;
newNode = Node(constructLong(value), tag);
break;
}
}
static MyStruct constructMyStructSequence(ref Node node) @safe
{
//node is guaranteed to be sequence.
//!mystruct [x, y, z]
return MyStruct(node[0].as!int, node[1].as!int, node[2].as!int);
}
import dyaml.loader : Loader;
auto loader = Loader.fromString("!mystruct [1,2,3]");
auto constructor = new Constructor;
constructor.addConstructorSequence("!mystruct", &constructMyStructSequence);
loader.constructor = constructor;
Node node = loader.load();
assert(node.get!MyStruct == MyStruct(1, 2, 3));
}
/** Add a constructor function from a mapping.
*
* See_Also: addConstructorScalar
*/
void addConstructorMapping(T)(const string tag, T function(ref Node) @safe ctor)
{
const t = tag;
const deleg = addConstructor!T(t, ctor);
(*delegates!(Node.Pair[]))[t] = deleg;
}
///
@safe unittest {
static struct MyStruct
{
int x, y, z;
//Any D:YAML type must have a custom opCmp operator.
//This is used for ordering in mappings.
int opCmp(ref const MyStruct s) const
else throw new Exception("Only scalars can be ints");
case "tag:yaml.org,2002:float":
static if(is(T == string))
{
if(x != s.x){return x - s.x;}
if(y != s.y){return y - s.y;}
if(z != s.z){return z - s.z;}
return 0;
newNode = Node(constructReal(value), tag);
break;
}
}
static MyStruct constructMyStructMapping(ref Node node) @safe
{
//node is guaranteed to be mapping.
//!mystruct {"x": x, "y": y, "z": z}
return MyStruct(node["x"].as!int, node["y"].as!int, node["z"].as!int);
}
import dyaml.loader : Loader;
auto loader = Loader.fromString(`!mystruct {"x": 11, "y": 22, "z": 33}`);
auto constructor = new Constructor;
constructor.addConstructorMapping("!mystruct", &constructMyStructMapping);
loader.constructor = constructor;
Node node = loader.load();
assert(node.get!MyStruct == MyStruct(11, 22, 33));
}
package:
/*
* Construct a node.
*
* Params: start = Start position of the node.
* end = End position of the node.
* tag = Tag (data type) of the node.
* value = Value to construct node from (string, nodes or pairs).
* style = Style of the node (scalar or collection style).
*
* Returns: Constructed node.
*/
Node node(T, U)(const Mark start, const Mark end, const string tag,
T value, U style) @safe
if((is(T : string) || is(T == Node[]) || is(T == Node.Pair[])) &&
(is(U : CollectionStyle) || is(U : ScalarStyle)))
{
enum type = is(T : string) ? "scalar" :
is(T == Node[]) ? "sequence" :
is(T == Node.Pair[]) ? "mapping" :
"ERROR";
enforce((tag in *delegates!T) !is null,
new ConstructorException("No constructor function from " ~ type ~
" for tag " ~ tag, start, end));
Node node = Node(value);
try
{
static if(is(U : ScalarStyle))
else throw new Exception("Only scalars can be floats");
case "tag:yaml.org,2002:binary":
static if(is(T == string))
{
auto newNode = (*delegates!T)[tag](node);
newNode.startMark_ = start;
newNode.scalarStyle = style;
return newNode;
newNode = Node(constructBinary(value), tag);
break;
}
else static if(is(U : CollectionStyle))
else throw new Exception("Only scalars can be binary data");
case "tag:yaml.org,2002:timestamp":
static if(is(T == string))
{
auto newNode = (*delegates!T)[tag](node);
newNode.startMark_ = start;
newNode.collectionStyle = style;
return newNode;
newNode = Node(constructTimestamp(value), tag);
break;
}
else static assert(false);
}
catch(Exception e)
{
throw new ConstructorException("Error constructing " ~ typeid(T).toString()
~ ":\n" ~ e.msg, start, end);
}
}
private:
/*
* Add a constructor function.
*
* Params: tag = Tag for the function to handle.
* ctor = Constructor function.
*/
auto addConstructor(T)(const string tag, T function(ref Node) @safe ctor)
{
assert((tag in fromScalar_) is null &&
(tag in fromSequence_) is null &&
(tag in fromMapping_) is null,
"Constructor function for tag " ~ tag ~ " is already " ~
"specified. Can't specify another one.");
return (ref Node n) @safe
{
return Node(ctor(n), tag);
};
}
//Get the array of constructor functions for scalar, sequence or mapping.
@property auto delegates(T)()
{
static if(is(T : string)) {return &fromScalar_;}
else static if(is(T : Node[])) {return &fromSequence_;}
else static if(is(T : Node.Pair[])){return &fromMapping_;}
else static assert(false);
}
}
///Construct a struct from a scalar
@safe unittest
{
static struct MyStruct
{
int x, y, z;
int opCmp(ref const MyStruct s) const pure @safe nothrow
{
if(x != s.x){return x - s.x;}
if(y != s.y){return y - s.y;}
if(z != s.z){return z - s.z;}
return 0;
else throw new Exception("Only scalars can be timestamps");
case "tag:yaml.org,2002:str":
static if(is(T == string))
{
newNode = Node(constructString(value), tag);
break;
}
else throw new Exception("Only scalars can be strings");
case "tag:yaml.org,2002:value":
static if(is(T == string))
{
newNode = Node(constructString(value), tag);
break;
}
else throw new Exception("Only scalars can be values");
case "tag:yaml.org,2002:omap":
static if(is(T == Node[]))
{
newNode = Node(constructOrderedMap(value), tag);
break;
}
else throw new Exception("Only sequences can be ordered maps");
case "tag:yaml.org,2002:pairs":
static if(is(T == Node[]))
{
newNode = Node(constructPairs(value), tag);
break;
}
else throw new Exception("Only sequences can be pairs");
case "tag:yaml.org,2002:set":
static if(is(T == Node.Pair[]))
{
newNode = Node(constructSet(value), tag);
break;
}
else throw new Exception("Only mappings can be sets");
case "tag:yaml.org,2002:seq":
static if(is(T == Node[]))
{
newNode = Node(constructSequence(value), tag);
break;
}
else throw new Exception("Only sequences can be sequences");
case "tag:yaml.org,2002:map":
static if(is(T == Node.Pair[]))
{
newNode = Node(constructMap(value), tag);
break;
}
else throw new Exception("Only mappings can be maps");
case "tag:yaml.org,2002:merge":
newNode = Node(YAMLMerge(), tag);
break;
default:
newNode = Node(value, tag);
break;
}
}
static MyStruct constructMyStructScalar(ref Node node) @safe
catch(Exception e)
{
// Guaranteed to be string as we construct from scalar.
auto parts = node.as!string().split(":");
return MyStruct(to!int(parts[0]), to!int(parts[1]), to!int(parts[2]));
throw new ConstructorException("Error constructing " ~ typeid(T).toString()
~ ":\n" ~ e.msg, start, end);
}
import dyaml.loader : Loader;
string data = "!mystruct 1:2:3";
auto loader = Loader.fromString(data);
auto constructor = new Constructor;
constructor.addConstructorScalar("!mystruct", &constructMyStructScalar);
loader.constructor = constructor;
Node node = loader.load();
newNode.startMark_ = start;
assert(node.as!MyStruct == MyStruct(1, 2, 3));
}
///Construct a struct from a sequence
@safe unittest
{
static struct MyStruct
{
int x, y, z;
int opCmp(ref const MyStruct s) const pure @safe nothrow
{
if(x != s.x){return x - s.x;}
if(y != s.y){return y - s.y;}
if(z != s.z){return z - s.z;}
return 0;
}
}
static MyStruct constructMyStructSequence(ref Node node) @safe
{
// node is guaranteed to be sequence.
return MyStruct(node[0].as!int, node[1].as!int, node[2].as!int);
}
import dyaml.loader : Loader;
string data = "!mystruct [1, 2, 3]";
auto loader = Loader.fromString(data);
auto constructor = new Constructor;
constructor.addConstructorSequence("!mystruct", &constructMyStructSequence);
loader.constructor = constructor;
Node node = loader.load();
assert(node.as!MyStruct == MyStruct(1, 2, 3));
}
///Construct a struct from a mapping
@safe unittest
{
static struct MyStruct
{
int x, y, z;
int opCmp(ref const MyStruct s) const pure @safe nothrow
{
if(x != s.x){return x - s.x;}
if(y != s.y){return y - s.y;}
if(z != s.z){return z - s.z;}
return 0;
}
}
static MyStruct constructMyStructMapping(ref Node node) @safe
{
// node is guaranteed to be mapping.
return MyStruct(node["x"].as!int, node["y"].as!int, node["z"].as!int);
}
import dyaml.loader : Loader;
string data = "!mystruct {x: 1, y: 2, z: 3}";
auto loader = Loader.fromString(data);
auto constructor = new Constructor;
constructor.addConstructorMapping("!mystruct", &constructMyStructMapping);
loader.constructor = constructor;
Node node = loader.load();
assert(node.as!MyStruct == MyStruct(1, 2, 3));
return newNode;
}
/// Construct a _null _node.
YAMLNull constructNull(ref Node node) @safe pure nothrow @nogc
{
return YAMLNull();
}
/// Construct a merge _node - a _node that merges another _node into a mapping.
YAMLMerge constructMerge(ref Node node) @safe pure nothrow @nogc
{
return YAMLMerge();
}
/// Construct a boolean _node.
bool constructBool(ref Node node) @safe
private:
// Construct a boolean _node.
bool constructBool(const string str) @safe
{
static yes = ["yes", "true", "on"];
static no = ["no", "false", "off"];
string value = node.as!string().toLower();
string value = str.toLower();
if(yes.canFind(value)){return true;}
if(no.canFind(value)) {return false;}
throw new Exception("Unable to parse boolean value: " ~ value);
}
/// Construct an integer (long) _node.
long constructLong(ref Node node) @safe
// Construct an integer (long) _node.
long constructLong(const string str) @safe
{
string value = node.as!string().replace("_", "");
string value = str.replace("_", "");
const char c = value[0];
const long sign = c != '-' ? 1 : -1;
if(c == '-' || c == '+')
@ -505,12 +246,6 @@ long constructLong(ref Node node) @safe
}
@safe unittest
{
long getLong(string str) @safe
{
auto node = Node(str);
return constructLong(node);
}
string canonical = "685230";
string decimal = "+685_230";
string octal = "02472256";
@ -518,18 +253,18 @@ long constructLong(ref Node node) @safe
string binary = "0b1010_0111_0100_1010_1110";
string sexagesimal = "190:20:30";
assert(685230 == getLong(canonical));
assert(685230 == getLong(decimal));
assert(685230 == getLong(octal));
assert(685230 == getLong(hexadecimal));
assert(685230 == getLong(binary));
assert(685230 == getLong(sexagesimal));
assert(685230 == constructLong(canonical));
assert(685230 == constructLong(decimal));
assert(685230 == constructLong(octal));
assert(685230 == constructLong(hexadecimal));
assert(685230 == constructLong(binary));
assert(685230 == constructLong(sexagesimal));
}
/// Construct a floating point (real) _node.
real constructReal(ref Node node) @safe
// Construct a floating point (real) _node.
real constructReal(const string str) @safe
{
string value = node.as!string().replace("_", "").toLower();
string value = str.replace("_", "").toLower();
const char c = value[0];
const real sign = c != '-' ? 1.0 : -1.0;
if(c == '-' || c == '+')
@ -576,12 +311,6 @@ real constructReal(ref Node node) @safe
return a >= (b - epsilon) && a <= (b + epsilon);
}
real getReal(string str) @safe
{
auto node = Node(str);
return constructReal(node);
}
string canonical = "6.8523015e+5";
string exponential = "685.230_15e+03";
string fixed = "685_230.15";
@ -589,21 +318,20 @@ real constructReal(ref Node node) @safe
string negativeInf = "-.inf";
string NaN = ".NaN";
assert(eq(685230.15, getReal(canonical)));
assert(eq(685230.15, getReal(exponential)));
assert(eq(685230.15, getReal(fixed)));
assert(eq(685230.15, getReal(sexagesimal)));
assert(eq(-real.infinity, getReal(negativeInf)));
assert(to!string(getReal(NaN)) == "nan");
assert(eq(685230.15, constructReal(canonical)));
assert(eq(685230.15, constructReal(exponential)));
assert(eq(685230.15, constructReal(fixed)));
assert(eq(685230.15, constructReal(sexagesimal)));
assert(eq(-real.infinity, constructReal(negativeInf)));
assert(to!string(constructReal(NaN)) == "nan");
}
/// Construct a binary (base64) _node.
ubyte[] constructBinary(ref Node node) @safe
// Construct a binary (base64) _node.
ubyte[] constructBinary(const string value) @safe
{
import std.ascii : newline;
import std.array : array;
string value = node.as!string;
// For an unknown reason, this must be nested to work (compiler bug?).
try
{
@ -621,16 +349,15 @@ ubyte[] constructBinary(ref Node node) @safe
char[] buffer;
buffer.length = 256;
string input = Base64.encode(test, buffer).idup;
auto node = Node(input);
const value = constructBinary(node);
const value = constructBinary(input);
assert(value == test);
assert(value == [84, 104, 101, 32, 65, 110, 115, 119, 101, 114, 58, 32, 52, 50]);
}
/// Construct a timestamp (SysTime) _node.
SysTime constructTimestamp(ref Node node) @safe
// Construct a timestamp (SysTime) _node.
SysTime constructTimestamp(const string str) @safe
{
string value = node.as!string;
string value = str;
auto YMDRegexp = regex("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)");
auto HMSRegexp = regex("^[Tt \t]+([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(\\.[0-9]*)?");
@ -704,8 +431,7 @@ SysTime constructTimestamp(ref Node node) @safe
{
string timestamp(string value)
{
auto node = Node(value);
return constructTimestamp(node).toISOString();
return constructTimestamp(value).toISOString();
}
string canonical = "2001-12-15T02:59:43.1Z";
@ -727,18 +453,18 @@ SysTime constructTimestamp(ref Node node) @safe
assert(timestamp(ymd) == "20021214T000000Z");
}
/// Construct a string _node.
string constructString(ref Node node) @safe
// Construct a string _node.
string constructString(const string str) @safe
{
return node.as!string;
return str;
}
/// Convert a sequence of single-element mappings into a sequence of pairs.
Node.Pair[] getPairs(string type, Node[] nodes) @safe
// Convert a sequence of single-element mappings into a sequence of pairs.
Node.Pair[] getPairs(string type, const Node[] nodes) @safe
{
Node.Pair[] pairs;
pairs.reserve(nodes.length);
foreach(ref node; nodes)
foreach(node; nodes)
{
enforce(node.isMapping && node.length == 1,
new Exception("While constructing " ~ type ~
@ -750,10 +476,10 @@ Node.Pair[] getPairs(string type, Node[] nodes) @safe
return pairs;
}
/// Construct an ordered map (ordered sequence of key:value pairs without duplicates) _node.
Node.Pair[] constructOrderedMap(ref Node node) @safe
// Construct an ordered map (ordered sequence of key:value pairs without duplicates) _node.
Node.Pair[] constructOrderedMap(const Node[] nodes) @safe
{
auto pairs = getPairs("ordered map", node.as!(Node[]));
auto pairs = getPairs("ordered map", nodes);
//Detect duplicates.
//TODO this should be replaced by something with deterministic memory allocation.
@ -791,38 +517,30 @@ Node.Pair[] constructOrderedMap(ref Node node) @safe
return pairs;
}
bool hasDuplicates(Node[] nodes) @safe
{
auto node = Node(nodes);
return null !is collectException(constructOrderedMap(node));
}
assert(hasDuplicates(alternateTypes(8) ~ alternateTypes(2)));
assert(!hasDuplicates(alternateTypes(8)));
assert(hasDuplicates(sameType(64) ~ sameType(16)));
assert(hasDuplicates(alternateTypes(64) ~ alternateTypes(16)));
assert(!hasDuplicates(sameType(64)));
assert(!hasDuplicates(alternateTypes(64)));
assertThrown(constructOrderedMap(alternateTypes(8) ~ alternateTypes(2)));
assertNotThrown(constructOrderedMap(alternateTypes(8)));
assertThrown(constructOrderedMap(sameType(64) ~ sameType(16)));
assertThrown(constructOrderedMap(alternateTypes(64) ~ alternateTypes(16)));
assertNotThrown(constructOrderedMap(sameType(64)));
assertNotThrown(constructOrderedMap(alternateTypes(64)));
}
/// Construct a pairs (ordered sequence of key: value pairs allowing duplicates) _node.
Node.Pair[] constructPairs(ref Node node) @safe
// Construct a pairs (ordered sequence of key: value pairs allowing duplicates) _node.
Node.Pair[] constructPairs(const Node[] nodes) @safe
{
return getPairs("pairs", node.as!(Node[]));
return getPairs("pairs", nodes);
}
/// Construct a set _node.
Node[] constructSet(ref Node node) @safe
// Construct a set _node.
Node[] constructSet(const Node.Pair[] pairs) @safe
{
auto pairs = node.as!(Node.Pair[]);
// In future, the map here should be replaced with something with deterministic
// memory allocation if possible.
// Detect duplicates.
ubyte[Node] map;
Node[] nodes;
nodes.reserve(pairs.length);
foreach(ref pair; pairs)
foreach(pair; pairs)
{
enforce((pair.key in map) is null, new Exception("Duplicate entry in a set"));
map[pair.key] = 0;
@ -862,27 +580,26 @@ Node[] constructSet(ref Node node) @safe
return true;
}
auto nodeDuplicatesShort = Node(DuplicatesShort.dup);
auto nodeNoDuplicatesShort = Node(noDuplicatesShort.dup);
auto nodeDuplicatesLong = Node(DuplicatesLong.dup);
auto nodeNoDuplicatesLong = Node(noDuplicatesLong.dup);
auto nodeDuplicatesShort = DuplicatesShort.dup;
auto nodeNoDuplicatesShort = noDuplicatesShort.dup;
auto nodeDuplicatesLong = DuplicatesLong.dup;
auto nodeNoDuplicatesLong = noDuplicatesLong.dup;
assert(null !is collectException(constructSet(nodeDuplicatesShort)));
assert(null is collectException(constructSet(nodeNoDuplicatesShort)));
assert(null !is collectException(constructSet(nodeDuplicatesLong)));
assert(null is collectException(constructSet(nodeNoDuplicatesLong)));
assertThrown(constructSet(nodeDuplicatesShort));
assertNotThrown(constructSet(nodeNoDuplicatesShort));
assertThrown(constructSet(nodeDuplicatesLong));
assertNotThrown(constructSet(nodeNoDuplicatesLong));
}
/// Construct a sequence (array) _node.
Node[] constructSequence(ref Node node) @safe
// Construct a sequence (array) _node.
Node[] constructSequence(Node[] nodes) @safe
{
return node.as!(Node[]);
return nodes;
}
/// Construct an unordered map (unordered set of key:value _pairs without duplicates) _node.
Node.Pair[] constructMap(ref Node node) @safe
// Construct an unordered map (unordered set of key:value _pairs without duplicates) _node.
Node.Pair[] constructMap(Node.Pair[] pairs) @safe
{
auto pairs = node.as!(Node.Pair[]);
//Detect duplicates.
//TODO this should be replaced by something with deterministic memory allocation.
auto keys = redBlackTree!Node();

View file

@ -23,6 +23,7 @@ import dyaml.node;
import dyaml.representer;
import dyaml.resolver;
import dyaml.serializer;
import dyaml.style;
import dyaml.tagdirective;
@ -45,8 +46,6 @@ struct Dumper(Range)
private:
//Resolver to resolve tags.
Resolver resolver_;
//Representer to represent data types.
Representer representer_;
//Stream to write to.
Range stream_;
@ -71,7 +70,24 @@ struct Dumper(Range)
//Name of the output file or stream, used in error messages.
string name_ = "<unknown>";
// Default style for scalar nodes.
ScalarStyle defaultScalarStyle_ = ScalarStyle.invalid;
// Default style for collection nodes.
CollectionStyle defaultCollectionStyle_ = CollectionStyle.invalid;
public:
///Set default _style for scalars. If style is $(D ScalarStyle.invalid), the _style is chosen automatically.
@property void defaultScalarStyle(ScalarStyle style) pure @safe nothrow
{
defaultScalarStyle_ = style;
}
///Set default _style for collections. If style is $(D CollectionStyle.invalid), the _style is chosen automatically.
@property void defaultCollectionStyle(CollectionStyle style) pure @safe nothrow
{
defaultCollectionStyle_ = style;
}
@disable this();
@disable bool opEquals(ref Dumper!Range);
@disable int opCmp(ref Dumper!Range);
@ -84,7 +100,6 @@ struct Dumper(Range)
this(Range stream) @safe
{
resolver_ = new Resolver();
representer_ = new Representer();
stream_ = stream;
}
@ -100,12 +115,6 @@ struct Dumper(Range)
resolver_ = resolver;
}
///Specify custom Representer to use.
@property void representer(Representer representer) @safe
{
representer_ = representer;
}
///Write scalars in _canonical form?
@property void canonical(bool canonical) pure @safe nothrow
{
@ -219,7 +228,8 @@ struct Dumper(Range)
explicitEnd_, YAMLVersion_, tags_);
foreach(ref document; documents)
{
representer_.represent(serializer, document);
auto data = representData(document, defaultScalarStyle_, defaultCollectionStyle_);
serializer.serialize(data);
}
}
catch(YAMLException e)
@ -277,18 +287,47 @@ struct Dumper(Range)
auto node = Node([1, 2, 3, 4, 5]);
dumper(stream).dump(node);
}
///Use a custom representer/resolver to support custom data types and/or implicit tags
///Use a custom resolver to support custom data types and/or implicit tags
@safe unittest
{
auto node = Node([1, 2, 3, 4, 5]);
auto representer = new Representer();
auto resolver = new Resolver();
//Add representer functions / resolver expressions here...
auto dumper = dumper(new Appender!string());
dumper.representer = representer;
dumper.resolver = resolver;
dumper.dump(node);
}
/// Set default scalar style
@safe unittest
{
auto stream = new Appender!string();
auto node = Node("Hello world!");
auto dumper = dumper(stream);
dumper.defaultScalarStyle = ScalarStyle.singleQuoted;
dumper.dump(node);
}
/// Set default collection style
@safe unittest
{
auto stream = new Appender!string();
auto node = Node(["Hello", "world!"]);
auto dumper = dumper(stream);
dumper.defaultCollectionStyle = CollectionStyle.flow;
dumper.dump(node);
}
// Make sure the styles are actually used
@safe unittest
{
auto stream = new Appender!string();
auto node = Node([Node("Hello world!"), Node(["Hello", "world!"])]);
auto dumper = dumper(stream);
dumper.defaultScalarStyle = ScalarStyle.singleQuoted;
dumper.defaultCollectionStyle = CollectionStyle.flow;
dumper.explicitEnd = false;
dumper.explicitStart = false;
dumper.YAMLVersion = null;
dumper.dump(node);
assert(stream.data == "[!!str 'Hello world!', [!!str 'Hello', !!str 'world!']]\n");
}
// Explicit document start/end markers
@safe unittest
{

View file

@ -41,8 +41,6 @@ struct Loader
Parser parser_;
// Resolves tags (data types).
Resolver resolver_;
// Constructs YAML data types.
Constructor constructor_;
// Name of the input file or stream, used in error messages.
string name_ = "<unknown>";
// Are we done loading?
@ -177,12 +175,6 @@ struct Loader
resolver_ = resolver;
}
/// Specify custom Constructor to use.
void constructor(Constructor constructor) pure @safe nothrow @nogc
{
constructor_ = constructor;
}
/** Load single YAML document.
*
* If none or more than one YAML document is found, this throws a YAMLException.
@ -230,7 +222,7 @@ struct Loader
if (!rangeInitialized)
{
lazyInitConstructorResolver();
composer = new Composer(parser_, resolver_, constructor_);
composer = new Composer(parser_, resolver_);
rangeInitialized = true;
}
assert(!done_, "Loader.popFront called on empty range");
@ -301,7 +293,6 @@ struct Loader
void lazyInitConstructorResolver() @safe
{
if(resolver_ is null) { resolver_ = new Resolver(); }
if(constructor_ is null) { constructor_ = new Constructor(); }
}
}
/// Load single YAML document from a file:
@ -410,13 +401,11 @@ struct Loader
"Hello world!\n"~
"...\n"
);
auto constructor = new Constructor();
auto resolver = new Resolver();
// Add constructor functions / resolver expressions here...
auto loader = Loader.fromFile("example.yaml");
loader.constructor = constructor;
loader.resolver = resolver;
auto rootNode = loader.load();
}

View file

@ -58,59 +58,6 @@ struct YAMLNull
// Merge YAML type, used to support "tag:yaml.org,2002:merge".
package struct YAMLMerge{}
// Interface for YAMLContainer - used for user defined YAML types.
package interface YAMLObject
{
public:
// Get type of the stored value.
@property TypeInfo type() const pure @safe nothrow;
protected:
// Compare with another YAMLObject.
int cmp(const YAMLObject) const @system;
}
// Stores a user defined YAML data type.
package class YAMLContainer(T) if (!Node.allowed!T): YAMLObject
{
private:
// Stored value.
T value_;
// Construct a YAMLContainer holding specified value.
this(T value) @safe
{
value_ = value;
}
public:
// Get type of the stored value.
@property override TypeInfo type() const pure @safe nothrow
{
return typeid(T);
}
// Get string representation of the container.
override string toString() @system
{
return format("YAMLContainer(%s)", value_);
}
protected:
// Compare with another YAMLObject.
override int cmp(const YAMLObject rhs) const @system
{
const typeCmp = type.opCmp(rhs.type);
if(typeCmp != 0){return typeCmp;}
// Const-casting here as Object opCmp is not const.
T* v1 = cast(T*)&value_;
T* v2 = cast(T*)&((cast(YAMLContainer)rhs).value_);
return (*v1).opCmp(*v2);
}
}
// Key-value pair of YAML nodes, used in mappings.
private struct Pair
{
@ -150,6 +97,20 @@ private struct Pair
@disable int opCmp(ref Pair);
}
enum NodeType
{
null_,
merge,
boolean,
integer,
decimal,
binary,
timestamp,
string,
mapping,
sequence
}
/** YAML node.
*
* This is a pseudo-dynamic type that can store any YAML value, including a
@ -164,9 +125,9 @@ struct Node
package:
// YAML value type.
alias Value = Algebraic!(YAMLNull, YAMLMerge, bool, long, real, ubyte[], SysTime, string,
Node.Pair[], Node[], YAMLObject);
Node.Pair[], Node[]);
// Can Value hold this type without wrapping it in a YAMLObject?
// Can Value hold this type naturally?
enum allowed(T) = isIntegral!T ||
isFloatingPoint!T ||
isSomeString!T ||
@ -212,7 +173,8 @@ struct Node
* be in full form, e.g. "tag:yaml.org,2002:int", not
* a shortcut, like "!!int".
*/
this(T)(T value, const string tag = null)
this(T)(T value, const string tag = null) @safe
if (allowed!T || isArray!T || isAssociativeArray!T || is(Unqual!T == Node) || castableToNode!T)
{
tag_ = tag;
@ -333,15 +295,10 @@ struct Node
{
{
auto node = Node(42);
assert(node.isScalar && !node.isSequence && !node.isMapping && !node.isUserType);
assert(node.isScalar && !node.isSequence && !node.isMapping);
assert(node.as!int == 42 && node.as!float == 42.0f && node.as!string == "42");
assert(!node.isUserType);
}
{
auto node = Node(new class{int a = 5;});
assert(node.isUserType);
}
{
auto node = Node("string");
assert(node.as!string == "string");
@ -351,7 +308,7 @@ struct Node
{
with(Node([1, 2, 3]))
{
assert(!isScalar() && isSequence && !isMapping && !isUserType);
assert(!isScalar() && isSequence && !isMapping);
assert(length == 3);
assert(opIndex(2).as!int == 3);
}
@ -364,7 +321,7 @@ struct Node
aa["2"] = 2;
with(Node(aa))
{
assert(!isScalar() && !isSequence && isMapping && !isUserType);
assert(!isScalar() && !isSequence && isMapping);
assert(length == 2);
assert(opIndex("2").as!int == 2);
}
@ -433,7 +390,7 @@ struct Node
{
with(Node(["1", "2"], [1, 2]))
{
assert(!isScalar() && !isSequence && isMapping && !isUserType);
assert(!isScalar() && !isSequence && isMapping);
assert(length == 2);
assert(opIndex("2").as!int == 2);
}
@ -464,12 +421,6 @@ struct Node
return isType!(Pair[]);
}
/// Is this node a user defined type?
@property bool isUserType() const @safe nothrow
{
return isType!YAMLObject;
}
/// Is this node null?
@property bool isNull() const @safe nothrow
{
@ -552,64 +503,85 @@ struct Node
* the value is out of range of requested type.
*/
inout(T) get(T, Flag!"stringConversion" stringConversion = Yes.stringConversion)() inout
if (allowed!(Unqual!T) || hasNodeConstructor!(Unqual!T))
{
if(isType!(Unqual!T)){return getValue!T;}
/// Must go before others, as even string/int/etc could be stored in a YAMLObject.
static if(!allowed!(Unqual!T)) if(isUserType)
static if(!allowed!(Unqual!T))
{
auto object = getValue!YAMLObject();
enforce(object.type is typeid(T),
new NodeException("Node has unexpected type: " ~ object.type.toString() ~
". Expected: " ~ typeid(T).toString, startMark_));
return (cast(inout YAMLContainer!(Unqual!T))(object)).value_;
}
// If we're getting from a mapping and we're not getting Node.Pair[],
// we're getting the default value.
if(isMapping){return this["="].get!( T, stringConversion);}
static if(isSomeString!T)
{
static if(!stringConversion)
static if (hasSimpleNodeConstructor!T)
{
if(isString){return to!T(getValue!string);}
throw new NodeException("Node stores unexpected type: " ~ type.toString() ~
". Expected: " ~ typeid(T).toString(), startMark_);
alias params = AliasSeq!(this);
}
else static if (hasExpandedNodeConstructor!T)
{
alias params = AliasSeq!(this, tag_);
}
else
{
// Try to convert to string.
try
static assert(0, "Unknown Node constructor?");
}
static if (is(T == class))
{
return new inout T(params);
}
else static if (is(T == struct))
{
return T(params);
}
else
{
static assert(0, "Unhandled user type");
}
} else {
// If we're getting from a mapping and we're not getting Node.Pair[],
// we're getting the default value.
if(isMapping){return this["="].get!( T, stringConversion);}
static if(isSomeString!T)
{
static if(!stringConversion)
{
return coerceValue!T();
if(isString){return to!T(getValue!string);}
throw new NodeException("Node stores unexpected type: " ~ type.toString() ~
". Expected: " ~ typeid(T).toString(), startMark_);
}
catch(VariantException e)
else
{
throw new NodeException("Unable to convert node value to string", startMark_);
// Try to convert to string.
try
{
return coerceValue!T();
}
catch(VariantException e)
{
throw new NodeException("Unable to convert node value to string", startMark_);
}
}
}
}
else static if(isFloatingPoint!T)
{
/// Can convert int to float.
if(isInt()) {return to!T(getValue!long);}
else if(isFloat()){return to!T(getValue!real);}
else static if(isFloatingPoint!T)
{
/// Can convert int to float.
if(isInt()) {return to!T(getValue!long);}
else if(isFloat()){return to!T(getValue!real);}
else throw new NodeException("Node stores unexpected type: " ~ type.toString() ~
". Expected: " ~ typeid(T).toString, startMark_);
}
else static if(isIntegral!T)
{
enforce(isInt(), new NodeException("Node stores unexpected type: " ~ type.toString() ~
". Expected: " ~ typeid(T).toString, startMark_));
immutable temp = getValue!long;
enforce(temp >= T.min && temp <= T.max,
new NodeException("Integer value of type " ~ typeid(T).toString() ~
" out of range. Value: " ~ to!string(temp), startMark_));
return temp.to!T;
}
else throw new NodeException("Node stores unexpected type: " ~ type.toString() ~
". Expected: " ~ typeid(T).toString, startMark_);
}
else static if(isIntegral!T)
{
enforce(isInt(), new NodeException("Node stores unexpected type: " ~ type.toString() ~
". Expected: " ~ typeid(T).toString, startMark_));
immutable temp = getValue!long;
enforce(temp >= T.min && temp <= T.max,
new NodeException("Integer value of type " ~ typeid(T).toString() ~
" out of range. Value: " ~ to!string(temp), startMark_));
return temp.to!T;
}
else throw new NodeException("Node stores unexpected type: " ~ type.toString() ~
". Expected: " ~ typeid(T).toString, startMark_);
}
/// Automatic type conversion
@safe unittest
@ -620,6 +592,186 @@ struct Node
assert(node.get!string == "42");
assert(node.get!double == 42.0);
}
/// Scalar node to struct and vice versa
@safe unittest
{
import dyaml.dumper : dumper;
import dyaml.loader : Loader;
static struct MyStruct
{
int x, y, z;
this(int x, int y, int z) @safe
{
this.x = x;
this.y = y;
this.z = z;
}
this(Node node) @safe
{
auto parts = node.as!string().split(":");
x = parts[0].to!int;
y = parts[1].to!int;
z = parts[2].to!int;
}
Node opCast(T: Node)() @safe
{
//Using custom scalar format, x:y:z.
auto scalar = format("%s:%s:%s", x, y, z);
//Representing as a scalar, with custom tag to specify this data type.
return Node(scalar, "!mystruct.tag");
}
}
auto appender = new Appender!string;
// Dump struct to yaml document
dumper(appender).dump(Node(MyStruct(1,2,3)));
// Read yaml document back as a MyStruct
auto loader = Loader.fromString(appender.data);
Node node = loader.load();
assert(node.as!MyStruct == MyStruct(1,2,3));
}
/// Sequence node to struct and vice versa
@safe unittest
{
import dyaml.dumper : dumper;
import dyaml.loader : Loader;
static struct MyStruct
{
int x, y, z;
this(int x, int y, int z) @safe
{
this.x = x;
this.y = y;
this.z = z;
}
this(Node node) @safe
{
x = node[0].as!int;
y = node[1].as!int;
z = node[2].as!int;
}
Node opCast(T: Node)()
{
return Node([x, y, z], "!mystruct.tag");
}
}
auto appender = new Appender!string;
// Dump struct to yaml document
dumper(appender).dump(Node(MyStruct(1,2,3)));
// Read yaml document back as a MyStruct
auto loader = Loader.fromString(appender.data);
Node node = loader.load();
assert(node.as!MyStruct == MyStruct(1,2,3));
}
/// Mapping node to struct and vice versa
@safe unittest
{
import dyaml.dumper : dumper;
import dyaml.loader : Loader;
static struct MyStruct
{
int x, y, z;
Node opCast(T: Node)()
{
auto pairs = [Node.Pair("x", x),
Node.Pair("y", y),
Node.Pair("z", z)];
return Node(pairs, "!mystruct.tag");
}
this(int x, int y, int z)
{
this.x = x;
this.y = y;
this.z = z;
}
this(Node node) @safe
{
x = node["x"].as!int;
y = node["y"].as!int;
z = node["z"].as!int;
}
}
auto appender = new Appender!string;
// Dump struct to yaml document
dumper(appender).dump(Node(MyStruct(1,2,3)));
// Read yaml document back as a MyStruct
auto loader = Loader.fromString(appender.data);
Node node = loader.load();
assert(node.as!MyStruct == MyStruct(1,2,3));
}
/// Classes can be used too
@system unittest {
import dyaml.dumper : dumper;
import dyaml.loader : Loader;
static class MyClass
{
int x, y, z;
this(int x, int y, int z)
{
this.x = x;
this.y = y;
this.z = z;
}
this(Node node) @safe inout
{
auto parts = node.as!string().split(":");
x = parts[0].to!int;
y = parts[1].to!int;
z = parts[2].to!int;
}
///Useful for Node.as!string.
override string toString()
{
return format("MyClass(%s, %s, %s)", x, y, z);
}
Node opCast(T: Node)() @safe
{
//Using custom scalar format, x:y:z.
auto scalar = format("%s:%s:%s", x, y, z);
//Representing as a scalar, with custom tag to specify this data type.
return Node(scalar, "!myclass.tag");
}
override bool opEquals(Object o)
{
if (auto other = cast(MyClass)o)
{
return (other.x == x) && (other.y == y) && (other.z == z);
}
return false;
}
}
auto appender = new Appender!string;
// Dump class to yaml document
dumper(appender).dump(Node(new MyClass(1,2,3)));
// Read yaml document back as a MyClass
auto loader = Loader.fromString(appender.data);
Node node = loader.load();
assert(node.as!MyClass == new MyClass(1,2,3));
}
@safe unittest
{
assertThrown!NodeException(Node("42").get!int);
@ -1876,10 +2028,6 @@ struct Node
const t2 = rhs.getValue!SysTime;
return cmp(t1, t2);
}
else if(isUserType)
{
return getValue!YAMLObject.cmp(rhs.getValue!YAMLObject);
}
assert(false, "Unknown type of node for comparison : " ~ type.toString());
}
@ -1923,12 +2071,58 @@ struct Node
assert(false);
}
// Get type of the node value (YAMLObject for user types).
// Get type of the node value.
@property TypeInfo type() const @safe nothrow
{
return value_.type;
}
// Get type of the node value.
@property NodeType newType() const @safe nothrow
{
if (value_.type is typeid(bool))
{
return NodeType.boolean;
}
else if (value_.type is typeid(long))
{
return NodeType.integer;
}
else if (value_.type is typeid(Node[]))
{
return NodeType.sequence;
}
else if (value_.type is typeid(ubyte[]))
{
return NodeType.binary;
}
else if (value_.type is typeid(string))
{
return NodeType.string;
}
else if (value_.type is typeid(Node.Pair[]))
{
return NodeType.mapping;
}
else if (value_.type is typeid(SysTime))
{
return NodeType.timestamp;
}
else if (value_.type is typeid(YAMLNull))
{
return NodeType.null_;
}
else if (value_.type is typeid(YAMLMerge))
{
return NodeType.merge;
}
else if (value_.type is typeid(real))
{
return NodeType.decimal;
}
else assert(0, text(value_.type));
}
public:
// Determine if the value stored by the node is of specified type.
//
@ -2210,7 +2404,7 @@ struct Node
}
else
{
value_ = cast(YAMLObject)new YAMLContainer!T(value);
value_ = (cast(Node)value).value_;
}
}
}
@ -2232,3 +2426,30 @@ void merge(ref Appender!(Node.Pair[]) pairs, Node.Pair[] toMerge) @safe
pairs.put(pair);
}
}
enum hasNodeConstructor(T) = hasSimpleNodeConstructor!T || hasExpandedNodeConstructor!T;
template hasSimpleNodeConstructor(T)
{
static if (is(T == struct))
{
enum hasSimpleNodeConstructor = is(typeof(T(Node.init)));
}
else static if (is(T == class))
{
enum hasSimpleNodeConstructor = is(typeof(new inout T(Node.init)));
}
else enum hasSimpleNodeConstructor = false;
}
template hasExpandedNodeConstructor(T)
{
static if (is(T == struct))
{
enum hasExpandedNodeConstructor = is(typeof(T(Node.init, "")));
}
else static if (is(T == class))
{
enum hasExpandedNodeConstructor = is(typeof(new inout T(Node.init, "")));
}
else enum hasExpandedNodeConstructor = false;
}
enum castableToNode(T) = (is(T == struct) || is(T == class)) && is(typeof(T.opCast!Node()) : Node);

View file

@ -5,13 +5,11 @@
module dyaml;
public import dyaml.constructor;
public import dyaml.dumper;
public import dyaml.encoding;
public import dyaml.exception;
public import dyaml.linebreak;
public import dyaml.loader;
public import dyaml.representer;
public import dyaml.resolver;
public import dyaml.style;
public import dyaml.node;

File diff suppressed because it is too large Load diff

View file

@ -1951,91 +1951,36 @@ final class Scanner
/// In case of an error, error_ is set. Use throwIfError() to handle this.
void scanURIEscapesToSlice(string name)(const Mark startMark)
{
import core.exception : UnicodeException;
// URI escapes encode a UTF-8 string. We store UTF-8 code units here for
// decoding into UTF-32.
char[4] bytes;
size_t bytesUsed;
Appender!string buffer;
// Get one dchar by decoding data from bytes.
//
// This is probably slow, but simple and URI escapes are extremely uncommon
// in YAML.
//
// Returns the number of bytes used by the dchar in bytes on success,
// size_t.max on failure.
static size_t getDchar(char[] bytes, Reader reader_) @safe
{
size_t nextChar;
dchar c;
if(bytes[0] < 0x80)
{
c = bytes[0];
++nextChar;
}
else
{
try
{
c = decode(bytes[], nextChar);
}
catch (UTFException)
{
return size_t.max;
}
}
reader_.sliceBuilder.write(c);
if(bytes.length - nextChar > 0)
{
copy(bytes[nextChar..bytes.length], bytes[0..bytes.length-nextChar]);
}
return bytes.length - nextChar;
}
enum contextMsg = "While scanning a " ~ name;
while(reader_.peekByte() == '%')
{
reader_.forward();
if(bytesUsed == bytes.length)
char[2] nextByte = [reader_.peekByte(), reader_.peekByte(1)];
if(!nextByte[0].isHexDigit || !nextByte[1].isHexDigit)
{
bytesUsed = getDchar(bytes[], reader_);
if(bytesUsed == size_t.max)
{
error(contextMsg, startMark,
"Invalid UTF-8 data encoded in URI escape sequence",
reader_.mark);
return;
}
auto msg = expected("URI escape sequence of 2 hexadecimal " ~
"numbers", nextByte);
error(contextMsg, startMark, msg, reader_.mark);
return;
}
char b = 0;
uint mult = 16;
// Converting 2 hexadecimal digits to a byte.
foreach(k; 0 .. 2)
{
const dchar c = reader_.peek(k);
if(!c.isHexDigit)
{
auto msg = expected("URI escape sequence of 2 hexadecimal " ~
"numbers", c);
error(contextMsg, startMark, msg, reader_.mark);
return;
}
uint digit;
if(c - '0' < 10) { digit = c - '0'; }
else if(c - 'A' < 6) { digit = 10 + c - 'A'; }
else if(c - 'a' < 6) { digit = 10 + c - 'a'; }
else { assert(false); }
b += mult * digit;
mult /= 16;
}
bytes[bytesUsed++] = b;
buffer ~= nextByte[].to!ubyte(16);
reader_.forward(2);
}
bytesUsed = getDchar(bytes[0 .. bytesUsed], reader_);
if(bytesUsed == size_t.max)
try
{
foreach (dchar chr; buffer.data)
{
reader_.sliceBuilder.write(chr);
}
}
catch (UnicodeException)
{
error(contextMsg, startMark,
"Invalid UTF-8 data encoded in URI escape sequence",

View file

@ -328,21 +328,12 @@ class TestClass
this.z = z;
}
//Any D:YAML type must have a custom opCmp operator.
//This is used for ordering in mappings.
override int opCmp(Object o) @safe
Node opCast(T: Node)() @safe
{
TestClass s = cast(TestClass)o;
if(s is null){return -1;}
if(x != s.x){return x - s.x;}
if(y != s.y){return y - s.y;}
if(z != s.z){return z - s.z;}
return 0;
}
override string toString() @safe
{
return format("TestClass(", x, ", ", y, ", ", z, ")");
auto pairs = [Node.Pair("x", x),
Node.Pair("y", y),
Node.Pair("z", z)];
return Node(pairs, "!tag1");
}
}
@ -351,43 +342,22 @@ struct TestStruct
{
int value;
//Any D:YAML type must have a custom opCmp operator.
//This is used for ordering in mappings.
int opCmp(ref const TestStruct s) const @safe
this (int x) @safe
{
return value - s.value;
value = x;
}
}
///Constructor function for TestClass.
TestClass constructClass(ref Node node) @safe
{
return new TestClass(node["x"].as!int, node["y"].as!int, node["z"].as!int);
}
///Constructor function for TestStruct.
this(ref Node node) @safe
{
value = node.as!string.to!int;
}
Node representClass(ref Node node, Representer representer) @safe
{
auto value = node.as!TestClass;
auto pairs = [Node.Pair("x", value.x),
Node.Pair("y", value.y),
Node.Pair("z", value.z)];
auto result = representer.representMapping("!tag1", pairs);
return result;
}
///Constructor function for TestStruct.
TestStruct constructStruct(ref Node node) @safe
{
return TestStruct(to!int(node.as!string));
}
///Representer function for TestStruct.
Node representStruct(ref Node node, Representer representer) @safe
{
string[] keys, values;
auto value = node.as!TestStruct;
return representer.representScalar("!tag2", to!string(value.value));
///Representer function for TestStruct.
Node opCast(T: Node)() @safe
{
return Node(value.to!string, "!tag2");
}
}
/**
@ -403,12 +373,7 @@ void testConstructor(string dataFilename, string codeDummy) @safe
enforce((base in expected) !is null,
new Exception("Unimplemented constructor test: " ~ base));
auto constructor = new Constructor;
constructor.addConstructorMapping("!tag1", &constructClass);
constructor.addConstructorScalar("!tag2", &constructStruct);
auto loader = Loader.fromFile(dataFilename);
loader.constructor = constructor;
loader.resolver = new Resolver;
Node[] exp = expected[base];

View file

@ -90,7 +90,6 @@ void testEmitterOnData(string dataFilename, string canonicalFilename) @safe
auto loader2 = Loader.fromString(emitStream.data);
loader2.name = "TEST";
loader2.constructor = new Constructor;
loader2.resolver = new Resolver;
auto newEvents = loader2.parse();
assert(compareEvents(events, newEvents));
@ -119,7 +118,6 @@ void testEmitterOnCanonical(string canonicalFilename) @safe
}
auto loader2 = Loader.fromString(emitStream.data);
loader2.name = "TEST";
loader2.constructor = new Constructor;
loader2.resolver = new Resolver;
auto newEvents = loader2.parse();
assert(compareEvents(events, newEvents));
@ -177,7 +175,6 @@ void testEmitterStyles(string dataFilename, string canonicalFilename) @safe
}
auto loader2 = Loader.fromString(emitStream.data);
loader2.name = "TEST";
loader2.constructor = new Constructor;
loader2.resolver = new Resolver;
auto newEvents = loader2.parse();
assert(compareEvents(events, newEvents));

View file

@ -53,21 +53,13 @@ void testRepresenterTypes(string codeFilename) @safe
}
auto emitStream = new Appender!(immutable(encoding)[]);
auto representer = new Representer;
representer.addRepresenter!TestClass(&representClass);
representer.addRepresenter!TestStruct(&representStruct);
auto dumper = dumper(emitStream);
dumper.representer = representer;
dumper.dump!encoding(expectedNodes);
output = emitStream.data;
auto constructor = new Constructor;
constructor.addConstructorMapping("!tag1", &constructClass);
constructor.addConstructorScalar("!tag2", &constructStruct);
auto loader = Loader.fromString(emitStream.data.toUTF8);
loader.name = "TEST";
loader.constructor = constructor;
readNodes = loader.array;
assert(expectedNodes.length == readNodes.length);

View file

@ -16,12 +16,6 @@ import std.string;
import dyaml.test.common;
string construct(ref Node node) @safe
{
return node.as!string;
}
/**
* Implicit tag resolution unittest.
*
@ -44,12 +38,7 @@ void testImplicitResolver(string dataFilename, string detectFilename) @safe
correctTag = readText(detectFilename).strip();
auto constructor = new Constructor;
constructor.addConstructorScalar("tag:example.com,2000:app/tag🤔", &construct);
auto loader = Loader.fromFile(dataFilename);
loader.constructor = constructor;
node = loader.load();
node = Loader.fromFile(dataFilename).load();
assert(node.isSequence);
foreach(ref Node scalar; node)
{