Commit ca5992b9 authored by Jesse Beder's avatar Jesse Beder
Browse files

Merged r444:449 from the node refactoring branch to the trunk

parent ced50538
...@@ -18,18 +18,12 @@ namespace YAML ...@@ -18,18 +18,12 @@ namespace YAML
AliasManager(); AliasManager();
void RegisterReference(const Node& node); void RegisterReference(const Node& node);
const Node *LookupReference(const Node& node) const;
anchor_t LookupAnchor(const Node& node) const; anchor_t LookupAnchor(const Node& node) const;
private: private:
const Node *_LookupReference(const Node& oldIdentity) const;
anchor_t _CreateNewAnchor(); anchor_t _CreateNewAnchor();
private: private:
typedef std::map<const Node*, const Node*> NodeByNode;
NodeByNode m_newIdentityByOldIdentity;
typedef std::map<const Node*, anchor_t> AnchorByIdentity; typedef std::map<const Node*, anchor_t> AnchorByIdentity;
AnchorByIdentity m_anchorByIdentity; AnchorByIdentity m_anchorByIdentity;
......
...@@ -20,7 +20,7 @@ namespace YAML ...@@ -20,7 +20,7 @@ namespace YAML
{ {
public: public:
// Create and return a new node with a null value. // Create and return a new node with a null value.
virtual void *NewNull(const std::string& tag, void *pParentNode) = 0; virtual void *NewNull(const Mark& mark, void *pParentNode) = 0;
// Create and return a new node with the given tag and value. // Create and return a new node with the given tag and value.
virtual void *NewScalar(const Mark& mark, const std::string& tag, void *pParentNode, const std::string& value) = 0; virtual void *NewScalar(const Mark& mark, const std::string& tag, void *pParentNode, const std::string& value) = 0;
...@@ -74,8 +74,8 @@ namespace YAML ...@@ -74,8 +74,8 @@ namespace YAML
GraphBuilderInterface& AsBuilderInterface() {return *this;} GraphBuilderInterface& AsBuilderInterface() {return *this;}
virtual void *NewNull(const std::string& tag, void* pParentNode) { virtual void *NewNull(const Mark& mark, void* pParentNode) {
return CheckType<Node>(m_impl.NewNull(tag, AsNode(pParentNode))); return CheckType<Node>(m_impl.NewNull(mark, AsNode(pParentNode)));
} }
virtual void *NewScalar(const Mark& mark, const std::string& tag, void *pParentNode, const std::string& value) { virtual void *NewScalar(const Mark& mark, const std::string& tag, void *pParentNode, const std::string& value) {
......
...@@ -20,7 +20,7 @@ namespace YAML ...@@ -20,7 +20,7 @@ namespace YAML
virtual void OnDocumentStart(const Mark& mark); virtual void OnDocumentStart(const Mark& mark);
virtual void OnDocumentEnd(); virtual void OnDocumentEnd();
virtual void OnNull(const std::string& tag, anchor_t anchor); virtual void OnNull(const Mark& mark, anchor_t anchor);
virtual void OnAlias(const Mark& mark, anchor_t anchor); virtual void OnAlias(const Mark& mark, anchor_t anchor);
virtual void OnScalar(const Mark& mark, const std::string& tag, anchor_t anchor, const std::string& value); virtual void OnScalar(const Mark& mark, const std::string& tag, anchor_t anchor, const std::string& value);
......
...@@ -20,7 +20,7 @@ namespace YAML ...@@ -20,7 +20,7 @@ namespace YAML
virtual void OnDocumentStart(const Mark& mark) = 0; virtual void OnDocumentStart(const Mark& mark) = 0;
virtual void OnDocumentEnd() = 0; virtual void OnDocumentEnd() = 0;
virtual void OnNull(const std::string& tag, anchor_t anchor) = 0; virtual void OnNull(const Mark& mark, anchor_t anchor) = 0;
virtual void OnAlias(const Mark& mark, anchor_t anchor) = 0; virtual void OnAlias(const Mark& mark, anchor_t anchor) = 0;
virtual void OnScalar(const Mark& mark, const std::string& tag, anchor_t anchor, const std::string& value) = 0; virtual void OnScalar(const Mark& mark, const std::string& tag, anchor_t anchor, const std::string& value) = 0;
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#pragma once #pragma once
#endif #endif
#include <memory>
namespace YAML namespace YAML
{ {
...@@ -15,7 +16,7 @@ namespace YAML ...@@ -15,7 +16,7 @@ namespace YAML
{ {
public: public:
Iterator(); Iterator();
Iterator(IterPriv *pData); Iterator(std::auto_ptr<IterPriv> pData);
Iterator(const Iterator& rhs); Iterator(const Iterator& rhs);
~Iterator(); ~Iterator();
...@@ -31,7 +32,7 @@ namespace YAML ...@@ -31,7 +32,7 @@ namespace YAML
friend bool operator != (const Iterator& it, const Iterator& jt); friend bool operator != (const Iterator& it, const Iterator& jt);
private: private:
IterPriv *m_pData; std::auto_ptr<IterPriv> m_pData;
}; };
} }
......
...@@ -9,28 +9,32 @@ ...@@ -9,28 +9,32 @@
#include "yaml-cpp/conversion.h" #include "yaml-cpp/conversion.h"
#include "yaml-cpp/exceptions.h" #include "yaml-cpp/exceptions.h"
#include "yaml-cpp/iterator.h" #include "yaml-cpp/iterator.h"
#include "yaml-cpp/ltnode.h"
#include "yaml-cpp/mark.h" #include "yaml-cpp/mark.h"
#include "yaml-cpp/noncopyable.h" #include "yaml-cpp/noncopyable.h"
#include <iostream> #include <iostream>
#include <string>
#include <vector>
#include <map> #include <map>
#include <memory> #include <memory>
#include <string>
#include <vector>
namespace YAML namespace YAML
{ {
class AliasManager; class AliasManager;
class Content; class Content;
class NodeOwnership;
class Scanner; class Scanner;
class Emitter; class Emitter;
class EventHandler; class EventHandler;
struct NodeProperties;
enum CONTENT_TYPE { CT_NONE, CT_SCALAR, CT_SEQUENCE, CT_MAP }; struct NodeType { enum value { Null, Scalar, Sequence, Map }; };
class Node: private noncopyable class Node: private noncopyable
{ {
public: public:
friend class NodeOwnership;
friend class NodeBuilder;
Node(); Node();
~Node(); ~Node();
...@@ -39,15 +43,8 @@ namespace YAML ...@@ -39,15 +43,8 @@ namespace YAML
void EmitEvents(EventHandler& eventHandler) const; void EmitEvents(EventHandler& eventHandler) const;
void EmitEvents(AliasManager& am, EventHandler& eventHandler) const; void EmitEvents(AliasManager& am, EventHandler& eventHandler) const;
void Init(CONTENT_TYPE type, const Mark& mark, const std::string& tag); NodeType::value Type() const { return m_type; }
void InitNull(const std::string& tag); bool IsAliased() const;
void InitAlias(const Mark& mark, const Node& identity);
void SetData(const std::string& data);
void Append(std::auto_ptr<Node> pNode);
void Insert(std::auto_ptr<Node> pKey, std::auto_ptr<Node> pValue);
CONTENT_TYPE GetType() const { return m_type; }
// file location of start of this node // file location of start of this node
const Mark GetMark() const { return m_mark; } const Mark GetMark() const { return m_mark; }
...@@ -84,13 +81,8 @@ namespace YAML ...@@ -84,13 +81,8 @@ namespace YAML
const Node *FindValue(const char *key) const; const Node *FindValue(const char *key) const;
const Node& operator [] (const char *key) const; const Node& operator [] (const char *key) const;
// for anchors/aliases
const Node *Identity() const { return m_pIdentity; }
bool IsAlias() const { return m_alias; }
bool IsReferenced() const { return m_referenced; }
// for tags // for tags
const std::string GetTag() const { return IsAlias() ? m_pIdentity->GetTag() : m_tag; } const std::string& Tag() const { return m_tag; }
// emitting // emitting
friend Emitter& operator << (Emitter& out, const Node& node); friend Emitter& operator << (Emitter& out, const Node& node);
...@@ -100,6 +92,16 @@ namespace YAML ...@@ -100,6 +92,16 @@ namespace YAML
friend bool operator < (const Node& n1, const Node& n2); friend bool operator < (const Node& n1, const Node& n2);
private: private:
explicit Node(NodeOwnership& owner);
Node& CreateNode();
void Init(NodeType::value type, const Mark& mark, const std::string& tag);
void MarkAsAliased();
void SetScalarData(const std::string& data);
void Append(Node& node);
void Insert(Node& key, Node& value);
// helper for sequences // helper for sequences
template <typename, bool> friend struct _FindFromNodeAtIndex; template <typename, bool> friend struct _FindFromNodeAtIndex;
const Node *FindAtIndex(std::size_t i) const; const Node *FindAtIndex(std::size_t i) const;
...@@ -112,13 +114,18 @@ namespace YAML ...@@ -112,13 +114,18 @@ namespace YAML
const Node *FindValueForKey(const T& key) const; const Node *FindValueForKey(const T& key) const;
private: private:
std::auto_ptr<NodeOwnership> m_pOwnership;
Mark m_mark; Mark m_mark;
std::string m_tag; std::string m_tag;
CONTENT_TYPE m_type;
Content *m_pContent; typedef std::vector<Node *> node_seq;
bool m_alias; typedef std::map<Node *, Node *, ltnode> node_map;
const Node *m_pIdentity;
mutable bool m_referenced; NodeType::value m_type;
std::string m_scalarData;
node_seq m_seqData;
node_map m_mapData;
}; };
// comparisons with auto-conversion // comparisons with auto-conversion
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "yaml-cpp/nodeutil.h" #include "yaml-cpp/nodeutil.h"
#include <cassert>
namespace YAML namespace YAML
{ {
...@@ -31,14 +32,17 @@ namespace YAML ...@@ -31,14 +32,17 @@ namespace YAML
template <typename T> template <typename T>
inline const Node *Node::FindValue(const T& key) const { inline const Node *Node::FindValue(const T& key) const {
switch(GetType()) { switch(m_type) {
case CT_MAP: case NodeType::Null:
return FindValueForKey(key); case NodeType::Scalar:
case CT_SEQUENCE: throw BadDereference();
case NodeType::Sequence:
return FindFromNodeAtIndex(*this, key); return FindFromNodeAtIndex(*this, key);
default: case NodeType::Map:
return 0; return FindValueForKey(key);
} }
assert(false);
throw BadDereference();
} }
template <typename T> template <typename T>
...@@ -56,14 +60,9 @@ namespace YAML ...@@ -56,14 +60,9 @@ namespace YAML
template <typename T> template <typename T>
inline const Node& Node::GetValue(const T& key) const { inline const Node& Node::GetValue(const T& key) const {
if(!m_pContent) if(const Node *pValue = FindValue(key))
throw BadDereference();
const Node *pValue = FindValue(key);
if(!pValue)
throw MakeTypedKeyNotFound(m_mark, key);
return *pValue; return *pValue;
throw MakeTypedKeyNotFound(m_mark, key);
} }
template <typename T> template <typename T>
......
#ifndef NODEPROPERTIES_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define NODEPROPERTIES_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
namespace YAML
{
struct NodeProperties {
std::string tag;
std::string anchor;
};
}
#endif // NODEPROPERTIES_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#include "aliascontent.h"
namespace YAML
{
AliasContent::AliasContent(Content* pNodeContent): m_pRef(pNodeContent)
{
}
bool AliasContent::GetBegin(std::vector <Node *>::const_iterator& i) const
{
return m_pRef->GetBegin(i);
}
bool AliasContent::GetBegin(std::map <Node *, Node *, ltnode>::const_iterator& i) const
{
return m_pRef->GetBegin(i);
}
bool AliasContent::GetEnd(std::vector <Node *>::const_iterator& i) const
{
return m_pRef->GetEnd(i);
}
bool AliasContent::GetEnd(std::map <Node *, Node *, ltnode>::const_iterator& i) const
{
return m_pRef->GetEnd(i);
}
Node* AliasContent::GetNode(std::size_t n) const
{
return m_pRef->GetNode(n);
}
std::size_t AliasContent::GetSize() const
{
return m_pRef->GetSize();
}
bool AliasContent::IsScalar() const
{
return m_pRef->IsScalar();
}
bool AliasContent::IsMap() const
{
return m_pRef->IsMap();
}
bool AliasContent::IsSequence() const
{
return m_pRef->IsSequence();
}
bool AliasContent::GetScalar(std::string& scalar) const
{
return m_pRef->GetScalar(scalar);
}
void AliasContent::EmitEvents(AliasManager& am, EventHandler& eventHandler, const Mark& mark, const std::string& tag, anchor_t anchor) const
{
m_pRef->EmitEvents(am, eventHandler, mark, tag, anchor);
}
int AliasContent::Compare(Content *pContent)
{
return m_pRef->Compare(pContent);
}
int AliasContent::Compare(Scalar *pScalar)
{
return m_pRef->Compare(pScalar);
}
int AliasContent::Compare(Sequence *pSequence)
{
return m_pRef->Compare(pSequence);
}
int AliasContent::Compare(Map *pMap)
{
return m_pRef->Compare(pMap);
}
}
#ifndef ALIASCONTENT_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define ALIASCONTENT_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
#include "content.h"
namespace YAML
{
class AliasContent : public Content
{
public:
AliasContent(Content *pNodeContent);
virtual bool GetBegin(std::vector <Node *>::const_iterator&) const;
virtual bool GetBegin(std::map <Node *, Node *, ltnode>::const_iterator&) const;
virtual bool GetEnd(std::vector <Node *>::const_iterator&) const;
virtual bool GetEnd(std::map <Node *, Node *, ltnode>::const_iterator&) const;
virtual Node* GetNode(std::size_t) const;
virtual std::size_t GetSize() const;
virtual bool IsScalar() const;
virtual bool IsMap() const;
virtual bool IsSequence() const;
virtual bool GetScalar(std::string& s) const;
virtual void EmitEvents(AliasManager& am, EventHandler& eventHandler, const Mark& mark, const std::string& tag, anchor_t anchor) const;
virtual int Compare(Content *);
virtual int Compare(Scalar *);
virtual int Compare(Sequence *);
virtual int Compare(Map *);
private:
Content* m_pRef;
};
}
#endif // ALIASCONTENT_H_62B23520_7C8E_11DE_8A39_0800200C9A66
...@@ -11,29 +11,13 @@ namespace YAML ...@@ -11,29 +11,13 @@ namespace YAML
void AliasManager::RegisterReference(const Node& node) void AliasManager::RegisterReference(const Node& node)
{ {
const Node *pIdentity = node.Identity();
m_newIdentityByOldIdentity.insert(std::make_pair(pIdentity, &node));
m_anchorByIdentity.insert(std::make_pair(&node, _CreateNewAnchor())); m_anchorByIdentity.insert(std::make_pair(&node, _CreateNewAnchor()));
} }
const Node *AliasManager::LookupReference(const Node& node) const
{
const Node *pIdentity = node.Identity();
return _LookupReference(*pIdentity);
}
anchor_t AliasManager::LookupAnchor(const Node& node) const anchor_t AliasManager::LookupAnchor(const Node& node) const
{ {
AnchorByIdentity::const_iterator it = m_anchorByIdentity.find(&node); AnchorByIdentity::const_iterator it = m_anchorByIdentity.find(&node);
if(it == m_anchorByIdentity.end()) if(it == m_anchorByIdentity.end())
assert(false); // TODO: throw
return it->second;
}
const Node *AliasManager::_LookupReference(const Node& oldIdentity) const
{
NodeByNode::const_iterator it = m_newIdentityByOldIdentity.find(&oldIdentity);
if(it == m_newIdentityByOldIdentity.end())
return 0; return 0;
return it->second; return it->second;
} }
......
#include "content.h"
#include "yaml-cpp/node.h"
#include <cassert>
namespace YAML
{
void Content::SetData(const std::string&)
{
assert(false); // TODO: throw
}
void Content::Append(std::auto_ptr<Node>)
{
assert(false); // TODO: throw
}
void Content::Insert(std::auto_ptr<Node>, std::auto_ptr<Node>)
{
assert(false); // TODO: throw
}
}
#ifndef CONTENT_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define CONTENT_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
#include "yaml-cpp/anchor.h"
#include "yaml-cpp/exceptions.h"
#include "ltnode.h"
#include <map>
#include <memory>
#include <vector>
namespace YAML
{
struct Mark;
struct NodeProperties;
class AliasManager;
class EventHandler;
class Map;
class Node;
class Scalar;
class Scanner;
class Sequence;
class Content
{
public:
Content() {}
virtual ~Content() {}
virtual bool GetBegin(std::vector <Node *>::const_iterator&) const { return false; }
virtual bool GetBegin(std::map <Node *, Node *, ltnode>::const_iterator&) const { return false; }
virtual bool GetEnd(std::vector <Node *>::const_iterator&) const { return false; }
virtual bool GetEnd(std::map <Node *, Node *, ltnode>::const_iterator&) const { return false; }
virtual Node *GetNode(std::size_t) const { return 0; }
virtual std::size_t GetSize() const { return 0; }
virtual bool IsScalar() const { return false; }
virtual bool IsMap() const { return false; }
virtual bool IsSequence() const { return false; }
virtual void SetData(const std::string& data);
virtual void Append(std::auto_ptr<Node> pNode);
virtual void Insert(std::auto_ptr<Node> pKey, std::auto_ptr<Node> pValue);
virtual void EmitEvents(AliasManager& am, EventHandler& eventHandler, const Mark& mark, const std::string& tag, anchor_t anchor) const = 0;
// extraction
virtual bool GetScalar(std::string&) const { return false; }
// ordering
virtual int Compare(Content *) { return 0; }
virtual int Compare(Scalar *) { return 0; }
virtual int Compare(Sequence *) { return 0; }
virtual int Compare(Map *) { return 0; }
protected:
};
}
#endif // CONTENT_H_62B23520_7C8E_11DE_8A39_0800200C9A66
...@@ -4,10 +4,10 @@ namespace YAML ...@@ -4,10 +4,10 @@ namespace YAML
{ {
int GraphBuilderAdapter::ContainerFrame::sequenceMarker; int GraphBuilderAdapter::ContainerFrame::sequenceMarker;
void GraphBuilderAdapter::OnNull(const std::string& tag, anchor_t anchor) void GraphBuilderAdapter::OnNull(const Mark& mark, anchor_t anchor)
{ {
void *pParent = GetCurrentParent(); void *pParent = GetCurrentParent();
void *pNode = m_builder.NewNull(tag, pParent); void *pNode = m_builder.NewNull(mark, pParent);
RegisterAnchor(anchor, pNode); RegisterAnchor(anchor, pNode);
DispositionNode(pNode); DispositionNode(pNode);
......
...@@ -24,7 +24,7 @@ namespace YAML ...@@ -24,7 +24,7 @@ namespace YAML
virtual void OnDocumentStart(const Mark& mark) {(void)mark;} virtual void OnDocumentStart(const Mark& mark) {(void)mark;}
virtual void OnDocumentEnd() {} virtual void OnDocumentEnd() {}
virtual void OnNull(const std::string& tag, anchor_t anchor); virtual void OnNull(const Mark& mark, anchor_t anchor);
virtual void OnAlias(const Mark& mark, anchor_t anchor); virtual void OnAlias(const Mark& mark, anchor_t anchor);
virtual void OnScalar(const Mark& mark, const std::string& tag, anchor_t anchor, const std::string& value); virtual void OnScalar(const Mark& mark, const std::string& tag, anchor_t anchor, const std::string& value);
......
...@@ -26,11 +26,10 @@ namespace YAML ...@@ -26,11 +26,10 @@ namespace YAML
{ {
} }
void EmitFromEvents::OnNull(const std::string& tag, anchor_t anchor) void EmitFromEvents::OnNull(const Mark&, anchor_t anchor)
{ {
BeginNode(); BeginNode();
EmitProps(tag, anchor); EmitProps("", anchor);
if(tag.empty())
m_emitter << Null; m_emitter << Null;
} }
......
...@@ -4,18 +4,16 @@ ...@@ -4,18 +4,16 @@
namespace YAML namespace YAML
{ {
Iterator::Iterator(): m_pData(0) Iterator::Iterator(): m_pData(new IterPriv)
{ {
m_pData = new IterPriv;
} }
Iterator::Iterator(IterPriv *pData): m_pData(pData) Iterator::Iterator(std::auto_ptr<IterPriv> pData): m_pData(pData)
{ {
} }
Iterator::Iterator(const Iterator& rhs): m_pData(0) Iterator::Iterator(const Iterator& rhs): m_pData(new IterPriv(*rhs.m_pData))
{ {
m_pData = new IterPriv(*rhs.m_pData);
} }
Iterator& Iterator::operator = (const Iterator& rhs) Iterator& Iterator::operator = (const Iterator& rhs)
...@@ -23,14 +21,12 @@ namespace YAML ...@@ -23,14 +21,12 @@ namespace YAML
if(this == &rhs) if(this == &rhs)
return *this; return *this;
delete m_pData; m_pData.reset(new IterPriv(*rhs.m_pData));
m_pData = new IterPriv(*rhs.m_pData);
return *this; return *this;
} }
Iterator::~Iterator() Iterator::~Iterator()
{ {
delete m_pData;
} }
Iterator& Iterator::operator ++ () Iterator& Iterator::operator ++ ()
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
#endif #endif
#include "ltnode.h" #include "yaml-cpp/ltnode.h"
#include <vector> #include <vector>
#include <map> #include <map>
......
#include "map.h"
#include "yaml-cpp/node.h"
#include "yaml-cpp/eventhandler.h"
#include "yaml-cpp/exceptions.h"
namespace YAML
{
Map::Map()
{
}
Map::~Map()
{
Clear();
}
void Map::Clear()
{
for(node_map::const_iterator it=m_data.begin();it!=m_data.end();++it) {
delete it->first;
delete it->second;
}
m_data.clear();
}
bool Map::GetBegin(std::map <Node *, Node *, ltnode>::const_iterator& it) const
{
it = m_data.begin();
return true;
}
bool Map::GetEnd(std::map <Node *, Node *, ltnode>::const_iterator& it) const
{
it = m_data.end();
return true;
}
std::size_t Map::GetSize() const
{
return m_data.size();
}
void Map::Insert(std::auto_ptr<Node> pKey, std::auto_ptr<Node> pValue)
{
node_map::const_iterator it = m_data.find(pKey.get());
if(it != m_data.end())
return;
m_data[pKey.release()] = pValue.release();
}
void Map::EmitEvents(AliasManager& am, EventHandler& eventHandler, const Mark& mark, const std::string& tag, anchor_t anchor) const
{
eventHandler.OnMapStart(mark, tag, anchor);
for(node_map::const_iterator it=m_data.begin();it!=m_data.end();++it) {
it->first->EmitEvents(am, eventHandler);
it->second->EmitEvents(am, eventHandler);
}
eventHandler.OnMapEnd();
}
int Map::Compare(Content *pContent)
{
return -pContent->Compare(this);
}
int Map::Compare(Map *pMap)
{
node_map::const_iterator it = m_data.begin(), jt = pMap->m_data.begin();
while(1) {
if(it == m_data.end()) {
if(jt == pMap->m_data.end())
return 0;
else
return -1;
}
if(jt == pMap->m_data.end())
return 1;
int cmp = it->first->Compare(*jt->first);
if(cmp != 0)
return cmp;
cmp = it->second->Compare(*jt->second);
if(cmp != 0)
return cmp;
}
return 0;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment