Compare commits
3 Commits
master
...
6c1eb176be
| Author | SHA1 | Date | |
|---|---|---|---|
|
6c1eb176be
|
|||
|
db8f6b6345
|
|||
|
3b68f1dc7a
|
555
src/config.zig
555
src/config.zig
@@ -54,6 +54,9 @@
|
||||
// Like multiline strings, the final space is stripped (I guess this is a very
|
||||
// janky way to add trailing whitespace to a string).
|
||||
//
|
||||
// - terminated strings to allow trailing whitespace:
|
||||
// | this string has trailing whitespace |
|
||||
// > and so does this one |
|
||||
// - The parser is both strict and probably sloppy and may have weird edge
|
||||
// cases since I'm slinging code, not writing a spec. For example, tabs are
|
||||
// not trimmed from the values of inline lists/maps
|
||||
@@ -94,10 +97,19 @@ pub const LineTokenizer = struct {
|
||||
const InlineItem = union(enum) {
|
||||
empty: void,
|
||||
scalar: []const u8,
|
||||
string: []const u8,
|
||||
line_string: []const u8,
|
||||
space_string: []const u8,
|
||||
|
||||
flow_list: []const u8,
|
||||
flow_map: []const u8,
|
||||
|
||||
fn lineEnding(self: InlineItem) u8 {
|
||||
return switch (self) {
|
||||
.line_string => '\n',
|
||||
.space_string => ' ',
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const LineContents = union(enum) {
|
||||
@@ -288,26 +300,38 @@ pub const LineTokenizer = struct {
|
||||
if (buf.len == 0) return .empty;
|
||||
|
||||
switch (buf[0]) {
|
||||
'|', '>' => |char| {
|
||||
'>', '|' => |char| {
|
||||
if (buf.len > 1 and buf[1] != ' ') return error.BadToken;
|
||||
|
||||
return .{
|
||||
.string = buf.ptr[@min(2, buf.len) .. buf.len + @intFromBool(char == '|')],
|
||||
const slice: []const u8 = switch (buf[buf.len - 1]) {
|
||||
' ', '\t' => return error.TrailingWhitespace,
|
||||
'|' => buf[@min(2, buf.len) .. buf.len - @intFromBool(buf.len > 1)],
|
||||
else => buf[@min(2, buf.len)..buf.len],
|
||||
};
|
||||
|
||||
return if (char == '>')
|
||||
.{ .line_string = slice }
|
||||
else
|
||||
.{ .space_string = slice };
|
||||
},
|
||||
'[' => {
|
||||
if (buf.len < 2 or buf[buf.len - 1] != ']') return error.BadToken;
|
||||
if (buf.len < 2 or buf[buf.len - 1] != ']')
|
||||
return error.BadToken;
|
||||
|
||||
// keep the closing ] for the flow parser
|
||||
return .{ .flow_list = buf[1..] };
|
||||
},
|
||||
'{' => {
|
||||
if (buf.len < 2 or buf[buf.len - 1] != '}') return error.BadToken;
|
||||
if (buf.len < 2 or buf[buf.len - 1] != '}')
|
||||
return error.BadToken;
|
||||
|
||||
// keep the closing } fpr the flow parser
|
||||
return .{ .flow_map = buf[1..] };
|
||||
},
|
||||
else => {
|
||||
if (buf[buf.len - 1] == ' ' or buf[buf.len - 1] == '\t')
|
||||
return error.TrailingWhitespace;
|
||||
|
||||
return .{ .scalar = buf };
|
||||
},
|
||||
}
|
||||
@@ -326,6 +350,127 @@ pub const LineTokenizer = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const Value = union(enum) {
|
||||
pub const String = std.ArrayList(u8);
|
||||
pub const Map = std.StringHashMap(Value);
|
||||
pub const List = std.ArrayList(Value);
|
||||
pub const TagType = @typeInfo(Value).Union.tag_type.?;
|
||||
|
||||
scalar: String,
|
||||
string: String,
|
||||
list: List,
|
||||
flow_list: List,
|
||||
map: Map,
|
||||
flow_map: Map,
|
||||
|
||||
pub inline fn fromScalar(alloc: std.mem.Allocator, input: []const u8) !Value {
|
||||
return try _fromScalarOrString(alloc, .scalar, input);
|
||||
}
|
||||
|
||||
pub inline fn fromString(alloc: std.mem.Allocator, input: []const u8) !Value {
|
||||
return try _fromScalarOrString(alloc, .string, input);
|
||||
}
|
||||
|
||||
inline fn _fromScalarOrString(alloc: std.mem.Allocator, comptime classification: TagType, input: []const u8) !Value {
|
||||
var res = @unionInit(Value, @tagName(classification), try String.initCapacity(alloc, input.len));
|
||||
@field(res, @tagName(classification)).appendSliceAssumeCapacity(input);
|
||||
return res;
|
||||
}
|
||||
|
||||
pub inline fn newScalar(alloc: std.mem.Allocator) Value {
|
||||
return .{ .scalar = String.init(alloc) };
|
||||
}
|
||||
|
||||
pub inline fn newString(alloc: std.mem.Allocator) Value {
|
||||
return .{ .string = String.init(alloc) };
|
||||
}
|
||||
|
||||
pub inline fn newList(alloc: std.mem.Allocator) Value {
|
||||
return .{ .list = List.init(alloc) };
|
||||
}
|
||||
|
||||
pub inline fn newFlowList(alloc: std.mem.Allocator) Value {
|
||||
return .{ .flow_list = List.init(alloc) };
|
||||
}
|
||||
|
||||
pub inline fn newMap(alloc: std.mem.Allocator) Value {
|
||||
return .{ .map = Map.init(alloc) };
|
||||
}
|
||||
|
||||
pub inline fn newFlowMap(alloc: std.mem.Allocator) Value {
|
||||
return .{ .flow_map = Map.init(alloc) };
|
||||
}
|
||||
|
||||
pub fn printDebug(self: Value) void {
|
||||
self.printRecursive(0);
|
||||
std.debug.print("\n", .{});
|
||||
}
|
||||
|
||||
fn printRecursive(self: Value, indent: usize) void {
|
||||
switch (self) {
|
||||
.scalar, .string => |str| {
|
||||
if (std.mem.indexOfScalar(u8, str.items, '\n')) |_| {
|
||||
var lines = std.mem.splitScalar(u8, str.items, '\n');
|
||||
std.debug.print("\n", .{});
|
||||
while (lines.next()) |line| {
|
||||
std.debug.print(
|
||||
"{[empty]s: >[indent]}{[line]s}{[nl]s}",
|
||||
.{
|
||||
.empty = "",
|
||||
.indent = indent,
|
||||
.line = line,
|
||||
.nl = if (lines.peek() == null) "" else "\n",
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
std.debug.print("{s}", .{str.items});
|
||||
}
|
||||
},
|
||||
.list, .flow_list => |list| {
|
||||
if (list.items.len == 0) {
|
||||
std.debug.print("[]", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
std.debug.print("[\n", .{});
|
||||
for (list.items, 0..) |value, idx| {
|
||||
std.debug.print("{[empty]s: >[indent]}[{[idx]d}] = ", .{ .empty = "", .indent = indent, .idx = idx });
|
||||
value.printRecursive(indent + 2);
|
||||
std.debug.print(",\n", .{});
|
||||
}
|
||||
std.debug.print(
|
||||
"{[empty]s: >[indent]}]",
|
||||
.{ .empty = "", .indent = indent },
|
||||
);
|
||||
},
|
||||
.map, .flow_map => |map| {
|
||||
if (map.count() == 0) {
|
||||
std.debug.print("{{}}", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
std.debug.print("{{\n", .{});
|
||||
|
||||
var iter = map.iterator();
|
||||
|
||||
while (iter.next()) |entry| {
|
||||
std.debug.print(
|
||||
"{[empty]s: >[indent]}{[key]s}: ",
|
||||
.{ .empty = "", .indent = indent + 2, .key = entry.key_ptr.* },
|
||||
);
|
||||
entry.value_ptr.printRecursive(indent + 4);
|
||||
std.debug.print(",\n", .{});
|
||||
}
|
||||
std.debug.print(
|
||||
"{[empty]s: >[indent]}}}",
|
||||
.{ .empty = "", .indent = indent },
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Parser = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
dupe_behavior: DuplicateKeyBehavior = .fail,
|
||||
@@ -359,102 +504,6 @@ pub const Parser = struct {
|
||||
fail,
|
||||
};
|
||||
|
||||
pub const Map = std.StringHashMap;
|
||||
pub const List = std.ArrayList;
|
||||
|
||||
pub const Value = union(enum) {
|
||||
string: std.ArrayList(u8),
|
||||
list: List(Value),
|
||||
map: Map(Value),
|
||||
|
||||
pub inline fn fromString(alloc: std.mem.Allocator, input: []const u8) !Value {
|
||||
var res: Value = .{ .string = try std.ArrayList(u8).initCapacity(alloc, input.len) };
|
||||
res.string.appendSliceAssumeCapacity(input);
|
||||
return res;
|
||||
}
|
||||
|
||||
pub inline fn newString(alloc: std.mem.Allocator) Value {
|
||||
return .{ .string = std.ArrayList(u8).init(alloc) };
|
||||
}
|
||||
|
||||
pub inline fn newList(alloc: std.mem.Allocator) Value {
|
||||
return .{ .list = List(Value).init(alloc) };
|
||||
}
|
||||
|
||||
pub inline fn newMap(alloc: std.mem.Allocator) Value {
|
||||
return .{ .map = Map(Value).init(alloc) };
|
||||
}
|
||||
|
||||
pub fn printDebug(self: Value) void {
|
||||
self.printRecursive(0);
|
||||
std.debug.print("\n", .{});
|
||||
}
|
||||
|
||||
fn printRecursive(self: Value, indent: usize) void {
|
||||
switch (self) {
|
||||
.string => |str| {
|
||||
if (std.mem.indexOfScalar(u8, str.items, '\n')) |_| {
|
||||
var lines = std.mem.splitScalar(u8, str.items, '\n');
|
||||
std.debug.print("\n", .{});
|
||||
while (lines.next()) |line| {
|
||||
std.debug.print(
|
||||
"{[empty]s: >[indent]}{[line]s}{[nl]s}",
|
||||
.{
|
||||
.empty = "",
|
||||
.indent = indent,
|
||||
.line = line,
|
||||
.nl = if (lines.peek() == null) "" else "\n",
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
std.debug.print("{s}", .{str.items});
|
||||
}
|
||||
},
|
||||
.list => |list| {
|
||||
if (list.items.len == 0) {
|
||||
std.debug.print("[]", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
std.debug.print("[\n", .{});
|
||||
for (list.items, 0..) |value, idx| {
|
||||
std.debug.print("{[empty]s: >[indent]}[{[idx]d}] = ", .{ .empty = "", .indent = indent, .idx = idx });
|
||||
value.printRecursive(indent + 2);
|
||||
std.debug.print(",\n", .{});
|
||||
}
|
||||
std.debug.print(
|
||||
"{[empty]s: >[indent]}]",
|
||||
.{ .empty = "", .indent = indent },
|
||||
);
|
||||
},
|
||||
.map => |map| {
|
||||
if (map.count() == 0) {
|
||||
std.debug.print("{{}}", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
std.debug.print("{{\n", .{});
|
||||
|
||||
var iter = map.iterator();
|
||||
|
||||
while (iter.next()) |entry| {
|
||||
std.debug.print(
|
||||
"{[empty]s: >[indent]}{[key]s}: ",
|
||||
.{ .empty = "", .indent = indent + 2, .key = entry.key_ptr.* },
|
||||
);
|
||||
entry.value_ptr.printRecursive(indent + 4);
|
||||
std.debug.print(",\n", .{});
|
||||
}
|
||||
std.debug.print(
|
||||
"{[empty]s: >[indent]}}}",
|
||||
.{ .empty = "", .indent = indent },
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const ParseState = enum {
|
||||
initial,
|
||||
value,
|
||||
@@ -465,6 +514,13 @@ pub const Parser = struct {
|
||||
arena: std.heap.ArenaAllocator,
|
||||
root: Value,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator) Document {
|
||||
return .{
|
||||
.arena = std.heap.ArenaAllocator.init(alloc),
|
||||
.root = undefined,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn printDebug(self: Document) void {
|
||||
return self.root.printDebug();
|
||||
}
|
||||
@@ -474,11 +530,29 @@ pub const Parser = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn parseBuffer(self: *Parser, buffer: []const u8) Error!Document {
|
||||
var document: Document = .{
|
||||
.arena = std.heap.ArenaAllocator.init(self.allocator),
|
||||
.root = undefined,
|
||||
pub const State = struct {
|
||||
pub const Stack = std.ArrayList(*Value);
|
||||
|
||||
document: Document,
|
||||
value_stack: Stack,
|
||||
state: ParseState = .initial,
|
||||
expect_shift: LineTokenizer.ShiftDirection = .none,
|
||||
dangling_key: ?[]const u8 = null,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator) State {
|
||||
return .{
|
||||
.document = Document.init(alloc),
|
||||
.value_stack = Stack.init(alloc),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: State) void {
|
||||
self.value_stack.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
pub fn parseBuffer(self: *Parser, buffer: []const u8) Error!Document {
|
||||
var document = Document.init(self.allocator);
|
||||
errdefer document.deinit();
|
||||
const arena_alloc = document.arena.allocator();
|
||||
|
||||
@@ -507,13 +581,18 @@ pub const Parser = struct {
|
||||
// empty scalars are only emitted for a list_item or a map_item
|
||||
.empty => unreachable,
|
||||
.scalar => |str| {
|
||||
document.root = try valueFromString(arena_alloc, str);
|
||||
document.root = try Value.fromScalar(arena_alloc, str);
|
||||
// this is a cheesy hack. If the document consists
|
||||
// solely of a scalar, the finalizer will try to
|
||||
// chop a line ending off of it, so we need to add
|
||||
// a sacrificial padding character to avoid
|
||||
// chopping off something that matters.
|
||||
try document.root.string.append(' ');
|
||||
state = .done;
|
||||
},
|
||||
.string => |str| {
|
||||
document.root = try valueFromString(arena_alloc, str);
|
||||
// cheesy technique for differentiating the different string types
|
||||
if (str[str.len - 1] != '\n') try document.root.string.append(' ');
|
||||
.line_string, .space_string => |str| {
|
||||
document.root = try Value.fromString(arena_alloc, str);
|
||||
try document.root.string.append(in_line.lineEnding());
|
||||
try stack.append(&document.root);
|
||||
state = .value;
|
||||
},
|
||||
@@ -527,7 +606,7 @@ pub const Parser = struct {
|
||||
},
|
||||
},
|
||||
.list_item => |value| {
|
||||
document.root = .{ .list = List(Value).init(arena_alloc) };
|
||||
document.root = Value.newList(arena_alloc);
|
||||
try stack.append(&document.root);
|
||||
|
||||
switch (value) {
|
||||
@@ -535,8 +614,12 @@ pub const Parser = struct {
|
||||
expect_shift = .indent;
|
||||
state = .value;
|
||||
},
|
||||
.string, .scalar => |str| {
|
||||
try document.root.list.append(try valueFromString(arena_alloc, chopNewline(str)));
|
||||
.scalar => |str| {
|
||||
try document.root.list.append(try Value.fromScalar(arena_alloc, str));
|
||||
state = .value;
|
||||
},
|
||||
.line_string, .space_string => |str| {
|
||||
try document.root.list.append(try Value.fromString(arena_alloc, str));
|
||||
state = .value;
|
||||
},
|
||||
.flow_list => |str| {
|
||||
@@ -550,7 +633,7 @@ pub const Parser = struct {
|
||||
}
|
||||
},
|
||||
.map_item => |pair| {
|
||||
document.root = .{ .map = Map(Value).init(arena_alloc) };
|
||||
document.root = Value.newMap(arena_alloc);
|
||||
try stack.append(&document.root);
|
||||
|
||||
switch (pair.val) {
|
||||
@@ -565,10 +648,16 @@ pub const Parser = struct {
|
||||
dangling_key = pair.key;
|
||||
state = .value;
|
||||
},
|
||||
.string, .scalar => |str| {
|
||||
.scalar => |str| {
|
||||
// we can do direct puts here because this is
|
||||
// the very first line of the document
|
||||
try document.root.map.put(pair.key, try valueFromString(arena_alloc, chopNewline(str)));
|
||||
try document.root.map.put(pair.key, try Value.fromScalar(arena_alloc, str));
|
||||
state = .value;
|
||||
},
|
||||
.line_string, .space_string => |str| {
|
||||
// we can do direct puts here because this is
|
||||
// the very first line of the document
|
||||
try document.root.map.put(pair.key, try Value.fromString(arena_alloc, str));
|
||||
state = .value;
|
||||
},
|
||||
.flow_list => |str| {
|
||||
@@ -584,9 +673,20 @@ pub const Parser = struct {
|
||||
}
|
||||
},
|
||||
.value => switch (stack.getLast().*) {
|
||||
// these three states are never reachable here. flow_list and
|
||||
// flow_map are parsed with a separate state machine. These
|
||||
// value tyeps can only be present by themselves as the first
|
||||
// line of the document, in which case the document consists
|
||||
// only of that single line: this parser jumps immediately into
|
||||
// the .done state, bypassing the .value state in which this
|
||||
// switch is embedded.
|
||||
.scalar, .flow_list, .flow_map => unreachable,
|
||||
.string => |*string| {
|
||||
if (line.indent == .indent) return error.UnexpectedIndent;
|
||||
if (line.indent == .indent)
|
||||
return error.UnexpectedIndent;
|
||||
|
||||
if (!flop and line.indent == .dedent) {
|
||||
// kick off the last trailing space or newline
|
||||
_ = string.pop();
|
||||
|
||||
var dedent_depth = line.indent.dedent;
|
||||
@@ -600,9 +700,9 @@ pub const Parser = struct {
|
||||
.comment => unreachable,
|
||||
.in_line => |in_line| switch (in_line) {
|
||||
.empty => unreachable,
|
||||
.string => |str| {
|
||||
.line_string, .space_string => |str| {
|
||||
try string.appendSlice(str);
|
||||
if (str[str.len - 1] != '\n') try string.append(' ');
|
||||
try string.append(in_line.lineEnding());
|
||||
},
|
||||
else => return error.UnexpectedValue,
|
||||
},
|
||||
@@ -610,8 +710,15 @@ pub const Parser = struct {
|
||||
}
|
||||
},
|
||||
.list => |*list| {
|
||||
// detect that the previous item was actually empty
|
||||
//
|
||||
// -
|
||||
// - something
|
||||
//
|
||||
// the first line here creates the expect_shift, but the second line
|
||||
// is a valid continuation of the list despite not being indented
|
||||
if (expect_shift == .indent and line.indent != .indent)
|
||||
try list.append(try valueFromString(arena_alloc, ""));
|
||||
try list.append(Value.newScalar(arena_alloc));
|
||||
|
||||
// Consider:
|
||||
//
|
||||
@@ -643,15 +750,14 @@ pub const Parser = struct {
|
||||
expect_shift = .dedent;
|
||||
switch (in_line) {
|
||||
.empty => unreachable,
|
||||
.scalar => |str| try list.append(try valueFromString(arena_alloc, str)),
|
||||
.scalar => |str| try list.append(try Value.fromScalar(arena_alloc, str)),
|
||||
.flow_list => |str| try list.append(try parseFlowList(arena_alloc, str, self.dupe_behavior)),
|
||||
.flow_map => |str| try list.append(try parseFlowMap(arena_alloc, str, self.dupe_behavior)),
|
||||
.string => |str| {
|
||||
.line_string, .space_string => |str| {
|
||||
// string pushes the stack
|
||||
const new_string = try appendListGetValue(list, try valueFromString(arena_alloc, str));
|
||||
const new_string = try appendListGetValue(list, try Value.fromString(arena_alloc, str));
|
||||
|
||||
if (str[str.len - 1] != '\n')
|
||||
try new_string.string.append(' ');
|
||||
try new_string.string.append(in_line.lineEnding());
|
||||
|
||||
try stack.append(new_string);
|
||||
expect_shift = .none;
|
||||
@@ -665,7 +771,8 @@ pub const Parser = struct {
|
||||
expect_shift = .none;
|
||||
switch (value) {
|
||||
.empty => expect_shift = .indent,
|
||||
.scalar, .string => |str| try list.append(try valueFromString(arena_alloc, chopNewline(str))),
|
||||
.scalar => |str| try list.append(try Value.fromScalar(arena_alloc, str)),
|
||||
.line_string, .space_string => |str| try list.append(try Value.fromString(arena_alloc, str)),
|
||||
.flow_list => |str| try list.append(try parseFlowList(arena_alloc, str, self.dupe_behavior)),
|
||||
.flow_map => |str| try list.append(try parseFlowMap(arena_alloc, str, self.dupe_behavior)),
|
||||
}
|
||||
@@ -675,13 +782,14 @@ pub const Parser = struct {
|
||||
if (expect_shift != .indent)
|
||||
return error.UnexpectedIndent;
|
||||
|
||||
const new_list = try appendListGetValue(list, .{ .list = List(Value).init(arena_alloc) });
|
||||
const new_list = try appendListGetValue(list, Value.newList(arena_alloc));
|
||||
try stack.append(new_list);
|
||||
|
||||
expect_shift = .none;
|
||||
switch (value) {
|
||||
.empty => expect_shift = .indent,
|
||||
.scalar, .string => |str| try new_list.list.append(try valueFromString(arena_alloc, chopNewline(str))),
|
||||
.scalar => |str| try new_list.list.append(try Value.fromScalar(arena_alloc, str)),
|
||||
.line_string, .space_string => |str| try new_list.list.append(try Value.fromString(arena_alloc, str)),
|
||||
.flow_list => |str| try new_list.list.append(try parseFlowList(arena_alloc, str, self.dupe_behavior)),
|
||||
.flow_map => |str| try new_list.list.append(try parseFlowMap(arena_alloc, str, self.dupe_behavior)),
|
||||
}
|
||||
@@ -701,7 +809,7 @@ pub const Parser = struct {
|
||||
if (line.indent != .indent)
|
||||
return error.UnexpectedValue;
|
||||
|
||||
const new_map = try appendListGetValue(list, .{ .map = Map(Value).init(arena_alloc) });
|
||||
const new_map = try appendListGetValue(list, Value.newMap(arena_alloc));
|
||||
try stack.append(new_map);
|
||||
expect_shift = .none;
|
||||
|
||||
@@ -710,7 +818,8 @@ pub const Parser = struct {
|
||||
dangling_key = pair.key;
|
||||
expect_shift = .indent;
|
||||
},
|
||||
.scalar, .string => |str| try new_map.map.put(pair.key, try valueFromString(arena_alloc, chopNewline(str))),
|
||||
.scalar => |str| try new_map.map.put(pair.key, try Value.fromScalar(arena_alloc, str)),
|
||||
.line_string, .space_string => |str| try new_map.map.put(pair.key, try Value.fromString(arena_alloc, str)),
|
||||
.flow_list => |str| try new_map.map.put(pair.key, try parseFlowList(arena_alloc, str, self.dupe_behavior)),
|
||||
.flow_map => |str| try new_map.map.put(pair.key, try parseFlowMap(arena_alloc, str, self.dupe_behavior)),
|
||||
}
|
||||
@@ -718,11 +827,18 @@ pub const Parser = struct {
|
||||
}
|
||||
},
|
||||
.map => |*map| {
|
||||
// detect that the previous item was actually empty
|
||||
//
|
||||
// foo:
|
||||
// bar: baz
|
||||
//
|
||||
// the first line here creates the expect_shift, but the second line
|
||||
// is a valid continuation of the map despite not being indented
|
||||
if (expect_shift == .indent and line.indent != .indent) {
|
||||
try putMap(
|
||||
map,
|
||||
dangling_key orelse return error.Fail,
|
||||
try valueFromString(arena_alloc, ""),
|
||||
Value.newScalar(arena_alloc),
|
||||
self.dupe_behavior,
|
||||
);
|
||||
dangling_key = null;
|
||||
@@ -749,15 +865,15 @@ pub const Parser = struct {
|
||||
|
||||
switch (in_line) {
|
||||
.empty => unreachable,
|
||||
.scalar => |str| try putMap(map, dangling_key.?, try valueFromString(arena_alloc, str), self.dupe_behavior),
|
||||
.scalar => |str| try putMap(map, dangling_key.?, try Value.fromScalar(arena_alloc, str), self.dupe_behavior),
|
||||
.flow_list => |str| try putMap(map, dangling_key.?, try parseFlowList(arena_alloc, str, self.dupe_behavior), self.dupe_behavior),
|
||||
.flow_map => |str| {
|
||||
try putMap(map, dangling_key.?, try parseFlowMap(arena_alloc, str, self.dupe_behavior), self.dupe_behavior);
|
||||
},
|
||||
.string => |str| {
|
||||
.line_string, .space_string => |str| {
|
||||
// string pushes the stack
|
||||
const new_string = try putMapGetValue(map, dangling_key.?, try valueFromString(arena_alloc, str), self.dupe_behavior);
|
||||
if (str[str.len - 1] != '\n') try new_string.string.append(' ');
|
||||
const new_string = try putMapGetValue(map, dangling_key.?, try Value.fromString(arena_alloc, str), self.dupe_behavior);
|
||||
try new_string.string.append(in_line.lineEnding());
|
||||
try stack.append(new_string);
|
||||
expect_shift = .none;
|
||||
},
|
||||
@@ -777,14 +893,15 @@ pub const Parser = struct {
|
||||
if (expect_shift != .indent or line.indent != .indent or dangling_key == null)
|
||||
return error.UnexpectedValue;
|
||||
|
||||
const new_list = try putMapGetValue(map, dangling_key.?, .{ .list = List(Value).init(arena_alloc) }, self.dupe_behavior);
|
||||
const new_list = try putMapGetValue(map, dangling_key.?, Value.newList(arena_alloc), self.dupe_behavior);
|
||||
try stack.append(new_list);
|
||||
dangling_key = null;
|
||||
|
||||
expect_shift = .none;
|
||||
switch (value) {
|
||||
.empty => expect_shift = .indent,
|
||||
.scalar, .string => |str| try new_list.list.append(try valueFromString(arena_alloc, chopNewline(str))),
|
||||
.scalar => |str| try new_list.list.append(try Value.fromScalar(arena_alloc, str)),
|
||||
.line_string, .space_string => |str| try new_list.list.append(try Value.fromString(arena_alloc, str)),
|
||||
.flow_list => |str| try new_list.list.append(try parseFlowList(arena_alloc, str, self.dupe_behavior)),
|
||||
.flow_map => |str| try new_list.list.append(try parseFlowMap(arena_alloc, str, self.dupe_behavior)),
|
||||
}
|
||||
@@ -798,7 +915,8 @@ pub const Parser = struct {
|
||||
expect_shift = .indent;
|
||||
dangling_key = pair.key;
|
||||
},
|
||||
.scalar, .string => |str| try putMap(map, pair.key, try valueFromString(arena_alloc, chopNewline(str)), self.dupe_behavior),
|
||||
.scalar => |str| try putMap(map, pair.key, try Value.fromScalar(arena_alloc, str), self.dupe_behavior),
|
||||
.line_string, .space_string => |str| try putMap(map, pair.key, try Value.fromString(arena_alloc, str), self.dupe_behavior),
|
||||
.flow_list => |str| try putMap(map, pair.key, try parseFlowList(arena_alloc, str, self.dupe_behavior), self.dupe_behavior),
|
||||
.flow_map => |str| try putMap(map, pair.key, try parseFlowMap(arena_alloc, str, self.dupe_behavior), self.dupe_behavior),
|
||||
},
|
||||
@@ -806,7 +924,7 @@ pub const Parser = struct {
|
||||
.indent => {
|
||||
if (expect_shift != .indent or dangling_key == null) return error.UnexpectedValue;
|
||||
|
||||
const new_map = try putMapGetValue(map, dangling_key.?, .{ .map = Map(Value).init(arena_alloc) }, self.dupe_behavior);
|
||||
const new_map = try putMapGetValue(map, dangling_key.?, Value.newMap(arena_alloc), self.dupe_behavior);
|
||||
try stack.append(new_map);
|
||||
dangling_key = null;
|
||||
|
||||
@@ -815,7 +933,8 @@ pub const Parser = struct {
|
||||
expect_shift = .indent;
|
||||
dangling_key = pair.key;
|
||||
},
|
||||
.scalar, .string => |str| try new_map.map.put(pair.key, try valueFromString(arena_alloc, chopNewline(str))),
|
||||
.scalar => |str| try new_map.map.put(pair.key, try Value.fromScalar(arena_alloc, str)),
|
||||
.line_string, .space_string => |str| try new_map.map.put(pair.key, try Value.fromString(arena_alloc, str)),
|
||||
.flow_list => |str| try new_map.map.put(pair.key, try parseFlowList(arena_alloc, str, self.dupe_behavior)),
|
||||
.flow_map => |str| try new_map.map.put(pair.key, try parseFlowMap(arena_alloc, str, self.dupe_behavior)),
|
||||
}
|
||||
@@ -837,17 +956,18 @@ pub const Parser = struct {
|
||||
switch (state) {
|
||||
.initial => switch (self.default_object) {
|
||||
.string => document.root = .{ .string = std.ArrayList(u8).init(arena_alloc) },
|
||||
.list => document.root = .{ .list = List(Value).init(arena_alloc) },
|
||||
.map => document.root = .{ .map = Map(Value).init(arena_alloc) },
|
||||
.list => document.root = Value.newList(arena_alloc),
|
||||
.map => document.root = Value.newMap(arena_alloc),
|
||||
.fail => return error.EmptyDocument,
|
||||
},
|
||||
.value => switch (stack.getLast().*) {
|
||||
// remove the final trailing newline or space
|
||||
.string => |*string| _ = string.popOrNull(),
|
||||
.scalar, .string => |*string| _ = string.popOrNull(),
|
||||
// if we have a dangling -, attach an empty string to it
|
||||
.list => |*list| if (expect_shift == .indent) try list.append(try valueFromString(arena_alloc, "")),
|
||||
.list => |*list| if (expect_shift == .indent) try list.append(Value.newScalar(arena_alloc)),
|
||||
// if we have a dangling "key:", attach an empty string to it
|
||||
.map => |*map| if (dangling_key) |dk| try putMap(map, dk, try valueFromString(arena_alloc, ""), self.dupe_behavior),
|
||||
.map => |*map| if (dangling_key) |dk| try putMap(map, dk, Value.newScalar(arena_alloc), self.dupe_behavior),
|
||||
.flow_list, .flow_map => {},
|
||||
},
|
||||
.done => {},
|
||||
}
|
||||
@@ -855,16 +975,6 @@ pub const Parser = struct {
|
||||
return document;
|
||||
}
|
||||
|
||||
inline fn chopNewline(buf: []const u8) []const u8 {
|
||||
return if (buf.len > 0 and buf[buf.len - 1] == '\n') buf[0 .. buf.len - 1] else buf;
|
||||
}
|
||||
|
||||
fn valueFromString(alloc: std.mem.Allocator, buffer: []const u8) Error!Value {
|
||||
var result: Value = .{ .string = try std.ArrayList(u8).initCapacity(alloc, buffer.len) };
|
||||
result.string.appendSliceAssumeCapacity(buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn parseFlowList(alloc: std.mem.Allocator, contents: []const u8, dupe_behavior: DuplicateKeyBehavior) Error!Value {
|
||||
var parser = try FlowParser.initList(alloc, contents);
|
||||
defer parser.deinit();
|
||||
@@ -879,16 +989,16 @@ pub const Parser = struct {
|
||||
return try parser.parse(dupe_behavior);
|
||||
}
|
||||
|
||||
inline fn appendListGetValue(list: *List(Value), value: Value) Error!*Value {
|
||||
inline fn appendListGetValue(list: *Value.List, value: Value) Error!*Value {
|
||||
try list.append(value);
|
||||
return &list.items[list.items.len - 1];
|
||||
}
|
||||
|
||||
inline fn putMap(map: *Map(Value), key: []const u8, value: Value, dupe_behavior: DuplicateKeyBehavior) Error!void {
|
||||
inline fn putMap(map: *Value.Map, key: []const u8, value: Value, dupe_behavior: DuplicateKeyBehavior) Error!void {
|
||||
_ = try putMapGetValue(map, key, value, dupe_behavior);
|
||||
}
|
||||
|
||||
inline fn putMapGetValue(map: *Map(Value), key: []const u8, value: Value, dupe_behavior: DuplicateKeyBehavior) Error!*Value {
|
||||
inline fn putMapGetValue(map: *Value.Map, key: []const u8, value: Value, dupe_behavior: DuplicateKeyBehavior) Error!*Value {
|
||||
const gop = try map.getOrPut(key);
|
||||
|
||||
if (gop.found_existing)
|
||||
@@ -948,13 +1058,10 @@ pub const Parser = struct {
|
||||
};
|
||||
|
||||
pub const FlowParser = struct {
|
||||
pub const Value = Parser.Value;
|
||||
|
||||
const FlowStackItem = struct {
|
||||
value: *Value,
|
||||
// lists need this. maps do also for keys and values.
|
||||
item_start: usize = 0,
|
||||
dangling_key: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
const FlowStack: type = std.ArrayList(FlowStackItem);
|
||||
@@ -1017,52 +1124,29 @@ pub const FlowParser = struct {
|
||||
stack.items[stack.items.len - 1].item_start = start;
|
||||
}
|
||||
|
||||
inline fn setStackDanglingKey(stack: FlowStack, key: []const u8) Error!void {
|
||||
if (stack.items.len == 0) return error.BadState;
|
||||
stack.items[stack.items.len - 1].dangling_key = key;
|
||||
}
|
||||
inline fn popStack(self: *FlowParser) Parser.Error!ParseState {
|
||||
if (self.stack.popOrNull() == null)
|
||||
return error.BadState;
|
||||
|
||||
inline fn popStack(self: *FlowParser, idx: usize) Parser.Error!void {
|
||||
const finished = self.stack.popOrNull() orelse return error.BadState;
|
||||
if (finished.value.* == .list) {
|
||||
// this is not valid if we are in the want_list_separator state because
|
||||
// there is no trailing comma in that state
|
||||
const parent = self.stack.getLastOrNull() orelse return .done;
|
||||
|
||||
if (self.state == .want_list_item and (finished.value.list.items.len > 0 or idx > finished.item_start + 1))
|
||||
try finished.value.list.append(
|
||||
try Parser.valueFromString(self.alloc, ""),
|
||||
)
|
||||
else if (self.state == .consuming_list_item)
|
||||
try finished.value.list.append(
|
||||
try Parser.valueFromString(
|
||||
self.alloc,
|
||||
self.buffer[finished.item_start..idx],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const parent = self.stack.getLastOrNull() orelse {
|
||||
self.state = .done;
|
||||
return;
|
||||
};
|
||||
|
||||
switch (parent.value.*) {
|
||||
.list => self.state = .want_list_separator,
|
||||
.map => self.state = .want_map_separator,
|
||||
return switch (parent.value.*) {
|
||||
.flow_list => .want_list_separator,
|
||||
.flow_map => .want_map_separator,
|
||||
else => return error.BadState,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn parse(self: *FlowParser, dupe_behavior: Parser.DuplicateKeyBehavior) Parser.Error!Value {
|
||||
// prime the stack:
|
||||
switch (self.state) {
|
||||
.want_list_item => {
|
||||
self.root = Value.newList(self.alloc);
|
||||
self.root = Value.newFlowList(self.alloc);
|
||||
self.stack = try FlowStack.initCapacity(self.alloc, 1);
|
||||
self.stack.appendAssumeCapacity(.{ .value = &self.root });
|
||||
},
|
||||
.want_map_key => {
|
||||
self.root = Value.newMap(self.alloc);
|
||||
self.root = Value.newFlowMap(self.alloc);
|
||||
self.stack = try FlowStack.initCapacity(self.alloc, 1);
|
||||
self.stack.appendAssumeCapacity(.{ .value = &self.root });
|
||||
},
|
||||
@@ -1071,6 +1155,8 @@ pub const FlowParser = struct {
|
||||
},
|
||||
}
|
||||
|
||||
var dangling_key: ?[]const u8 = null;
|
||||
|
||||
charloop: for (self.buffer, 0..) |char, idx| {
|
||||
// std.debug.print("{s} => {c}\n", .{ @tagName(self.state), char });
|
||||
switch (self.state) {
|
||||
@@ -1079,15 +1165,15 @@ pub const FlowParser = struct {
|
||||
',' => {
|
||||
// empty value
|
||||
const tip = try getStackTip(self.stack);
|
||||
try tip.value.list.append(try Value.fromString(self.alloc, ""));
|
||||
try tip.value.flow_list.append(Value.newScalar(self.alloc));
|
||||
tip.item_start = idx + 1;
|
||||
},
|
||||
'{' => {
|
||||
const tip = try getStackTip(self.stack);
|
||||
|
||||
const new_map = try Parser.appendListGetValue(
|
||||
&tip.value.list,
|
||||
Value.newMap(self.alloc),
|
||||
&tip.value.flow_list,
|
||||
Value.newFlowMap(self.alloc),
|
||||
);
|
||||
|
||||
tip.item_start = idx;
|
||||
@@ -1098,15 +1184,20 @@ pub const FlowParser = struct {
|
||||
const tip = try getStackTip(self.stack);
|
||||
|
||||
const new_list = try Parser.appendListGetValue(
|
||||
&tip.value.list,
|
||||
Value.newList(self.alloc),
|
||||
&tip.value.flow_list,
|
||||
Value.newFlowList(self.alloc),
|
||||
);
|
||||
|
||||
tip.item_start = idx;
|
||||
try self.stack.append(.{ .value = new_list, .item_start = idx + 1 });
|
||||
self.state = .want_list_item;
|
||||
},
|
||||
']' => try self.popStack(idx),
|
||||
']' => {
|
||||
const finished = self.stack.getLastOrNull() orelse return error.BadState;
|
||||
if (finished.value.flow_list.items.len > 0 or idx > finished.item_start)
|
||||
try finished.value.flow_list.append(Value.newScalar(self.alloc));
|
||||
self.state = try self.popStack();
|
||||
},
|
||||
else => {
|
||||
try setStackItemStart(self.stack, idx);
|
||||
self.state = .consuming_list_item;
|
||||
@@ -1116,14 +1207,20 @@ pub const FlowParser = struct {
|
||||
',' => {
|
||||
const tip = try getStackTip(self.stack);
|
||||
|
||||
try tip.value.list.append(
|
||||
try Value.fromString(self.alloc, self.buffer[tip.item_start..idx]),
|
||||
try tip.value.flow_list.append(
|
||||
try Value.fromScalar(self.alloc, self.buffer[tip.item_start..idx]),
|
||||
);
|
||||
tip.item_start = idx + 1;
|
||||
|
||||
self.state = .want_list_item;
|
||||
},
|
||||
']' => try self.popStack(idx),
|
||||
']' => {
|
||||
const finished = self.stack.getLastOrNull() orelse return error.BadState;
|
||||
try finished.value.flow_list.append(
|
||||
try Value.fromScalar(self.alloc, self.buffer[finished.item_start..idx]),
|
||||
);
|
||||
self.state = try self.popStack();
|
||||
},
|
||||
else => continue :charloop,
|
||||
},
|
||||
.want_list_separator => switch (char) {
|
||||
@@ -1132,7 +1229,7 @@ pub const FlowParser = struct {
|
||||
try setStackItemStart(self.stack, idx);
|
||||
self.state = .want_list_item;
|
||||
},
|
||||
']' => try self.popStack(idx),
|
||||
']' => self.state = try self.popStack(),
|
||||
else => return error.BadToken,
|
||||
},
|
||||
.want_map_key => switch (char) {
|
||||
@@ -1143,10 +1240,10 @@ pub const FlowParser = struct {
|
||||
'{', '[', '#', '>', '|', ',' => return error.BadToken,
|
||||
':' => {
|
||||
// we have an empty map key
|
||||
try setStackDanglingKey(self.stack, "");
|
||||
dangling_key = "";
|
||||
self.state = .want_map_value;
|
||||
},
|
||||
'}' => try self.popStack(idx),
|
||||
'}' => self.state = try self.popStack(),
|
||||
else => {
|
||||
try setStackItemStart(self.stack, idx);
|
||||
self.state = .consuming_map_key;
|
||||
@@ -1155,7 +1252,7 @@ pub const FlowParser = struct {
|
||||
.consuming_map_key => switch (char) {
|
||||
':' => {
|
||||
const tip = try getStackTip(self.stack);
|
||||
tip.dangling_key = self.buffer[tip.item_start..idx];
|
||||
dangling_key = self.buffer[tip.item_start..idx];
|
||||
|
||||
self.state = .want_map_value;
|
||||
},
|
||||
@@ -1166,51 +1263,55 @@ pub const FlowParser = struct {
|
||||
',' => {
|
||||
const tip = try getStackTip(self.stack);
|
||||
try Parser.putMap(
|
||||
&tip.value.map,
|
||||
tip.dangling_key.?,
|
||||
try Parser.valueFromString(self.alloc, ""),
|
||||
&tip.value.flow_map,
|
||||
dangling_key.?,
|
||||
Value.newScalar(self.alloc),
|
||||
dupe_behavior,
|
||||
);
|
||||
|
||||
dangling_key = null;
|
||||
self.state = .want_map_key;
|
||||
},
|
||||
'[' => {
|
||||
const tip = try getStackTip(self.stack);
|
||||
|
||||
const new_list = try Parser.putMapGetValue(
|
||||
&tip.value.map,
|
||||
tip.dangling_key.?,
|
||||
Value.newList(self.alloc),
|
||||
&tip.value.flow_map,
|
||||
dangling_key.?,
|
||||
Value.newFlowList(self.alloc),
|
||||
dupe_behavior,
|
||||
);
|
||||
|
||||
try self.stack.append(.{ .value = new_list, .item_start = idx + 1 });
|
||||
dangling_key = null;
|
||||
self.state = .want_list_item;
|
||||
},
|
||||
'{' => {
|
||||
const tip = try getStackTip(self.stack);
|
||||
|
||||
const new_map = try Parser.putMapGetValue(
|
||||
&tip.value.map,
|
||||
tip.dangling_key.?,
|
||||
Value.newMap(self.alloc),
|
||||
&tip.value.flow_map,
|
||||
dangling_key.?,
|
||||
Value.newFlowMap(self.alloc),
|
||||
dupe_behavior,
|
||||
);
|
||||
|
||||
try self.stack.append(.{ .value = new_map });
|
||||
dangling_key = null;
|
||||
self.state = .want_map_key;
|
||||
},
|
||||
'}' => {
|
||||
// the value is an empty string and this map is closed
|
||||
const tip = try getStackTip(self.stack);
|
||||
try Parser.putMap(
|
||||
&tip.value.map,
|
||||
tip.dangling_key.?,
|
||||
try Parser.valueFromString(self.alloc, ""),
|
||||
&tip.value.flow_map,
|
||||
dangling_key.?,
|
||||
Value.newScalar(self.alloc),
|
||||
dupe_behavior,
|
||||
);
|
||||
|
||||
try self.popStack(idx);
|
||||
dangling_key = null;
|
||||
self.state = try self.popStack();
|
||||
},
|
||||
else => {
|
||||
try setStackItemStart(self.stack, idx);
|
||||
@@ -1221,21 +1322,21 @@ pub const FlowParser = struct {
|
||||
',', '}' => |term| {
|
||||
const tip = try getStackTip(self.stack);
|
||||
try Parser.putMap(
|
||||
&tip.value.map,
|
||||
tip.dangling_key.?,
|
||||
try Parser.valueFromString(self.alloc, self.buffer[tip.item_start..idx]),
|
||||
&tip.value.flow_map,
|
||||
dangling_key.?,
|
||||
try Value.fromScalar(self.alloc, self.buffer[tip.item_start..idx]),
|
||||
dupe_behavior,
|
||||
);
|
||||
|
||||
dangling_key = null;
|
||||
self.state = .want_map_key;
|
||||
if (term == '}') try self.popStack(idx);
|
||||
if (term == '}') self.state = try self.popStack();
|
||||
},
|
||||
else => continue :charloop,
|
||||
},
|
||||
.want_map_separator => switch (char) {
|
||||
' ', '\t' => continue :charloop,
|
||||
',' => self.state = .want_map_key,
|
||||
'}' => try self.popStack(idx),
|
||||
'}' => self.state = try self.popStack(),
|
||||
else => return error.BadToken,
|
||||
},
|
||||
// the root value was closed but there are characters remaining
|
||||
|
||||
Reference in New Issue
Block a user