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 }