C-style string parsing with a few caveats:
strThe result is a UTF-8 encoded string.
fn parseFilename(allocator: Allocator, str: []const u8) error
fn parseFilename(allocator: Allocator, str: []const u8) error{ OutOfMemory, InvalidString }![]u8 {
const State = enum {
string,
escape,
escape_hex,
escape_octal,
escape_u,
};
var filename = try std.ArrayList(u8).initCapacity(allocator, str.len);
errdefer filename.deinit(allocator);
var state: State = .string;
var index: usize = 0;
var escape_len: usize = undefined;
var escape_val: u64 = undefined;
var escape_expected_len: u8 = undefined;
while (index < str.len) : (index += 1) {
const c = str[index];
switch (state) {
.string => switch (c) {
'\\' => state = .escape,
'"' => return error.InvalidString,
else => filename.appendAssumeCapacity(c),
},
.escape => switch (c) {
'\'', '"', '\\', '?', 'n', 'r', 't', 'a', 'b', 'e', 'f', 'v' => {
const escaped_c = switch (c) {
'\'', '"', '\\', '?' => c,
'n' => '\n',
'r' => '\r',
't' => '\t',
'a' => '\x07',
'b' => '\x08',
'e' => '\x1b', // non-standard
'f' => '\x0c',
'v' => '\x0b',
else => unreachable,
};
filename.appendAssumeCapacity(escaped_c);
state = .string;
},
'x' => {
escape_val = 0;
escape_len = 0;
state = .escape_hex;
},
'0'...'7' => {
escape_val = std.fmt.charToDigit(c, 8) catch unreachable;
escape_len = 1;
state = .escape_octal;
},
'u' => {
escape_val = 0;
escape_len = 0;
state = .escape_u;
escape_expected_len = 4;
},
'U' => {
escape_val = 0;
escape_len = 0;
state = .escape_u;
escape_expected_len = 8;
},
else => return error.InvalidString,
},
.escape_hex => switch (c) {
'0'...'9', 'a'...'f', 'A'...'F' => {
const digit = std.fmt.charToDigit(c, 16) catch unreachable;
if (escape_val != 0) escape_val = std.math.mul(u8, @as(u8, @intCast(escape_val)), 16) catch return error.InvalidString;
escape_val = std.math.add(u8, @as(u8, @intCast(escape_val)), digit) catch return error.InvalidString;
escape_len += 1;
if (escape_len == 2) {
filename.appendAssumeCapacity(@intCast(escape_val));
state = .string;
}
},
else => {
if (escape_len == 0) return error.InvalidString;
filename.appendAssumeCapacity(@intCast(escape_val));
state = .string;
index -= 1; // reconsume
},
},
.escape_octal => switch (c) {
'0'...'7' => {
const digit = std.fmt.charToDigit(c, 8) catch unreachable;
if (escape_val != 0) escape_val = std.math.mul(u8, @as(u8, @intCast(escape_val)), 8) catch return error.InvalidString;
escape_val = std.math.add(u8, @as(u8, @intCast(escape_val)), digit) catch return error.InvalidString;
escape_len += 1;
if (escape_len == 3) {
filename.appendAssumeCapacity(@intCast(escape_val));
state = .string;
}
},
else => {
if (escape_len == 0) return error.InvalidString;
filename.appendAssumeCapacity(@intCast(escape_val));
state = .string;
index -= 1; // reconsume
},
},
.escape_u => switch (c) {
'0'...'9', 'a'...'f', 'A'...'F' => {
const digit = std.fmt.charToDigit(c, 16) catch unreachable;
if (escape_val != 0) escape_val = std.math.mul(u21, @as(u21, @intCast(escape_val)), 16) catch return error.InvalidString;
escape_val = std.math.add(u21, @as(u21, @intCast(escape_val)), digit) catch return error.InvalidString;
escape_len += 1;
if (escape_len == escape_expected_len) {
var buf: [4]u8 = undefined;
const utf8_len = std.unicode.utf8Encode(@intCast(escape_val), &buf) catch return error.InvalidString;
filename.appendSliceAssumeCapacity(buf[0..utf8_len]);
state = .string;
}
},
// Requires escape_expected_len valid hex digits
else => return error.InvalidString,
},
}
} else {
switch (state) {
.string => {},
.escape, .escape_u => return error.InvalidString,
.escape_hex => {
if (escape_len == 0) return error.InvalidString;
filename.appendAssumeCapacity(@intCast(escape_val));
},
.escape_octal => {
filename.appendAssumeCapacity(@intCast(escape_val));
},
}
}
return filename.toOwnedSlice(allocator);
}