// Expect retcode = 0 // LSP 传输帧:Content-Length 头 + 空行 + 正文。 // 这里只做纯逻辑(写入/解析 ByteBuffer/ByteView),真正的 stdin/stdout I/O 由上层负责。 type .{ .Error, .Status, .make } = @import("error.spl"); type .{ .ByteView } = @import("byte_view.spl"); type .{ .ByteBuffer } = @import("byte_buffer.spl"); type .{ .append_usize } = @import("fmt.spl"); type FrameResult = enum { Ok: usize, // 消耗的字节数 Incomplete, Err: Error, } fn ascii_lower(c: u8): u8 { if c >= 'A' && c <= 'Z' { ret c + ('a' - 'A'); } ret c; } // data[j..] 是否以 word 开头(ASCII 大小写不敏感)。 fn ci_prefix(data: *u8, len: usize, j: usize, word: []u8): bool { if j + word.len > len { ret false; } var i: usize = 0; while i < word.len { if ascii_lower(data[j + i]) != ascii_lower(word.ptr[i]) { ret false; } i = i + 1; } ret true; } fn write_frame(out: *ByteBuffer, body: []u8): Status { match ByteBuffer.append(out, "Content-Length: ") { .Ok => {}, .Err(e) => { ret .Err(e); } } match append_usize(out, body.len, 10, false) { .Ok => {}, .Err(e) => { ret .Err(e); } } match ByteBuffer.append(out, "\r\n\r\n") { .Ok => {}, .Err(e) => { ret .Err(e); } } ret ByteBuffer.append(out, body); } // 从 input 解析一个帧;正文复制到 body_out。缺数据返回 Incomplete。 fn parse_frame(input: ByteView, body_out: *ByteBuffer): FrameResult { var data = ByteView.as_slice(input); var len = data.len; // 找头结束标记 \r\n\r\n var term = ~(0 as usize); var i: usize = 0; while i + 3 < len { if data[i] == '\r' && data[i + 1] == '\n' && data[i + 2] == '\r' && data[i + 3] == '\n' { term = i; break; } i = i + 1; } if term == ~(0 as usize) { ret .Incomplete; } // 在头内找 Content-Length(大小写不敏感) var content_len: usize = 0; var found = false; var j: usize = 0; while j + 15 <= term { if ci_prefix(data.ptr, term, j, "content-length:") { var k = j + 15; while k < term && (data[k] == ' ' || data[k] == '\t') { k = k + 1; } var n: usize = 0; var digits = 0; while k < term && data[k] >= '0' && data[k] <= '9' { n = n * 10 + (data[k] - '0') as usize; digits = digits + 1; k = k + 1; } if digits == 0 { ret .Err(make(.InvalidDigit)); } content_len = n; found = true; break; } j = j + 1; } if !found { ret .Err(make(.InvalidFormat)); } var body_start = term + 4; if len < body_start || content_len > len - body_start { ret .Incomplete; } ByteBuffer.clear(body_out); var body = data[body_start..body_start + content_len]; match ByteBuffer.append(body_out, body) { .Ok => {}, .Err(e) => { ret .Err(e); } } ret .Ok(body_start + content_len); } // ---- 测试辅助 ---- fn expect_ok(result: Status) { match result { .Ok => {}, .Err(error) => { @assert(false, "unexpected frame error"); }, } } fn expect_body(out: *ByteBuffer, expected: []u8) { var actual = ByteBuffer.as_slice(out.*); @assert(actual.len == expected.len, "body length"); var i: usize = 0; while i < expected.len { @assert(actual.ptr[i] == expected.ptr[i], "body byte"); i = i + 1; } } fn test_roundtrip() { var wire = ByteBuffer.new(); expect_ok(write_frame(&wire, "{\"jsonrpc\":\"2.0\"}")); var bytes = ByteBuffer.as_slice(wire); @assert(bytes.len == 16 + 2 + 4 + 17, "wire length"); // "Content-Length: 17\r\n\r\n" + body var body = ByteBuffer.new(); match parse_frame(ByteView.from_slice(bytes), &body) { .Ok(used) => { @assert(used == bytes.len, "consumed all"); expect_body(&body, "{\"jsonrpc\":\"2.0\"}"); }, .Incomplete => { @assert(false, "unexpected incomplete"); }, .Err(error) => { @assert(false, "unexpected err"); }, } ByteBuffer.drop(&body); ByteBuffer.drop(&wire); } fn test_multiple_and_leftover() { var wire = ByteBuffer.new(); expect_ok(write_frame(&wire, "abcd")); expect_ok(write_frame(&wire, "ef")); var bytes = ByteBuffer.as_slice(wire); var body = ByteBuffer.new(); match parse_frame(ByteView.from_slice(bytes), &body) { .Ok(used) => { expect_body(&body, "abcd"); var rest = bytes[used..bytes.len]; match parse_frame(ByteView.from_slice(rest), &body) { .Ok(used2) => { expect_body(&body, "ef"); @assert(used2 == rest.len, "second"); }, _ => { @assert(false, "second frame"); }, } }, _ => { @assert(false, "first frame"); }, } ByteBuffer.drop(&body); ByteBuffer.drop(&wire); } fn test_incomplete_and_errors() { var body = ByteBuffer.new(); // 头未结束 var h1 = "Content-Length: 3\r\n"; match parse_frame(ByteView.from_slice(h1), &body) { .Incomplete => {}, _ => { @assert(false, "h1"); }, } // 正文不足 var h2 = "Content-Length: 3\r\n\r\nab"; match parse_frame(ByteView.from_slice(h2), &body) { .Incomplete => {}, _ => { @assert(false, "h2"); }, } // 缺 Content-Length var h3 = "X: 1\r\n\r\n"; match parse_frame(ByteView.from_slice(h3), &body) { .Ok(used) => { @assert(false, "h3 ok"); }, .Incomplete => { @assert(false, "h3 incomplete"); }, .Err(error) => {}, } // 大小写不敏感 + 额外头 var h4 = "X-Test: yes\r\ncontent-length: 2\r\n\r\nhi"; match parse_frame(ByteView.from_slice(h4), &body) { .Ok(used) => { expect_body(&body, "hi"); }, _ => { @assert(false, "h4"); }, } ByteBuffer.drop(&body); } fn main() { test_roundtrip(); test_multiple_and_leftover(); test_incomplete_and_errors(); }