parseQuotedString
This commit is contained in:
parent
4c661672c2
commit
7f689c7030
1 changed files with 125 additions and 5 deletions
|
@ -275,6 +275,76 @@ fn isTokenChar(ch: u8) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parses a quoted-string (rfc 9110) off the stream. Backslash-tokens are unescaped.
|
||||||
|
// The caller takes responsibility for deallocating the memory returned.
|
||||||
|
fn parseQuotedString(alloc: std.mem.Allocator, peek_stream: anytype) ![]const u8 {
|
||||||
|
const reader = peek_stream.reader();
|
||||||
|
|
||||||
|
var data = std.ArrayList(u8).init(alloc);
|
||||||
|
errdefer data.deinit();
|
||||||
|
|
||||||
|
{
|
||||||
|
const start = try reader.readByte();
|
||||||
|
if (start != '"') {
|
||||||
|
try peek_stream.putBackByte(start);
|
||||||
|
return error.MissingStartQuote;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const ch = switch (try reader.readByte()) {
|
||||||
|
'\t', ' ', '!', 0x23...0x5b, 0x5d...0x7e, 0x80...0xff => |c| c,
|
||||||
|
|
||||||
|
'\\' => switch (try reader.readByte()) {
|
||||||
|
'\t', ' ', 0x21...0x7e, 0x80...0xff => |c| c,
|
||||||
|
else => return error.UnexpectedChar,
|
||||||
|
},
|
||||||
|
|
||||||
|
'"' => break,
|
||||||
|
else => return error.UnexpectedChar,
|
||||||
|
};
|
||||||
|
|
||||||
|
try data.append(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parseQuotedString" {
|
||||||
|
const testCase = struct {
|
||||||
|
fn func(data: []const u8, stream_error: ?anyerror, expected: anyerror![]const u8, remaining: []const u8) !void {
|
||||||
|
var fbs = std.io.fixedBufferStream(data);
|
||||||
|
var stream = errorReader(stream_error orelse error.EndOfStream, fbs.reader());
|
||||||
|
var peeker = std.io.peekStream(1, stream.reader());
|
||||||
|
|
||||||
|
const result = parseQuotedString(std.testing.allocator, &peeker);
|
||||||
|
defer if (result) |v| std.testing.allocator.free(v) else |_| {};
|
||||||
|
|
||||||
|
if (expected) |val|
|
||||||
|
try std.testing.expectEqualStrings(val, try result)
|
||||||
|
else |expected_err|
|
||||||
|
try std.testing.expectError(expected_err, result);
|
||||||
|
|
||||||
|
try std.testing.expect(try peeker.reader().isBytes(remaining));
|
||||||
|
try std.testing.expectError(stream_error orelse error.EndOfStream, peeker.reader().readByte());
|
||||||
|
}
|
||||||
|
}.func;
|
||||||
|
|
||||||
|
try testCase("\"abcdefg\"", null, "abcdefg", "");
|
||||||
|
try testCase("\"abcdefg\"abcd", null, "abcdefg", "abcd");
|
||||||
|
try testCase("\"xyz\\\"z\"", null, "xyz\"z", "");
|
||||||
|
try testCase("\"xyz\\\\z\"", null, "xyz\\z", "");
|
||||||
|
try testCase("\"💯\"", null, "💯", "");
|
||||||
|
|
||||||
|
try testCase("abcdefg\"abcd", null, error.MissingStartQuote, "abcdefg\"abcd");
|
||||||
|
try testCase("\"abcdefg", null, error.EndOfStream, "");
|
||||||
|
|
||||||
|
try testCase("\"abcdefg", error.ClosedPipe, error.ClosedPipe, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to parse a token (rfc 9110) off the stream. It stops at the first non-token
|
||||||
|
// char. Said char reamins on the stream. If the token is empty, returns error.EmptyToken;
|
||||||
|
// The caller takes responsibility for deallocating the memory returned.
|
||||||
fn parseToken(alloc: std.mem.Allocator, peek_stream: anytype) ![]const u8 {
|
fn parseToken(alloc: std.mem.Allocator, peek_stream: anytype) ![]const u8 {
|
||||||
var data = std.ArrayList(u8).init(alloc);
|
var data = std.ArrayList(u8).init(alloc);
|
||||||
errdefer data.deinit();
|
errdefer data.deinit();
|
||||||
|
@ -288,14 +358,16 @@ fn parseToken(alloc: std.mem.Allocator, peek_stream: anytype) ![]const u8 {
|
||||||
try data.append(ch);
|
try data.append(ch);
|
||||||
} else |err| if (err != error.EndOfStream) return err;
|
} else |err| if (err != error.EndOfStream) return err;
|
||||||
|
|
||||||
|
if (data.items.len == 0) return error.EmptyToken;
|
||||||
|
|
||||||
return data.toOwnedSlice();
|
return data.toOwnedSlice();
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parseToken" {
|
test "parseToken" {
|
||||||
const testCase = struct {
|
const testCase = struct {
|
||||||
fn func(data: []const u8, err: ?anyerror, expected: anyerror![]const u8, remaining: []const u8) !void {
|
fn func(data: []const u8, stream_error: ?anyerror, expected: anyerror![]const u8, remaining: []const u8) !void {
|
||||||
var fbs = std.io.fixedBufferStream(data);
|
var fbs = std.io.fixedBufferStream(data);
|
||||||
var stream = errorReader(err orelse error.EndOfStream, fbs.reader());
|
var stream = errorReader(stream_error orelse error.EndOfStream, fbs.reader());
|
||||||
var peeker = std.io.peekStream(1, stream.reader());
|
var peeker = std.io.peekStream(1, stream.reader());
|
||||||
|
|
||||||
const result = parseToken(std.testing.allocator, &peeker);
|
const result = parseToken(std.testing.allocator, &peeker);
|
||||||
|
@ -307,7 +379,7 @@ test "parseToken" {
|
||||||
try std.testing.expectError(expected_err, result);
|
try std.testing.expectError(expected_err, result);
|
||||||
|
|
||||||
try std.testing.expect(try peeker.reader().isBytes(remaining));
|
try std.testing.expect(try peeker.reader().isBytes(remaining));
|
||||||
try std.testing.expectError(err orelse error.EndOfStream, peeker.reader().readByte());
|
try std.testing.expectError(stream_error orelse error.EndOfStream, peeker.reader().readByte());
|
||||||
}
|
}
|
||||||
}.func;
|
}.func;
|
||||||
|
|
||||||
|
@ -316,12 +388,60 @@ test "parseToken" {
|
||||||
try testCase("abc;defg", null, "abc", ";defg");
|
try testCase("abc;defg", null, "abc", ";defg");
|
||||||
try testCase("abc%defg$; ", null, "abc%defg$", "; ");
|
try testCase("abc%defg$; ", null, "abc%defg$", "; ");
|
||||||
|
|
||||||
try testCase(" ", null, "", " ");
|
try testCase(" ", null, error.EmptyToken, " ");
|
||||||
try testCase(";", null, "", ";");
|
try testCase(";", null, error.EmptyToken, ";");
|
||||||
|
|
||||||
try testCase("abcdefg", error.ClosedPipe, error.ClosedPipe, "");
|
try testCase("abcdefg", error.ClosedPipe, error.ClosedPipe, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parses a token or quoted string (rfc 9110) off the stream, as appropriate.
|
||||||
|
// The caller takes responsibility for deallocating the memory returned.
|
||||||
|
fn parseTokenOrQuotedString(alloc: std.mem.Allocator, peek_stream: anytype) ![]const u8 {
|
||||||
|
return parseToken(alloc, peek_stream) catch |err| switch (err) {
|
||||||
|
error.EmptyToken => return try parseQuotedString(alloc, peek_stream),
|
||||||
|
else => |e| return e,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parseTokenOrQuotedString" {
|
||||||
|
const testCase = struct {
|
||||||
|
fn func(data: []const u8, stream_error: ?anyerror, expected: anyerror![]const u8, remaining: []const u8) !void {
|
||||||
|
var fbs = std.io.fixedBufferStream(data);
|
||||||
|
var stream = errorReader(stream_error orelse error.EndOfStream, fbs.reader());
|
||||||
|
var peeker = std.io.peekStream(1, stream.reader());
|
||||||
|
|
||||||
|
const result = parseTokenOrQuotedString(std.testing.allocator, &peeker);
|
||||||
|
defer if (result) |v| std.testing.allocator.free(v) else |_| {};
|
||||||
|
|
||||||
|
if (expected) |val|
|
||||||
|
try std.testing.expectEqualStrings(val, try result)
|
||||||
|
else |expected_err|
|
||||||
|
try std.testing.expectError(expected_err, result);
|
||||||
|
|
||||||
|
try std.testing.expect(try peeker.reader().isBytes(remaining));
|
||||||
|
try std.testing.expectError(stream_error orelse error.EndOfStream, peeker.reader().readByte());
|
||||||
|
}
|
||||||
|
}.func;
|
||||||
|
|
||||||
|
try testCase("abcdefg", null, "abcdefg", "");
|
||||||
|
try testCase("abc defg", null, "abc", " defg");
|
||||||
|
try testCase("abc;defg", null, "abc", ";defg");
|
||||||
|
try testCase("abc%defg$; ", null, "abc%defg$", "; ");
|
||||||
|
|
||||||
|
try testCase("\"abcdefg\"", null, "abcdefg", "");
|
||||||
|
try testCase("\"abcdefg\"abcd", null, "abcdefg", "abcd");
|
||||||
|
try testCase("\"xyz\\\"z\"", null, "xyz\"z", "");
|
||||||
|
try testCase("\"xyz\\\\z\"", null, "xyz\\z", "");
|
||||||
|
try testCase("\"💯\"", null, "💯", "");
|
||||||
|
|
||||||
|
try testCase(" ", null, error.MissingStartQuote, " ");
|
||||||
|
try testCase(";", null, error.MissingStartQuote, ";");
|
||||||
|
try testCase("\"abcdefg", null, error.EndOfStream, "");
|
||||||
|
|
||||||
|
try testCase("abcdefg", error.ClosedPipe, error.ClosedPipe, "");
|
||||||
|
try testCase("\"abcdefg", error.ClosedPipe, error.ClosedPipe, "");
|
||||||
|
}
|
||||||
|
|
||||||
fn ErrorReader(comptime E: type, comptime ReaderType: type) type {
|
fn ErrorReader(comptime E: type, comptime ReaderType: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
inner_reader: ReaderType,
|
inner_reader: ReaderType,
|
||||||
|
|
Loading…
Reference in a new issue