From cba20711b027799082b032c17a05ca5012360fb5 Mon Sep 17 00:00:00 2001 From: jbeder Date: Fri, 22 May 2009 21:52:31 +0000 Subject: [PATCH] Merged emitter branch into trunk, changes r105:r151 --- include/emitter.h | 85 +++++ include/emittermanip.h | 84 +++++ include/exceptions.h | 83 +++-- include/noncopyable.h | 16 + include/ostream.h | 31 ++ include/stlemitter.h | 38 +++ src/CMakeLists.txt | 4 +- src/emitter.cpp | 697 +++++++++++++++++++++++++++++++++++++++++ src/emitterstate.cpp | 263 ++++++++++++++++ src/emitterstate.h | 195 ++++++++++++ src/emitterutils.cpp | 143 +++++++++ src/emitterutils.h | 18 ++ src/exp.h | 5 +- src/indentation.h | 30 ++ src/ostream.cpp | 63 ++++ src/setting.h | 97 ++++++ src/stream.h | 5 +- yaml-reader/main.cpp | 6 + yaml-reader/tests.cpp | 603 +++++++++++++++++++++++++++++++++++ yaml-reader/tests.h | 49 +++ 20 files changed, 2478 insertions(+), 37 deletions(-) create mode 100644 include/emitter.h create mode 100644 include/emittermanip.h create mode 100644 include/noncopyable.h create mode 100644 include/ostream.h create mode 100644 include/stlemitter.h create mode 100644 src/emitter.cpp create mode 100644 src/emitterstate.cpp create mode 100644 src/emitterstate.h create mode 100644 src/emitterutils.cpp create mode 100644 src/emitterutils.h create mode 100644 src/indentation.h create mode 100644 src/ostream.cpp create mode 100644 src/setting.h diff --git a/include/emitter.h b/include/emitter.h new file mode 100644 index 0000000..92d9993 --- /dev/null +++ b/include/emitter.h @@ -0,0 +1,85 @@ +#pragma once + +#include "emittermanip.h" +#include "ostream.h" +#include +#include + +namespace YAML +{ + class EmitterState; + + class Emitter + { + public: + Emitter(); + ~Emitter(); + + bool WriteToStream(std::ostream& out) const; + bool WriteToFile(const std::string& fileName) const; + + // state checking + bool good() const; + const std::string GetLastError() const; + + // global setters + bool SetStringFormat(EMITTER_MANIP value); + bool SetBoolFormat(EMITTER_MANIP value); + bool SetIntBase(EMITTER_MANIP value); + bool SetSeqFormat(EMITTER_MANIP value); + bool SetMapFormat(EMITTER_MANIP value); + bool SetIndent(unsigned n); + bool SetPreCommentIndent(unsigned n); + bool SetPostCommentIndent(unsigned n); + + // local setters + Emitter& SetLocalValue(EMITTER_MANIP value); + Emitter& SetLocalIndent(const _Indent& indent); + + // overloads of write + Emitter& Write(const std::string& str); + Emitter& Write(const char *str); + Emitter& Write(int i); + Emitter& Write(bool b); + Emitter& Write(float f); + Emitter& Write(double d); + Emitter& Write(const _Alias& alias); + Emitter& Write(const _Anchor& anchor); + Emitter& Write(const _Comment& comment); + + private: + enum ATOMIC_TYPE { AT_SCALAR, AT_SEQ, AT_BLOCK_SEQ, AT_FLOW_SEQ, AT_MAP, AT_BLOCK_MAP, AT_FLOW_MAP }; + + void PreAtomicWrite(); + bool GotoNextPreAtomicState(); + void PostAtomicWrite(); + void EmitSeparationIfNecessary(); + + void EmitBeginSeq(); + void EmitEndSeq(); + void EmitBeginMap(); + void EmitEndMap(); + void EmitKey(); + void EmitValue(); + + private: + ostream m_stream; + std::auto_ptr m_pState; + }; + + // overloads of insertion + template + inline Emitter& operator << (Emitter& emitter, T v) { + return emitter.Write(v); + } + + template <> + inline Emitter& operator << (Emitter& emitter, EMITTER_MANIP value) { + return emitter.SetLocalValue(value); + } + + template <> + inline Emitter& operator << (Emitter& emitter, _Indent indent) { + return emitter.SetLocalIndent(indent); + } +} diff --git a/include/emittermanip.h b/include/emittermanip.h new file mode 100644 index 0000000..05a9e64 --- /dev/null +++ b/include/emittermanip.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +namespace YAML +{ + enum EMITTER_MANIP { + // general manipulators + Auto, + + // string manipulators + // Auto, // duplicate + SingleQuoted, + DoubleQuoted, + Literal, + + // bool manipulators + YesNoBool, // yes, no + TrueFalseBool, // true, false + OnOffBool, // on, off + UpperCase, // TRUE, N + LowerCase, // f, yes + CamelCase, // No, Off + LongBool, // yes, On + ShortBool, // y, t + + // int manipulators + Dec, + Hex, + Oct, + + // sequence manipulators + BeginSeq, + EndSeq, + Flow, + Block, + + // map manipulators + BeginMap, + EndMap, + Key, + Value, + // Flow, // duplicate + // Block, // duplicate + // Auto, // duplicate + LongKey, + }; + + struct _Indent { + _Indent(int value_): value(value_) {} + int value; + }; + + inline _Indent Indent(int value) { + return _Indent(value); + } + + struct _Alias { + _Alias(const std::string& content_): content(content_) {} + std::string content; + }; + + inline _Alias Alias(const std::string content) { + return _Alias(content); + } + + struct _Anchor { + _Anchor(const std::string& content_): content(content_) {} + std::string content; + }; + + inline _Anchor Anchor(const std::string content) { + return _Anchor(content); + } + + struct _Comment { + _Comment(const std::string& content_): content(content_) {} + std::string content; + }; + + inline _Comment Comment(const std::string content) { + return _Comment(content); + } +} diff --git a/include/exceptions.h b/include/exceptions.h index 8378369..784dc05 100644 --- a/include/exceptions.h +++ b/include/exceptions.h @@ -8,42 +8,53 @@ namespace YAML // error messages namespace ErrorMsg { - const std::string YAML_DIRECTIVE_ARGS = "YAML directives must have exactly one argument"; - const std::string YAML_VERSION = "bad YAML version: "; - const std::string YAML_MAJOR_VERSION = "YAML major version too large"; - const std::string TAG_DIRECTIVE_ARGS = "TAG directives must have exactly two arguments"; - const std::string END_OF_MAP = "end of map not found"; - const std::string END_OF_MAP_FLOW = "end of map flow not found"; - const std::string END_OF_SEQ = "end of sequence not found"; - const std::string END_OF_SEQ_FLOW = "end of sequence flow not found"; - const std::string MULTIPLE_TAGS = "cannot assign multiple tags to the same node"; - const std::string MULTIPLE_ANCHORS = "cannot assign multiple anchors to the same node"; - const std::string MULTIPLE_ALIASES = "cannot assign multiple aliases to the same node"; - const std::string ALIAS_CONTENT = "aliases can't have any content, *including* tags"; - const std::string INVALID_HEX = "bad character found while scanning hex number"; - const std::string INVALID_UNICODE = "invalid unicode: "; - const std::string INVALID_ESCAPE = "unknown escape character: "; - const std::string UNKNOWN_TOKEN = "unknown token"; - const std::string DOC_IN_SCALAR = "illegal document indicator in scalar"; - const std::string EOF_IN_SCALAR = "illegal EOF in scalar"; - const std::string CHAR_IN_SCALAR = "illegal character in scalar"; - const std::string TAB_IN_INDENTATION = "illegal tab when looking for indentation"; - const std::string FLOW_END = "illegal flow end"; - const std::string BLOCK_ENTRY = "illegal block entry"; - const std::string MAP_KEY = "illegal map key"; - const std::string MAP_VALUE = "illegal map value"; - const std::string ALIAS_NOT_FOUND = "alias not found after *"; - const std::string ANCHOR_NOT_FOUND = "anchor not found after &"; - const std::string CHAR_IN_ALIAS = "illegal character found while scanning alias"; - const std::string CHAR_IN_ANCHOR = "illegal character found while scanning anchor"; - const std::string ZERO_INDENT_IN_BLOCK = "cannot set zero indentation for a block scalar"; - const std::string CHAR_IN_BLOCK = "unexpected character in block scalar"; + const std::string YAML_DIRECTIVE_ARGS = "YAML directives must have exactly one argument"; + const std::string YAML_VERSION = "bad YAML version: "; + const std::string YAML_MAJOR_VERSION = "YAML major version too large"; + const std::string TAG_DIRECTIVE_ARGS = "TAG directives must have exactly two arguments"; + const std::string END_OF_MAP = "end of map not found"; + const std::string END_OF_MAP_FLOW = "end of map flow not found"; + const std::string END_OF_SEQ = "end of sequence not found"; + const std::string END_OF_SEQ_FLOW = "end of sequence flow not found"; + const std::string MULTIPLE_TAGS = "cannot assign multiple tags to the same node"; + const std::string MULTIPLE_ANCHORS = "cannot assign multiple anchors to the same node"; + const std::string MULTIPLE_ALIASES = "cannot assign multiple aliases to the same node"; + const std::string ALIAS_CONTENT = "aliases can't have any content, *including* tags"; + const std::string INVALID_HEX = "bad character found while scanning hex number"; + const std::string INVALID_UNICODE = "invalid unicode: "; + const std::string INVALID_ESCAPE = "unknown escape character: "; + const std::string UNKNOWN_TOKEN = "unknown token"; + const std::string DOC_IN_SCALAR = "illegal document indicator in scalar"; + const std::string EOF_IN_SCALAR = "illegal EOF in scalar"; + const std::string CHAR_IN_SCALAR = "illegal character in scalar"; + const std::string TAB_IN_INDENTATION = "illegal tab when looking for indentation"; + const std::string FLOW_END = "illegal flow end"; + const std::string BLOCK_ENTRY = "illegal block entry"; + const std::string MAP_KEY = "illegal map key"; + const std::string MAP_VALUE = "illegal map value"; + const std::string ALIAS_NOT_FOUND = "alias not found after *"; + const std::string ANCHOR_NOT_FOUND = "anchor not found after &"; + const std::string CHAR_IN_ALIAS = "illegal character found while scanning alias"; + const std::string CHAR_IN_ANCHOR = "illegal character found while scanning anchor"; + const std::string ZERO_INDENT_IN_BLOCK = "cannot set zero indentation for a block scalar"; + const std::string CHAR_IN_BLOCK = "unexpected character in block scalar"; const std::string AMBIGUOUS_ANCHOR = "cannot assign the same alias to multiple nodes"; const std::string UNKNOWN_ANCHOR = "the referenced anchor is not defined"; - const std::string INVALID_SCALAR = "invalid scalar"; - const std::string KEY_NOT_FOUND = "key not found"; - const std::string BAD_DEREFERENCE = "bad dereference"; + const std::string INVALID_SCALAR = "invalid scalar"; + const std::string KEY_NOT_FOUND = "key not found"; + const std::string BAD_DEREFERENCE = "bad dereference"; + + const std::string UNMATCHED_GROUP_TAG = "unmatched group tag"; + const std::string UNEXPECTED_END_SEQ = "unexpected end sequence token"; + const std::string UNEXPECTED_END_MAP = "unexpected end map token"; + const std::string SINGLE_QUOTED_CHAR = "invalid character in single-quoted string"; + const std::string INVALID_ANCHOR = "invalid anchor"; + const std::string INVALID_ALIAS = "invalid alias"; + const std::string EXPECTED_KEY_TOKEN = "expected key token"; + const std::string EXPECTED_VALUE_TOKEN = "expected value token"; + const std::string UNEXPECTED_KEY_TOKEN = "unexpected key token"; + const std::string UNEXPECTED_VALUE_TOKEN = "unexpected value token"; } class Exception: public std::exception { @@ -101,4 +112,10 @@ namespace YAML BadDereference() : RepresentationException(-1, -1, ErrorMsg::BAD_DEREFERENCE) {} }; + + class EmitterException: public Exception { + public: + EmitterException(const std::string& msg_) + : Exception(-1, -1, msg_) {} + }; } diff --git a/include/noncopyable.h b/include/noncopyable.h new file mode 100644 index 0000000..2ae4c68 --- /dev/null +++ b/include/noncopyable.h @@ -0,0 +1,16 @@ +#pragma once + +namespace YAML +{ + // this is basically boost::noncopyable + class noncopyable + { + protected: + noncopyable() {} + ~noncopyable() {} + + private: + noncopyable(const noncopyable&); + const noncopyable& operator = (const noncopyable&); + }; +} diff --git a/include/ostream.h b/include/ostream.h new file mode 100644 index 0000000..5051576 --- /dev/null +++ b/include/ostream.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +namespace YAML +{ + class ostream + { + public: + ostream(); + ~ostream(); + + void reserve(unsigned size); + void put(char ch); + const char *str() const { return m_buffer; } + + unsigned row() const { return m_row; } + unsigned col() const { return m_col; } + + private: + char *m_buffer; + unsigned m_pos; + unsigned m_size; + + unsigned m_row, m_col; + }; + + ostream& operator << (ostream& out, const char *str); + ostream& operator << (ostream& out, const std::string& str); + ostream& operator << (ostream& out, char ch); +} diff --git a/include/stlemitter.h b/include/stlemitter.h new file mode 100644 index 0000000..5caa872 --- /dev/null +++ b/include/stlemitter.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +namespace YAML +{ + template + inline Emitter& operator << (Emitter& emitter, const std::vector & v) { + typedef typename std::vector vec; + emitter << BeginSeq; + for(typename vec::const_iterator it=v.begin();it!=v.end();++it) + emitter << *it; + emitter << EndSeq; + return emitter; + } + + template + inline Emitter& operator << (Emitter& emitter, const std::list & v) { + typedef typename std::list list; + emitter << BeginSeq; + for(typename list::const_iterator it=v.begin();it!=v.end();++it) + emitter << *it; + emitter << EndSeq; + return emitter; + } + + template + inline Emitter& operator << (Emitter& emitter, const std::map & m) { + typedef typename std::map map; + emitter << BeginMap; + for(typename map::const_iterator it=m.begin();it!=m.end();++it) + emitter << Key << it->first << Value << it->second; + emitter << EndMap; + return emitter; + } +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9753189..8e59ffe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,9 @@ set(FILES alias.cpp content.cpp iterator.cpp node.cpp parserstate.cpp scalar.cpp scanscalar.cpp sequence.cpp stream.cpp exp.cpp map.cpp parser.cpp regex.cpp scanner.cpp - scantoken.cpp simplekey.cpp) + scantoken.cpp simplekey.cpp + emitter.cpp emitterstate.h emitterstate.cpp emitterutils.h emitterutils.cpp + ostream.cpp) include_directories(${YAML_CPP_SOURCE_DIR}/include) add_library(yaml-cpp ${FILES}) diff --git a/src/emitter.cpp b/src/emitter.cpp new file mode 100644 index 0000000..bdc2fba --- /dev/null +++ b/src/emitter.cpp @@ -0,0 +1,697 @@ +#include "emitter.h" +#include "emitterstate.h" +#include "emitterutils.h" +#include "indentation.h" +#include "exceptions.h" +#include +#include + +namespace YAML +{ + Emitter::Emitter(): m_pState(new EmitterState) + { + } + + Emitter::~Emitter() + { + } + + bool Emitter::WriteToStream(std::ostream& out) const + { + out << m_stream.str(); + return true; + } + + bool Emitter::WriteToFile(const std::string& fileName) const + { + std::ofstream fout(fileName.c_str()); + if(!fout) + return false; + + return WriteToStream(fout); + } + + // state checking + bool Emitter::good() const + { + return m_pState->good(); + } + + const std::string Emitter::GetLastError() const + { + return m_pState->GetLastError(); + } + + // global setters + bool Emitter::SetStringFormat(EMITTER_MANIP value) + { + return m_pState->SetStringFormat(value, GLOBAL); + } + + bool Emitter::SetBoolFormat(EMITTER_MANIP value) + { + bool ok = false; + if(m_pState->SetBoolFormat(value, GLOBAL)) + ok = true; + if(m_pState->SetBoolCaseFormat(value, GLOBAL)) + ok = true; + if(m_pState->SetBoolLengthFormat(value, GLOBAL)) + ok = true; + return ok; + } + + bool Emitter::SetIntBase(EMITTER_MANIP value) + { + return m_pState->SetIntFormat(value, GLOBAL); + } + + bool Emitter::SetSeqFormat(EMITTER_MANIP value) + { + return m_pState->SetFlowType(GT_SEQ, value, GLOBAL); + } + + bool Emitter::SetMapFormat(EMITTER_MANIP value) + { + bool ok = false; + if(m_pState->SetFlowType(GT_MAP, value, GLOBAL)) + ok = true; + if(m_pState->SetMapKeyFormat(value, GLOBAL)) + ok = true; + return ok; + } + + bool Emitter::SetIndent(unsigned n) + { + return m_pState->SetIndent(n, GLOBAL); + } + + bool Emitter::SetPreCommentIndent(unsigned n) + { + return m_pState->SetPreCommentIndent(n, GLOBAL); + } + + bool Emitter::SetPostCommentIndent(unsigned n) + { + return m_pState->SetPostCommentIndent(n, GLOBAL); + } + + // SetLocalValue + // . Either start/end a group, or set a modifier locally + Emitter& Emitter::SetLocalValue(EMITTER_MANIP value) + { + if(!good()) + return *this; + + switch(value) { + case BeginSeq: + EmitBeginSeq(); + break; + case EndSeq: + EmitEndSeq(); + break; + case BeginMap: + EmitBeginMap(); + break; + case EndMap: + EmitEndMap(); + break; + case Key: + EmitKey(); + break; + case Value: + EmitValue(); + break; + default: + m_pState->SetLocalValue(value); + break; + } + return *this; + } + + Emitter& Emitter::SetLocalIndent(const _Indent& indent) + { + m_pState->SetIndent(indent.value, LOCAL); + return *this; + } + + // GotoNextPreAtomicState + // . Runs the state machine, emitting if necessary, and returns 'true' if done (i.e., ready to emit an atom) + bool Emitter::GotoNextPreAtomicState() + { + if(!good()) + return true; + + unsigned curIndent = m_pState->GetCurIndent(); + + EMITTER_STATE curState = m_pState->GetCurState(); + switch(curState) { + // document-level + case ES_WAITING_FOR_DOC: + std::cerr << "waiting for doc (pre)\n"; + m_pState->SwitchState(ES_WRITING_DOC); + return true; + case ES_WRITING_DOC: + std::cerr << "writing doc (pre)\n"; + return true; + + // block sequence + case ES_WAITING_FOR_BLOCK_SEQ_ENTRY: + std::cerr << "waiting for block seq entry (pre)\n"; + m_stream << IndentTo(curIndent) << "-"; + m_pState->RequireSeparation(); + m_pState->SwitchState(ES_WRITING_BLOCK_SEQ_ENTRY); + return true; + case ES_WRITING_BLOCK_SEQ_ENTRY: + std::cerr << "writing block seq entry (pre)\n"; + return true; + case ES_DONE_WITH_BLOCK_SEQ_ENTRY: + std::cerr << "done with block seq entry (pre)\n"; + m_stream << '\n'; + m_pState->SwitchState(ES_WAITING_FOR_BLOCK_SEQ_ENTRY); + return false; + + // flow sequence + case ES_WAITING_FOR_FLOW_SEQ_ENTRY: + std::cerr << "waiting for flow seq entry (pre)\n"; + m_pState->SwitchState(ES_WRITING_FLOW_SEQ_ENTRY); + return true; + case ES_WRITING_FLOW_SEQ_ENTRY: + std::cerr << "writing flow seq entry (pre)\n"; + return true; + case ES_DONE_WITH_FLOW_SEQ_ENTRY: + std::cerr << "done with flow seq entry (pre)\n"; + m_stream << ','; + m_pState->RequireSeparation(); + m_pState->SwitchState(ES_WAITING_FOR_FLOW_SEQ_ENTRY); + return false; + + // block map + case ES_WAITING_FOR_BLOCK_MAP_ENTRY: + std::cerr << "waiting for block map entry (pre)\n"; + m_pState->SetError(ErrorMsg::EXPECTED_KEY_TOKEN); + return true; + case ES_WAITING_FOR_BLOCK_MAP_KEY: + std::cerr << "waiting for block map key (pre)\n"; + if(m_pState->CurrentlyInLongKey()) { + m_stream << IndentTo(curIndent) << '?'; + m_pState->RequireSeparation(); + } + m_pState->SwitchState(ES_WRITING_BLOCK_MAP_KEY); + return true; + case ES_WRITING_BLOCK_MAP_KEY: + std::cerr << "writing block map key (pre)\n"; + return true; + case ES_DONE_WITH_BLOCK_MAP_KEY: + std::cerr << "done with block map key (pre)\n"; + m_pState->SetError(ErrorMsg::EXPECTED_VALUE_TOKEN); + return true; + case ES_WAITING_FOR_BLOCK_MAP_VALUE: + std::cerr << "waiting for block map value (pre)\n"; + if(m_pState->CurrentlyInLongKey()) + m_stream << IndentTo(curIndent); + m_stream << ':'; + m_pState->RequireSeparation(); + m_pState->SwitchState(ES_WRITING_BLOCK_MAP_VALUE); + return true; + case ES_WRITING_BLOCK_MAP_VALUE: + std::cerr << "writing block map value (pre)\n"; + return true; + case ES_DONE_WITH_BLOCK_MAP_VALUE: + std::cerr << "done with block map value (pre)\n"; + m_pState->SetError(ErrorMsg::EXPECTED_KEY_TOKEN); + return true; + + // flow map + case ES_WAITING_FOR_FLOW_MAP_ENTRY: + std::cerr << "waiting for flow map entry (pre)\n"; + m_pState->SetError(ErrorMsg::EXPECTED_KEY_TOKEN); + return true; + case ES_WAITING_FOR_FLOW_MAP_KEY: + std::cerr << "waiting for flow map key (pre)\n"; + m_pState->SwitchState(ES_WRITING_FLOW_MAP_KEY); + if(m_pState->CurrentlyInLongKey()) { + EmitSeparationIfNecessary(); + m_stream << '?'; + m_pState->RequireSeparation(); + } + return true; + case ES_WRITING_FLOW_MAP_KEY: + std::cerr << "writing flow map key (pre)\n"; + return true; + case ES_DONE_WITH_FLOW_MAP_KEY: + std::cerr << "done with flow map key (pre)\n"; + m_pState->SetError(ErrorMsg::EXPECTED_VALUE_TOKEN); + return true; + case ES_WAITING_FOR_FLOW_MAP_VALUE: + std::cerr << "waiting for flow map value (pre)\n"; + m_stream << ':'; + m_pState->RequireSeparation(); + m_pState->SwitchState(ES_WRITING_FLOW_MAP_VALUE); + return true; + case ES_WRITING_FLOW_MAP_VALUE: + std::cerr << "writing flow map value (pre)\n"; + return true; + case ES_DONE_WITH_FLOW_MAP_VALUE: + std::cerr << "done with flow map value (pre)\n"; + m_pState->SetError(ErrorMsg::EXPECTED_KEY_TOKEN); + return true; + default: + assert(false); + } + + assert(false); + return true; + } + + // PreAtomicWrite + // . Depending on the emitter state, write to the stream to get it + // in position to do an atomic write (e.g., scalar, sequence, or map) + void Emitter::PreAtomicWrite() + { + if(!good()) + return; + + while(!GotoNextPreAtomicState()) + ; + } + + // PostAtomicWrite + // . Clean up + void Emitter::PostAtomicWrite() + { + if(!good()) + return; + + EMITTER_STATE curState = m_pState->GetCurState(); + switch(curState) { + // document-level + case ES_WRITING_DOC: + m_pState->SwitchState(ES_DONE_WITH_DOC); + break; + + // block seq + case ES_WRITING_BLOCK_SEQ_ENTRY: + m_pState->SwitchState(ES_DONE_WITH_BLOCK_SEQ_ENTRY); + break; + + // flow seq + case ES_WRITING_FLOW_SEQ_ENTRY: + m_pState->SwitchState(ES_DONE_WITH_FLOW_SEQ_ENTRY); + break; + + // block map + case ES_WRITING_BLOCK_MAP_KEY: + m_pState->SwitchState(ES_DONE_WITH_BLOCK_MAP_KEY); + break; + case ES_WRITING_BLOCK_MAP_VALUE: + m_pState->SwitchState(ES_DONE_WITH_BLOCK_MAP_VALUE); + break; + + // flow map + case ES_WRITING_FLOW_MAP_KEY: + m_pState->SwitchState(ES_DONE_WITH_FLOW_MAP_KEY); + break; + case ES_WRITING_FLOW_MAP_VALUE: + m_pState->SwitchState(ES_DONE_WITH_FLOW_MAP_VALUE); + break; + default: + assert(false); + }; + + m_pState->ClearModifiedSettings(); + } + + // EmitSeparationIfNecessary + void Emitter::EmitSeparationIfNecessary() + { + if(!good()) + return; + + if(m_pState->RequiresSeparation()) + m_stream << ' '; + m_pState->UnsetSeparation(); + } + + // EmitBeginSeq + void Emitter::EmitBeginSeq() + { + if(!good()) + return; + + // must have a long key if we're emitting a sequence + m_pState->StartLongKey(); + + PreAtomicWrite(); + + EMITTER_STATE curState = m_pState->GetCurState(); + EMITTER_MANIP flowType = m_pState->GetFlowType(GT_SEQ); + if(flowType == Block) { + if(curState == ES_WRITING_BLOCK_SEQ_ENTRY || curState == ES_WRITING_BLOCK_MAP_KEY || curState == ES_WRITING_BLOCK_MAP_VALUE) { + m_stream << "\n"; + m_pState->UnsetSeparation(); + } + m_pState->PushState(ES_WAITING_FOR_BLOCK_SEQ_ENTRY); + } else if(flowType == Flow) { + EmitSeparationIfNecessary(); + m_stream << "["; + m_pState->PushState(ES_WAITING_FOR_FLOW_SEQ_ENTRY); + } else + assert(false); + + m_pState->BeginGroup(GT_SEQ); + } + + // EmitEndSeq + void Emitter::EmitEndSeq() + { + if(!good()) + return; + + if(m_pState->GetCurGroupType() != GT_SEQ) + return m_pState->SetError(ErrorMsg::UNEXPECTED_END_SEQ); + + EMITTER_STATE curState = m_pState->GetCurState(); + FLOW_TYPE flowType = m_pState->GetCurGroupFlowType(); + if(flowType == FT_BLOCK) + assert(curState == ES_DONE_WITH_BLOCK_SEQ_ENTRY); + else if(flowType == FT_FLOW) { + m_stream << "]"; + // Note: flow sequences are allowed to be empty + assert(curState == ES_DONE_WITH_FLOW_SEQ_ENTRY || curState == ES_WAITING_FOR_FLOW_SEQ_ENTRY); + } else + assert(false); + + m_pState->PopState(); + m_pState->EndGroup(GT_SEQ); + + PostAtomicWrite(); + } + + // EmitBeginMap + void Emitter::EmitBeginMap() + { + if(!good()) + return; + + // must have a long key if we're emitting a map + m_pState->StartLongKey(); + + PreAtomicWrite(); + + EMITTER_STATE curState = m_pState->GetCurState(); + EMITTER_MANIP flowType = m_pState->GetFlowType(GT_MAP); + if(flowType == Block) { + if(curState == ES_WRITING_BLOCK_SEQ_ENTRY || curState == ES_WRITING_BLOCK_MAP_KEY || curState == ES_WRITING_BLOCK_MAP_VALUE) { + m_stream << "\n"; + m_pState->UnsetSeparation(); + } + m_pState->PushState(ES_WAITING_FOR_BLOCK_MAP_ENTRY); + } else if(flowType == Flow) { + EmitSeparationIfNecessary(); + m_stream << "{"; + m_pState->PushState(ES_WAITING_FOR_FLOW_MAP_ENTRY); + } else + assert(false); + + m_pState->BeginGroup(GT_MAP); + } + + // EmitEndMap + void Emitter::EmitEndMap() + { + if(!good()) + return; + + if(m_pState->GetCurGroupType() != GT_MAP) + return m_pState->SetError(ErrorMsg::UNEXPECTED_END_MAP); + + EMITTER_STATE curState = m_pState->GetCurState(); + FLOW_TYPE flowType = m_pState->GetCurGroupFlowType(); + if(flowType == FT_BLOCK) + assert(curState == ES_DONE_WITH_BLOCK_MAP_VALUE); + else if(flowType == FT_FLOW) { + m_stream << "}"; + // Note: flow maps are allowed to be empty + assert(curState == ES_DONE_WITH_FLOW_MAP_VALUE || curState == ES_WAITING_FOR_FLOW_MAP_ENTRY); + } else + assert(false); + + m_pState->PopState(); + m_pState->EndGroup(GT_MAP); + + PostAtomicWrite(); + } + + // EmitKey + void Emitter::EmitKey() + { + if(!good()) + return; + + EMITTER_STATE curState = m_pState->GetCurState(); + FLOW_TYPE flowType = m_pState->GetCurGroupFlowType(); + if(curState != ES_WAITING_FOR_BLOCK_MAP_ENTRY && curState != ES_DONE_WITH_BLOCK_MAP_VALUE + && curState != ES_WAITING_FOR_FLOW_MAP_ENTRY && curState != ES_DONE_WITH_FLOW_MAP_VALUE) + return m_pState->SetError(ErrorMsg::UNEXPECTED_KEY_TOKEN); + + if(flowType == FT_BLOCK) { + if(curState == ES_DONE_WITH_BLOCK_MAP_VALUE) + m_stream << '\n'; + unsigned curIndent = m_pState->GetCurIndent(); + m_stream << IndentTo(curIndent); + m_pState->SwitchState(ES_WAITING_FOR_BLOCK_MAP_KEY); + } else if(flowType == FT_FLOW) { + if(curState == ES_DONE_WITH_FLOW_MAP_VALUE) { + m_stream << ','; + m_pState->RequireSeparation(); + } + m_pState->SwitchState(ES_WAITING_FOR_FLOW_MAP_KEY); + } else + assert(false); + + if(m_pState->GetMapKeyFormat() == LongKey) + m_pState->StartLongKey(); + else if(m_pState->GetMapKeyFormat() == Auto) + m_pState->StartSimpleKey(); + else + assert(false); + } + + // EmitValue + void Emitter::EmitValue() + { + if(!good()) + return; + + EMITTER_STATE curState = m_pState->GetCurState(); + FLOW_TYPE flowType = m_pState->GetCurGroupFlowType(); + if(curState != ES_DONE_WITH_BLOCK_MAP_KEY && curState != ES_DONE_WITH_FLOW_MAP_KEY) + return m_pState->SetError(ErrorMsg::UNEXPECTED_VALUE_TOKEN); + + if(flowType == FT_BLOCK) { + if(m_pState->CurrentlyInLongKey()) + m_stream << '\n'; + m_pState->SwitchState(ES_WAITING_FOR_BLOCK_MAP_VALUE); + } else if(flowType == FT_FLOW) { + m_pState->SwitchState(ES_WAITING_FOR_FLOW_MAP_VALUE); + } else + assert(false); + } + + // ******************************************************************************************* + // overloads of Write + + Emitter& Emitter::Write(const std::string& str) + { + if(!good()) + return *this; + + // literal scalars must use long keys + if(m_pState->GetStringFormat() == Literal && m_pState->GetCurGroupFlowType() != FT_FLOW) + m_pState->StartLongKey(); + + PreAtomicWrite(); + EmitSeparationIfNecessary(); + + EMITTER_MANIP strFmt = m_pState->GetStringFormat(); + FLOW_TYPE flowType = m_pState->GetCurGroupFlowType(); + unsigned curIndent = m_pState->GetCurIndent(); + + switch(strFmt) { + case Auto: + Utils::WriteString(m_stream, str, flowType == FT_FLOW); + break; + case SingleQuoted: + if(!Utils::WriteSingleQuotedString(m_stream, str)) { + m_pState->SetError(ErrorMsg::SINGLE_QUOTED_CHAR); + return *this; + } + break; + case DoubleQuoted: + Utils::WriteDoubleQuotedString(m_stream, str); + break; + case Literal: + if(flowType == FT_FLOW) + Utils::WriteString(m_stream, str, flowType == FT_FLOW); + else + Utils::WriteLiteralString(m_stream, str, curIndent + m_pState->GetIndent()); + break; + default: + assert(false); + } + + PostAtomicWrite(); + return *this; + } + + Emitter& Emitter::Write(const char *str) + { + if(!good()) + return *this; + + return Write(std::string(str)); + } + + Emitter& Emitter::Write(int i) + { + if(!good()) + return *this; + + PreAtomicWrite(); + EmitSeparationIfNecessary(); + + EMITTER_MANIP intFmt = m_pState->GetIntFormat(); + std::stringstream str; + switch(intFmt) { + case Dec: + str << std::dec; + break; + case Hex: + str << std::hex; + break; + case Oct: + str << std::oct; + break; + default: + assert(false); + } + + str << i; + m_stream << str.str(); + + PostAtomicWrite(); + return *this; + } + + Emitter& Emitter::Write(bool b) + { + if(!good()) + return *this; + + PreAtomicWrite(); + EmitSeparationIfNecessary(); + + // set up all possible bools to write + struct BoolName { std::string trueName, falseName; }; + struct BoolFormatNames { BoolName upper, lower, camel; }; + struct BoolTypes { BoolFormatNames yesNo, trueFalse, onOff; }; + + static const BoolTypes boolTypes = { + { { "YES", "NO" }, { "yes", "no" }, { "Yes", "No" } }, + { { "TRUE", "FALSE" }, { "true", "false" }, { "True", "False" } }, + { { "ON", "OFF" }, { "on", "off" }, { "On", "Off" } } + }; + + // select the right one + EMITTER_MANIP boolFmt = m_pState->GetBoolFormat(); + EMITTER_MANIP boolLengthFmt = m_pState->GetBoolLengthFormat(); + EMITTER_MANIP boolCaseFmt = m_pState->GetBoolCaseFormat(); + + const BoolFormatNames& fmtNames = (boolFmt == YesNoBool ? boolTypes.yesNo : boolFmt == TrueFalseBool ? boolTypes.trueFalse : boolTypes.onOff); + const BoolName& boolName = (boolCaseFmt == UpperCase ? fmtNames.upper : boolCaseFmt == LowerCase ? fmtNames.lower : fmtNames.camel); + const std::string& name = (b ? boolName.trueName : boolName.falseName); + + // and say it! + // TODO: should we disallow writing OnOffBool with ShortBool? (it'll just print "o" for both, which is silly) + if(boolLengthFmt == ShortBool) + m_stream << name[0]; + else + m_stream << name; + + PostAtomicWrite(); + return *this; + } + + Emitter& Emitter::Write(float f) + { + if(!good()) + return *this; + + PreAtomicWrite(); + EmitSeparationIfNecessary(); + + std::stringstream str; + str << f; + m_stream << str.str(); + + PostAtomicWrite(); + return *this; + } + + Emitter& Emitter::Write(double d) + { + if(!good()) + return *this; + + PreAtomicWrite(); + EmitSeparationIfNecessary(); + + std::stringstream str; + str << d; + m_stream << str.str(); + + PostAtomicWrite(); + return *this; + } + + Emitter& Emitter::Write(const _Alias& alias) + { + if(!good()) + return *this; + + PreAtomicWrite(); + EmitSeparationIfNecessary(); + if(!Utils::WriteAlias(m_stream, alias.content)) { + m_pState->SetError(ErrorMsg::INVALID_ALIAS); + return *this; + } + PostAtomicWrite(); + return *this; + } + + Emitter& Emitter::Write(const _Anchor& anchor) + { + if(!good()) + return *this; + + PreAtomicWrite(); + EmitSeparationIfNecessary(); + if(!Utils::WriteAnchor(m_stream, anchor.content)) { + m_pState->SetError(ErrorMsg::INVALID_ANCHOR); + return *this; + } + m_pState->RequireSeparation(); + // Note: no PostAtomicWrite() because we need another value for this node + return *this; + } + + Emitter& Emitter::Write(const _Comment& comment) + { + if(!good()) + return *this; + + m_stream << Indentation(m_pState->GetPreCommentIndent()); + Utils::WriteComment(m_stream, comment.content, m_pState->GetPostCommentIndent()); + return *this; + } +} diff --git a/src/emitterstate.cpp b/src/emitterstate.cpp new file mode 100644 index 0000000..5a2d8bf --- /dev/null +++ b/src/emitterstate.cpp @@ -0,0 +1,263 @@ +#include "emitterstate.h" +#include "exceptions.h" + +namespace YAML +{ + EmitterState::EmitterState(): m_isGood(true), m_curIndent(0), m_requiresSeparation(false) + { + // start up + m_stateStack.push(ES_WAITING_FOR_DOC); + + // set default global manipulators + m_strFmt.set(Auto); + m_boolFmt.set(TrueFalseBool); + m_boolLengthFmt.set(LongBool); + m_boolCaseFmt.set(LowerCase); + m_intFmt.set(Dec); + m_indent.set(2); + m_preCommentIndent.set(2); + m_postCommentIndent.set(1); + m_seqFmt.set(Block); + m_mapFmt.set(Block); + m_mapKeyFmt.set(Auto); + } + + EmitterState::~EmitterState() + { + while(!m_groups.empty()) + _PopGroup(); + } + + std::auto_ptr EmitterState::_PopGroup() + { + if(m_groups.empty()) + return std::auto_ptr (0); + + std::auto_ptr pGroup(m_groups.top()); + m_groups.pop(); + return pGroup; + } + + // SetLocalValue + // . We blindly tries to set all possible formatters to this value + // . Only the ones that make sense will be accepted + void EmitterState::SetLocalValue(EMITTER_MANIP value) + { + SetStringFormat(value, LOCAL); + SetBoolFormat(value, LOCAL); + SetBoolCaseFormat(value, LOCAL); + SetBoolLengthFormat(value, LOCAL); + SetIntFormat(value, LOCAL); + SetFlowType(GT_SEQ, value, LOCAL); + SetFlowType(GT_MAP, value, LOCAL); + SetMapKeyFormat(value, LOCAL); + } + + void EmitterState::BeginGroup(GROUP_TYPE type) + { + unsigned lastIndent = (m_groups.empty() ? 0 : m_groups.top()->indent); + m_curIndent += lastIndent; + + std::auto_ptr pGroup(new Group(type)); + + // transfer settings (which last until this group is done) + pGroup->modifiedSettings = m_modifiedSettings; + + // set up group + pGroup->flow = GetFlowType(type); + pGroup->indent = GetIndent(); + pGroup->usingLongKey = (GetMapKeyFormat() == LongKey ? true : false); + + m_groups.push(pGroup.release()); + } + + void EmitterState::EndGroup(GROUP_TYPE type) + { + if(m_groups.empty()) + return SetError(ErrorMsg::UNMATCHED_GROUP_TAG); + + // get rid of the current group + { + std::auto_ptr pFinishedGroup = _PopGroup(); + if(pFinishedGroup->type != type) + return SetError(ErrorMsg::UNMATCHED_GROUP_TAG); + } + + // reset old settings + unsigned lastIndent = (m_groups.empty() ? 0 : m_groups.top()->indent); + assert(m_curIndent >= lastIndent); + m_curIndent -= lastIndent; + + // some global settings that we changed may have been overridden + // by a local setting we just popped, so we need to restore them + m_globalModifiedSettings.restore(); + } + + GROUP_TYPE EmitterState::GetCurGroupType() const + { + if(m_groups.empty()) + return GT_NONE; + + return m_groups.top()->type; + } + + FLOW_TYPE EmitterState::GetCurGroupFlowType() const + { + if(m_groups.empty()) + return FT_NONE; + + return (m_groups.top()->flow == Flow ? FT_FLOW : FT_BLOCK); + } + + bool EmitterState::CurrentlyInLongKey() + { + if(m_groups.empty()) + return false; + return m_groups.top()->usingLongKey; + } + + void EmitterState::StartLongKey() + { + if(!m_groups.empty()) + m_groups.top()->usingLongKey = true; + } + + void EmitterState::StartSimpleKey() + { + if(!m_groups.empty()) + m_groups.top()->usingLongKey = false; + } + + void EmitterState::ClearModifiedSettings() + { + m_modifiedSettings.clear(); + } + + bool EmitterState::SetStringFormat(EMITTER_MANIP value, FMT_SCOPE scope) + { + switch(value) { + case Auto: + case SingleQuoted: + case DoubleQuoted: + case Literal: + _Set(m_strFmt, value, scope); + return true; + default: + return false; + } + } + + bool EmitterState::SetBoolFormat(EMITTER_MANIP value, FMT_SCOPE scope) + { + switch(value) { + case OnOffBool: + case TrueFalseBool: + case YesNoBool: + _Set(m_boolFmt, value, scope); + return true; + default: + return false; + } + } + + bool EmitterState::SetBoolLengthFormat(EMITTER_MANIP value, FMT_SCOPE scope) + { + switch(value) { + case LongBool: + case ShortBool: + _Set(m_boolLengthFmt, value, scope); + return true; + default: + return false; + } + } + + bool EmitterState::SetBoolCaseFormat(EMITTER_MANIP value, FMT_SCOPE scope) + { + switch(value) { + case UpperCase: + case LowerCase: + case CamelCase: + _Set(m_boolCaseFmt, value, scope); + return true; + default: + return false; + } + } + + bool EmitterState::SetIntFormat(EMITTER_MANIP value, FMT_SCOPE scope) + { + switch(value) { + case Dec: + case Hex: + case Oct: + _Set(m_intFmt, value, scope); + return true; + default: + return false; + } + } + + bool EmitterState::SetIndent(unsigned value, FMT_SCOPE scope) + { + if(value == 0) + return false; + + _Set(m_indent, value, scope); + return true; + } + + bool EmitterState::SetPreCommentIndent(unsigned value, FMT_SCOPE scope) + { + if(value == 0) + return false; + + _Set(m_preCommentIndent, value, scope); + return true; + } + + bool EmitterState::SetPostCommentIndent(unsigned value, FMT_SCOPE scope) + { + if(value == 0) + return false; + + _Set(m_postCommentIndent, value, scope); + return true; + } + + bool EmitterState::SetFlowType(GROUP_TYPE groupType, EMITTER_MANIP value, FMT_SCOPE scope) + { + switch(value) { + case Block: + case Flow: + _Set(groupType == GT_SEQ ? m_seqFmt : m_mapFmt, value, scope); + return true; + default: + return false; + } + } + + EMITTER_MANIP EmitterState::GetFlowType(GROUP_TYPE groupType) const + { + // force flow style if we're currently in a flow + FLOW_TYPE flowType = GetCurGroupFlowType(); + if(flowType == FT_FLOW) + return Flow; + + // otherwise, go with what's asked of use + return (groupType == GT_SEQ ? m_seqFmt.get() : m_mapFmt.get()); + } + + bool EmitterState::SetMapKeyFormat(EMITTER_MANIP value, FMT_SCOPE scope) + { + switch(value) { + case Auto: + case LongKey: + _Set(m_mapKeyFmt, value, scope); + return true; + default: + return false; + } + } +} + diff --git a/src/emitterstate.h b/src/emitterstate.h new file mode 100644 index 0000000..763047e --- /dev/null +++ b/src/emitterstate.h @@ -0,0 +1,195 @@ +#pragma once + +#include "setting.h" +#include "emittermanip.h" +#include +#include +#include +#include + +namespace YAML +{ + enum FMT_SCOPE { + LOCAL, + GLOBAL + }; + + enum GROUP_TYPE { + GT_NONE, + GT_SEQ, + GT_MAP + }; + + enum FLOW_TYPE { + FT_NONE, + FT_FLOW, + FT_BLOCK + }; + + enum NODE_STATE { + NS_START, + NS_READY_FOR_ATOM, + NS_END + }; + + enum EMITTER_STATE { + ES_WAITING_FOR_DOC, + ES_WRITING_DOC, + ES_DONE_WITH_DOC, + + // block seq + ES_WAITING_FOR_BLOCK_SEQ_ENTRY, + ES_WRITING_BLOCK_SEQ_ENTRY, + ES_DONE_WITH_BLOCK_SEQ_ENTRY, + + // flow seq + ES_WAITING_FOR_FLOW_SEQ_ENTRY, + ES_WRITING_FLOW_SEQ_ENTRY, + ES_DONE_WITH_FLOW_SEQ_ENTRY, + + // block map + ES_WAITING_FOR_BLOCK_MAP_ENTRY, + ES_WAITING_FOR_BLOCK_MAP_KEY, + ES_WRITING_BLOCK_MAP_KEY, + ES_DONE_WITH_BLOCK_MAP_KEY, + ES_WAITING_FOR_BLOCK_MAP_VALUE, + ES_WRITING_BLOCK_MAP_VALUE, + ES_DONE_WITH_BLOCK_MAP_VALUE, + + // flow map + ES_WAITING_FOR_FLOW_MAP_ENTRY, + ES_WAITING_FOR_FLOW_MAP_KEY, + ES_WRITING_FLOW_MAP_KEY, + ES_DONE_WITH_FLOW_MAP_KEY, + ES_WAITING_FOR_FLOW_MAP_VALUE, + ES_WRITING_FLOW_MAP_VALUE, + ES_DONE_WITH_FLOW_MAP_VALUE, + }; + + class EmitterState + { + public: + EmitterState(); + ~EmitterState(); + + // basic state checking + bool good() const { return m_isGood; } + const std::string GetLastError() const { return m_lastError; } + void SetError(const std::string& error) { m_isGood = false; m_lastError = error; } + + // main state of the machine + EMITTER_STATE GetCurState() const { return m_stateStack.top(); } + void SwitchState(EMITTER_STATE state) { PopState(); PushState(state); } + void PushState(EMITTER_STATE state) { m_stateStack.push(state); } + void PopState() { m_stateStack.pop(); } + + void SetLocalValue(EMITTER_MANIP value); + + // group handling + void BeginGroup(GROUP_TYPE type); + void EndGroup(GROUP_TYPE type); + + GROUP_TYPE GetCurGroupType() const; + FLOW_TYPE GetCurGroupFlowType() const; + int GetCurIndent() const { return m_curIndent; } + + bool CurrentlyInLongKey(); + void StartLongKey(); + void StartSimpleKey(); + + bool RequiresSeparation() const { return m_requiresSeparation; } + void RequireSeparation() { m_requiresSeparation = true; } + void UnsetSeparation() { m_requiresSeparation = false; } + + void ClearModifiedSettings(); + + // formatters + bool SetStringFormat(EMITTER_MANIP value, FMT_SCOPE scope); + EMITTER_MANIP GetStringFormat() const { return m_strFmt.get(); } + + bool SetBoolFormat(EMITTER_MANIP value, FMT_SCOPE scope); + EMITTER_MANIP GetBoolFormat() const { return m_boolFmt.get(); } + + bool SetBoolLengthFormat(EMITTER_MANIP value, FMT_SCOPE scope); + EMITTER_MANIP GetBoolLengthFormat() const { return m_boolLengthFmt.get(); } + + bool SetBoolCaseFormat(EMITTER_MANIP value, FMT_SCOPE scope); + EMITTER_MANIP GetBoolCaseFormat() const { return m_boolCaseFmt.get(); } + + bool SetIntFormat(EMITTER_MANIP value, FMT_SCOPE scope); + EMITTER_MANIP GetIntFormat() const { return m_intFmt.get(); } + + bool SetIndent(unsigned value, FMT_SCOPE scope); + int GetIndent() const { return m_indent.get(); } + + bool SetPreCommentIndent(unsigned value, FMT_SCOPE scope); + int GetPreCommentIndent() const { return m_preCommentIndent.get(); } + bool SetPostCommentIndent(unsigned value, FMT_SCOPE scope); + int GetPostCommentIndent() const { return m_postCommentIndent.get(); } + + bool SetFlowType(GROUP_TYPE groupType, EMITTER_MANIP value, FMT_SCOPE scope); + EMITTER_MANIP GetFlowType(GROUP_TYPE groupType) const; + + bool SetMapKeyFormat(EMITTER_MANIP value, FMT_SCOPE scope); + EMITTER_MANIP GetMapKeyFormat() const { return m_mapKeyFmt.get(); } + + private: + template + void _Set(Setting& fmt, T value, FMT_SCOPE scope); + + private: + // basic state ok? + bool m_isGood; + std::string m_lastError; + + // other state + std::stack m_stateStack; + + Setting m_strFmt; + Setting m_boolFmt; + Setting m_boolLengthFmt; + Setting m_boolCaseFmt; + Setting m_intFmt; + Setting m_indent; + Setting m_preCommentIndent, m_postCommentIndent; + Setting m_seqFmt; + Setting m_mapFmt; + Setting m_mapKeyFmt; + + SettingChanges m_modifiedSettings; + SettingChanges m_globalModifiedSettings; + + struct Group { + Group(GROUP_TYPE type_): type(type_), usingLongKey(false), indent(0) {} + + GROUP_TYPE type; + EMITTER_MANIP flow; + bool usingLongKey; + int indent; + + SettingChanges modifiedSettings; + }; + + std::auto_ptr _PopGroup(); + + std::stack m_groups; + unsigned m_curIndent; + bool m_requiresSeparation; + }; + + template + void EmitterState::_Set(Setting& fmt, T value, FMT_SCOPE scope) { + switch(scope) { + case LOCAL: + m_modifiedSettings.push(fmt.set(value)); + break; + case GLOBAL: + fmt.set(value); + m_globalModifiedSettings.push(fmt.set(value)); // this pushes an identity set, so when we restore, + // it restores to the value here, and not the previous one + break; + default: + assert(false); + } + } +} diff --git a/src/emitterutils.cpp b/src/emitterutils.cpp new file mode 100644 index 0000000..1779a9b --- /dev/null +++ b/src/emitterutils.cpp @@ -0,0 +1,143 @@ +#include "emitterutils.h" +#include "exp.h" +#include "indentation.h" +#include "exceptions.h" +#include + +namespace YAML +{ + namespace Utils + { + namespace { + bool IsPrintable(char ch) { + return (0x20 <= ch && ch <= 0x7E); + } + + bool IsValidPlainScalar(const std::string& str, bool inFlow) { + // first check the start + const RegEx& start = (inFlow ? Exp::PlainScalarInFlow : Exp::PlainScalar); + if(!start.Matches(str)) + return false; + + // and check the end for plain whitespace (which can't be faithfully kept in a plain scalar) + if(!str.empty() && *str.rbegin() == ' ') + return false; + + // then check until something is disallowed + const RegEx& disallowed = (inFlow ? Exp::EndScalarInFlow : Exp::EndScalar) + || (Exp::BlankOrBreak + Exp::Comment) + || (!Exp::Printable) + || Exp::Break + || Exp::Tab; + Buffer buffer(&str[0], str.size()); + while(buffer.size) { + if(disallowed.Matches(buffer)) + return false; + ++buffer; + } + + return true; + } + } + + bool WriteString(ostream& out, const std::string& str, bool inFlow) + { + if(IsValidPlainScalar(str, inFlow)) { + out << str; + return true; + } else + return WriteDoubleQuotedString(out, str); + } + + bool WriteSingleQuotedString(ostream& out, const std::string& str) + { + out << "'"; + for(unsigned i=0;i(ch); + out << str.str(); + } + } + out << "\""; + return true; + } + + bool WriteLiteralString(ostream& out, const std::string& str, int indent) + { + out << "|\n"; + out << IndentTo(indent); + for(unsigned i=0;i + +namespace YAML +{ + namespace Utils + { + bool WriteString(ostream& out, const std::string& str, bool inFlow); + bool WriteSingleQuotedString(ostream& out, const std::string& str); + bool WriteDoubleQuotedString(ostream& out, const std::string& str); + bool WriteLiteralString(ostream& out, const std::string& str, int indent); + bool WriteComment(ostream& out, const std::string& str, int postCommentIndent); + bool WriteAlias(ostream& out, const std::string& str); + bool WriteAnchor(ostream& out, const std::string& str); + } +} diff --git a/src/exp.h b/src/exp.h index e878a80..12bd6ac 100644 --- a/src/exp.h +++ b/src/exp.h @@ -13,13 +13,16 @@ namespace YAML namespace Exp { // misc - const RegEx Blank = RegEx(' ') || RegEx('\t'); + const RegEx Space = RegEx(' '); + const RegEx Tab = RegEx('\t'); + const RegEx Blank = Space || Tab; const RegEx Break = RegEx('\n') || RegEx("\r\n"); const RegEx BlankOrBreak = Blank || Break; const RegEx Digit = RegEx('0', '9'); const RegEx Alpha = RegEx('a', 'z') || RegEx('A', 'Z'); const RegEx AlphaNumeric = Alpha || Digit; const RegEx Hex = Digit || RegEx('A', 'F') || RegEx('a', 'f'); + const RegEx Printable = RegEx(0x20, 0x7E); // actual tags diff --git a/src/indentation.h b/src/indentation.h new file mode 100644 index 0000000..b21412b --- /dev/null +++ b/src/indentation.h @@ -0,0 +1,30 @@ +#pragma once + +#include "ostream.h" +#include + +namespace YAML +{ + struct Indentation { + Indentation(unsigned n_): n(n_) {} + unsigned n; + }; + + inline ostream& operator << (ostream& out, const Indentation& indent) { + for(unsigned i=0;i + +namespace YAML +{ + ostream::ostream(): m_buffer(0), m_pos(0), m_size(0), m_row(0), m_col(0) + { + reserve(1024); + } + + ostream::~ostream() + { + delete [] m_buffer; + } + + void ostream::reserve(unsigned size) + { + if(size <= m_size) + return; + + char *newBuffer = new char[size]; + std::memset(newBuffer, 0, size * sizeof(char)); + std::memcpy(newBuffer, m_buffer, m_size * sizeof(char)); + delete [] m_buffer; + m_buffer = newBuffer; + m_size = size; + } + + void ostream::put(char ch) + { + if(m_pos >= m_size - 1) // an extra space for the NULL terminator + reserve(m_size * 2); + + m_buffer[m_pos] = ch; + m_pos++; + + if(ch == '\n') { + m_row++; + m_col = 0; + } else + m_col++; + } + + ostream& operator << (ostream& out, const char *str) + { + unsigned length = std::strlen(str); + for(unsigned i=0;i +#include +#include "noncopyable.h" + +namespace YAML +{ + class SettingChangeBase; + + template + class Setting + { + public: + Setting(): m_value() {} + + const T get() const { return m_value; } + std::auto_ptr set(const T& value); + void restore(const Setting& oldSetting) { + m_value = oldSetting.get(); + } + + private: + T m_value; + }; + + class SettingChangeBase + { + public: + virtual ~SettingChangeBase() {} + virtual void pop() = 0; + }; + + template + class SettingChange: public SettingChangeBase + { + public: + SettingChange(Setting *pSetting): m_pCurSetting(pSetting) { + // copy old setting to save its state + m_oldSetting = *pSetting; + } + + virtual void pop() { + m_pCurSetting->restore(m_oldSetting); + } + + private: + Setting *m_pCurSetting; + Setting m_oldSetting; + }; + + template + inline std::auto_ptr Setting::set(const T& value) { + std::auto_ptr pChange(new SettingChange (this)); + m_value = value; + return pChange; + } + + class SettingChanges: private noncopyable + { + public: + SettingChanges() {} + ~SettingChanges() { clear(); } + + void clear() { + restore(); + + for(setting_changes::const_iterator it=m_settingChanges.begin();it!=m_settingChanges.end();++it) + delete *it; + m_settingChanges.clear(); + } + + void restore() { + for(setting_changes::const_iterator it=m_settingChanges.begin();it!=m_settingChanges.end();++it) + (*it)->pop(); + } + + void push(std::auto_ptr pSettingChange) { + m_settingChanges.push_back(pSettingChange.release()); + } + + // like std::auto_ptr - assignment is transfer of ownership + SettingChanges& operator = (SettingChanges& rhs) { + if(this == &rhs) + return *this; + + clear(); + m_settingChanges = rhs.m_settingChanges; + rhs.m_settingChanges.clear(); + return *this; + } + + private: + typedef std::vector setting_changes; + setting_changes m_settingChanges; + }; +} diff --git a/src/stream.h b/src/stream.h index d35f3fe..5bc2c11 100644 --- a/src/stream.h +++ b/src/stream.h @@ -7,14 +7,15 @@ namespace YAML { // a simple buffer wrapper that knows how big it is struct Buffer { - Buffer(char *b, int s): buffer(b), size(s) {} + Buffer(const char *b, int s): buffer(b), size(s) {} operator bool() const { return size > 0; } bool operator !() const { return !static_cast (*this); } char operator [] (int i) const { return buffer[i]; } const Buffer operator + (int offset) const { return Buffer(buffer + offset, size - offset); } + Buffer& operator ++ () { ++buffer; --size; return *this; } - char *buffer; + const char *buffer; int size; }; diff --git a/yaml-reader/main.cpp b/yaml-reader/main.cpp index 00e47ed..b8e5ff7 100644 --- a/yaml-reader/main.cpp +++ b/yaml-reader/main.cpp @@ -4,6 +4,9 @@ #include #include +#include "emitter.h" +#include "stlemitter.h" + void run() { std::ifstream fin("tests/test.yaml"); @@ -15,6 +18,9 @@ void run() parser.GetNextDocument(doc); std::cout << doc; } + + // try some output + YAML::Emitter out; } int main(int argc, char **argv) diff --git a/yaml-reader/tests.cpp b/yaml-reader/tests.cpp index 4f3ba6b..f7f1f5d 100644 --- a/yaml-reader/tests.cpp +++ b/yaml-reader/tests.cpp @@ -1,6 +1,9 @@ #include "yaml.h" #include "tests.h" #include "parser.h" +#include "emitter.h" +#include "stlemitter.h" +#include "exceptions.h" #include #include #include @@ -26,6 +29,9 @@ namespace Test } else std::cout << "Inout test passed: " << files[i] << "\n"; } + + if(!RunEmitterTests()) + passed = false; if(passed) std::cout << "All tests passed!\n"; @@ -86,4 +92,601 @@ namespace Test return true; } + + //////////////////////////////////////////////////////////////////////////////////////// + // Emitter tests + + namespace { + void RunTest(void (*test)(YAML::Emitter&, std::string&), const std::string& name, bool& passed) { + YAML::Emitter out; + std::string desiredOutput; + test(out, desiredOutput); + std::stringstream output; + out.WriteToStream(output); + + if(output.str() == desiredOutput) { + std::cout << "Test passed: " << name << "\n"; + } else { + passed = false; + std::cout << "Test failed: " << name << "\n"; + std::cout << "Output:\n"; + std::cout << output.str() << "<<<\n"; + std::cout << "Desired output:\n"; + std::cout << desiredOutput << "<<<\n"; + } + } + + void RunErrorTest(void (*test)(YAML::Emitter&, std::string&), const std::string& name, bool& passed) { + YAML::Emitter out; + std::string desiredError; + test(out, desiredError); + std::string lastError = out.GetLastError(); + if(!out.good() && lastError == desiredError) { + std::cout << "Test passed: " << name << "\n"; + } else { + passed = false; + std::cout << "Test failed: " << name << "\n"; + if(out.good()) + std::cout << "No error detected\n"; + else + std::cout << "Detected error: " << lastError << "\n"; + std::cout << "Expected error: " << desiredError << "\n"; + } + } + } + + bool RunEmitterTests() + { + bool passed = true; + RunTest(&Emitter::SimpleScalar, "simple scalar", passed); + RunTest(&Emitter::SimpleSeq, "simple seq", passed); + RunTest(&Emitter::SimpleFlowSeq, "simple flow seq", passed); + RunTest(&Emitter::EmptyFlowSeq, "empty flow seq", passed); + RunTest(&Emitter::NestedBlockSeq, "nested block seq", passed); + RunTest(&Emitter::NestedFlowSeq, "nested flow seq", passed); + RunTest(&Emitter::SimpleMap, "simple map", passed); + RunTest(&Emitter::SimpleFlowMap, "simple flow map", passed); + RunTest(&Emitter::MapAndList, "map and list", passed); + RunTest(&Emitter::ListAndMap, "list and map", passed); + RunTest(&Emitter::NestedBlockMap, "nested block map", passed); + RunTest(&Emitter::NestedFlowMap, "nested flow map", passed); + RunTest(&Emitter::MapListMix, "map list mix", passed); + RunTest(&Emitter::SimpleLongKey, "simple long key", passed); + RunTest(&Emitter::SingleLongKey, "single long key", passed); + RunTest(&Emitter::ComplexLongKey, "complex long key", passed); + RunTest(&Emitter::AutoLongKey, "auto long key", passed); + RunTest(&Emitter::ScalarFormat, "scalar format", passed); + RunTest(&Emitter::AutoLongKeyScalar, "auto long key scalar", passed); + RunTest(&Emitter::LongKeyFlowMap, "long key flow map", passed); + RunTest(&Emitter::BlockMapAsKey, "block map as key", passed); + RunTest(&Emitter::AliasAndAnchor, "alias and anchor", passed); + RunTest(&Emitter::ComplexDoc, "complex doc", passed); + RunTest(&Emitter::STLContainers, "STL containers", passed); + RunTest(&Emitter::SimpleComment, "simple comment", passed); + RunTest(&Emitter::MultiLineComment, "multi-line comment", passed); + RunTest(&Emitter::ComplexComments, "complex comments", passed); + RunTest(&Emitter::Indentation, "indentation", passed); + RunTest(&Emitter::SimpleGlobalSettings, "simple global settings", passed); + RunTest(&Emitter::ComplexGlobalSettings, "complex global settings", passed); + + RunErrorTest(&Emitter::ExtraEndSeq, "extra EndSeq", passed); + RunErrorTest(&Emitter::ExtraEndMap, "extra EndMap", passed); + RunErrorTest(&Emitter::BadSingleQuoted, "bad single quoted string", passed); + RunErrorTest(&Emitter::InvalidAnchor, "invalid anchor", passed); + RunErrorTest(&Emitter::InvalidAlias, "invalid alias", passed); + RunErrorTest(&Emitter::MissingKey, "missing key", passed); + RunErrorTest(&Emitter::MissingValue, "missing value", passed); + RunErrorTest(&Emitter::UnexpectedKey, "unexpected key", passed); + RunErrorTest(&Emitter::UnexpectedValue, "unexpected value", passed); + return passed; + } + + namespace Emitter { + //////////////////////////////////////////////////////////////////////////////////////////////////////// + // correct emitting + + void SimpleScalar(YAML::Emitter& out, std::string& desiredOutput) { + out << "Hello, World!"; + desiredOutput = "Hello, World!"; + } + + void SimpleSeq(YAML::Emitter& out, std::string& desiredOutput) { + out << YAML::BeginSeq; + out << "eggs"; + out << "bread"; + out << "milk"; + out << YAML::EndSeq; + + desiredOutput = "- eggs\n- bread\n- milk"; + } + + void SimpleFlowSeq(YAML::Emitter& out, std::string& desiredOutput) { + out << YAML::Flow; + out << YAML::BeginSeq; + out << "Larry"; + out << "Curly"; + out << "Moe"; + out << YAML::EndSeq; + + desiredOutput = "[Larry, Curly, Moe]"; + } + + void EmptyFlowSeq(YAML::Emitter& out, std::string& desiredOutput) { + out << YAML::Flow; + out << YAML::BeginSeq; + out << YAML::EndSeq; + + desiredOutput = "[]"; + } + + void NestedBlockSeq(YAML::Emitter& out, std::string& desiredOutput) { + out << YAML::BeginSeq; + out << "item 1"; + out << YAML::BeginSeq << "subitem 1" << "subitem 2" << YAML::EndSeq; + out << YAML::EndSeq; + + desiredOutput = "- item 1\n-\n - subitem 1\n - subitem 2"; + } + + void NestedFlowSeq(YAML::Emitter& out, std::string& desiredOutput) { + out << YAML::BeginSeq; + out << "one"; + out << YAML::Flow << YAML::BeginSeq << "two" << "three" << YAML::EndSeq; + out << YAML::EndSeq; + + desiredOutput = "- one\n- [two, three]"; + } + + void SimpleMap(YAML::Emitter& out, std::string& desiredOutput) { + out << YAML::BeginMap; + out << YAML::Key << "name"; + out << YAML::Value << "Ryan Braun"; + out << YAML::Key << "position"; + out << YAML::Value << "3B"; + out << YAML::EndMap; + + desiredOutput = "name: Ryan Braun\nposition: 3B"; + } + + void SimpleFlowMap(YAML::Emitter& out, std::string& desiredOutput) { + out << YAML::Flow; + out << YAML::BeginMap; + out << YAML::Key << "shape"; + out << YAML::Value << "square"; + out << YAML::Key << "color"; + out << YAML::Value << "blue"; + out << YAML::EndMap; + + desiredOutput = "{shape: square, color: blue}"; + } + + void MapAndList(YAML::Emitter& out, std::string& desiredOutput) { + out << YAML::BeginMap; + out << YAML::Key << "name"; + out << YAML::Value << "Barack Obama"; + out << YAML::Key << "children"; + out << YAML::Value << YAML::BeginSeq << "Sasha" << "Malia" << YAML::EndSeq; + out << YAML::EndMap; + + desiredOutput = "name: Barack Obama\nchildren:\n - Sasha\n - Malia"; + } + + void ListAndMap(YAML::Emitter& out, std::string& desiredOutput) { + out << YAML::BeginSeq; + out << "item 1"; + out << YAML::BeginMap; + out << YAML::Key << "pens" << YAML::Value << 8; + out << YAML::Key << "pencils" << YAML::Value << 14; + out << YAML::EndMap; + out << "item 2"; + out << YAML::EndSeq; + + desiredOutput = "- item 1\n-\n pens: 8\n pencils: 14\n- item 2"; + } + + void NestedBlockMap(YAML::Emitter& out, std::string& desiredOutput) { + out << YAML::BeginMap; + out << YAML::Key << "name"; + out << YAML::Value << "Fred"; + out << YAML::Key << "grades"; + out << YAML::Value; + out << YAML::BeginMap; + out << YAML::Key << "algebra" << YAML::Value << "A"; + out << YAML::Key << "physics" << YAML::Value << "C+"; + out << YAML::Key << "literature" << YAML::Value << "B"; + out << YAML::EndMap; + out << YAML::EndMap; + + desiredOutput = "name: Fred\ngrades:\n algebra: A\n physics: C+\n literature: B"; + } + + void NestedFlowMap(YAML::Emitter& out, std::string& desiredOutput) { + out << YAML::Flow; + out << YAML::BeginMap; + out << YAML::Key << "name"; + out << YAML::Value << "Fred"; + out << YAML::Key << "grades"; + out << YAML::Value; + out << YAML::BeginMap; + out << YAML::Key << "algebra" << YAML::Value << "A"; + out << YAML::Key << "physics" << YAML::Value << "C+"; + out << YAML::Key << "literature" << YAML::Value << "B"; + out << YAML::EndMap; + out << YAML::EndMap; + + desiredOutput = "{name: Fred, grades: {algebra: A, physics: C+, literature: B}}"; + } + + void MapListMix(YAML::Emitter& out, std::string& desiredOutput) { + out << YAML::BeginMap; + out << YAML::Key << "name"; + out << YAML::Value << "Bob"; + out << YAML::Key << "position"; + out << YAML::Value; + out << YAML::Flow << YAML::BeginSeq << 2 << 4 << YAML::EndSeq; + out << YAML::Key << "invincible" << YAML::Value << YAML::OnOffBool << false; + out << YAML::EndMap; + + desiredOutput = "name: Bob\nposition: [2, 4]\ninvincible: off"; + } + + void SimpleLongKey(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::LongKey; + out << YAML::BeginMap; + out << YAML::Key << "height"; + out << YAML::Value << "5'9\""; + out << YAML::Key << "weight"; + out << YAML::Value << 145; + out << YAML::EndMap; + + desiredOutput = "? height\n: 5'9\"\n? weight\n: 145"; + } + + void SingleLongKey(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginMap; + out << YAML::Key << "age"; + out << YAML::Value << "24"; + out << YAML::LongKey << YAML::Key << "height"; + out << YAML::Value << "5'9\""; + out << YAML::Key << "weight"; + out << YAML::Value << 145; + out << YAML::EndMap; + + desiredOutput = "age: 24\n? height\n: 5'9\"\nweight: 145"; + } + + void ComplexLongKey(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::LongKey; + out << YAML::BeginMap; + out << YAML::Key << YAML::BeginSeq << 1 << 3 << YAML::EndSeq; + out << YAML::Value << "monster"; + out << YAML::Key << YAML::Flow << YAML::BeginSeq << 2 << 0 << YAML::EndSeq; + out << YAML::Value << "demon"; + out << YAML::EndMap; + + desiredOutput = "?\n - 1\n - 3\n: monster\n? [2, 0]\n: demon"; + } + + void AutoLongKey(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginMap; + out << YAML::Key << YAML::BeginSeq << 1 << 3 << YAML::EndSeq; + out << YAML::Value << "monster"; + out << YAML::Key << YAML::Flow << YAML::BeginSeq << 2 << 0 << YAML::EndSeq; + out << YAML::Value << "demon"; + out << YAML::Key << "the origin"; + out << YAML::Value << "angel"; + out << YAML::EndMap; + + desiredOutput = "?\n - 1\n - 3\n: monster\n? [2, 0]\n: demon\nthe origin: angel"; + } + + void ScalarFormat(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginSeq; + out << "simple scalar"; + out << YAML::SingleQuoted << "explicit single-quoted scalar"; + out << YAML::DoubleQuoted << "explicit double-quoted scalar"; + out << "auto-detected\ndouble-quoted scalar"; + out << "a non-\"auto-detected\" double-quoted scalar"; + out << YAML::Literal << "literal scalar\nthat may span\nmany, many\nlines and have \"whatever\" crazy\tsymbols that we like"; + out << YAML::EndSeq; + + desiredOutput = "- simple scalar\n- 'explicit single-quoted scalar'\n- \"explicit double-quoted scalar\"\n- \"auto-detected\\xadouble-quoted scalar\"\n- a non-\"auto-detected\" double-quoted scalar\n- |\n literal scalar\n that may span\n many, many\n lines and have \"whatever\" crazy\tsymbols that we like"; + } + + void AutoLongKeyScalar(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginMap; + out << YAML::Key << YAML::Literal << "multi-line\nscalar"; + out << YAML::Value << "and its value"; + out << YAML::EndMap; + + desiredOutput = "? |\n multi-line\n scalar\n: and its value"; + } + + void LongKeyFlowMap(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::Flow; + out << YAML::BeginMap; + out << YAML::Key << "simple key"; + out << YAML::Value << "and value"; + out << YAML::LongKey << YAML::Key << "long key"; + out << YAML::Value << "and its value"; + out << YAML::EndMap; + + desiredOutput = "{simple key: and value, ? long key: and its value}"; + } + + void BlockMapAsKey(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginMap; + out << YAML::Key; + out << YAML::BeginMap; + out << YAML::Key << "key" << YAML::Value << "value"; + out << YAML::Key << "next key" << YAML::Value << "next value"; + out << YAML::EndMap; + out << YAML::Value; + out << "total value"; + out << YAML::EndMap; + + desiredOutput = "?\n key: value\n next key: next value\n: total value"; + } + + void AliasAndAnchor(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginSeq; + out << YAML::Anchor("fred"); + out << YAML::BeginMap; + out << YAML::Key << "name" << YAML::Value << "Fred"; + out << YAML::Key << "age" << YAML::Value << 42; + out << YAML::EndMap; + out << YAML::Alias("fred"); + out << YAML::EndSeq; + + desiredOutput = "- &fred\n name: Fred\n age: 42\n- *fred"; + } + + void ComplexDoc(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginMap; + out << YAML::Key << "receipt"; + out << YAML::Value << "Oz-Ware Purchase Invoice"; + out << YAML::Key << "date"; + out << YAML::Value << "2007-08-06"; + out << YAML::Key << "customer"; + out << YAML::Value; + out << YAML::BeginMap; + out << YAML::Key << "given"; + out << YAML::Value << "Dorothy"; + out << YAML::Key << "family"; + out << YAML::Value << "Gale"; + out << YAML::EndMap; + out << YAML::Key << "items"; + out << YAML::Value; + out << YAML::BeginSeq; + out << YAML::BeginMap; + out << YAML::Key << "part_no"; + out << YAML::Value << "A4786"; + out << YAML::Key << "descrip"; + out << YAML::Value << "Water Bucket (Filled)"; + out << YAML::Key << "price"; + out << YAML::Value << 1.47; + out << YAML::Key << "quantity"; + out << YAML::Value << 4; + out << YAML::EndMap; + out << YAML::BeginMap; + out << YAML::Key << "part_no"; + out << YAML::Value << "E1628"; + out << YAML::Key << "descrip"; + out << YAML::Value << "High Heeled \"Ruby\" Slippers"; + out << YAML::Key << "price"; + out << YAML::Value << 100.27; + out << YAML::Key << "quantity"; + out << YAML::Value << 1; + out << YAML::EndMap; + out << YAML::EndSeq; + out << YAML::Key << "bill-to"; + out << YAML::Value << YAML::Anchor("id001"); + out << YAML::BeginMap; + out << YAML::Key << "street"; + out << YAML::Value << YAML::Literal << "123 Tornado Alley\nSuite 16"; + out << YAML::Key << "city"; + out << YAML::Value << "East Westville"; + out << YAML::Key << "state"; + out << YAML::Value << "KS"; + out << YAML::EndMap; + out << YAML::Key << "ship-to"; + out << YAML::Value << YAML::Alias("id001"); + out << YAML::EndMap; + + desiredOutput = "receipt: Oz-Ware Purchase Invoice\ndate: 2007-08-06\ncustomer:\n given: Dorothy\n family: Gale\nitems:\n -\n part_no: A4786\n descrip: Water Bucket (Filled)\n price: 1.47\n quantity: 4\n -\n part_no: E1628\n descrip: High Heeled \"Ruby\" Slippers\n price: 100.27\n quantity: 1\nbill-to: &id001\n street: |\n 123 Tornado Alley\n Suite 16\n city: East Westville\n state: KS\nship-to: *id001"; + } + + void STLContainers(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginSeq; + std::vector primes; + primes.push_back(2); + primes.push_back(3); + primes.push_back(5); + primes.push_back(7); + primes.push_back(11); + primes.push_back(13); + out << YAML::Flow << primes; + std::map ages; + ages["Daniel"] = 26; + ages["Jesse"] = 24; + out << ages; + out << YAML::EndSeq; + + desiredOutput = "- [2, 3, 5, 7, 11, 13]\n-\n Daniel: 26\n Jesse: 24"; + } + + void SimpleComment(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginMap; + out << YAML::Key << "method"; + out << YAML::Value << "least squares" << YAML::Comment("should we change this method?"); + out << YAML::EndMap; + + desiredOutput = "method: least squares # should we change this method?"; + } + + void MultiLineComment(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginSeq; + out << "item 1" << YAML::Comment("really really long\ncomment that couldn't possibly\nfit on one line"); + out << "item 2"; + out << YAML::EndSeq; + + desiredOutput = "- item 1 # really really long\n # comment that couldn't possibly\n # fit on one line\n- item 2"; + } + + void ComplexComments(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginMap; + out << YAML::LongKey << YAML::Key << "long key" << YAML::Comment("long key"); + out << YAML::Value << "value"; + out << YAML::EndMap; + + desiredOutput = "? long key # long key\n: value"; + } + + void Indentation(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::Indent(4); + out << YAML::BeginSeq; + out << YAML::BeginMap; + out << YAML::Key << "key 1" << YAML::Value << "value 1"; + out << YAML::Key << "key 2" << YAML::Value << YAML::BeginSeq << "a" << "b" << "c" << YAML::EndSeq; + out << YAML::EndMap; + out << YAML::EndSeq; + + desiredOutput = "-\n key 1: value 1\n key 2:\n - a\n - b\n - c"; + } + + void SimpleGlobalSettings(YAML::Emitter& out, std::string& desiredOutput) + { + out.SetIndent(4); + out.SetMapFormat(YAML::LongKey); + + out << YAML::BeginSeq; + out << YAML::BeginMap; + out << YAML::Key << "key 1" << YAML::Value << "value 1"; + out << YAML::Key << "key 2" << YAML::Value << YAML::Flow << YAML::BeginSeq << "a" << "b" << "c" << YAML::EndSeq; + out << YAML::EndMap; + out << YAML::EndSeq; + + desiredOutput = "-\n ? key 1\n : value 1\n ? key 2\n : [a, b, c]"; + } + + void ComplexGlobalSettings(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginSeq; + out << YAML::Block; + out << YAML::BeginMap; + out << YAML::Key << "key 1" << YAML::Value << "value 1"; + out << YAML::Key << "key 2" << YAML::Value; + out.SetSeqFormat(YAML::Flow); + out << YAML::BeginSeq << "a" << "b" << "c" << YAML::EndSeq; + out << YAML::EndMap; + out << YAML::BeginMap; + out << YAML::Key << YAML::BeginSeq << 1 << 2 << YAML::EndSeq; + out << YAML::Value << YAML::BeginMap << YAML::Key << "a" << YAML::Value << "b" << YAML::EndMap; + out << YAML::EndMap; + out << YAML::EndSeq; + + desiredOutput = "-\n key 1: value 1\n key 2: [a, b, c]\n-\n ? [1, 2]\n :\n a: b"; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////// + // incorrect emitting + + void ExtraEndSeq(YAML::Emitter& out, std::string& desiredError) + { + desiredError = YAML::ErrorMsg::UNEXPECTED_END_SEQ; + + out << YAML::BeginSeq; + out << "Hello"; + out << "World"; + out << YAML::EndSeq; + out << YAML::EndSeq; + } + + void ExtraEndMap(YAML::Emitter& out, std::string& desiredError) + { + desiredError = YAML::ErrorMsg::UNEXPECTED_END_MAP; + + out << YAML::BeginMap; + out << YAML::Key << "Hello" << YAML::Value << "World"; + out << YAML::EndMap; + out << YAML::EndMap; + } + + void BadSingleQuoted(YAML::Emitter& out, std::string& desiredError) + { + desiredError = YAML::ErrorMsg::SINGLE_QUOTED_CHAR; + + out << YAML::SingleQuoted << "Hello\nWorld"; + } + + void InvalidAnchor(YAML::Emitter& out, std::string& desiredError) + { + desiredError = YAML::ErrorMsg::INVALID_ANCHOR; + + out << YAML::BeginSeq; + out << YAML::Anchor("new\nline") << "Test"; + out << YAML::EndSeq; + } + + void InvalidAlias(YAML::Emitter& out, std::string& desiredError) + { + desiredError = YAML::ErrorMsg::INVALID_ALIAS; + + out << YAML::BeginSeq; + out << YAML::Alias("new\nline"); + out << YAML::EndSeq; + } + + void MissingKey(YAML::Emitter& out, std::string& desiredError) + { + desiredError = YAML::ErrorMsg::EXPECTED_KEY_TOKEN; + + out << YAML::BeginMap; + out << YAML::Key << "key" << YAML::Value << "value"; + out << "missing key" << YAML::Value << "value"; + out << YAML::EndMap; + } + + void MissingValue(YAML::Emitter& out, std::string& desiredError) + { + desiredError = YAML::ErrorMsg::EXPECTED_VALUE_TOKEN; + + out << YAML::BeginMap; + out << YAML::Key << "key" << "value"; + out << YAML::EndMap; + } + + void UnexpectedKey(YAML::Emitter& out, std::string& desiredError) + { + desiredError = YAML::ErrorMsg::UNEXPECTED_KEY_TOKEN; + + out << YAML::BeginSeq; + out << YAML::Key << "hi"; + out << YAML::EndSeq; + } + + void UnexpectedValue(YAML::Emitter& out, std::string& desiredError) + { + desiredError = YAML::ErrorMsg::UNEXPECTED_VALUE_TOKEN; + + out << YAML::BeginSeq; + out << YAML::Value << "hi"; + out << YAML::EndSeq; + } + } } diff --git a/yaml-reader/tests.h b/yaml-reader/tests.h index c615b24..a062a09 100644 --- a/yaml-reader/tests.h +++ b/yaml-reader/tests.h @@ -1,6 +1,55 @@ #include +namespace YAML { class Emitter; } + namespace Test { void RunAll(bool verbose); bool Inout(const std::string& file, bool verbose); + + bool RunEmitterTests(); + + namespace Emitter { + // correct emitting + void SimpleScalar(YAML::Emitter& out, std::string& desiredOutput); + void SimpleSeq(YAML::Emitter& out, std::string& desiredOutput); + void SimpleFlowSeq(YAML::Emitter& ouptut, std::string& desiredOutput); + void EmptyFlowSeq(YAML::Emitter& out, std::string& desiredOutput); + void NestedBlockSeq(YAML::Emitter& out, std::string& desiredOutput); + void NestedFlowSeq(YAML::Emitter& out, std::string& desiredOutput); + void SimpleMap(YAML::Emitter& out, std::string& desiredOutput); + void SimpleFlowMap(YAML::Emitter& out, std::string& desiredOutput); + void MapAndList(YAML::Emitter& out, std::string& desiredOutput); + void ListAndMap(YAML::Emitter& out, std::string& desiredOutput); + void NestedBlockMap(YAML::Emitter& out, std::string& desiredOutput); + void NestedFlowMap(YAML::Emitter& out, std::string& desiredOutput); + void MapListMix(YAML::Emitter& out, std::string& desiredOutput); + void SimpleLongKey(YAML::Emitter& out, std::string& desiredOutput); + void SingleLongKey(YAML::Emitter& out, std::string& desiredOutput); + void ComplexLongKey(YAML::Emitter& out, std::string& desiredOutput); + void AutoLongKey(YAML::Emitter& out, std::string& desiredOutput); + void ScalarFormat(YAML::Emitter& out, std::string& desiredOutput); + void AutoLongKeyScalar(YAML::Emitter& out, std::string& desiredOutput); + void LongKeyFlowMap(YAML::Emitter& out, std::string& desiredOutput); + void BlockMapAsKey(YAML::Emitter& out, std::string& desiredOutput); + void AliasAndAnchor(YAML::Emitter& out, std::string& desiredOutput); + void ComplexDoc(YAML::Emitter& out, std::string& desiredOutput); + void STLContainers(YAML::Emitter& out, std::string& desiredOutput); + void SimpleComment(YAML::Emitter& out, std::string& desiredOutput); + void MultiLineComment(YAML::Emitter& out, std::string& desiredOutput); + void ComplexComments(YAML::Emitter& out, std::string& desiredOutput); + void Indentation(YAML::Emitter& out, std::string& desiredOutput); + void SimpleGlobalSettings(YAML::Emitter& out, std::string& desiredOutput); + void ComplexGlobalSettings(YAML::Emitter& out, std::string& desiredOutput); + + // incorrect emitting + void ExtraEndSeq(YAML::Emitter& out, std::string& desiredError); + void ExtraEndMap(YAML::Emitter& out, std::string& desiredError); + void BadSingleQuoted(YAML::Emitter& out, std::string& desiredError); + void InvalidAnchor(YAML::Emitter& out, std::string& desiredError); + void InvalidAlias(YAML::Emitter& out, std::string& desiredError); + void MissingKey(YAML::Emitter& out, std::string& desiredError); + void MissingValue(YAML::Emitter& out, std::string& desiredError); + void UnexpectedKey(YAML::Emitter& out, std::string& desiredError); + void UnexpectedValue(YAML::Emitter& out, std::string& desiredError); + } }