1 module irc.linebuffer; 2 3 import irc.exception; 4 5 import std.exception; 6 import core.stdc.string : memmove; 7 8 struct LineBuffer 9 { 10 private: 11 char[] buffer; 12 size_t lineStart, bufferPos; 13 void delegate(in char[] line) onReceivedLine; 14 15 public: 16 this(char[] buffer, void delegate(in char[] line) onReceivedLine) 17 { 18 this.buffer = buffer; 19 this.onReceivedLine = onReceivedLine; 20 } 21 22 /// End of the current line. 23 size_t position() @property 24 { 25 return bufferPos; 26 } 27 28 /// Notify that n number of bytes have been committed after the current position. 29 /// Call with $(D n = 0) to invoke the callback for any lines that were skipped 30 /// due to an exception being thrown during a previous commit. 31 void commit(size_t n) 32 { 33 auto nextBufferPos = bufferPos + n; 34 35 if(nextBufferPos == buffer.length) 36 { 37 bufferPos = nextBufferPos; 38 nextBufferPos = moveDown(); 39 } 40 41 foreach(i; bufferPos .. nextBufferPos) 42 { 43 if(buffer[i] == '\n') 44 { 45 auto line = buffer[lineStart .. i]; 46 47 if(line.length > 0 && line[$ - 1] == '\r') 48 --line.length; // Skip \r 49 50 lineStart = i + 1; // Skip \n 51 52 // If onReceivedLine throws, we want to just skip 53 // the the current line, leaving the next lines 54 // to be parsed on the next commit. 55 bufferPos = lineStart; 56 57 onReceivedLine(line); 58 } 59 } 60 61 bufferPos = nextBufferPos; 62 } 63 64 private: 65 size_t moveDown() 66 { 67 enforceEx!IrcParseErrorException(lineStart != 0, "line too long for buffer"); 68 69 auto length = bufferPos - lineStart; 70 memmove(buffer.ptr, buffer.ptr + lineStart, length); 71 lineStart = 0; 72 bufferPos = 0; 73 74 return length; 75 } 76 }