@@ -2954,6 +2954,10 @@ class TextIOWrapper : public TextIOBase
29542954
29552955 size_t m_chunk_size{ 8192 };
29562956
2957+ // line terminators recognised by readline; universal newlines for now
2958+ // TODO: derive the set from the newline constructor argument
2959+ std::vector<std::string> m_line_delimiters{ " \r\n " , " \r " , " \n " };
2960+
29572961 TextIOWrapper (PyType *type) : TextIOBase(type) {}
29582962
29592963 public:
@@ -3128,27 +3132,38 @@ class TextIOWrapper : public TextIOBase
31283132 {
31293133 if (auto result = writeflush (); result.is_err ()) { return Err (result.unwrap_err ()); }
31303134
3131- // Lines in the input can end in \'\\n\', \'\\r\', or \'\\r\\n\'
3132- auto is_line_end = [](std::string_view remaining) {
3133- if (remaining.starts_with (" \r\n " )) { return 2 ; }
3134- if (remaining.starts_with (" \n " ) || remaining.starts_with (" \r " )) { return 1 ; }
3135- return 0 ;
3135+ // length of the longest line delimiter at the start of text, 0 if none
3136+ auto match_delimiter = [this ](std::string_view text) -> size_t {
3137+ size_t longest = 0 ;
3138+ for (const auto &delimiter : m_line_delimiters) {
3139+ if (delimiter.size () > longest && text.starts_with (delimiter)) {
3140+ longest = delimiter.size ();
3141+ }
3142+ }
3143+ return longest;
3144+ };
3145+ // text runs to the end of the available input; a proper prefix of a
3146+ // delimiter cannot be classified until more input arrives
3147+ auto is_delimiter_prefix = [this ](std::string_view text) {
3148+ for (const auto &delimiter : m_line_delimiters) {
3149+ if (delimiter.size () > text.size ()
3150+ && std::string_view{ delimiter }.starts_with (text)) {
3151+ return true ;
3152+ }
3153+ }
3154+ return false ;
31363155 };
31373156 std::string line;
31383157 bool found_nl = false ;
3158+ bool eof = false ;
3159+ bool need_more = false ;
31393160 while (!found_nl && line.size () < limit) {
3140- if (!m_buffer_bytes.has_value () || m_buffer_bytes->b .empty ()) {
3161+ if (need_more || !m_buffer_bytes.has_value () || m_buffer_bytes->b .empty ()) {
3162+ need_more = false ;
31413163 auto chunk = read_chunk (0 );
31423164 if (chunk.is_err ()) { return Err (chunk.unwrap_err ()); }
3143- if (!chunk.unwrap ()) { break ; }
3144- }
3145-
3146- // a trailing '\r' may be the first byte of a "\r\n" that straddles the
3147- // chunk boundary; read ahead so is_line_end can see the full terminator
3148- while (m_buffer_bytes->b .back () == std::byte{ ' \r ' }) {
3149- auto chunk = read_chunk (0 );
3150- if (chunk.is_err ()) { return Err (chunk.unwrap_err ()); }
3151- if (!chunk.unwrap ()) { break ; }
3165+ eof = !chunk.unwrap ();
3166+ if (eof && (!m_buffer_bytes.has_value () || m_buffer_bytes->b .empty ())) { break ; }
31523167 }
31533168
31543169 std::string_view remaining{ bit_cast<const char *>(m_buffer_bytes->b .data ())
@@ -3161,8 +3176,16 @@ class TextIOWrapper : public TextIOBase
31613176 const size_t budget = limit - line.size ();
31623177 const size_t scan_limit = std::min (budget, remaining.size ());
31633178 for (; len < scan_limit; ++len) {
3164- if (auto chars = is_line_end (remaining)) {
3165- // the terminator may extend past the caller's limit (e.g. a
3179+ // the rest of the buffer may hold an incomplete delimiter (e.g. the
3180+ // '\r' of a "\r\n" straddling the chunk boundary); fetch more input
3181+ // before classifying it, unless the size limit would clamp the
3182+ // consumed length to the same result either way
3183+ if (!eof && len + 1 < budget && is_delimiter_prefix (remaining)) {
3184+ need_more = true ;
3185+ break ;
3186+ }
3187+ if (auto chars = match_delimiter (remaining)) {
3188+ // the delimiter may extend past the caller's limit (e.g. a
31663189 // "\r\n" straddling it); consume only up to the limit and
31673190 // leave the rest buffered for the next call
31683191 len = std::min (len + chars, budget);
0 commit comments