| #include <sstream> | |
| #include "exp.h" | |
| #include "regex_yaml.h" | |
| #include "regeximpl.h" | |
| #include "scanner.h" | |
| #include "scanscalar.h" | |
| #include "scantag.h" // IWYU pragma: keep | |
| #include "tag.h" // IWYU pragma: keep | |
| #include "token.h" | |
| #include "yaml-cpp/exceptions.h" // IWYU pragma: keep | |
| #include "yaml-cpp/mark.h" | |
| namespace YAML { | |
| /////////////////////////////////////////////////////////////////////// | |
| // Specialization for scanning specific tokens | |
| // Directive | |
| // . Note: no semantic checking is done here (that's for the parser to do) | |
| void Scanner::ScanDirective() { | |
| std::string name; | |
| std::vector<std::string> params; | |
| // pop indents and simple keys | |
| PopAllIndents(); | |
| PopAllSimpleKeys(); | |
| m_simpleKeyAllowed = false; | |
| m_canBeJSONFlow = false; | |
| // store pos and eat indicator | |
| Token token(Token::DIRECTIVE, INPUT.mark()); | |
| INPUT.eat(1); | |
| // read name | |
| while (INPUT && !Exp::BlankOrBreak().Matches(INPUT)) | |
| token.value += INPUT.get(); | |
| // read parameters | |
| while (true) { | |
| // first get rid of whitespace | |
| while (Exp::Blank().Matches(INPUT)) | |
| INPUT.eat(1); | |
| // break on newline or comment | |
| if (!INPUT || Exp::Break().Matches(INPUT) || Exp::Comment().Matches(INPUT)) | |
| break; | |
| // now read parameter | |
| std::string param; | |
| while (INPUT && !Exp::BlankOrBreak().Matches(INPUT)) | |
| param += INPUT.get(); | |
| token.params.push_back(param); | |
| } | |
| m_tokens.push(token); | |
| } | |
| // DocStart | |
| void Scanner::ScanDocStart() { | |
| PopAllIndents(); | |
| PopAllSimpleKeys(); | |
| m_simpleKeyAllowed = false; | |
| m_canBeJSONFlow = false; | |
| // eat | |
| Mark mark = INPUT.mark(); | |
| INPUT.eat(3); | |
| m_tokens.push(Token(Token::DOC_START, mark)); | |
| } | |
| // DocEnd | |
| void Scanner::ScanDocEnd() { | |
| PopAllIndents(); | |
| PopAllSimpleKeys(); | |
| m_simpleKeyAllowed = false; | |
| m_canBeJSONFlow = false; | |
| // eat | |
| Mark mark = INPUT.mark(); | |
| INPUT.eat(3); | |
| m_tokens.push(Token(Token::DOC_END, mark)); | |
| } | |
| // FlowStart | |
| void Scanner::ScanFlowStart() { | |
| // flows can be simple keys | |
| InsertPotentialSimpleKey(); | |
| m_simpleKeyAllowed = true; | |
| m_canBeJSONFlow = false; | |
| // eat | |
| Mark mark = INPUT.mark(); | |
| char ch = INPUT.get(); | |
| 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 (InBlockContext()) | |
| throw ParserException(INPUT.mark(), ErrorMsg::FLOW_END); | |
| // we might have a solo entry in the flow context | |
| if (InFlowContext()) { | |
| if (m_flows.top() == FLOW_MAP && VerifySimpleKey()) | |
| m_tokens.push(Token(Token::VALUE, INPUT.mark())); | |
| else if (m_flows.top() == FLOW_SEQ) | |
| InvalidateSimpleKey(); | |
| } | |
| m_simpleKeyAllowed = false; | |
| m_canBeJSONFlow = true; | |
| // eat | |
| Mark mark = INPUT.mark(); | |
| char ch = INPUT.get(); | |
| // 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 (InFlowContext()) { | |
| if (m_flows.top() == FLOW_MAP && VerifySimpleKey()) | |
| m_tokens.push(Token(Token::VALUE, INPUT.mark())); | |
| else if (m_flows.top() == FLOW_SEQ) | |
| InvalidateSimpleKey(); | |
| } | |
| m_simpleKeyAllowed = true; | |
| m_canBeJSONFlow = false; | |
| // eat | |
| Mark mark = INPUT.mark(); | |
| INPUT.eat(1); | |
| m_tokens.push(Token(Token::FLOW_ENTRY, mark)); | |
| } | |
| // BlockEntry | |
| void Scanner::ScanBlockEntry() { | |
| // we better be in the block context! | |
| if (InFlowContext()) | |
| throw ParserException(INPUT.mark(), ErrorMsg::BLOCK_ENTRY); | |
| // can we put it here? | |
| if (!m_simpleKeyAllowed) | |
| throw ParserException(INPUT.mark(), ErrorMsg::BLOCK_ENTRY); | |
| PushIndentTo(INPUT.column(), IndentMarker::SEQ); | |
| m_simpleKeyAllowed = true; | |
| m_canBeJSONFlow = false; | |
| // eat | |
| Mark mark = INPUT.mark(); | |
| INPUT.eat(1); | |
| m_tokens.push(Token(Token::BLOCK_ENTRY, mark)); | |
| } | |
| // Key | |
| void Scanner::ScanKey() { | |
| // handle keys differently in the block context (and manage indents) | |
| if (InBlockContext()) { | |
| if (!m_simpleKeyAllowed) | |
| throw ParserException(INPUT.mark(), ErrorMsg::MAP_KEY); | |
| PushIndentTo(INPUT.column(), IndentMarker::MAP); | |
| } | |
| // can only put a simple key here if we're in block context | |
| m_simpleKeyAllowed = InBlockContext(); | |
| // eat | |
| Mark mark = INPUT.mark(); | |
| INPUT.eat(1); | |
| m_tokens.push(Token(Token::KEY, mark)); | |
| } | |
| // Value | |
| void Scanner::ScanValue() { | |
| // and check that simple key | |
| bool isSimpleKey = VerifySimpleKey(); | |
| m_canBeJSONFlow = false; | |
| if (isSimpleKey) { | |
| // can't follow a simple key with another simple key (dunno why, though - it | |
| // seems fine) | |
| m_simpleKeyAllowed = false; | |
| } else { | |
| // handle values differently in the block context (and manage indents) | |
| if (InBlockContext()) { | |
| if (!m_simpleKeyAllowed) | |
| throw ParserException(INPUT.mark(), ErrorMsg::MAP_VALUE); | |
| PushIndentTo(INPUT.column(), IndentMarker::MAP); | |
| } | |
| // can only put a simple key here if we're in block context | |
| m_simpleKeyAllowed = InBlockContext(); | |
| } | |
| // we are parsing a `key: value` pair; scalars are always allowed | |
| m_scalarValueAllowed = true; | |
| // eat | |
| Mark mark = INPUT.mark(); | |
| INPUT.eat(1); | |
| m_tokens.push(Token(Token::VALUE, mark)); | |
| } | |
| // AnchorOrAlias | |
| void Scanner::ScanAnchorOrAlias() { | |
| bool alias; | |
| std::string name; | |
| // insert a potential simple key | |
| InsertPotentialSimpleKey(); | |
| m_simpleKeyAllowed = false; | |
| m_canBeJSONFlow = false; | |
| // eat the indicator | |
| Mark mark = INPUT.mark(); | |
| char indicator = INPUT.get(); | |
| alias = (indicator == Keys::Alias); | |
| // now eat the content | |
| while (INPUT && Exp::Anchor().Matches(INPUT)) | |
| name += INPUT.get(); | |
| // we need to have read SOMETHING! | |
| if (name.empty()) | |
| throw ParserException(INPUT.mark(), alias ? ErrorMsg::ALIAS_NOT_FOUND | |
| : ErrorMsg::ANCHOR_NOT_FOUND); | |
| // and needs to end correctly | |
| if (INPUT && !Exp::AnchorEnd().Matches(INPUT)) | |
| throw ParserException(INPUT.mark(), alias ? ErrorMsg::CHAR_IN_ALIAS | |
| : ErrorMsg::CHAR_IN_ANCHOR); | |
| // and we're done | |
| Token token(alias ? Token::ALIAS : Token::ANCHOR, mark); | |
| token.value = name; | |
| m_tokens.push(token); | |
| } | |
| // Tag | |
| void Scanner::ScanTag() { | |
| // insert a potential simple key | |
| InsertPotentialSimpleKey(); | |
| m_simpleKeyAllowed = false; | |
| m_canBeJSONFlow = false; | |
| Token token(Token::TAG, INPUT.mark()); | |
| // eat the indicator | |
| INPUT.get(); | |
| if (INPUT && INPUT.peek() == Keys::VerbatimTagStart) { | |
| std::string tag = ScanVerbatimTag(INPUT); | |
| token.value = tag; | |
| token.data = Tag::VERBATIM; | |
| } else { | |
| bool canBeHandle; | |
| token.value = ScanTagHandle(INPUT, canBeHandle); | |
| if (!canBeHandle && token.value.empty()) | |
| token.data = Tag::NON_SPECIFIC; | |
| else if (token.value.empty()) | |
| token.data = Tag::SECONDARY_HANDLE; | |
| else | |
| token.data = 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; | |
| } | |
| } | |
| m_tokens.push(token); | |
| } | |
| // PlainScalar | |
| void Scanner::ScanPlainScalar() { | |
| std::string scalar; | |
| // set up the scanning parameters | |
| ScanScalarParams params; | |
| params.end = | |
| (InFlowContext() ? &Exp::ScanScalarEndInFlow() : &Exp::ScanScalarEnd()); | |
| params.eatEnd = false; | |
| params.indent = (InFlowContext() ? 0 : GetTopIndent() + 1); | |
| params.fold = FOLD_FLOW; | |
| params.eatLeadingWhitespace = true; | |
| params.trimTrailingSpaces = true; | |
| params.chomp = STRIP; | |
| params.onDocIndicator = BREAK; | |
| params.onTabInIndentation = THROW; | |
| // insert a potential simple key | |
| InsertPotentialSimpleKey(); | |
| Mark mark = INPUT.mark(); | |
| scalar = ScanScalar(INPUT, params); | |
| // can have a simple key only if we ended the scalar by starting a new line | |
| m_simpleKeyAllowed = params.leadingSpaces; | |
| m_canBeJSONFlow = false; | |
| // finally, check and see if we ended on an illegal character | |
| // if(Exp::IllegalCharInScalar.Matches(INPUT)) | |
| // throw ParserException(INPUT.mark(), ErrorMsg::CHAR_IN_SCALAR); | |
| Token token(Token::PLAIN_SCALAR, mark); | |
| token.value = scalar; | |
| m_tokens.push(token); | |
| } | |
| // QuotedScalar | |
| void Scanner::ScanQuotedScalar() { | |
| std::string scalar; | |
| // peek at single or double quote (don't eat because we need to preserve (for | |
| // the time being) the input position) | |
| char quote = INPUT.peek(); | |
| bool single = (quote == '\''); | |
| // setup the scanning parameters | |
| ScanScalarParams params; | |
| RegEx end = (single ? RegEx(quote) & !Exp::EscSingleQuote() : RegEx(quote)); | |
| params.end = &end; | |
| params.eatEnd = true; | |
| params.escape = (single ? '\'' : '\\'); | |
| params.indent = 0; | |
| params.fold = FOLD_FLOW; | |
| params.eatLeadingWhitespace = true; | |
| params.trimTrailingSpaces = false; | |
| params.chomp = CLIP; | |
| params.onDocIndicator = THROW; | |
| // insert a potential simple key | |
| InsertPotentialSimpleKey(); | |
| Mark mark = INPUT.mark(); | |
| // now eat that opening quote | |
| INPUT.get(); | |
| // and scan | |
| scalar = ScanScalar(INPUT, params); | |
| m_simpleKeyAllowed = false; | |
| // we just scanned a quoted scalar; | |
| // we can only have another scalar in this line | |
| // if we are in a flow, eg: `[ "foo", "bar" ]` is ok, but `"foo", "bar"` isn't. | |
| m_scalarValueAllowed = InFlowContext(); | |
| m_canBeJSONFlow = true; | |
| Token token(Token::NON_PLAIN_SCALAR, mark); | |
| token.value = scalar; | |
| m_tokens.push(token); | |
| } | |
| // BlockScalarToken | |
| // . These need a little extra processing beforehand. | |
| // . We need to scan the line where the indicator is (this doesn't count as part | |
| // of the scalar), | |
| // and then we need to figure out what level of indentation we'll be using. | |
| void Scanner::ScanBlockScalar() { | |
| std::string scalar; | |
| ScanScalarParams params; | |
| params.indent = 1; | |
| params.detectIndent = true; | |
| // eat block indicator ('|' or '>') | |
| Mark mark = INPUT.mark(); | |
| char indicator = INPUT.get(); | |
| params.fold = (indicator == Keys::FoldedScalar ? FOLD_BLOCK : DONT_FOLD); | |
| // eat chomping/indentation indicators | |
| params.chomp = CLIP; | |
| int n = Exp::Chomp().Match(INPUT); | |
| for (int i = 0; i < n; i++) { | |
| char ch = INPUT.get(); | |
| if (ch == '+') | |
| params.chomp = KEEP; | |
| else if (ch == '-') | |
| params.chomp = STRIP; | |
| else if (Exp::Digit().Matches(ch)) { | |
| if (ch == '0') | |
| throw ParserException(INPUT.mark(), ErrorMsg::ZERO_INDENT_IN_BLOCK); | |
| params.indent = ch - '0'; | |
| params.detectIndent = false; | |
| } | |
| } | |
| // now eat whitespace | |
| while (Exp::Blank().Matches(INPUT)) | |
| INPUT.eat(1); | |
| // and comments to the end of the line | |
| if (Exp::Comment().Matches(INPUT)) | |
| while (INPUT && !Exp::Break().Matches(INPUT)) | |
| INPUT.eat(1); | |
| // if it's not a line break, then we ran into a bad character inline | |
| if (INPUT && !Exp::Break().Matches(INPUT)) | |
| throw ParserException(INPUT.mark(), ErrorMsg::CHAR_IN_BLOCK); | |
| // set the initial indentation | |
| if (GetTopIndent() >= 0) | |
| params.indent += GetTopIndent(); | |
| params.eatLeadingWhitespace = false; | |
| params.trimTrailingSpaces = false; | |
| params.onTabInIndentation = THROW; | |
| scalar = ScanScalar(INPUT, params); | |
| // simple keys always ok after block scalars (since we're gonna start a new | |
| // line anyways) | |
| m_simpleKeyAllowed = true; | |
| m_canBeJSONFlow = false; | |
| Token token(Token::NON_PLAIN_SCALAR, mark); | |
| token.value = scalar; | |
| m_tokens.push(token); | |
| } | |
| } // namespace YAML |