diff --git a/include/emitter.h b/include/emitter.h index d512bb8..c13b9cc 100644 --- a/include/emitter.h +++ b/include/emitter.h @@ -52,6 +52,7 @@ namespace YAML Emitter& Write(double d); Emitter& Write(const _Alias& alias); Emitter& Write(const _Anchor& anchor); + Emitter& Write(const _Tag& tag); Emitter& Write(const _Comment& comment); Emitter& Write(const _Null& null); diff --git a/include/emittermanip.h b/include/emittermanip.h index 9284768..c88054f 100644 --- a/include/emittermanip.h +++ b/include/emittermanip.h @@ -80,6 +80,16 @@ namespace YAML inline _Anchor Anchor(const std::string content) { return _Anchor(content); } + + struct _Tag { + _Tag(const std::string& content_): content(content_), verbatim(true) {} + std::string content; + bool verbatim; + }; + + inline _Tag VerbatimTag(const std::string& content) { + return _Tag(content); + } struct _Comment { _Comment(const std::string& content_): content(content_) {} diff --git a/include/exceptions.h b/include/exceptions.h index 8a246f0..2a9c1d1 100644 --- a/include/exceptions.h +++ b/include/exceptions.h @@ -17,7 +17,12 @@ namespace YAML 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 REPEATED_YAML_DIRECTIVE= "repeated YAML directive"; const std::string TAG_DIRECTIVE_ARGS = "TAG directives must have exactly two arguments"; + const std::string REPEATED_TAG_DIRECTIVE = "repeated TAG directive"; + const std::string CHAR_IN_TAG_HANDLE = "illegal character found while scanning tag handle"; + const std::string TAG_WITH_NO_SUFFIX = "tag handle with no suffix"; + const std::string END_OF_VERBATIM_TAG = "end of verbatim tag not found"; 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"; @@ -57,6 +62,7 @@ namespace YAML 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 INVALID_TAG = "invalid tag"; 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"; diff --git a/include/node.h b/include/node.h index 1503e3b..4218444 100644 --- a/include/node.h +++ b/include/node.h @@ -9,7 +9,6 @@ #include "iterator.h" #include "mark.h" #include "noncopyable.h" -#include "parserstate.h" #include #include #include @@ -21,6 +20,7 @@ namespace YAML class Content; class Scanner; class Emitter; + class ParserState; enum CONTENT_TYPE { CT_NONE, CT_SCALAR, CT_SEQUENCE, CT_MAP }; @@ -75,6 +75,9 @@ namespace YAML const Node *Identity() const { return m_pIdentity; } bool IsAlias() const { return m_alias; } bool IsReferenced() const { return m_referenced; } + + // for tags + const std::string GetTag() const { return m_tag; } // TODO: should an aliased node return its alias's tag? // emitting friend Emitter& operator << (Emitter& out, const Node& node); diff --git a/include/parser.h b/include/parser.h index 9f24916..cd61c1c 100644 --- a/include/parser.h +++ b/include/parser.h @@ -5,7 +5,6 @@ #include "node.h" -#include "parserstate.h" #include "noncopyable.h" #include #include @@ -16,6 +15,7 @@ namespace YAML { class Scanner; + class ParserState; struct Token; class Parser: private noncopyable @@ -33,13 +33,13 @@ namespace YAML private: void ParseDirectives(); - void HandleDirective(Token *pToken); - void HandleYamlDirective(Token *pToken); - void HandleTagDirective(Token *pToken); + void HandleDirective(const Token& token); + void HandleYamlDirective(const Token& token); + void HandleTagDirective(const Token& token); private: std::auto_ptr m_pScanner; - ParserState m_state; + std::auto_ptr m_pState; }; } diff --git a/src/emitter.cpp b/src/emitter.cpp index 5eb4bc4..e987ad0 100644 --- a/src/emitter.cpp +++ b/src/emitter.cpp @@ -146,6 +146,8 @@ namespace YAML switch(curState) { // document-level case ES_WAITING_FOR_DOC: + m_stream << "---"; + m_pState->RequireSeparation(); m_pState->SwitchState(ES_WRITING_DOC); return true; case ES_WRITING_DOC: @@ -323,7 +325,10 @@ namespace YAML 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) { + if(curState == ES_WRITING_BLOCK_SEQ_ENTRY || + curState == ES_WRITING_BLOCK_MAP_KEY || curState == ES_WRITING_BLOCK_MAP_VALUE || + curState == ES_WRITING_DOC + ) { m_stream << "\n"; m_pState->UnsetSeparation(); } @@ -354,8 +359,12 @@ namespace YAML // to a flow sequence if it is assert(curState == ES_DONE_WITH_BLOCK_SEQ_ENTRY || curState == ES_WAITING_FOR_BLOCK_SEQ_ENTRY); if(curState == ES_WAITING_FOR_BLOCK_SEQ_ENTRY) { + // Note: only one of these will actually output anything for a given situation + EmitSeparationIfNecessary(); unsigned curIndent = m_pState->GetCurIndent(); - m_stream << IndentTo(curIndent) << "[]"; + m_stream << IndentTo(curIndent); + + m_stream << "[]"; } } else if(flowType == FT_FLOW) { // Note: flow sequences are allowed to be empty @@ -384,7 +393,10 @@ namespace YAML 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) { + if(curState == ES_WRITING_BLOCK_SEQ_ENTRY || + curState == ES_WRITING_BLOCK_MAP_KEY || curState == ES_WRITING_BLOCK_MAP_VALUE || + curState == ES_WRITING_DOC + ) { m_stream << "\n"; m_pState->UnsetSeparation(); } @@ -415,8 +427,11 @@ namespace YAML // to a flow sequence if it is assert(curState == ES_DONE_WITH_BLOCK_MAP_VALUE || curState == ES_WAITING_FOR_BLOCK_MAP_ENTRY); if(curState == ES_WAITING_FOR_BLOCK_MAP_ENTRY) { + // Note: only one of these will actually output anything for a given situation + EmitSeparationIfNecessary(); unsigned curIndent = m_pState->GetCurIndent(); - m_stream << IndentTo(curIndent) << "{}"; + m_stream << IndentTo(curIndent); + m_stream << "{}"; } } else if(flowType == FT_FLOW) { // Note: flow maps are allowed to be empty @@ -675,6 +690,22 @@ namespace YAML return *this; } + Emitter& Emitter::Write(const _Tag& tag) + { + if(!good()) + return *this; + + PreAtomicWrite(); + EmitSeparationIfNecessary(); + if(!Utils::WriteTag(m_stream, tag.content)) { + m_pState->SetError(ErrorMsg::INVALID_TAG); + 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()) diff --git a/src/emitterutils.cpp b/src/emitterutils.cpp index 837ae20..202750a 100644 --- a/src/emitterutils.cpp +++ b/src/emitterutils.cpp @@ -293,6 +293,24 @@ namespace YAML out << "&"; return WriteAliasName(out, str); } + + bool WriteTag(ostream& out, const std::string& str) + { + out << "!<"; + StringCharSource buffer(str.c_str(), str.size()); + while(buffer) { + int n = Exp::URI.Match(buffer); + if(n <= 0) + return false; + + while(--n >= 0) { + out << buffer[0]; + ++buffer; + } + } + out << ">"; + return true; + } } } diff --git a/src/emitterutils.h b/src/emitterutils.h index b7fa346..7ceb6ff 100644 --- a/src/emitterutils.h +++ b/src/emitterutils.h @@ -18,6 +18,7 @@ namespace YAML 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); + bool WriteTag(ostream& out, const std::string& str); } } diff --git a/src/exp.h b/src/exp.h index 126c2c9..c84f12a 100644 --- a/src/exp.h +++ b/src/exp.h @@ -25,6 +25,7 @@ namespace YAML const RegEx Digit = RegEx('0', '9'); const RegEx Alpha = RegEx('a', 'z') || RegEx('A', 'Z'); const RegEx AlphaNumeric = Alpha || Digit; + const RegEx Word = AlphaNumeric || RegEx('-'); const RegEx Hex = Digit || RegEx('A', 'F') || RegEx('a', 'f'); // Valid Unicode code points that are not part of c-printable (YAML 1.2, sec. 5.1) const RegEx NotPrintable = RegEx(0) || @@ -45,6 +46,8 @@ namespace YAML ValueInFlow = RegEx(':') + (BlankOrBreak || RegEx(",}", REGEX_OR)); const RegEx Comment = RegEx('#'); const RegEx AnchorEnd = RegEx("?:,]}%@`", REGEX_OR) || BlankOrBreak; + const RegEx URI = Word || RegEx("#;/?:@&=+$,_.!~*'()[]", REGEX_OR) || (RegEx('%') + Hex + Hex); + const RegEx Tag = Word || RegEx("#;/?:@&=+$_.~*'", REGEX_OR) || (RegEx('%') + Hex + Hex); // Plain scalar rules: // . Cannot start with a blank. @@ -79,6 +82,8 @@ namespace YAML const char Tag = '!'; const char LiteralScalar = '|'; const char FoldedScalar = '>'; + const char VerbatimTagStart = '<'; + const char VerbatimTagEnd = '>'; } } diff --git a/src/node.cpp b/src/node.cpp index 680729a..178377f 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -9,6 +9,7 @@ #include "aliascontent.h" #include "iterpriv.h" #include "emitter.h" +#include "tag.h" #include namespace YAML @@ -137,10 +138,8 @@ namespace YAML if(m_tag != "") throw ParserException(token.mark, ErrorMsg::MULTIPLE_TAGS); - m_tag = state.TranslateTag(token.value); - - for(std::size_t i=0;ipop(); } @@ -241,7 +240,10 @@ namespace YAML bool Node::GetScalar(std::string& s) const { if(!m_pContent) { - s = "~"; + if(m_tag.empty()) + s = "~"; + else + s = ""; return true; } @@ -258,7 +260,8 @@ namespace YAML out << Anchor(node.m_anchor); } - // TODO: write tag + if(node.m_tag != "") + out << VerbatimTag(node.m_tag); // write content if(node.m_pContent) diff --git a/src/parser.cpp b/src/parser.cpp index b08a08e..e9fd0a1 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2,6 +2,7 @@ #include "scanner.h" #include "token.h" #include "exceptions.h" +#include "parserstate.h" #include #include @@ -28,7 +29,7 @@ namespace YAML void Parser::Load(std::istream& in) { m_pScanner.reset(new Scanner(in)); - m_state.Reset(); + m_pState.reset(new ParserState); } // GetNextDocument @@ -54,7 +55,7 @@ namespace YAML m_pScanner->pop(); // now parse our root node - document.Parse(m_pScanner.get(), m_state); + document.Parse(m_pScanner.get(), *m_pState); // and finally eat any doc ends we see while(!m_pScanner->empty() && m_pScanner->peek().type == Token::DOC_END) @@ -83,51 +84,59 @@ namespace YAML // we keep the directives from the last document if none are specified; // but if any directives are specific, then we reset them if(!readDirective) - m_state.Reset(); + m_pState.reset(new ParserState); readDirective = true; - HandleDirective(&token); + HandleDirective(token); m_pScanner->pop(); } } - void Parser::HandleDirective(Token *pToken) + void Parser::HandleDirective(const Token& token) { - if(pToken->value == "YAML") - HandleYamlDirective(pToken); - else if(pToken->value == "TAG") - HandleTagDirective(pToken); + if(token.value == "YAML") + HandleYamlDirective(token); + else if(token.value == "TAG") + HandleTagDirective(token); } // HandleYamlDirective // . Should be of the form 'major.minor' (like a version number) - void Parser::HandleYamlDirective(Token *pToken) + void Parser::HandleYamlDirective(const Token& token) { - if(pToken->params.size() != 1) - throw ParserException(pToken->mark, ErrorMsg::YAML_DIRECTIVE_ARGS); + if(token.params.size() != 1) + throw ParserException(token.mark, ErrorMsg::YAML_DIRECTIVE_ARGS); + + if(!m_pState->version.isDefault) + throw ParserException(token.mark, ErrorMsg::REPEATED_YAML_DIRECTIVE); - std::stringstream str(pToken->params[0]); - str >> m_state.version.major; + std::stringstream str(token.params[0]); + str >> m_pState->version.major; str.get(); - str >> m_state.version.minor; + str >> m_pState->version.minor; if(!str || str.peek() != EOF) - throw ParserException(pToken->mark, ErrorMsg::YAML_VERSION + pToken->params[0]); + throw ParserException(token.mark, ErrorMsg::YAML_VERSION + token.params[0]); - if(m_state.version.major > 1) - throw ParserException(pToken->mark, ErrorMsg::YAML_MAJOR_VERSION); + if(m_pState->version.major > 1) + throw ParserException(token.mark, ErrorMsg::YAML_MAJOR_VERSION); + m_pState->version.isDefault = false; // TODO: warning on major == 1, minor > 2? } // HandleTagDirective // . Should be of the form 'handle prefix', where 'handle' is converted to 'prefix' in the file. - void Parser::HandleTagDirective(Token *pToken) + void Parser::HandleTagDirective(const Token& token) { - if(pToken->params.size() != 2) - throw ParserException(pToken->mark, ErrorMsg::TAG_DIRECTIVE_ARGS); + if(token.params.size() != 2) + throw ParserException(token.mark, ErrorMsg::TAG_DIRECTIVE_ARGS); - std::string handle = pToken->params[0], prefix = pToken->params[1]; - m_state.tags[handle] = prefix; + const std::string& handle = token.params[0]; + const std::string& prefix = token.params[1]; + if(m_pState->tags.find(handle) != m_pState->tags.end()) + throw ParserException(token.mark, ErrorMsg::REPEATED_TAG_DIRECTIVE); + + m_pState->tags[handle] = prefix; } void Parser::PrintTokens(std::ostream& out) diff --git a/src/parserstate.cpp b/src/parserstate.cpp index 8b9531a..ebd6609 100644 --- a/src/parserstate.cpp +++ b/src/parserstate.cpp @@ -2,23 +2,22 @@ namespace YAML { - void ParserState::Reset() + ParserState::ParserState() { // version + version.isDefault = true; version.major = 1; version.minor = 2; - - // and tags - tags.clear(); - tags["!"] = "!"; - tags["!!"] = "tag:yaml.org,2002:"; } - std::string ParserState::TranslateTag(const std::string& handle) const + const std::string ParserState::TranslateTagHandle(const std::string& handle) const { std::map ::const_iterator it = tags.find(handle); - if(it == tags.end()) + if(it == tags.end()) { + if(handle == "!!") + return "tag:yaml.org,2002:"; return handle; + } return it->second; } diff --git a/include/parserstate.h b/src/parserstate.h similarity index 77% rename from include/parserstate.h rename to src/parserstate.h index 1f27a7f..09b4afd 100644 --- a/include/parserstate.h +++ b/src/parserstate.h @@ -10,16 +10,17 @@ namespace YAML { struct Version { + bool isDefault; int major, minor; }; - + struct ParserState { + ParserState(); + const std::string TranslateTagHandle(const std::string& handle) const; + Version version; std::map tags; - - void Reset(); - std::string TranslateTag(const std::string& handle) const; }; } diff --git a/src/regex.h b/src/regex.h index 59a4833..12d9be7 100644 --- a/src/regex.h +++ b/src/regex.h @@ -37,12 +37,12 @@ namespace YAML int Match(const std::string& str) const; int Match(const Stream& in) const; + template int Match(const Source& source) const; private: RegEx(REGEX_OP op); template bool IsValidSource(const Source& source) const; - template int Match(const Source& source) const; template int MatchUnchecked(const Source& source) const; template int MatchOpEmpty(const Source& source) const; diff --git a/src/scanner.cpp b/src/scanner.cpp index 725aadc..877c404 100644 --- a/src/scanner.cpp +++ b/src/scanner.cpp @@ -3,6 +3,7 @@ #include "exceptions.h" #include "exp.h" #include +#include namespace YAML { @@ -13,6 +14,9 @@ namespace YAML Scanner::~Scanner() { + for(unsigned i=0;i pIndent(new IndentMarker(column, type)); + IndentMarker& indent = *pIndent; + const IndentMarker& lastIndent = *m_indents.top(); // is this actually an indentation? if(indent.column < lastIndent.column) @@ -276,13 +283,15 @@ namespace YAML indent.pStartToken = &m_tokens.back(); // and then the indent - m_indents.push(indent); - return &m_indents.top(); + m_indents.push(&indent); + m_indentRefs.push_back(pIndent.release()); + return m_indentRefs.back(); } // PopIndentToHere // . Pops indentations off the stack until we reach the current indentation level, // and enqueues the proper token each time. + // . Then pops all invalid indentations off. void Scanner::PopIndentToHere() { // are we in flow? @@ -291,7 +300,7 @@ namespace YAML // now pop away while(!m_indents.empty()) { - const IndentMarker& indent = m_indents.top(); + const IndentMarker& indent = *m_indents.top(); if(indent.column < INPUT.column()) break; if(indent.column == INPUT.column() && !(indent.type == IndentMarker::SEQ && !Exp::BlockEntry.Matches(INPUT))) @@ -299,6 +308,9 @@ namespace YAML PopIndent(); } + + while(!m_indents.empty() && m_indents.top()->status == IndentMarker::INVALID) + PopIndent(); } // PopAllIndents @@ -312,7 +324,7 @@ namespace YAML // now pop away while(!m_indents.empty()) { - const IndentMarker& indent = m_indents.top(); + const IndentMarker& indent = *m_indents.top(); if(indent.type == IndentMarker::NONE) break; @@ -324,17 +336,17 @@ namespace YAML // . Pops a single indent, pushing the proper token void Scanner::PopIndent() { - IndentMarker indent = m_indents.top(); - IndentMarker::INDENT_TYPE type = indent.type; + const IndentMarker& indent = *m_indents.top(); m_indents.pop(); - if(!indent.isValid) { + + if(indent.status != IndentMarker::VALID) { InvalidateSimpleKey(); return; } - if(type == IndentMarker::SEQ) + if(indent.type == IndentMarker::SEQ) m_tokens.push(Token(Token::BLOCK_SEQ_END, INPUT.mark())); - else if(type == IndentMarker::MAP) + else if(indent.type == IndentMarker::MAP) m_tokens.push(Token(Token::BLOCK_MAP_END, INPUT.mark())); } @@ -343,7 +355,7 @@ namespace YAML { if(m_indents.empty()) return 0; - return m_indents.top().column; + return m_indents.top()->column; } // Save diff --git a/src/scanner.h b/src/scanner.h index 19ee048..ddae0de 100644 --- a/src/scanner.h +++ b/src/scanner.h @@ -36,11 +36,12 @@ namespace YAML private: struct IndentMarker { enum INDENT_TYPE { MAP, SEQ, NONE }; - IndentMarker(int column_, INDENT_TYPE type_): column(column_), type(type_), isValid(true), pStartToken(0) {} + enum STATUS { VALID, INVALID, UNKNOWN }; + IndentMarker(int column_, INDENT_TYPE type_): column(column_), type(type_), status(VALID), pStartToken(0) {} int column; INDENT_TYPE type; - bool isValid; + STATUS status; Token *pStartToken; }; @@ -118,10 +119,12 @@ namespace YAML bool m_startedStream, m_endedStream; bool m_simpleKeyAllowed; std::stack m_simpleKeys; - std::stack m_indents; + std::stack m_indents; + std::vector m_indentRefs; // for "garbage collection" std::stack m_flows; std::map m_anchors; }; } #endif // SCANNER_H_62B23520_7C8E_11DE_8A39_0800200C9A66 + diff --git a/src/scanscalar.h b/src/scanscalar.h index 6f6f559..1f92b9e 100644 --- a/src/scanscalar.h +++ b/src/scanscalar.h @@ -40,3 +40,4 @@ namespace YAML } #endif // SCANSCALAR_H_62B23520_7C8E_11DE_8A39_0800200C9A66 + diff --git a/src/scantag.cpp b/src/scantag.cpp new file mode 100644 index 0000000..17a6d65 --- /dev/null +++ b/src/scantag.cpp @@ -0,0 +1,84 @@ +#include "scanner.h" +#include "regex.h" +#include "exp.h" +#include "exceptions.h" + +namespace YAML +{ + const std::string ScanVerbatimTag(Stream& INPUT) + { + std::string tag; + + // eat the start character + INPUT.get(); + + while(INPUT) { + if(INPUT.peek() == Keys::VerbatimTagEnd) { + // eat the end character + INPUT.get(); + return tag; + } + + int n = Exp::URI.Match(INPUT); + if(n <= 0) + break; + + tag += INPUT.get(n); + } + + throw ParserException(INPUT.mark(), ErrorMsg::END_OF_VERBATIM_TAG); + } + + const std::string ScanTagHandle(Stream& INPUT, bool& canBeHandle) + { + std::string tag; + canBeHandle = true; + Mark firstNonWordChar; + + while(INPUT) { + if(INPUT.peek() == Keys::Tag) { + if(!canBeHandle) + throw ParserException(firstNonWordChar, ErrorMsg::CHAR_IN_TAG_HANDLE); + break; + } + + int n = 0; + if(canBeHandle) { + n = Exp::Word.Match(INPUT); + if(n <= 0) { + canBeHandle = false; + firstNonWordChar = INPUT.mark(); + } + } + + if(!canBeHandle) + n = Exp::Tag.Match(INPUT); + + if(n <= 0) + break; + + tag += INPUT.get(n); + } + + return tag; + } + + const std::string ScanTagSuffix(Stream& INPUT) + { + std::string tag; + + while(INPUT) { + int n = Exp::Tag.Match(INPUT); + if(n <= 0) + break; + + tag += INPUT.get(n); + } + + if(tag.empty()) + throw ParserException(INPUT.mark(), ErrorMsg::TAG_WITH_NO_SUFFIX); + + return tag; + } +} + diff --git a/src/scantag.h b/src/scantag.h new file mode 100644 index 0000000..77b315d --- /dev/null +++ b/src/scantag.h @@ -0,0 +1,18 @@ +#pragma once + +#ifndef SCANTAG_H_62B23520_7C8E_11DE_8A39_0800200C9A66 +#define SCANTAG_H_62B23520_7C8E_11DE_8A39_0800200C9A66 + + +#include +#include "stream.h" + +namespace YAML +{ + const std::string ScanVerbatimTag(Stream& INPUT); + const std::string ScanTagHandle(Stream& INPUT, bool& canBeHandle); + const std::string ScanTagSuffix(Stream& INPUT); +} + +#endif // SCANTAG_H_62B23520_7C8E_11DE_8A39_0800200C9A66 + diff --git a/src/scantoken.cpp b/src/scantoken.cpp index 5bc0c17..c1551ac 100644 --- a/src/scantoken.cpp +++ b/src/scantoken.cpp @@ -3,6 +3,8 @@ #include "exceptions.h" #include "exp.h" #include "scanscalar.h" +#include "scantag.h" +#include "tag.h" #include namespace YAML @@ -24,12 +26,12 @@ namespace YAML m_simpleKeyAllowed = false; // store pos and eat indicator - Mark mark = INPUT.mark(); + Token token(Token::DIRECTIVE, INPUT.mark()); INPUT.eat(1); // read name while(INPUT && !Exp::BlankOrBreak.Matches(INPUT)) - name += INPUT.get(); + token.value += INPUT.get(); // read parameters while(1) { @@ -46,12 +48,9 @@ namespace YAML while(INPUT && !Exp::BlankOrBreak.Matches(INPUT)) param += INPUT.get(); - params.push_back(param); + token.params.push_back(param); } - Token token(Token::DIRECTIVE, mark); - token.value = name; - token.params = params; m_tokens.push(token); } @@ -242,37 +241,34 @@ namespace YAML // Tag void Scanner::ScanTag() { - std::string handle, suffix; - // insert a potential simple key InsertPotentialSimpleKey(); m_simpleKeyAllowed = false; + Token token(Token::TAG, INPUT.mark()); + // eat the indicator - Mark mark = INPUT.mark(); - handle += INPUT.get(); + INPUT.get(); + + if(INPUT && INPUT.peek() == Keys::VerbatimTagStart){ + std::string tag = ScanVerbatimTag(INPUT); - // read the handle - while(INPUT && INPUT.peek() != Keys::Tag && !Exp::BlankOrBreak.Matches(INPUT)) - handle += INPUT.get(); - - // is there a suffix? - if(INPUT.peek() == Keys::Tag) { - // eat the indicator - handle += INPUT.get(); - - // then read it - while(INPUT && !Exp::BlankOrBreak.Matches(INPUT)) - suffix += INPUT.get(); + token.value = tag; + token.data = Tag::VERBATIM; } else { - // this is a bit weird: we keep just the '!' as the handle and move the rest to the suffix - suffix = handle.substr(1); - handle = "!"; + bool canBeHandle; + token.value = ScanTagHandle(INPUT, canBeHandle); + token.data = (token.value.empty() ? Tag::SECONDARY_HANDLE : Tag::PRIMARY_HANDLE); + + // is there a suffix? + if(canBeHandle && INPUT.peek() == Keys::Tag) { + // eat the indicator + INPUT.get(); + token.params.push_back(ScanTagSuffix(INPUT)); + token.data = Tag::NAMED_HANDLE; + } } - Token token(Token::TAG, mark); - token.value = handle; - token.params.push_back(suffix); m_tokens.push(token); } diff --git a/src/simplekey.cpp b/src/simplekey.cpp index 6a605f0..f99cea6 100644 --- a/src/simplekey.cpp +++ b/src/simplekey.cpp @@ -12,9 +12,11 @@ namespace YAML void Scanner::SimpleKey::Validate() { - // Note: pIndent will *not* be garbage here; see below + // Note: pIndent will *not* be garbage here; + // we "garbage collect" them so we can + // always refer to them if(pIndent) - pIndent->isValid = true; + pIndent->status = IndentMarker::VALID; if(pMapStart) pMapStart->status = Token::VALID; if(pKey) @@ -23,8 +25,8 @@ namespace YAML void Scanner::SimpleKey::Invalidate() { - // Note: pIndent might be a garbage pointer here, but that's ok - // An indent will only be popped if the simple key is invalid + if(pIndent) + pIndent->status = IndentMarker::INVALID; if(pMapStart) pMapStart->status = Token::INVALID; if(pKey) @@ -68,7 +70,7 @@ namespace YAML // first add a map start, if necessary key.pIndent = PushIndentTo(INPUT.column(), IndentMarker::MAP); if(key.pIndent) { - key.pIndent->isValid = false; + key.pIndent->status = IndentMarker::UNKNOWN; key.pMapStart = key.pIndent->pStartToken; key.pMapStart->status = Token::UNVERIFIED; } @@ -135,3 +137,4 @@ namespace YAML m_simpleKeys.pop(); } } + diff --git a/src/stringsource.h b/src/stringsource.h index c4e4c13..20d56ae 100644 --- a/src/stringsource.h +++ b/src/stringsource.h @@ -30,6 +30,11 @@ namespace YAML ++m_offset; return *this; } + + StringCharSource& operator += (std::size_t offset) { + m_offset += offset; + return *this; + } private: const char *m_str; std::size_t m_size; diff --git a/src/tag.cpp b/src/tag.cpp new file mode 100644 index 0000000..694a4f3 --- /dev/null +++ b/src/tag.cpp @@ -0,0 +1,50 @@ +#include "tag.h" +#include "token.h" +#include "parserstate.h" +#include + +namespace YAML +{ + Tag::Tag(const Token& token): type(static_cast(token.data)) + { + switch(type) { + case VERBATIM: + value = token.value; + break; + case PRIMARY_HANDLE: + value = token.value; + break; + case SECONDARY_HANDLE: + value = token.value; + break; + case NAMED_HANDLE: + handle = token.value; + value = token.params[0]; + break; + case NON_SPECIFIC: + break; + default: + assert(false); + } + } + + const std::string Tag::Translate(const ParserState& state) + { + switch(type) { + case VERBATIM: + return value; + case PRIMARY_HANDLE: + return state.TranslateTagHandle("!") + value; + case SECONDARY_HANDLE: + return state.TranslateTagHandle("!!") + value; + case NAMED_HANDLE: + return state.TranslateTagHandle("!" + handle + "!") + value; + case NON_SPECIFIC: + // TODO: + return "!"; + default: + assert(false); + } + } +} + diff --git a/src/tag.h b/src/tag.h new file mode 100644 index 0000000..b448994 --- /dev/null +++ b/src/tag.h @@ -0,0 +1,26 @@ +#pragma once + +#ifndef TAG_H_62B23520_7C8E_11DE_8A39_0800200C9A66 +#define TAG_H_62B23520_7C8E_11DE_8A39_0800200C9A66 + +#include + +namespace YAML +{ + struct Token; + struct ParserState; + + struct Tag { + enum TYPE { + VERBATIM, PRIMARY_HANDLE, SECONDARY_HANDLE, NAMED_HANDLE, NON_SPECIFIC + }; + + Tag(const Token& token); + const std::string Translate(const ParserState& state); + + TYPE type; + std::string handle, value; + }; +} + +#endif // TAG_H_62B23520_7C8E_11DE_8A39_0800200C9A66 diff --git a/src/token.h b/src/token.h index 8ab82d5..ff1a457 100644 --- a/src/token.h +++ b/src/token.h @@ -59,7 +59,7 @@ namespace YAML }; // data - Token(TYPE type_, const Mark& mark_): status(VALID), type(type_), mark(mark_) {} + Token(TYPE type_, const Mark& mark_): status(VALID), type(type_), mark(mark_), data(0) {} friend std::ostream& operator << (std::ostream& out, const Token& token) { out << TokenNames[token.type] << std::string(": ") << token.value; @@ -73,6 +73,7 @@ namespace YAML Mark mark; std::string value; std::vector params; + int data; }; } diff --git a/test/emittertests.cpp b/test/emittertests.cpp index f85af8d..fa0ef5c 100644 --- a/test/emittertests.cpp +++ b/test/emittertests.cpp @@ -9,7 +9,7 @@ namespace Test void SimpleScalar(YAML::Emitter& out, std::string& desiredOutput) { out << "Hello, World!"; - desiredOutput = "Hello, World!"; + desiredOutput = "--- Hello, World!"; } void SimpleSeq(YAML::Emitter& out, std::string& desiredOutput) { @@ -19,7 +19,7 @@ namespace Test out << "milk"; out << YAML::EndSeq; - desiredOutput = "- eggs\n- bread\n- milk"; + desiredOutput = "---\n- eggs\n- bread\n- milk"; } void SimpleFlowSeq(YAML::Emitter& out, std::string& desiredOutput) { @@ -30,7 +30,7 @@ namespace Test out << "Moe"; out << YAML::EndSeq; - desiredOutput = "[Larry, Curly, Moe]"; + desiredOutput = "--- [Larry, Curly, Moe]"; } void EmptyFlowSeq(YAML::Emitter& out, std::string& desiredOutput) { @@ -38,7 +38,7 @@ namespace Test out << YAML::BeginSeq; out << YAML::EndSeq; - desiredOutput = "[]"; + desiredOutput = "--- []"; } void NestedBlockSeq(YAML::Emitter& out, std::string& desiredOutput) { @@ -47,7 +47,7 @@ namespace Test out << YAML::BeginSeq << "subitem 1" << "subitem 2" << YAML::EndSeq; out << YAML::EndSeq; - desiredOutput = "- item 1\n-\n - subitem 1\n - subitem 2"; + desiredOutput = "---\n- item 1\n-\n - subitem 1\n - subitem 2"; } void NestedFlowSeq(YAML::Emitter& out, std::string& desiredOutput) { @@ -56,7 +56,7 @@ namespace Test out << YAML::Flow << YAML::BeginSeq << "two" << "three" << YAML::EndSeq; out << YAML::EndSeq; - desiredOutput = "- one\n- [two, three]"; + desiredOutput = "---\n- one\n- [two, three]"; } void SimpleMap(YAML::Emitter& out, std::string& desiredOutput) { @@ -67,7 +67,7 @@ namespace Test out << YAML::Value << "3B"; out << YAML::EndMap; - desiredOutput = "name: Ryan Braun\nposition: 3B"; + desiredOutput = "---\nname: Ryan Braun\nposition: 3B"; } void SimpleFlowMap(YAML::Emitter& out, std::string& desiredOutput) { @@ -79,7 +79,7 @@ namespace Test out << YAML::Value << "blue"; out << YAML::EndMap; - desiredOutput = "{shape: square, color: blue}"; + desiredOutput = "--- {shape: square, color: blue}"; } void MapAndList(YAML::Emitter& out, std::string& desiredOutput) { @@ -90,7 +90,7 @@ namespace Test out << YAML::Value << YAML::BeginSeq << "Sasha" << "Malia" << YAML::EndSeq; out << YAML::EndMap; - desiredOutput = "name: Barack Obama\nchildren:\n - Sasha\n - Malia"; + desiredOutput = "---\nname: Barack Obama\nchildren:\n - Sasha\n - Malia"; } void ListAndMap(YAML::Emitter& out, std::string& desiredOutput) { @@ -103,7 +103,7 @@ namespace Test out << "item 2"; out << YAML::EndSeq; - desiredOutput = "- item 1\n-\n pens: 8\n pencils: 14\n- item 2"; + desiredOutput = "---\n- item 1\n-\n pens: 8\n pencils: 14\n- item 2"; } void NestedBlockMap(YAML::Emitter& out, std::string& desiredOutput) { @@ -119,7 +119,7 @@ namespace Test out << YAML::EndMap; out << YAML::EndMap; - desiredOutput = "name: Fred\ngrades:\n algebra: A\n physics: C+\n literature: B"; + desiredOutput = "---\nname: Fred\ngrades:\n algebra: A\n physics: C+\n literature: B"; } void NestedFlowMap(YAML::Emitter& out, std::string& desiredOutput) { @@ -136,7 +136,7 @@ namespace Test out << YAML::EndMap; out << YAML::EndMap; - desiredOutput = "{name: Fred, grades: {algebra: A, physics: C+, literature: B}}"; + desiredOutput = "--- {name: Fred, grades: {algebra: A, physics: C+, literature: B}}"; } void MapListMix(YAML::Emitter& out, std::string& desiredOutput) { @@ -149,7 +149,7 @@ namespace Test out << YAML::Key << "invincible" << YAML::Value << YAML::OnOffBool << false; out << YAML::EndMap; - desiredOutput = "name: Bob\nposition: [2, 4]\ninvincible: off"; + desiredOutput = "---\nname: Bob\nposition: [2, 4]\ninvincible: off"; } void SimpleLongKey(YAML::Emitter& out, std::string& desiredOutput) @@ -162,7 +162,7 @@ namespace Test out << YAML::Value << 145; out << YAML::EndMap; - desiredOutput = "? height\n: 5'9\"\n? weight\n: 145"; + desiredOutput = "---\n? height\n: 5'9\"\n? weight\n: 145"; } void SingleLongKey(YAML::Emitter& out, std::string& desiredOutput) @@ -176,7 +176,7 @@ namespace Test out << YAML::Value << 145; out << YAML::EndMap; - desiredOutput = "age: 24\n? height\n: 5'9\"\nweight: 145"; + desiredOutput = "---\nage: 24\n? height\n: 5'9\"\nweight: 145"; } void ComplexLongKey(YAML::Emitter& out, std::string& desiredOutput) @@ -189,7 +189,7 @@ namespace Test out << YAML::Value << "demon"; out << YAML::EndMap; - desiredOutput = "?\n - 1\n - 3\n: monster\n? [2, 0]\n: demon"; + desiredOutput = "---\n?\n - 1\n - 3\n: monster\n? [2, 0]\n: demon"; } void AutoLongKey(YAML::Emitter& out, std::string& desiredOutput) @@ -203,7 +203,7 @@ namespace Test out << YAML::Value << "angel"; out << YAML::EndMap; - desiredOutput = "?\n - 1\n - 3\n: monster\n? [2, 0]\n: demon\nthe origin: angel"; + desiredOutput = "---\n?\n - 1\n - 3\n: monster\n? [2, 0]\n: demon\nthe origin: angel"; } void ScalarFormat(YAML::Emitter& out, std::string& desiredOutput) @@ -217,7 +217,7 @@ namespace Test 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\\x0adouble-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"; + desiredOutput = "---\n- simple scalar\n- 'explicit single-quoted scalar'\n- \"explicit double-quoted scalar\"\n- \"auto-detected\\x0adouble-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) @@ -227,7 +227,7 @@ namespace Test out << YAML::Value << "and its value"; out << YAML::EndMap; - desiredOutput = "? |\n multi-line\n scalar\n: and its value"; + desiredOutput = "---\n? |\n multi-line\n scalar\n: and its value"; } void LongKeyFlowMap(YAML::Emitter& out, std::string& desiredOutput) @@ -240,7 +240,7 @@ namespace Test out << YAML::Value << "and its value"; out << YAML::EndMap; - desiredOutput = "{simple key: and value, ? long key: and its value}"; + desiredOutput = "--- {simple key: and value, ? long key: and its value}"; } void BlockMapAsKey(YAML::Emitter& out, std::string& desiredOutput) @@ -255,7 +255,7 @@ namespace Test out << "total value"; out << YAML::EndMap; - desiredOutput = "?\n key: value\n next key: next value\n: total value"; + desiredOutput = "---\n?\n key: value\n next key: next value\n: total value"; } void AliasAndAnchor(YAML::Emitter& out, std::string& desiredOutput) @@ -269,7 +269,7 @@ namespace Test out << YAML::Alias("fred"); out << YAML::EndSeq; - desiredOutput = "- &fred\n name: Fred\n age: 42\n- *fred"; + desiredOutput = "---\n- &fred\n name: Fred\n age: 42\n- *fred"; } void AliasAndAnchorWithNull(YAML::Emitter& out, std::string& desiredOutput) @@ -279,7 +279,98 @@ namespace Test out << YAML::Alias("fred"); out << YAML::EndSeq; - desiredOutput = "- &fred ~\n- *fred"; + desiredOutput = "---\n- &fred ~\n- *fred"; + } + + void SimpleVerbatimTag(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::VerbatimTag("!foo") << "bar"; + + desiredOutput = "--- ! bar"; + } + + void VerbatimTagInBlockSeq(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginSeq; + out << YAML::VerbatimTag("!foo") << "bar"; + out << "baz"; + out << YAML::EndSeq; + + desiredOutput = "---\n- ! bar\n- baz"; + } + + void VerbatimTagInFlowSeq(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::Flow << YAML::BeginSeq; + out << YAML::VerbatimTag("!foo") << "bar"; + out << "baz"; + out << YAML::EndSeq; + + desiredOutput = "--- [! bar, baz]"; + } + + void VerbatimTagInFlowSeqWithNull(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::Flow << YAML::BeginSeq; + out << YAML::VerbatimTag("!foo") << YAML::Null; + out << "baz"; + out << YAML::EndSeq; + + desiredOutput = "--- [! ~, baz]"; + } + + void VerbatimTagInBlockMap(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginMap; + out << YAML::Key << YAML::VerbatimTag("!foo") << "bar"; + out << YAML::Value << YAML::VerbatimTag("!waz") << "baz"; + out << YAML::EndMap; + + desiredOutput = "---\n! bar: ! baz"; + } + + void VerbatimTagInFlowMap(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::Flow << YAML::BeginMap; + out << YAML::Key << YAML::VerbatimTag("!foo") << "bar"; + out << YAML::Value << "baz"; + out << YAML::EndMap; + + desiredOutput = "--- {! bar: baz}"; + } + + void VerbatimTagInFlowMapWithNull(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::Flow << YAML::BeginMap; + out << YAML::Key << YAML::VerbatimTag("!foo") << YAML::Null; + out << YAML::Value << "baz"; + out << YAML::EndMap; + + desiredOutput = "--- {! ~: baz}"; + } + + void VerbatimTagWithEmptySeq(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::VerbatimTag("!foo") << YAML::BeginSeq << YAML::EndSeq; + + desiredOutput = "--- !\n[]"; + } + + void VerbatimTagWithEmptyMap(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::VerbatimTag("!bar") << YAML::BeginMap << YAML::EndMap; + + desiredOutput = "--- !\n{}"; + } + + void VerbatimTagWithEmptySeqAndMap(YAML::Emitter& out, std::string& desiredOutput) + { + out << YAML::BeginSeq; + out << YAML::VerbatimTag("!foo") << YAML::BeginSeq << YAML::EndSeq; + out << YAML::VerbatimTag("!bar") << YAML::BeginMap << YAML::EndMap; + out << YAML::EndSeq; + + desiredOutput = "---\n- !\n []\n- !\n {}"; } void ComplexDoc(YAML::Emitter& out, std::string& desiredOutput) @@ -335,7 +426,7 @@ namespace Test 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"; + desiredOutput = "---\nreceipt: 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) @@ -355,7 +446,7 @@ namespace Test out << ages; out << YAML::EndSeq; - desiredOutput = "- [2, 3, 5, 7, 11, 13]\n-\n Daniel: 26\n Jesse: 24"; + desiredOutput = "---\n- [2, 3, 5, 7, 11, 13]\n-\n Daniel: 26\n Jesse: 24"; } void SimpleComment(YAML::Emitter& out, std::string& desiredOutput) @@ -365,7 +456,7 @@ namespace Test out << YAML::Value << "least squares" << YAML::Comment("should we change this method?"); out << YAML::EndMap; - desiredOutput = "method: least squares # should we change this method?"; + desiredOutput = "---\nmethod: least squares # should we change this method?"; } void MultiLineComment(YAML::Emitter& out, std::string& desiredOutput) @@ -375,7 +466,7 @@ namespace Test 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"; + desiredOutput = "---\n- 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) @@ -385,7 +476,7 @@ namespace Test out << YAML::Value << "value"; out << YAML::EndMap; - desiredOutput = "? long key # long key\n: value"; + desiredOutput = "---\n? long key # long key\n: value"; } void Indentation(YAML::Emitter& out, std::string& desiredOutput) @@ -398,7 +489,7 @@ namespace Test out << YAML::EndMap; out << YAML::EndSeq; - desiredOutput = "-\n key 1: value 1\n key 2:\n - a\n - b\n - c"; + desiredOutput = "---\n-\n key 1: value 1\n key 2:\n - a\n - b\n - c"; } void SimpleGlobalSettings(YAML::Emitter& out, std::string& desiredOutput) @@ -413,7 +504,7 @@ namespace Test out << YAML::EndMap; out << YAML::EndSeq; - desiredOutput = "-\n ? key 1\n : value 1\n ? key 2\n : [a, b, c]"; + desiredOutput = "---\n-\n ? key 1\n : value 1\n ? key 2\n : [a, b, c]"; } void ComplexGlobalSettings(YAML::Emitter& out, std::string& desiredOutput) @@ -432,7 +523,7 @@ namespace Test 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"; + desiredOutput = "---\n-\n key 1: value 1\n key 2: [a, b, c]\n-\n ? [1, 2]\n :\n a: b"; } void Null(YAML::Emitter& out, std::string& desiredOutput) @@ -445,26 +536,26 @@ namespace Test out << YAML::EndMap; out << YAML::EndSeq; - desiredOutput = "- ~\n-\n null value: ~\n ~: null key"; + desiredOutput = "---\n- ~\n-\n null value: ~\n ~: null key"; } void EscapedUnicode(YAML::Emitter& out, std::string& desiredOutput) { out << YAML::EscapeNonAscii << "\x24 \xC2\xA2 \xE2\x82\xAC \xF0\xA4\xAD\xA2"; - desiredOutput = "\"$ \\xa2 \\u20ac \\U00024b62\""; + desiredOutput = "--- \"$ \\xa2 \\u20ac \\U00024b62\""; } void Unicode(YAML::Emitter& out, std::string& desiredOutput) { out << "\x24 \xC2\xA2 \xE2\x82\xAC \xF0\xA4\xAD\xA2"; - desiredOutput = "\x24 \xC2\xA2 \xE2\x82\xAC \xF0\xA4\xAD\xA2"; + desiredOutput = "--- \x24 \xC2\xA2 \xE2\x82\xAC \xF0\xA4\xAD\xA2"; } void DoubleQuotedUnicode(YAML::Emitter& out, std::string& desiredOutput) { out << YAML::DoubleQuoted << "\x24 \xC2\xA2 \xE2\x82\xAC \xF0\xA4\xAD\xA2"; - desiredOutput = "\"\x24 \xC2\xA2 \xE2\x82\xAC \xF0\xA4\xAD\xA2\""; + desiredOutput = "--- \"\x24 \xC2\xA2 \xE2\x82\xAC \xF0\xA4\xAD\xA2\""; } @@ -620,6 +711,16 @@ namespace Test RunEmitterTest(&Emitter::BlockMapAsKey, "block map as key", passed, total); RunEmitterTest(&Emitter::AliasAndAnchor, "alias and anchor", passed, total); RunEmitterTest(&Emitter::AliasAndAnchorWithNull, "alias and anchor with null", passed, total); + RunEmitterTest(&Emitter::SimpleVerbatimTag, "simple verbatim tag", passed, total); + RunEmitterTest(&Emitter::VerbatimTagInBlockSeq, "verbatim tag in block seq", passed, total); + RunEmitterTest(&Emitter::VerbatimTagInFlowSeq, "verbatim tag in flow seq", passed, total); + RunEmitterTest(&Emitter::VerbatimTagInFlowSeqWithNull, "verbatim tag in flow seq with null", passed, total); + RunEmitterTest(&Emitter::VerbatimTagInBlockMap, "verbatim tag in block map", passed, total); + RunEmitterTest(&Emitter::VerbatimTagInFlowMap, "verbatim tag in flow map", passed, total); + RunEmitterTest(&Emitter::VerbatimTagInFlowMapWithNull, "verbatim tag in flow map with null", passed, total); + RunEmitterTest(&Emitter::VerbatimTagWithEmptySeq, "verbatim tag with empty seq", passed, total); + RunEmitterTest(&Emitter::VerbatimTagWithEmptyMap, "verbatim tag with empty map", passed, total); + RunEmitterTest(&Emitter::VerbatimTagWithEmptySeqAndMap, "verbatim tag with empty seq and map", passed, total); RunEmitterTest(&Emitter::ComplexDoc, "complex doc", passed, total); RunEmitterTest(&Emitter::STLContainers, "STL containers", passed, total); RunEmitterTest(&Emitter::SimpleComment, "simple comment", passed, total); diff --git a/test/spectests.cpp b/test/spectests.cpp index c4da43f..0043c3a 100644 --- a/test/spectests.cpp +++ b/test/spectests.cpp @@ -32,7 +32,7 @@ namespace Test { ret = test(); } catch(const YAML::Exception& e) { ret.ok = false; - ret.error = " Exception caught: " + e.msg; + ret.error = std::string(" Exception caught: ") + e.what(); } if(!ret.ok) { @@ -459,7 +459,136 @@ namespace Test { return true; } - // TODO: 2.19 - 2.26 tags + // TODO: 2.19 - 2.22 tags + + // 2.23 + TEST VariousExplicitTags() + { + std::string input = + "---\n" + "not-date: !!str 2002-04-28\n" + "\n" + "picture: !!binary |\n" + " R0lGODlhDAAMAIQAAP//9/X\n" + " 17unp5WZmZgAAAOfn515eXv\n" + " Pz7Y6OjuDg4J+fn5OTk6enp\n" + " 56enmleECcgggoBADs=\n" + "\n" + "application specific tag: !something |\n" + " The semantics of the tag\n" + " above may be different for\n" + " different documents."; + + PARSE(doc, input); + YAML_ASSERT(doc.size() == 3); + YAML_ASSERT(doc["not-date"].GetTag() == "tag:yaml.org,2002:str"); + YAML_ASSERT(doc["not-date"] == "2002-04-28"); + YAML_ASSERT(doc["picture"].GetTag() == "tag:yaml.org,2002:binary"); + YAML_ASSERT(doc["picture"] == + "R0lGODlhDAAMAIQAAP//9/X\n" + "17unp5WZmZgAAAOfn515eXv\n" + "Pz7Y6OjuDg4J+fn5OTk6enp\n" + "56enmleECcgggoBADs=\n" + ); + YAML_ASSERT(doc["application specific tag"].GetTag() == "!something"); + YAML_ASSERT(doc["application specific tag"] == + "The semantics of the tag\n" + "above may be different for\n" + "different documents." + ); + return true; + } + + // 2.24 + TEST GlobalTags() + { + std::string input = + "%TAG ! tag:clarkevans.com,2002:\n" + "--- !shape\n" + " # Use the ! handle for presenting\n" + " # tag:clarkevans.com,2002:circle\n" + "- !circle\n" + " center: &ORIGIN {x: 73, y: 129}\n" + " radius: 7\n" + "- !line\n" + " start: *ORIGIN\n" + " finish: { x: 89, y: 102 }\n" + "- !label\n" + " start: *ORIGIN\n" + " color: 0xFFEEBB\n" + " text: Pretty vector drawing."; + + PARSE(doc, input); + YAML_ASSERT(doc.GetTag() == "tag:clarkevans.com,2002:shape"); + YAML_ASSERT(doc.size() == 3); + YAML_ASSERT(doc[0].GetTag() == "tag:clarkevans.com,2002:circle"); + YAML_ASSERT(doc[0].size() == 2); + YAML_ASSERT(doc[0]["center"].size() == 2); + YAML_ASSERT(doc[0]["center"]["x"] == 73); + YAML_ASSERT(doc[0]["center"]["y"] == 129); + YAML_ASSERT(doc[0]["radius"] == 7); + YAML_ASSERT(doc[1].GetTag() == "tag:clarkevans.com,2002:line"); + YAML_ASSERT(doc[1].size() == 2); + YAML_ASSERT(doc[1]["start"].size() == 2); + YAML_ASSERT(doc[1]["start"]["x"] == 73); + YAML_ASSERT(doc[1]["start"]["y"] == 129); + YAML_ASSERT(doc[1]["finish"].size() == 2); + YAML_ASSERT(doc[1]["finish"]["x"] == 89); + YAML_ASSERT(doc[1]["finish"]["y"] == 102); + YAML_ASSERT(doc[2].GetTag() == "tag:clarkevans.com,2002:label"); + YAML_ASSERT(doc[2].size() == 3); + YAML_ASSERT(doc[2]["start"].size() == 2); + YAML_ASSERT(doc[2]["start"]["x"] == 73); + YAML_ASSERT(doc[2]["start"]["y"] == 129); + YAML_ASSERT(doc[2]["color"] == "0xFFEEBB"); + YAML_ASSERT(doc[2]["text"] == "Pretty vector drawing."); + return true; + } + + // 2.25 + TEST UnorderedSets() + { + std::string input = + "# Sets are represented as a\n" + "# Mapping where each key is\n" + "# associated with a null value\n" + "--- !!set\n" + "? Mark McGwire\n" + "? Sammy Sosa\n" + "? Ken Griffey"; + + PARSE(doc, input); + YAML_ASSERT(doc.GetTag() == "tag:yaml.org,2002:set"); + YAML_ASSERT(doc.size() == 3); + YAML_ASSERT(IsNull(doc["Mark McGwire"])); + YAML_ASSERT(IsNull(doc["Sammy Sosa"])); + YAML_ASSERT(IsNull(doc["Ken Griffey"])); + return true; + } + + // 2.26 + TEST OrderedMappings() + { + std::string input = + "# Ordered maps are represented as\n" + "# A sequence of mappings, with\n" + "# each mapping having one key\n" + "--- !!omap\n" + "- Mark McGwire: 65\n" + "- Sammy Sosa: 63\n" + "- Ken Griffey: 58"; + + PARSE(doc, input); + YAML_ASSERT(doc.GetTag() == "tag:yaml.org,2002:omap"); + YAML_ASSERT(doc.size() == 3); + YAML_ASSERT(doc[0].size() == 1); + YAML_ASSERT(doc[0]["Mark McGwire"] == 65); + YAML_ASSERT(doc[1].size() == 1); + YAML_ASSERT(doc[1]["Sammy Sosa"] == 63); + YAML_ASSERT(doc[2].size() == 1); + YAML_ASSERT(doc[2]["Ken Griffey"] == 58); + return true; + } // 2.27 TEST Invoice() @@ -496,6 +625,7 @@ namespace Test { " Billsmer @ 338-4338."; PARSE(doc, input); + YAML_ASSERT(doc.GetTag() == "tag:clarkevans.com,2002:invoice"); YAML_ASSERT(doc.size() == 8); YAML_ASSERT(doc["invoice"] == 34843); YAML_ASSERT(doc["date"] == "2001-01-23"); @@ -993,8 +1123,291 @@ namespace Test { return true; } - // TODO: 6.13 - 6.17 directives - // TODO: 6.18 - 6.28 tags + // 6.13 + TEST ReservedDirectives() + { + std::string input = + "%FOO bar baz # Should be ignored\n" + " # with a warning.\n" + "--- \"foo\""; + + PARSE(doc, input); + return true; + } + + // 6.14 + TEST YAMLDirective() + { + std::string input = + "%YAML 1.3 # Attempt parsing\n" + " # with a warning\n" + "---\n" + "\"foo\""; + + PARSE(doc, input); + return true; + } + + // 6.15 + TEST InvalidRepeatedYAMLDirective() + { + std::string input = + "%YAML 1.2\n" + "%YAML 1.1\n" + "foo"; + + try { + PARSE(doc, input); + } catch(const YAML::ParserException& e) { + if(e.msg == YAML::ErrorMsg::REPEATED_YAML_DIRECTIVE) + return true; + + throw; + } + + return " No exception was thrown"; + } + + // 6.16 + TEST TagDirective() + { + std::string input = + "%TAG !yaml! tag:yaml.org,2002:\n" + "---\n" + "!yaml!str \"foo\""; + + PARSE(doc, input); + YAML_ASSERT(doc.GetTag() == "tag:yaml.org,2002:str"); + YAML_ASSERT(doc == "foo"); + return true; + } + + // 6.17 + TEST InvalidRepeatedTagDirective() + { + std::string input = + "%TAG ! !foo\n" + "%TAG ! !foo\n" + "bar"; + + try { + PARSE(doc, input); + } catch(const YAML::ParserException& e) { + if(e.msg == YAML::ErrorMsg::REPEATED_TAG_DIRECTIVE) + return true; + + throw; + } + + return " No exception was thrown"; + } + + // 6.18 + TEST PrimaryTagHandle() + { + std::string input = + "# Private\n" + "!foo \"bar\"\n" + "...\n" + "# Global\n" + "%TAG ! tag:example.com,2000:app/\n" + "---\n" + "!foo \"bar\""; + + PARSE(doc, input); + YAML_ASSERT(doc.GetTag() == "!foo"); + YAML_ASSERT(doc == "bar"); + + PARSE_NEXT(doc); + YAML_ASSERT(doc.GetTag() == "tag:example.com,2000:app/foo"); + YAML_ASSERT(doc == "bar"); + return true; + } + + // 6.19 + TEST SecondaryTagHandle() + { + std::string input = + "%TAG !! tag:example.com,2000:app/\n" + "---\n" + "!!int 1 - 3 # Interval, not integer"; + + PARSE(doc, input); + YAML_ASSERT(doc.GetTag() == "tag:example.com,2000:app/int"); + YAML_ASSERT(doc == "1 - 3"); + return true; + } + + // 6.20 + TEST TagHandles() + { + std::string input = + "%TAG !e! tag:example.com,2000:app/\n" + "---\n" + "!e!foo \"bar\""; + + PARSE(doc, input); + YAML_ASSERT(doc.GetTag() == "tag:example.com,2000:app/foo"); + YAML_ASSERT(doc == "bar"); + return true; + } + + // 6.21 + TEST LocalTagPrefix() + { + std::string input = + "%TAG !m! !my-\n" + "--- # Bulb here\n" + "!m!light fluorescent\n" + "...\n" + "%TAG !m! !my-\n" + "--- # Color here\n" + "!m!light green"; + + PARSE(doc, input); + YAML_ASSERT(doc.GetTag() == "!my-light"); + YAML_ASSERT(doc == "fluorescent"); + + PARSE_NEXT(doc); + YAML_ASSERT(doc.GetTag() == "!my-light"); + YAML_ASSERT(doc == "green"); + return true; + } + + // 6.22 + TEST GlobalTagPrefix() + { + std::string input = + "%TAG !e! tag:example.com,2000:app/\n" + "---\n" + "- !e!foo \"bar\""; + + PARSE(doc, input); + YAML_ASSERT(doc.size() == 1); + YAML_ASSERT(doc[0].GetTag() == "tag:example.com,2000:app/foo"); + YAML_ASSERT(doc[0] == "bar"); + return true; + } + + // 6.23 + TEST NodeProperties() + { + std::string input = + "!!str &a1 \"foo\":\n" + " !!str bar\n" + "&a2 baz : *a1"; + + PARSE(doc, input); + YAML_ASSERT(doc.size() == 2); + for(YAML::Iterator it=doc.begin();it!=doc.end();++it) { + if(it.first() == "foo") { + YAML_ASSERT(it.first().GetTag() == "tag:yaml.org,2002:str"); + YAML_ASSERT(it.second().GetTag() == "tag:yaml.org,2002:str"); + YAML_ASSERT(it.second() == "bar"); + } else if(it.first() == "baz") { + YAML_ASSERT(it.second() == "foo"); + } else + return " unknown key"; + } + + return true; + } + + // 6.24 + TEST VerbatimTags() + { + std::string input = + "! foo :\n" + " ! baz"; + + PARSE(doc, input); + YAML_ASSERT(doc.size() == 1); + for(YAML::Iterator it=doc.begin();it!=doc.end();++it) { + YAML_ASSERT(it.first().GetTag() == "tag:yaml.org,2002:str"); + YAML_ASSERT(it.first() == "foo"); + YAML_ASSERT(it.second().GetTag() == "!bar"); + YAML_ASSERT(it.second() == "baz"); + } + return true; + } + + // 6.25 + TEST InvalidVerbatimTags() + { + std::string input = + "- ! foo\n" + "- !<$:?> bar\n"; + + PARSE(doc, input); + return " not implemented yet"; // TODO: check tags (but we probably will say these are valid, I think) + } + + // 6.26 + TEST TagShorthands() + { + std::string input = + "%TAG !e! tag:example.com,2000:app/\n" + "---\n" + "- !local foo\n" + "- !!str bar\n" + "- !e!tag%21 baz\n"; + + PARSE(doc, input); + YAML_ASSERT(doc.size() == 3); + YAML_ASSERT(doc[0].GetTag() == "!local"); + YAML_ASSERT(doc[0] == "foo"); + YAML_ASSERT(doc[1].GetTag() == "tag:yaml.org,2002:str"); + YAML_ASSERT(doc[1] == "bar"); + YAML_ASSERT(doc[2].GetTag() == "tag:example.com,2000:app/tag%21"); + YAML_ASSERT(doc[2] == "baz"); + return true; + } + + // 6.27 + TEST InvalidTagShorthands() + { + std::string input1 = + "%TAG !e! tag:example,2000:app/\n" + "---\n" + "- !e! foo"; + + bool threw = false; + try { + PARSE(doc, input1); + } catch(const YAML::ParserException& e) { + threw = true; + if(e.msg != YAML::ErrorMsg::TAG_WITH_NO_SUFFIX) + throw; + } + + if(!threw) + return " No exception was thrown for a tag with no suffix"; + + std::string input2 = + "%TAG !e! tag:example,2000:app/\n" + "---\n" + "- !h!bar baz"; + + PARSE(doc, input2); // TODO: should we reject this one (since !h! is not declared)? + return " not implemented yet"; + } + + // 6.28 + TEST NonSpecificTags() + { + std::string input = + "# Assuming conventional resolution:\n" + "- \"12\"\n" + "- 12\n" + "- ! 12"; + + PARSE(doc, input); + YAML_ASSERT(doc.size() == 3); + YAML_ASSERT(doc[0] == "12"); // TODO: check tags. How? + YAML_ASSERT(doc[1] == 12); + YAML_ASSERT(doc[2] == "12"); + return true; + } // 6.29 TEST NodeAnchors() @@ -1039,8 +1452,16 @@ namespace Test { PARSE(doc, input); YAML_ASSERT(doc.size() == 2); - YAML_ASSERT(doc["foo"] == ""); // TODO: check tag - YAML_ASSERT(doc[""] == "bar"); + for(YAML::Iterator it=doc.begin();it!=doc.end();++it) { + if(it.first() == "foo") { + YAML_ASSERT(it.second().GetTag() == "tag:yaml.org,2002:str"); + YAML_ASSERT(it.second() == ""); + } else if(it.first() == "") { + YAML_ASSERT(it.first().GetTag() == "tag:yaml.org,2002:str"); + YAML_ASSERT(it.second() == "bar"); + } else + return " unexpected key"; + } return true; } @@ -1232,6 +1653,10 @@ namespace Test { RunSpecTest(&Spec::QuotedScalars, "2.17", "Quoted scalars", passed, total); RunSpecTest(&Spec::MultiLineFlowScalars, "2.18", "Multi-line flow scalars", passed, total); + RunSpecTest(&Spec::VariousExplicitTags, "2.23", "Various Explicit Tags", passed, total); + RunSpecTest(&Spec::GlobalTags, "2.24", "Global Tags", passed, total); + RunSpecTest(&Spec::UnorderedSets, "2.25", "Unordered Sets", passed, total); + RunSpecTest(&Spec::OrderedMappings, "2.26", "Ordered Mappings", passed, total); RunSpecTest(&Spec::Invoice, "2.27", "Invoice", passed, total); RunSpecTest(&Spec::LogFile, "2.28", "Log File", passed, total); @@ -1255,8 +1680,24 @@ namespace Test { RunSpecTest(&Spec::FlowFolding, "6.8", "Flow Folding", passed, total); RunSpecTest(&Spec::SeparatedComment, "6.9", "Separated Comment", passed, total); RunSpecTest(&Spec::CommentLines, "6.10", "Comment Lines", passed, total); - RunSpecTest(&Spec::SeparationSpacesII, "6.11", "Separation Spaces", passed, total); - + RunSpecTest(&Spec::MultiLineComments, "6.11", "Multi-Line Comments", passed, total); + RunSpecTest(&Spec::SeparationSpacesII, "6.12", "Separation Spaces", passed, total); + RunSpecTest(&Spec::ReservedDirectives, "6.13", "Reserved Directives", passed, total); + RunSpecTest(&Spec::YAMLDirective, "6.14", "YAML Directive", passed, total); + RunSpecTest(&Spec::InvalidRepeatedYAMLDirective, "6.15", "Invalid Repeated YAML Directive", passed, total); + RunSpecTest(&Spec::TagDirective, "6.16", "Tag Directive", passed, total); + RunSpecTest(&Spec::InvalidRepeatedTagDirective, "6.17", "Invalid Repeated Tag Directive", passed, total); + RunSpecTest(&Spec::PrimaryTagHandle, "6.18", "Primary Tag Handle", passed, total); + RunSpecTest(&Spec::SecondaryTagHandle, "6.19", "SecondaryTagHandle", passed, total); + RunSpecTest(&Spec::TagHandles, "6.20", "TagHandles", passed, total); + RunSpecTest(&Spec::LocalTagPrefix, "6.21", "LocalTagPrefix", passed, total); + RunSpecTest(&Spec::GlobalTagPrefix, "6.22", "GlobalTagPrefix", passed, total); + RunSpecTest(&Spec::NodeProperties, "6.23", "NodeProperties", passed, total); + RunSpecTest(&Spec::VerbatimTags, "6.24", "Verbatim Tags", passed, total); + RunSpecTest(&Spec::InvalidVerbatimTags, "6.25", "Invalid Verbatim Tags", passed, total); + RunSpecTest(&Spec::TagShorthands, "6.26", "Tag Shorthands", passed, total); + RunSpecTest(&Spec::InvalidTagShorthands, "6.27", "Invalid Tag Shorthands", passed, total); + RunSpecTest(&Spec::NonSpecificTags, "6.28", "Non Specific Tags", passed, total); RunSpecTest(&Spec::NodeAnchors, "6.29", "Node Anchors", passed, total); RunSpecTest(&Spec::AliasNodes, "7.1", "Alias Nodes", passed, total);