On 11/07/2021 21:10, Mark Wielaard wrote: > Remove (unused) comment related tokens and replace them with > INNER_DOC_COMMENT and OUTER_DOC_COMMENT tokens, which keep the comment > text as a string. These can be constructed with the new > make_inner_doc_comment and make_outer_doc_comment methods. > > Make sure to not confuse doc strings with normal comments in the lexer > when detecting shebang lines. Both single line //! and /*! */ blocks > are turned into INNER_DOC_COMMENT tokens. And both single line /// and > /** */ blocks are turned into OUTER_DOC_COMMENT tokens. > > Also fixes some issues with cr/lf line endings and keeping the line > map correct when seeing \n in a comment. > > In the parser handle INNER_DOC_COMMENT and OUTER_DOC_COMMENTS where > inner (#[]) and outer (#![]) attributes are handled. Add a method > parse_doc_comment which turns the tokens into an "doc" Attribute with > the string as literal expression. > > Add get_locus method to Attribute class for better error reporting. > > Tests are added for correctly placed and formatted doc strings, with > or without cr/lf line endings. Incorrect formatted (isolated CRs) doc > strings and badly placed inner doc strings. No tests add handling of > the actual doc attributes yet. These could be tested once we add > support for the #![warn(missing_docs)] attribute. > --- > gcc/rust/ast/rust-ast.h | 2 + > gcc/rust/lex/rust-lex.cc | 214 ++++++++++++++++-- > gcc/rust/lex/rust-token.h | 25 +- > gcc/rust/parse/rust-parse-impl.h | 60 ++++- > gcc/rust/parse/rust-parse.h | 1 + > gcc/testsuite/rust/compile/bad_inner_doc.rs | 15 ++ > .../compile/doc_isolated_cr_block_comment.rs | 3 + > .../doc_isolated_cr_inner_block_comment.rs | 5 + > .../doc_isolated_cr_inner_line_comment.rs | 5 + > .../compile/doc_isolated_cr_line_comment.rs | 3 + > .../torture/all_doc_comment_line_blocks.rs | 47 ++++ > .../all_doc_comment_line_blocks_crlf.rs | 47 ++++ > .../torture/isolated_cr_block_comment.rs | 2 + > .../torture/isolated_cr_line_comment.rs | 2 + > 14 files changed, 401 insertions(+), 30 deletions(-) > create mode 100644 gcc/testsuite/rust/compile/bad_inner_doc.rs > create mode 100644 gcc/testsuite/rust/compile/doc_isolated_cr_block_comment.rs > create mode 100644 gcc/testsuite/rust/compile/doc_isolated_cr_inner_block_comment.rs > create mode 100644 gcc/testsuite/rust/compile/doc_isolated_cr_inner_line_comment.rs > create mode 100644 gcc/testsuite/rust/compile/doc_isolated_cr_line_comment.rs > create mode 100644 gcc/testsuite/rust/compile/torture/all_doc_comment_line_blocks.rs > create mode 100644 gcc/testsuite/rust/compile/torture/all_doc_comment_line_blocks_crlf.rs > create mode 100644 gcc/testsuite/rust/compile/torture/isolated_cr_block_comment.rs > create mode 100644 gcc/testsuite/rust/compile/torture/isolated_cr_line_comment.rs > > diff --git a/gcc/rust/ast/rust-ast.h b/gcc/rust/ast/rust-ast.h > index 75b08f8aa66..3e3e185b9b5 100644 > --- a/gcc/rust/ast/rust-ast.h > +++ b/gcc/rust/ast/rust-ast.h > @@ -455,6 +455,8 @@ public: > // Returns whether the attribute is considered an "empty" attribute. > bool is_empty () const { return attr_input == nullptr && path.is_empty (); } > > + Location get_locus () const { return locus; } > + > /* e.g.: > #![crate_type = "lib"] > #[test] > diff --git a/gcc/rust/lex/rust-lex.cc b/gcc/rust/lex/rust-lex.cc > index 617dd69a080..0b8a8eae651 100644 > --- a/gcc/rust/lex/rust-lex.cc > +++ b/gcc/rust/lex/rust-lex.cc > @@ -265,9 +265,16 @@ Lexer::build_token () > int next_char = peek_input (n); > if (is_whitespace (next_char)) > n++; > - else if (next_char == '/' && peek_input (n + 1) == '/') > + else if ((next_char == '/' && peek_input (n + 1) == '/' > + && peek_input (n + 2) != '!' > + && peek_input (n + 2) != '/') > + || (next_char == '/' && peek_input (n + 1) == '/' > + && peek_input (n + 2) == '/' > + && peek_input (n + 3) == '/')) > { > + // two // or four //// > // A single line comment > + // (but not an inner or outer doc comment) > n += 2; > next_char = peek_input (n); > while (next_char != '\n' && next_char != EOF) > @@ -278,9 +285,30 @@ Lexer::build_token () > if (next_char == '\n') > n++; > } > - else if (next_char == '/' && peek_input (n + 1) == '*') > + else if (next_char == '/' && peek_input (n + 1) == '*' > + && peek_input (n + 2) == '*' > + && peek_input (n + 3) == '/') > { > + /**/ > + n += 4; > + } > + else if (next_char == '/' && peek_input (n + 1) == '*' > + && peek_input (n + 2) == '*' && peek_input (n + 3) == '*' > + && peek_input (n + 4) == '/') > + { > + /***/ > + n += 5; > + } > + else if ((next_char == '/' && peek_input (n + 1) == '*' > + && peek_input (n + 2) != '*' > + && peek_input (n + 2) != '!') > + || (next_char == '/' && peek_input (n + 1) == '*' > + && peek_input (n + 2) == '*' > + && peek_input (n + 3) == '*')) > + { > + // one /* or three /*** > // Start of a block comment > + // (but not an inner or outer doc comment) > n += 2; > int level = 1; > while (level > 0) > @@ -339,6 +367,9 @@ Lexer::build_token () > // tell line_table that new line starts > line_map->start_line (current_line, max_column_hint); > continue; > + case '\r': // cr > + // Ignore, we expect a newline (lf) soon. > + continue; > case ' ': // space > current_column++; > continue; > @@ -445,11 +476,14 @@ Lexer::build_token () > > return Token::make (DIV_EQ, loc); > } > - else if (peek_input () == '/') > + else if ((peek_input () == '/' && peek_input (1) != '!' > + && peek_input (1) != '/') > + || (peek_input () == '/' && peek_input (1) == '/' > + && peek_input (2) == '/')) > { > - // TODO: single-line doc comments > - > + // two // or four //// > // single line comment > + // (but not an inner or outer doc comment) > skip_input (); > current_column += 2; > > @@ -461,23 +495,85 @@ Lexer::build_token () > current_char = peek_input (); > } > continue; > - break; > } > - else if (peek_input () == '*') > + else if (peek_input () == '/' > + && (peek_input (1) == '!' || peek_input (1) == '/')) > { > + /* single line doc comment, inner or outer. */ > + bool is_inner = peek_input (1) == '!'; > + skip_input (1); > + current_column += 3; > + > + std::string str; > + str.reserve (32); > + current_char = peek_input (); > + while (current_char != '\n') > + { > + skip_input (); > + if (current_char == '\r') > + { > + char next_char = peek_input (); > + if (next_char == '\n') > + { > + current_char = '\n'; > + break; > + } > + rust_error_at ( > + loc, "Isolated CR %<\\r%> not allowed in doc comment"); > + current_char = next_char; > + continue; > + } > + if (current_char == EOF) > + { > + rust_error_at ( > + loc, "unexpected EOF while looking for end of comment"); > + break; > + } > + str += current_char; > + current_char = peek_input (); > + } > + skip_input (); > + current_line++; > + current_column = 1; > + // tell line_table that new line starts > + line_map->start_line (current_line, max_column_hint); > + > + str.shrink_to_fit (); > + if (is_inner) > + return Token::make_inner_doc_comment (loc, std::move (str)); > + else > + return Token::make_outer_doc_comment (loc, std::move (str)); > + } > + else if (peek_input () == '*' && peek_input (1) == '*' > + && peek_input (2) == '/') > + { > + /**/ > + skip_input (2); > + current_column += 4; > + continue; > + } > + else if (peek_input () == '*' && peek_input (1) == '*' > + && peek_input (2) == '*' && peek_input (3) == '/') > + { > + /***/ > + skip_input (3); > + current_column += 5; > + continue; > + } > + else if ((peek_input () == '*' && peek_input (1) != '!' > + && peek_input (1) != '*') > + || (peek_input () == '*' && peek_input (1) == '*' > + && peek_input (2) == '*')) > + { > + // one /* or three /*** > // block comment > + // (but not an inner or outer doc comment) > skip_input (); > current_column += 2; > > - // TODO: block doc comments > - > - current_char = peek_input (); > - > int level = 1; > while (level > 0) > { > - skip_input (); > - current_column++; // for error-handling > current_char = peek_input (); > > if (current_char == EOF) > @@ -496,6 +592,7 @@ Lexer::build_token () > current_column += 2; > > level += 1; > + continue; > } > > // ignore until */ is found > @@ -505,16 +602,101 @@ Lexer::build_token () > skip_input (1); > > current_column += 2; > - // should only break inner loop here - seems to do so > - // break; > > level -= 1; > + continue; > } > + > + if (current_char == '\n') > + { > + skip_input (); > + current_line++; > + current_column = 1; > + // tell line_table that new line starts > + line_map->start_line (current_line, max_column_hint); > + continue; > + } > + > + skip_input (); > + current_column++; > } > > // refresh new token > continue; > - break; > + } > + else if (peek_input () == '*' > + && (peek_input (1) == '!' || peek_input (1) == '*')) > + { > + // block doc comment, inner /*! or outer /** > + bool is_inner = peek_input (1) == '!'; > + skip_input (1); > + current_column += 3; > + > + std::string str; > + str.reserve (96); > + > + int level = 1; > + while (level > 0) > + { > + current_char = peek_input (); > + > + if (current_char == EOF) > + { > + rust_error_at ( > + loc, "unexpected EOF while looking for end of comment"); > + break; > + } > + > + // if /* found > + if (current_char == '/' && peek_input (1) == '*') > + { > + // skip /* characters > + skip_input (1); > + current_column += 2; > + > + level += 1; > + str += "/*"; > + continue; > + } > + > + // ignore until */ is found > + if (current_char == '*' && peek_input (1) == '/') > + { > + // skip */ characters > + skip_input (1); > + current_column += 2; > + > + level -= 1; > + if (level > 0) > + str += "*/"; > + continue; > + } > + > + if (current_char == '\r' && peek_input (1) != '\n') > + rust_error_at ( > + loc, "Isolated CR %<\\r%> not allowed in doc comment"); > + > + if (current_char == '\n') > + { > + skip_input (); > + current_line++; > + current_column = 1; > + // tell line_table that new line starts > + line_map->start_line (current_line, max_column_hint); > + str += '\n'; > + continue; > + } > + > + str += current_char; > + skip_input (); > + current_column++; > + } > + > + str.shrink_to_fit (); > + if (is_inner) > + return Token::make_inner_doc_comment (loc, std::move (str)); > + else > + return Token::make_outer_doc_comment (loc, std::move (str)); > } > else > { > diff --git a/gcc/rust/lex/rust-token.h b/gcc/rust/lex/rust-token.h > index 771910119b7..1c397c839fd 100644 > --- a/gcc/rust/lex/rust-token.h > +++ b/gcc/rust/lex/rust-token.h > @@ -151,15 +151,10 @@ enum PrimitiveCoreType > RS_TOKEN (RIGHT_SQUARE, "]") \ > /* Macros */ \ > RS_TOKEN (DOLLAR_SIGN, "$") \ > - /* Comments */ \ > - RS_TOKEN (LINE_COMMENT, "//") \ > - RS_TOKEN (INNER_LINE_DOC, "//!") \ > - RS_TOKEN (OUTER_LINE_DOC, "///") \ > - RS_TOKEN (BLOCK_COMMENT_START, "/*") \ > - RS_TOKEN (BLOCK_COMMENT_END, "*/") \ > - RS_TOKEN (INNER_BLOCK_DOC_START, "/*!") \ > - RS_TOKEN (OUTER_BLOCK_DOC_START, \ > - "/**") /* have "weak" union and 'static keywords? */ \ > + /* Doc Comments */ \ > + RS_TOKEN (INNER_DOC_COMMENT, "#![doc]") \ > + RS_TOKEN (OUTER_DOC_COMMENT, "#[doc]") \ > + /* have "weak" union and 'static keywords? */ \ > \ > RS_TOKEN_KEYWORD (ABSTRACT, "abstract") /* unused */ \ > RS_TOKEN_KEYWORD (AS, "as") \ > @@ -368,6 +363,18 @@ public: > return TokenPtr (new Token (BYTE_STRING_LITERAL, locus, std::move (str))); > } > > + // Makes and returns a new TokenPtr of type INNER_DOC_COMMENT. > + static TokenPtr make_inner_doc_comment (Location locus, std::string &&str) > + { > + return TokenPtr (new Token (INNER_DOC_COMMENT, locus, std::move (str))); > + } > + > + // Makes and returns a new TokenPtr of type OUTER_DOC_COMMENT. > + static TokenPtr make_outer_doc_comment (Location locus, std::string &&str) > + { > + return TokenPtr (new Token (OUTER_DOC_COMMENT, locus, std::move (str))); > + } > + > // Makes and returns a new TokenPtr of type LIFETIME. > static TokenPtr make_lifetime (Location locus, std::string &&str) > { > diff --git a/gcc/rust/parse/rust-parse-impl.h b/gcc/rust/parse/rust-parse-impl.h > index a8597fa401e..eedc76db43e 100644 > --- a/gcc/rust/parse/rust-parse-impl.h > +++ b/gcc/rust/parse/rust-parse-impl.h > @@ -434,8 +434,9 @@ Parser::parse_inner_attributes () > AST::AttrVec inner_attributes; > > // only try to parse it if it starts with "#!" not only "#" > - while (lexer.peek_token ()->get_id () == HASH > - && lexer.peek_token (1)->get_id () == EXCLAM) > + while ((lexer.peek_token ()->get_id () == HASH > + && lexer.peek_token (1)->get_id () == EXCLAM) > + || lexer.peek_token ()->get_id () == INNER_DOC_COMMENT) > { > AST::Attribute inner_attr = parse_inner_attribute (); > > @@ -457,11 +458,33 @@ Parser::parse_inner_attributes () > return inner_attributes; > } > > +// Parse a inner or outer doc comment into an doc attribute > +template > +AST::Attribute > +Parser::parse_doc_comment () > +{ > + const_TokenPtr token = lexer.peek_token (); > + Location locus = token->get_locus (); > + AST::SimplePathSegment segment ("doc", locus); > + std::vector segments; > + segments.push_back (std::move (segment)); > + AST::SimplePath attr_path (std::move (segments), false, locus); > + AST::LiteralExpr lit_expr (token->get_str (), AST::Literal::STRING, > + PrimitiveCoreType::CORETYPE_STR, {}, locus); > + std::unique_ptr attr_input ( > + new AST::AttrInputLiteral (std::move (lit_expr))); > + lexer.skip_token (); > + return AST::Attribute (std::move (attr_path), std::move (attr_input), locus); > +} > + > // Parse a single inner attribute. > template > AST::Attribute > Parser::parse_inner_attribute () > { > + if (lexer.peek_token ()->get_id () == INNER_DOC_COMMENT) > + return parse_doc_comment (); > + > if (lexer.peek_token ()->get_id () != HASH) > { > Error error (lexer.peek_token ()->get_locus (), > @@ -1019,7 +1042,15 @@ Parser::parse_item (bool called_from_statement) > switch (t->get_id ()) > { > case END_OF_FILE: > - // not necessarily an error > + // not necessarily an error, unless we just read outer > + // attributes which needs to be attached > + if (!outer_attrs.empty ()) > + { > + Rust::AST::Attribute attr = outer_attrs.back (); > + Error error (attr.get_locus (), > + "expected item after outer attribute or doc comment"); > + add_error (std::move (error)); > + } > return nullptr; > case PUB: > case MOD: > @@ -1091,7 +1122,11 @@ Parser::parse_outer_attributes () > { > AST::AttrVec outer_attributes; > > - while (lexer.peek_token ()->get_id () == HASH) > + while (lexer.peek_token ()->get_id () > + == HASH /* Can also be #!, which catches errors. */ > + || lexer.peek_token ()->get_id () == OUTER_DOC_COMMENT > + || lexer.peek_token ()->get_id () > + == INNER_DOC_COMMENT) /* For error handling. */ > { > AST::Attribute outer_attr = parse_outer_attribute (); > > @@ -1121,6 +1156,20 @@ template > AST::Attribute > Parser::parse_outer_attribute () > { > + if (lexer.peek_token ()->get_id () == OUTER_DOC_COMMENT) > + return parse_doc_comment (); > + > + if (lexer.peek_token ()->get_id () == INNER_DOC_COMMENT) > + { > + Error error ( > + lexer.peek_token ()->get_locus (), > + "inner doc (% or %) only allowed at start of item " > + "and before any outer attribute or doc (%<#[%>, % or %)"); > + add_error (std::move (error)); > + lexer.skip_token (); > + return AST::Attribute::create_empty (); > + } > + > /* OuterAttribute -> '#' '[' Attr ']' */ > > if (lexer.peek_token ()->get_id () != HASH) > @@ -1134,12 +1183,13 @@ Parser::parse_outer_attribute () > if (id == EXCLAM) > { > // this is inner attribute syntax, so throw error > + // inner attributes were either already parsed or not allowed here. > Error error ( > lexer.peek_token ()->get_locus (), > "token % found, indicating inner attribute definition. Inner " > "attributes are not possible at this location"); > add_error (std::move (error)); > - } // TODO: are there any cases where this wouldn't be an error? > + } > return AST::Attribute::create_empty (); > } > > diff --git a/gcc/rust/parse/rust-parse.h b/gcc/rust/parse/rust-parse.h > index bde2613f03d..1cd85eae8c2 100644 > --- a/gcc/rust/parse/rust-parse.h > +++ b/gcc/rust/parse/rust-parse.h > @@ -107,6 +107,7 @@ private: > AST::Attribute parse_outer_attribute (); > AST::Attribute parse_attribute_body (); > std::unique_ptr parse_attr_input (); > + AST::Attribute parse_doc_comment (); > > // Path-related > AST::SimplePath parse_simple_path (); > diff --git a/gcc/testsuite/rust/compile/bad_inner_doc.rs b/gcc/testsuite/rust/compile/bad_inner_doc.rs > new file mode 100644 > index 00000000000..cfd166ce3ec > --- /dev/null > +++ b/gcc/testsuite/rust/compile/bad_inner_doc.rs > @@ -0,0 +1,15 @@ > +pub fn main () > +{ > + //! inner doc allowed > + let _x = 42; > + // { dg-error "inner doc" "" { target *-*-* } .+1 } > + //! inner doc disallowed > + mod module > + { > + /*! inner doc allowed */ > + /// outer doc allowed > + // { dg-error "inner doc" "" { target *-*-* } .+1 } > + /*! but inner doc not here */ > + mod x { } > + } > +} > diff --git a/gcc/testsuite/rust/compile/doc_isolated_cr_block_comment.rs b/gcc/testsuite/rust/compile/doc_isolated_cr_block_comment.rs > new file mode 100644 > index 00000000000..0ada77f69cf > --- /dev/null > +++ b/gcc/testsuite/rust/compile/doc_isolated_cr_block_comment.rs > @@ -0,0 +1,3 @@ > +// { dg-error "Isolated CR" "" { target *-*-* } .+1 } > +/** doc cr > comment */ > +pub fn main () { } > diff --git a/gcc/testsuite/rust/compile/doc_isolated_cr_inner_block_comment.rs b/gcc/testsuite/rust/compile/doc_isolated_cr_inner_block_comment.rs > new file mode 100644 > index 00000000000..7db35341bee > --- /dev/null > +++ b/gcc/testsuite/rust/compile/doc_isolated_cr_inner_block_comment.rs > @@ -0,0 +1,5 @@ > +pub fn main () > +{ > +// { dg-error "Isolated CR" "" { target *-*-* } .+1 } > + /*! doc cr > comment */ > +} > diff --git a/gcc/testsuite/rust/compile/doc_isolated_cr_inner_line_comment.rs b/gcc/testsuite/rust/compile/doc_isolated_cr_inner_line_comment.rs > new file mode 100644 > index 00000000000..d75da75e218 > --- /dev/null > +++ b/gcc/testsuite/rust/compile/doc_isolated_cr_inner_line_comment.rs > @@ -0,0 +1,5 @@ > +pub fn main () > +{ > +// { dg-error "Isolated CR" "" { target *-*-* } .+1 } > + //! doc cr > comment > +} > diff --git a/gcc/testsuite/rust/compile/doc_isolated_cr_line_comment.rs b/gcc/testsuite/rust/compile/doc_isolated_cr_line_comment.rs > new file mode 100644 > index 00000000000..7b6ef989c30 > --- /dev/null > +++ b/gcc/testsuite/rust/compile/doc_isolated_cr_line_comment.rs > @@ -0,0 +1,3 @@ > +// { dg-error "Isolated CR" "" { target *-*-* } .+1 } > +/// doc cr > comment > +pub fn main () { } > diff --git a/gcc/testsuite/rust/compile/torture/all_doc_comment_line_blocks.rs b/gcc/testsuite/rust/compile/torture/all_doc_comment_line_blocks.rs > new file mode 100644 > index 00000000000..ab38ac69610 > --- /dev/null > +++ b/gcc/testsuite/rust/compile/torture/all_doc_comment_line_blocks.rs > @@ -0,0 +1,47 @@ > +// comment line not a doc > +/* comment block not a doc */ > + > +//! inner line comment for most outer crate > +/*! inner block comment for most outer crate */ > + > +// comment line not a doc > +/* comment block not a doc */ > + > +/// outer doc line for module > +/** outer doc block for module */ > +pub mod module > +{ > + //! inner line doc > + //!! inner line doc! > + /*! inner block doc */ > + /*!! inner block doc! */ > + > + // line comment > + /// outer line doc > + //// line comment > + > + /* block comment */ > + /** outer block doc */ > + /*** block comment */ > + > + mod block_doc_comments > + { > + /* /* */ /** */ /*! */ */ > + /*! /* */ /** */ /*! */ */ > + /** /* */ /** */ /*! */ */ > + mod item { } > + } > + > + pub mod empty > + { > + //! > + /*!*/ > + // > + > + /// > + mod doc { } > + /**/ > + /***/ > + } > +} > +pub fn main () { } > diff --git a/gcc/testsuite/rust/compile/torture/all_doc_comment_line_blocks_crlf.rs b/gcc/testsuite/rust/compile/torture/all_doc_comment_line_blocks_crlf.rs > new file mode 100644 > index 00000000000..3ea2cd01c8c > --- /dev/null > +++ b/gcc/testsuite/rust/compile/torture/all_doc_comment_line_blocks_crlf.rs > @@ -0,0 +1,47 @@ > +// comment line not a doc > +/* comment block not a doc */ > + > +//! inner line comment for most outer crate > +/*! inner block comment for most outer crate */ > + > +// comment line not a doc > +/* comment block not a doc */ > + > +/// outer doc line for module > +/** outer doc block for module */ > +pub mod module > +{ > + //! inner line doc > + //!! inner line doc! > + /*! inner block doc */ > + /*!! inner block doc! */ > + > + // line comment > + /// outer line doc > + //// line comment > + > + /* block comment */ > + /** outer block doc */ > + /*** block comment */ > + > + mod block_doc_comments > + { > + /* /* */ /** */ /*! */ */ > + /*! /* */ /** */ /*! */ */ > + /** /* */ /** */ /*! */ */ > + mod item { } > + } > + > + pub mod empty > + { > + //! > + /*!*/ > + // > + > + /// > + mod doc { } > + /**/ > + /***/ > + } > +} > +pub fn main () { } > diff --git a/gcc/testsuite/rust/compile/torture/isolated_cr_block_comment.rs b/gcc/testsuite/rust/compile/torture/isolated_cr_block_comment.rs > new file mode 100644 > index 00000000000..9a1e090f330 > --- /dev/null > +++ b/gcc/testsuite/rust/compile/torture/isolated_cr_block_comment.rs > @@ -0,0 +1,2 @@ > +/* comment cr > is allowed */ > +pub fn main () { } > diff --git a/gcc/testsuite/rust/compile/torture/isolated_cr_line_comment.rs b/gcc/testsuite/rust/compile/torture/isolated_cr_line_comment.rs > new file mode 100644 > index 00000000000..4e921a225c2 > --- /dev/null > +++ b/gcc/testsuite/rust/compile/torture/isolated_cr_line_comment.rs > @@ -0,0 +1,2 @@ > +// comment cr > is allowed > +pub fn main () { } Hi Mark, This patch looks good to me. When I tried to apply it to merge it I got the following: ``` $ git amĀ  '[PATCH] Handle doc comment strings in lexer and parser.eml' Applying: Handle doc comment strings in lexer and parser error: corrupt patch at line 531 Patch failed at 0001 Handle doc comment strings in lexer and parser hint: Use 'git am --show-current-patch' to see the failed patch When you have resolved this problem, run "git am --continue". If you prefer to skip this patch, run "git am --skip" instead. To restore the original branch and stop patching, run "git am --abort". ``` Not sure if I have done something wrong, have you any pointers? Thanks --Phil