From 91163d22964ed9a5a28d8b60f3a94c07ace66ef4 Mon Sep 17 00:00:00 2001 From: Jesse Beder Date: Sat, 5 Sep 2009 22:42:01 +0000 Subject: [PATCH] Allowed solo entries in a flow map to be read as keys with null value --- src/scanner.cpp | 20 +++++++------- src/scanner.h | 11 ++++++-- src/scantoken.cpp | 55 +++++++++++++++++++++---------------- src/simplekey.cpp | 34 +++++++++++++---------- yaml-reader/parsertests.cpp | 41 ++++++++++++++++++++++++++- yaml-reader/tests.cpp | 2 ++ yaml-reader/tests.h | 2 ++ 7 files changed, 113 insertions(+), 52 deletions(-) diff --git a/src/scanner.cpp b/src/scanner.cpp index 1cadb71..ffd00a5 100644 --- a/src/scanner.cpp +++ b/src/scanner.cpp @@ -8,7 +8,7 @@ namespace YAML { Scanner::Scanner(std::istream& in) - : INPUT(in), m_startedStream(false), m_endedStream(false), m_simpleKeyAllowed(false), m_flowLevel(0) + : INPUT(in), m_startedStream(false), m_endedStream(false), m_simpleKeyAllowed(false) { } @@ -136,10 +136,10 @@ namespace YAML if(Exp::BlockEntry.Matches(INPUT)) return ScanBlockEntry(); - if((m_flowLevel == 0 ? Exp::Key : Exp::KeyInFlow).Matches(INPUT)) + if((InBlockContext() ? Exp::Key : Exp::KeyInFlow).Matches(INPUT)) return ScanKey(); - if((m_flowLevel == 0 ? Exp::Value : Exp::ValueInFlow).Matches(INPUT)) + if((InBlockContext() ? Exp::Value : Exp::ValueInFlow).Matches(INPUT)) return ScanValue(); // alias/anchor @@ -151,14 +151,14 @@ namespace YAML return ScanTag(); // special scalars - if(m_flowLevel == 0 && (INPUT.peek() == Keys::LiteralScalar || INPUT.peek() == Keys::FoldedScalar)) + if(InBlockContext() && (INPUT.peek() == Keys::LiteralScalar || INPUT.peek() == Keys::FoldedScalar)) return ScanBlockScalar(); if(INPUT.peek() == '\'' || INPUT.peek() == '\"') return ScanQuotedScalar(); // plain scalars - if((m_flowLevel == 0 ? Exp::PlainScalar : Exp::PlainScalarInFlow).Matches(INPUT)) + if((InBlockContext() ? Exp::PlainScalar : Exp::PlainScalarInFlow).Matches(INPUT)) return ScanPlainScalar(); // don't know what it is! @@ -193,7 +193,7 @@ namespace YAML InvalidateSimpleKey(); // new line - we may be able to accept a simple key now - if(m_flowLevel == 0) + if(InBlockContext()) m_simpleKeyAllowed = true; } } @@ -213,7 +213,7 @@ namespace YAML if(ch == ' ') return true; - if(ch == '\t' && (m_flowLevel >= 0 || !m_simpleKeyAllowed)) + if(ch == '\t' && (InFlowContext() || !m_simpleKeyAllowed)) return true; return false; @@ -251,7 +251,7 @@ namespace YAML Scanner::IndentMarker *Scanner::PushIndentTo(int column, IndentMarker::INDENT_TYPE type) { // are we in flow? - if(m_flowLevel > 0) + if(InFlowContext()) return 0; IndentMarker indent(column, type); @@ -283,7 +283,7 @@ namespace YAML void Scanner::PopIndentToHere() { // are we in flow? - if(m_flowLevel > 0) + if(InFlowContext()) return; // now pop away @@ -304,7 +304,7 @@ namespace YAML void Scanner::PopAllIndents() { // are we in flow? - if(m_flowLevel > 0) + if(InFlowContext()) return; // now pop away diff --git a/src/scanner.h b/src/scanner.h index 05c79d4..19ee048 100644 --- a/src/scanner.h +++ b/src/scanner.h @@ -43,6 +43,8 @@ namespace YAML bool isValid; Token *pStartToken; }; + + enum FLOW_MARKER { FLOW_MAP, FLOW_SEQ }; private: // scanning @@ -51,6 +53,11 @@ namespace YAML void ScanToNextToken(); void StartStream(); void EndStream(); + + bool InFlowContext() const { return !m_flows.empty(); } + bool InBlockContext() const { return m_flows.empty(); } + int GetFlowLevel() const { return m_flows.size(); } + IndentMarker *PushIndentTo(int column, IndentMarker::INDENT_TYPE type); void PopIndentToHere(); void PopAllIndents(); @@ -58,6 +65,7 @@ namespace YAML int GetTopIndent() const; // checking input + bool CanInsertPotentialSimpleKey() const; bool ExistsActiveSimpleKey() const; void InsertPotentialSimpleKey(); void InvalidateSimpleKey(); @@ -109,10 +117,9 @@ namespace YAML // state info bool m_startedStream, m_endedStream; bool m_simpleKeyAllowed; - int m_flowLevel; // number of unclosed '[' and '{' indicators - bool m_isLastKeyValid; std::stack m_simpleKeys; std::stack m_indents; + std::stack m_flows; std::map m_anchors; }; } diff --git a/src/scantoken.cpp b/src/scantoken.cpp index 0c3b983..e92fd6d 100644 --- a/src/scantoken.cpp +++ b/src/scantoken.cpp @@ -87,36 +87,50 @@ namespace YAML { // flows can be simple keys InsertPotentialSimpleKey(); - m_flowLevel++; m_simpleKeyAllowed = true; // eat Mark mark = INPUT.mark(); char ch = INPUT.get(); - Token::TYPE type = (ch == Keys::FlowSeqStart ? Token::FLOW_SEQ_START : Token::FLOW_MAP_START); + FLOW_MARKER flowType = (ch == Keys::FlowSeqStart ? FLOW_SEQ : FLOW_MAP); + m_flows.push(flowType); + Token::TYPE type = (flowType == FLOW_SEQ ? Token::FLOW_SEQ_START : Token::FLOW_MAP_START); m_tokens.push(Token(type, mark)); } // FlowEnd void Scanner::ScanFlowEnd() { - if(m_flowLevel == 0) + if(InBlockContext()) throw ParserException(INPUT.mark(), ErrorMsg::FLOW_END); - InvalidateSimpleKey(); - m_flowLevel--; + // we might have a solo entry in the flow context + if(VerifySimpleKey()) + m_tokens.push(Token(Token::VALUE, INPUT.mark())); + m_simpleKeyAllowed = false; // eat Mark mark = INPUT.mark(); char ch = INPUT.get(); - Token::TYPE type = (ch == Keys::FlowSeqEnd ? Token::FLOW_SEQ_END : Token::FLOW_MAP_END); + + // check that it matches the start + FLOW_MARKER flowType = (ch == Keys::FlowSeqEnd ? FLOW_SEQ : FLOW_MAP); + if(m_flows.top() != flowType) + throw ParserException(mark, ErrorMsg::FLOW_END); + m_flows.pop(); + + Token::TYPE type = (flowType ? Token::FLOW_SEQ_END : Token::FLOW_MAP_END); m_tokens.push(Token(type, mark)); } // FlowEntry void Scanner::ScanFlowEntry() { + // we might have a solo entry in the flow context + if(VerifySimpleKey()) + m_tokens.push(Token(Token::VALUE, INPUT.mark())); + m_simpleKeyAllowed = true; // eat @@ -129,7 +143,7 @@ namespace YAML void Scanner::ScanBlockEntry() { // we better be in the block context! - if(m_flowLevel > 0) + if(InFlowContext()) throw ParserException(INPUT.mark(), ErrorMsg::BLOCK_ENTRY); // can we put it here? @@ -149,7 +163,7 @@ namespace YAML void Scanner::ScanKey() { // handle keys diffently in the block context (and manage indents) - if(m_flowLevel == 0) { + if(InBlockContext()) { if(!m_simpleKeyAllowed) throw ParserException(INPUT.mark(), ErrorMsg::MAP_KEY); @@ -157,10 +171,7 @@ namespace YAML } // can only put a simple key here if we're in block context - if(m_flowLevel == 0) - m_simpleKeyAllowed = true; - else - m_simpleKeyAllowed = false; + m_simpleKeyAllowed = InBlockContext(); // eat Mark mark = INPUT.mark(); @@ -182,7 +193,7 @@ namespace YAML m_simpleKeyAllowed = false; } else { // handle values diffently in the block context (and manage indents) - if(m_flowLevel == 0) { + if(InBlockContext()) { if(!m_simpleKeyAllowed) throw ParserException(INPUT.mark(), ErrorMsg::MAP_VALUE); @@ -190,7 +201,7 @@ namespace YAML } // can only put a simple key here if we're in block context - m_simpleKeyAllowed = (m_flowLevel == 0); + m_simpleKeyAllowed = InBlockContext(); } // eat @@ -206,8 +217,7 @@ namespace YAML std::string name; // insert a potential simple key - if(m_simpleKeyAllowed) - InsertPotentialSimpleKey(); + InsertPotentialSimpleKey(); m_simpleKeyAllowed = false; // eat the indicator @@ -239,8 +249,7 @@ namespace YAML std::string handle, suffix; // insert a potential simple key - if(m_simpleKeyAllowed) - InsertPotentialSimpleKey(); + InsertPotentialSimpleKey(); m_simpleKeyAllowed = false; // eat the indicator @@ -278,9 +287,9 @@ namespace YAML // set up the scanning parameters ScanScalarParams params; - params.end = (m_flowLevel > 0 ? Exp::EndScalarInFlow : Exp::EndScalar) || (Exp::BlankOrBreak + Exp::Comment); + params.end = (InFlowContext() ? Exp::EndScalarInFlow : Exp::EndScalar) || (Exp::BlankOrBreak + Exp::Comment); params.eatEnd = false; - params.indent = (m_flowLevel > 0 ? 0 : GetTopIndent() + 1); + params.indent = (InFlowContext() ? 0 : GetTopIndent() + 1); params.fold = true; params.eatLeadingWhitespace = true; params.trimTrailingSpaces = true; @@ -289,8 +298,7 @@ namespace YAML params.onTabInIndentation = THROW; // insert a potential simple key - if(m_simpleKeyAllowed) - InsertPotentialSimpleKey(); + InsertPotentialSimpleKey(); Mark mark = INPUT.mark(); scalar = ScanScalar(INPUT, params); @@ -329,8 +337,7 @@ namespace YAML params.onDocIndicator = THROW; // insert a potential simple key - if(m_simpleKeyAllowed) - InsertPotentialSimpleKey(); + InsertPotentialSimpleKey(); Mark mark = INPUT.mark(); diff --git a/src/simplekey.cpp b/src/simplekey.cpp index c8cd2bf..e16dff9 100644 --- a/src/simplekey.cpp +++ b/src/simplekey.cpp @@ -31,6 +31,18 @@ namespace YAML if(pKey) pKey->status = Token::INVALID; } + + // CanInsertPotentialSimpleKey + bool Scanner::CanInsertPotentialSimpleKey() const + { + if(!m_simpleKeyAllowed) + return false; + + if(InFlowContext() && m_flows.top() != FLOW_MAP) + return false; + + return !ExistsActiveSimpleKey(); + } // ExistsActiveSimpleKey // . Returns true if there's a potential simple key at our flow level @@ -41,7 +53,7 @@ namespace YAML return false; const SimpleKey& key = m_simpleKeys.top(); - return key.flowLevel == m_flowLevel; + return key.flowLevel == GetFlowLevel(); } // InsertPotentialSimpleKey @@ -49,10 +61,10 @@ namespace YAML // and save it on a stack. void Scanner::InsertPotentialSimpleKey() { - if(ExistsActiveSimpleKey()) + if(!CanInsertPotentialSimpleKey()) return; - SimpleKey key(INPUT.mark(), m_flowLevel); + SimpleKey key(INPUT.mark(), GetFlowLevel()); // first add a map start, if necessary key.pIndent = PushIndentTo(INPUT.column(), IndentMarker::MAP); @@ -79,7 +91,7 @@ namespace YAML // grab top key SimpleKey& key = m_simpleKeys.top(); - if(key.flowLevel != m_flowLevel) + if(key.flowLevel != GetFlowLevel()) return; key.Invalidate(); @@ -91,28 +103,21 @@ namespace YAML // and if so, makes it valid. bool Scanner::VerifySimpleKey() { - m_isLastKeyValid = false; if(m_simpleKeys.empty()) - return m_isLastKeyValid; + return false; // grab top key SimpleKey key = m_simpleKeys.top(); // only validate if we're in the correct flow level - if(key.flowLevel != m_flowLevel) + if(key.flowLevel != GetFlowLevel()) return false; m_simpleKeys.pop(); bool isValid = true; - // needs to be followed immediately by a value - if(m_flowLevel > 0 && !Exp::ValueInFlow.Matches(INPUT)) - isValid = false; - if(m_flowLevel == 0 && !Exp::Value.Matches(INPUT)) - isValid = false; - - // also needs to be less than 1024 characters and inline + // needs to be less than 1024 characters and inline if(INPUT.line() != key.mark.line || INPUT.pos() - key.mark.pos > 1024) isValid = false; @@ -122,7 +127,6 @@ namespace YAML else key.Invalidate(); - m_isLastKeyValid = isValid; return isValid; } diff --git a/yaml-reader/parsertests.cpp b/yaml-reader/parsertests.cpp index aee6355..6aa524f 100644 --- a/yaml-reader/parsertests.cpp +++ b/yaml-reader/parsertests.cpp @@ -235,7 +235,7 @@ namespace Test return true; } - + bool FlowMapWithOmittedValue() { std::string input = "{a: b, c:, d:}"; @@ -256,6 +256,45 @@ namespace Test return true; } + bool FlowMapWithSoloEntry() + { + std::string input = "{a: b, c, d: e}"; + std::stringstream stream(input); + YAML::Parser parser(stream); + YAML::Node doc; + parser.GetNextDocument(doc); + + std::string output; + doc["a"] >> output; + if(output != "b") + return false; + if(!IsNull(doc["c"])) + return false; + doc["d"] >> output; + if(output != "e") + return false; + + return true; + } + + bool FlowMapEndingWithSoloEntry() + { + std::string input = "{a: b, c}"; + std::stringstream stream(input); + YAML::Parser parser(stream); + YAML::Node doc; + parser.GetNextDocument(doc); + + std::string output; + doc["a"] >> output; + if(output != "b") + return false; + if(!IsNull(doc["c"])) + return false; + + return true; + } + bool QuotedSimpleKeys() { std::string KeyValue[3] = { "\"double\": double\n", "'single': single\n", "plain: plain\n" }; diff --git a/yaml-reader/tests.cpp b/yaml-reader/tests.cpp index d727108..9191a2c 100644 --- a/yaml-reader/tests.cpp +++ b/yaml-reader/tests.cpp @@ -268,6 +268,8 @@ namespace Test RunParserTest(&Parser::FlowMap, "flow map", passed); RunParserTest(&Parser::FlowMapWithOmittedKey, "flow map with omitted key", passed); RunParserTest(&Parser::FlowMapWithOmittedValue, "flow map with omitted value", passed); + RunParserTest(&Parser::FlowMapWithSoloEntry, "flow map with solo entry", passed); + RunParserTest(&Parser::FlowMapEndingWithSoloEntry, "flow map ending with solo entry", passed); RunParserTest(&Parser::QuotedSimpleKeys, "quoted simple keys", passed); RunParserTest(&Parser::CompressedMapAndSeq, "compressed map and seq", passed); RunParserTest(&Parser::NullBlockSeqEntry, "null block seq entry", passed); diff --git a/yaml-reader/tests.h b/yaml-reader/tests.h index 80daf18..bcdc33e 100644 --- a/yaml-reader/tests.h +++ b/yaml-reader/tests.h @@ -35,6 +35,8 @@ namespace Test { bool FlowMap(); bool FlowMapWithOmittedKey(); bool FlowMapWithOmittedValue(); + bool FlowMapWithSoloEntry(); + bool FlowMapEndingWithSoloEntry(); bool QuotedSimpleKeys(); bool CompressedMapAndSeq(); bool NullBlockSeqEntry();