Compare commits
4 Commits
master
...
63ee3867be
Author | SHA1 | Date | |
---|---|---|---|
63ee3867be
|
|||
a3c0935f1e
|
|||
a88e890974
|
|||
64dac2fd51
|
695
src/config.zig
695
src/config.zig
@@ -566,10 +566,8 @@ pub const Parser = struct {
|
|||||||
EmptyDocument,
|
EmptyDocument,
|
||||||
DuplicateKey,
|
DuplicateKey,
|
||||||
BadMapEntry,
|
BadMapEntry,
|
||||||
BadState,
|
|
||||||
BadToken,
|
|
||||||
Fail,
|
Fail,
|
||||||
} || LineTokenizer(FixedLineBuffer).Error || std.mem.Allocator.Error;
|
} || LineTokenizer(FixedLineBuffer).Error || FlowParser.Error || std.mem.Allocator.Error;
|
||||||
|
|
||||||
pub const DuplicateKeyBehavior = enum {
|
pub const DuplicateKeyBehavior = enum {
|
||||||
use_first,
|
use_first,
|
||||||
@@ -681,11 +679,11 @@ pub const Parser = struct {
|
|||||||
state = .value;
|
state = .value;
|
||||||
},
|
},
|
||||||
.flow_list => |str| {
|
.flow_list => |str| {
|
||||||
document.root = try parseFlow(arena_alloc, str, .flow_list, self.dupe_behavior);
|
document.root = try parseFlowList(arena_alloc, str, self.dupe_behavior);
|
||||||
state = .done;
|
state = .done;
|
||||||
},
|
},
|
||||||
.flow_map => |str| {
|
.flow_map => |str| {
|
||||||
document.root = try parseFlow(arena_alloc, str, .flow_map, self.dupe_behavior);
|
document.root = try parseFlowMap(arena_alloc, str, self.dupe_behavior);
|
||||||
state = .done;
|
state = .done;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -707,11 +705,11 @@ pub const Parser = struct {
|
|||||||
state = .value;
|
state = .value;
|
||||||
},
|
},
|
||||||
.flow_list => |str| {
|
.flow_list => |str| {
|
||||||
try document.root.list.append(try parseFlow(arena_alloc, str, .flow_list, self.dupe_behavior));
|
try document.root.list.append(try parseFlowList(arena_alloc, str, self.dupe_behavior));
|
||||||
state = .value;
|
state = .value;
|
||||||
},
|
},
|
||||||
.flow_map => |str| {
|
.flow_map => |str| {
|
||||||
try document.root.list.append(try parseFlow(arena_alloc, str, .flow_map, self.dupe_behavior));
|
try document.root.list.append(try parseFlowMap(arena_alloc, str, self.dupe_behavior));
|
||||||
state = .value;
|
state = .value;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -745,11 +743,11 @@ pub const Parser = struct {
|
|||||||
state = .value;
|
state = .value;
|
||||||
},
|
},
|
||||||
.flow_list => |str| {
|
.flow_list => |str| {
|
||||||
try document.root.map.put(pair.key, try parseFlow(arena_alloc, str, .flow_list, self.dupe_behavior));
|
try document.root.map.put(pair.key, try parseFlowList(arena_alloc, str, self.dupe_behavior));
|
||||||
state = .value;
|
state = .value;
|
||||||
},
|
},
|
||||||
.flow_map => |str| {
|
.flow_map => |str| {
|
||||||
try document.root.map.put(pair.key, try parseFlow(arena_alloc, str, .flow_map, self.dupe_behavior));
|
try document.root.map.put(pair.key, try parseFlowMap(arena_alloc, str, self.dupe_behavior));
|
||||||
state = .value;
|
state = .value;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -759,7 +757,7 @@ pub const Parser = struct {
|
|||||||
.value => switch (stack.getLast().*) {
|
.value => switch (stack.getLast().*) {
|
||||||
// these three states are never reachable here. flow_list and
|
// these three states are never reachable here. flow_list and
|
||||||
// flow_map are parsed with a separate state machine. These
|
// flow_map are parsed with a separate state machine. These
|
||||||
// value types can only be present by themselves as the first
|
// value tyeps can only be present by themselves as the first
|
||||||
// line of the document, in which case the document consists
|
// line of the document, in which case the document consists
|
||||||
// only of that single line: this parser jumps immediately into
|
// only of that single line: this parser jumps immediately into
|
||||||
// the .done state, bypassing the .value state in which this
|
// the .done state, bypassing the .value state in which this
|
||||||
@@ -801,7 +799,7 @@ pub const Parser = struct {
|
|||||||
//
|
//
|
||||||
// the first line here creates the expect_shift, but the second line
|
// the first line here creates the expect_shift, but the second line
|
||||||
// is a valid continuation of the list despite not being indented
|
// is a valid continuation of the list despite not being indented
|
||||||
if (!flop and (expect_shift == .indent and line.indent != .indent))
|
if (expect_shift == .indent and line.indent != .indent)
|
||||||
try list.append(Value.newScalar(arena_alloc));
|
try list.append(Value.newScalar(arena_alloc));
|
||||||
|
|
||||||
// Consider:
|
// Consider:
|
||||||
@@ -835,38 +833,52 @@ pub const Parser = struct {
|
|||||||
switch (in_line) {
|
switch (in_line) {
|
||||||
.empty => unreachable,
|
.empty => unreachable,
|
||||||
.scalar => |str| try list.append(try Value.fromScalar(arena_alloc, str)),
|
.scalar => |str| try list.append(try Value.fromScalar(arena_alloc, str)),
|
||||||
.flow_list => |str| try list.append(try parseFlow(arena_alloc, str, .flow_list, self.dupe_behavior)),
|
.flow_list => |str| try list.append(try parseFlowList(arena_alloc, str, self.dupe_behavior)),
|
||||||
.flow_map => |str| try list.append(try parseFlow(arena_alloc, str, .flow_map, self.dupe_behavior)),
|
.flow_map => |str| try list.append(try parseFlowMap(arena_alloc, str, self.dupe_behavior)),
|
||||||
.line_string, .space_string => |str| {
|
.line_string, .space_string => |str| {
|
||||||
// string pushes the stack
|
// string pushes the stack
|
||||||
const new_string = try appendListGetValue(list, try Value.fromString(arena_alloc, str));
|
const new_string = try appendListGetValue(list, try Value.fromString(arena_alloc, str));
|
||||||
try stack.append(new_string);
|
|
||||||
|
|
||||||
try new_string.string.append(in_line.lineEnding());
|
try new_string.string.append(in_line.lineEnding());
|
||||||
|
|
||||||
|
try stack.append(new_string);
|
||||||
expect_shift = .none;
|
expect_shift = .none;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.list_item => |value| {
|
.list_item => |value| {
|
||||||
if (flop or (line.indent == .none or line.indent == .dedent)) {
|
switch (line.indent) {
|
||||||
expect_shift = .none;
|
// for dedent, the stack has already been popped, so this should be fine
|
||||||
switch (value) {
|
.none, .dedent => {
|
||||||
.empty => expect_shift = .indent,
|
expect_shift = .none;
|
||||||
.scalar => |str| try list.append(try Value.fromScalar(arena_alloc, str)),
|
switch (value) {
|
||||||
.line_string, .space_string => |str| try list.append(try Value.fromString(arena_alloc, str)),
|
.empty => expect_shift = .indent,
|
||||||
.flow_list => |str| try list.append(try parseFlow(arena_alloc, str, .flow_list, self.dupe_behavior)),
|
.scalar => |str| try list.append(try Value.fromScalar(arena_alloc, str)),
|
||||||
.flow_map => |str| try list.append(try parseFlow(arena_alloc, str, .flow_map, self.dupe_behavior)),
|
.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)),
|
||||||
} else if (line.indent == .indent) {
|
.flow_map => |str| try list.append(try parseFlowMap(arena_alloc, str, self.dupe_behavior)),
|
||||||
if (expect_shift != .indent) return error.UnexpectedIndent;
|
}
|
||||||
|
},
|
||||||
|
// a new list is being created
|
||||||
|
.indent => {
|
||||||
|
if (expect_shift != .indent)
|
||||||
|
return error.UnexpectedIndent;
|
||||||
|
|
||||||
const new_list = try appendListGetValue(list, Value.newList(arena_alloc));
|
const new_list = try appendListGetValue(list, Value.newList(arena_alloc));
|
||||||
try stack.append(new_list);
|
try stack.append(new_list);
|
||||||
expect_shift = .none;
|
|
||||||
continue :flipflop;
|
expect_shift = .none;
|
||||||
} else unreachable;
|
switch (value) {
|
||||||
|
.empty => expect_shift = .indent,
|
||||||
|
.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)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
.map_item => {
|
.map_item => |pair| {
|
||||||
// this prong cannot be hit on dedent in a valid way.
|
// this prong cannot be hit on dedent in a valid way.
|
||||||
//
|
//
|
||||||
// -
|
// -
|
||||||
@@ -882,7 +894,17 @@ pub const Parser = struct {
|
|||||||
const new_map = try appendListGetValue(list, Value.newMap(arena_alloc));
|
const new_map = try appendListGetValue(list, Value.newMap(arena_alloc));
|
||||||
try stack.append(new_map);
|
try stack.append(new_map);
|
||||||
expect_shift = .none;
|
expect_shift = .none;
|
||||||
continue :flipflop;
|
|
||||||
|
switch (pair.val) {
|
||||||
|
.empty => {
|
||||||
|
dangling_key = try arena_alloc.dupe(u8, pair.key);
|
||||||
|
expect_shift = .indent;
|
||||||
|
},
|
||||||
|
.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)),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -894,7 +916,7 @@ pub const Parser = struct {
|
|||||||
//
|
//
|
||||||
// the first line here creates the expect_shift, but the second line
|
// the first line here creates the expect_shift, but the second line
|
||||||
// is a valid continuation of the map despite not being indented
|
// is a valid continuation of the map despite not being indented
|
||||||
if (!flop and (expect_shift == .indent and line.indent != .indent)) {
|
if (expect_shift == .indent and line.indent != .indent) {
|
||||||
try putMap(
|
try putMap(
|
||||||
map,
|
map,
|
||||||
dangling_key orelse return error.Fail,
|
dangling_key orelse return error.Fail,
|
||||||
@@ -926,9 +948,9 @@ pub const Parser = struct {
|
|||||||
switch (in_line) {
|
switch (in_line) {
|
||||||
.empty => unreachable,
|
.empty => unreachable,
|
||||||
.scalar => |str| try putMap(map, dangling_key.?, try Value.fromScalar(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 parseFlow(arena_alloc, str, .flow_list, self.dupe_behavior), 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| {
|
.flow_map => |str| {
|
||||||
try putMap(map, dangling_key.?, try parseFlow(arena_alloc, str, .flow_map, self.dupe_behavior), self.dupe_behavior);
|
try putMap(map, dangling_key.?, try parseFlowMap(arena_alloc, str, self.dupe_behavior), self.dupe_behavior);
|
||||||
},
|
},
|
||||||
.line_string, .space_string => |str| {
|
.line_string, .space_string => |str| {
|
||||||
// string pushes the stack
|
// string pushes the stack
|
||||||
@@ -941,7 +963,7 @@ pub const Parser = struct {
|
|||||||
|
|
||||||
dangling_key = null;
|
dangling_key = null;
|
||||||
},
|
},
|
||||||
.list_item => {
|
.list_item => |value| {
|
||||||
// this prong cannot be hit on dedent in a valid way.
|
// this prong cannot be hit on dedent in a valid way.
|
||||||
//
|
//
|
||||||
// map:
|
// map:
|
||||||
@@ -956,30 +978,50 @@ pub const Parser = struct {
|
|||||||
const new_list = try putMapGetValue(map, dangling_key.?, Value.newList(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);
|
try stack.append(new_list);
|
||||||
dangling_key = null;
|
dangling_key = null;
|
||||||
|
|
||||||
expect_shift = .none;
|
expect_shift = .none;
|
||||||
continue :flipflop;
|
switch (value) {
|
||||||
|
.empty => expect_shift = .indent,
|
||||||
|
.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)),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
.map_item => |pair| {
|
.map_item => |pair| {
|
||||||
if (flop or (line.indent == .none or line.indent == .dedent)) {
|
expect_shift = .none;
|
||||||
expect_shift = .none;
|
switch (line.indent) {
|
||||||
switch (pair.val) {
|
// for dedent, the stack has already been popped, so this should be fine
|
||||||
|
.none, .dedent => switch (pair.val) {
|
||||||
.empty => {
|
.empty => {
|
||||||
expect_shift = .indent;
|
expect_shift = .indent;
|
||||||
dangling_key = try arena_alloc.dupe(u8, pair.key);
|
dangling_key = try arena_alloc.dupe(u8, pair.key);
|
||||||
},
|
},
|
||||||
.scalar => |str| try putMap(map, pair.key, try Value.fromScalar(arena_alloc, 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),
|
.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 parseFlow(arena_alloc, str, .flow_list, self.dupe_behavior), 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 parseFlow(arena_alloc, str, .flow_map, 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),
|
||||||
}
|
},
|
||||||
} else if (line.indent == .indent) {
|
// a new map is being created
|
||||||
if (expect_shift != .indent or dangling_key == null) return error.UnexpectedValue;
|
.indent => {
|
||||||
|
if (expect_shift != .indent or dangling_key == null) return error.UnexpectedValue;
|
||||||
|
|
||||||
const new_map = try putMapGetValue(map, dangling_key.?, Value.newMap(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);
|
try stack.append(new_map);
|
||||||
dangling_key = null;
|
dangling_key = null;
|
||||||
continue :flipflop;
|
|
||||||
} else unreachable;
|
switch (pair.val) {
|
||||||
|
.empty => {
|
||||||
|
expect_shift = .indent;
|
||||||
|
dangling_key = try arena_alloc.dupe(u8, pair.key);
|
||||||
|
},
|
||||||
|
.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)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1015,261 +1057,18 @@ pub const Parser = struct {
|
|||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FlowStackItem = struct {
|
fn parseFlowList(alloc: std.mem.Allocator, contents: []const u8, dupe_behavior: DuplicateKeyBehavior) Error!Value {
|
||||||
value: *Value,
|
var parser = try FlowParser.initList(alloc, contents);
|
||||||
// lists need this. maps do also for keys and values.
|
defer parser.deinit();
|
||||||
item_start: usize = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const FlowStack: type = std.ArrayList(FlowStackItem);
|
return try parser.parse(dupe_behavior);
|
||||||
|
|
||||||
inline fn getStackTip(stack: FlowStack) Error!*FlowStackItem {
|
|
||||||
if (stack.items.len == 0) return error.BadState;
|
|
||||||
return &stack.items[stack.items.len - 1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn setStackItemStart(stack: FlowStack, start: usize) Error!void {
|
fn parseFlowMap(alloc: std.mem.Allocator, contents: []const u8, dupe_behavior: DuplicateKeyBehavior) Error!Value {
|
||||||
if (stack.items.len == 0) return error.BadState;
|
var parser = try FlowParser.initMap(alloc, contents);
|
||||||
stack.items[stack.items.len - 1].item_start = start;
|
defer parser.deinit();
|
||||||
}
|
|
||||||
|
|
||||||
inline fn popStack(stack: *FlowStack) Error!FlowParseState {
|
return try parser.parse(dupe_behavior);
|
||||||
if (stack.popOrNull() == null)
|
|
||||||
return error.BadState;
|
|
||||||
|
|
||||||
const parent = stack.getLastOrNull() orelse return .done;
|
|
||||||
|
|
||||||
return switch (parent.value.*) {
|
|
||||||
.flow_list => .want_list_separator,
|
|
||||||
.flow_map => .want_map_separator,
|
|
||||||
else => return error.BadState,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const FlowParseState = enum {
|
|
||||||
want_list_item,
|
|
||||||
consuming_list_item,
|
|
||||||
want_list_separator,
|
|
||||||
want_map_key,
|
|
||||||
consuming_map_key,
|
|
||||||
want_map_value,
|
|
||||||
consuming_map_value,
|
|
||||||
want_map_separator,
|
|
||||||
done,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn parseFlow(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
contents: []const u8,
|
|
||||||
root_type: Value.TagType,
|
|
||||||
dupe_behavior: DuplicateKeyBehavior,
|
|
||||||
) Error!Value {
|
|
||||||
// prime the stack:
|
|
||||||
|
|
||||||
var root: Value = switch (root_type) {
|
|
||||||
.flow_list => Value.newFlowList(alloc),
|
|
||||||
.flow_map => Value.newFlowMap(alloc),
|
|
||||||
else => return error.BadState,
|
|
||||||
};
|
|
||||||
var state: FlowParseState = switch (root_type) {
|
|
||||||
.flow_list => .want_list_item,
|
|
||||||
.flow_map => .want_map_key,
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
var stack = try FlowStack.initCapacity(alloc, 1);
|
|
||||||
stack.appendAssumeCapacity(.{ .value = &root });
|
|
||||||
var dangling_key: ?[]const u8 = null;
|
|
||||||
|
|
||||||
charloop: for (contents, 0..) |char, idx| {
|
|
||||||
switch (state) {
|
|
||||||
.want_list_item => switch (char) {
|
|
||||||
' ', '\t' => continue :charloop,
|
|
||||||
',' => {
|
|
||||||
// empty value
|
|
||||||
const tip = try getStackTip(stack);
|
|
||||||
try tip.value.flow_list.append(Value.newScalar(alloc));
|
|
||||||
tip.item_start = idx + 1;
|
|
||||||
},
|
|
||||||
'{' => {
|
|
||||||
const tip = try getStackTip(stack);
|
|
||||||
|
|
||||||
const new_map = try Parser.appendListGetValue(
|
|
||||||
&tip.value.flow_list,
|
|
||||||
Value.newFlowMap(alloc),
|
|
||||||
);
|
|
||||||
|
|
||||||
tip.item_start = idx;
|
|
||||||
try stack.append(.{ .value = new_map });
|
|
||||||
state = .want_map_key;
|
|
||||||
},
|
|
||||||
'[' => {
|
|
||||||
const tip = try getStackTip(stack);
|
|
||||||
|
|
||||||
const new_list = try Parser.appendListGetValue(
|
|
||||||
&tip.value.flow_list,
|
|
||||||
Value.newFlowList(alloc),
|
|
||||||
);
|
|
||||||
|
|
||||||
tip.item_start = idx;
|
|
||||||
try stack.append(.{ .value = new_list, .item_start = idx + 1 });
|
|
||||||
state = .want_list_item;
|
|
||||||
},
|
|
||||||
']' => {
|
|
||||||
const finished = 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(alloc));
|
|
||||||
state = try popStack(&stack);
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
try setStackItemStart(stack, idx);
|
|
||||||
state = .consuming_list_item;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
.consuming_list_item => switch (char) {
|
|
||||||
',' => {
|
|
||||||
const tip = try getStackTip(stack);
|
|
||||||
|
|
||||||
try tip.value.flow_list.append(
|
|
||||||
try Value.fromScalar(alloc, contents[tip.item_start..idx]),
|
|
||||||
);
|
|
||||||
tip.item_start = idx + 1;
|
|
||||||
|
|
||||||
state = .want_list_item;
|
|
||||||
},
|
|
||||||
']' => {
|
|
||||||
const finished = stack.getLastOrNull() orelse return error.BadState;
|
|
||||||
try finished.value.flow_list.append(
|
|
||||||
try Value.fromScalar(alloc, contents[finished.item_start..idx]),
|
|
||||||
);
|
|
||||||
state = try popStack(&stack);
|
|
||||||
},
|
|
||||||
else => continue :charloop,
|
|
||||||
},
|
|
||||||
.want_list_separator => switch (char) {
|
|
||||||
' ', '\t' => continue :charloop,
|
|
||||||
',' => {
|
|
||||||
try setStackItemStart(stack, idx);
|
|
||||||
state = .want_list_item;
|
|
||||||
},
|
|
||||||
']' => state = try popStack(&stack),
|
|
||||||
else => return error.BadToken,
|
|
||||||
},
|
|
||||||
.want_map_key => switch (char) {
|
|
||||||
' ', '\t' => continue :charloop,
|
|
||||||
// forbid these characters so that flow dictionary keys cannot start
|
|
||||||
// with characters that regular dictionary keys cannot start with
|
|
||||||
// (even though they're unambiguous in this specific context).
|
|
||||||
'{', '[', '#', '-', '>', '|', ',' => return error.BadToken,
|
|
||||||
':' => {
|
|
||||||
// we have an empty map key
|
|
||||||
dangling_key = "";
|
|
||||||
state = .want_map_value;
|
|
||||||
},
|
|
||||||
'}' => state = try popStack(&stack),
|
|
||||||
else => {
|
|
||||||
try setStackItemStart(stack, idx);
|
|
||||||
state = .consuming_map_key;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
.consuming_map_key => switch (char) {
|
|
||||||
':' => {
|
|
||||||
const tip = try getStackTip(stack);
|
|
||||||
dangling_key = try alloc.dupe(u8, contents[tip.item_start..idx]);
|
|
||||||
|
|
||||||
state = .want_map_value;
|
|
||||||
},
|
|
||||||
else => continue :charloop,
|
|
||||||
},
|
|
||||||
.want_map_value => switch (char) {
|
|
||||||
' ', '\t' => continue :charloop,
|
|
||||||
',' => {
|
|
||||||
const tip = try getStackTip(stack);
|
|
||||||
try Parser.putMap(
|
|
||||||
&tip.value.flow_map,
|
|
||||||
dangling_key.?,
|
|
||||||
Value.newScalar(alloc),
|
|
||||||
dupe_behavior,
|
|
||||||
);
|
|
||||||
|
|
||||||
dangling_key = null;
|
|
||||||
state = .want_map_key;
|
|
||||||
},
|
|
||||||
'[' => {
|
|
||||||
const tip = try getStackTip(stack);
|
|
||||||
|
|
||||||
const new_list = try Parser.putMapGetValue(
|
|
||||||
&tip.value.flow_map,
|
|
||||||
dangling_key.?,
|
|
||||||
Value.newFlowList(alloc),
|
|
||||||
dupe_behavior,
|
|
||||||
);
|
|
||||||
|
|
||||||
try stack.append(.{ .value = new_list, .item_start = idx + 1 });
|
|
||||||
dangling_key = null;
|
|
||||||
state = .want_list_item;
|
|
||||||
},
|
|
||||||
'{' => {
|
|
||||||
const tip = try getStackTip(stack);
|
|
||||||
|
|
||||||
const new_map = try Parser.putMapGetValue(
|
|
||||||
&tip.value.flow_map,
|
|
||||||
dangling_key.?,
|
|
||||||
Value.newFlowMap(alloc),
|
|
||||||
dupe_behavior,
|
|
||||||
);
|
|
||||||
|
|
||||||
try stack.append(.{ .value = new_map });
|
|
||||||
dangling_key = null;
|
|
||||||
state = .want_map_key;
|
|
||||||
},
|
|
||||||
'}' => {
|
|
||||||
// the value is an empty string and this map is closed
|
|
||||||
const tip = try getStackTip(stack);
|
|
||||||
try Parser.putMap(
|
|
||||||
&tip.value.flow_map,
|
|
||||||
dangling_key.?,
|
|
||||||
Value.newScalar(alloc),
|
|
||||||
dupe_behavior,
|
|
||||||
);
|
|
||||||
|
|
||||||
dangling_key = null;
|
|
||||||
state = try popStack(&stack);
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
try setStackItemStart(stack, idx);
|
|
||||||
state = .consuming_map_value;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
.consuming_map_value => switch (char) {
|
|
||||||
',', '}' => |term| {
|
|
||||||
const tip = try getStackTip(stack);
|
|
||||||
try Parser.putMap(
|
|
||||||
&tip.value.flow_map,
|
|
||||||
dangling_key.?,
|
|
||||||
try Value.fromScalar(alloc, contents[tip.item_start..idx]),
|
|
||||||
dupe_behavior,
|
|
||||||
);
|
|
||||||
dangling_key = null;
|
|
||||||
state = .want_map_key;
|
|
||||||
if (term == '}') state = try popStack(&stack);
|
|
||||||
},
|
|
||||||
else => continue :charloop,
|
|
||||||
},
|
|
||||||
.want_map_separator => switch (char) {
|
|
||||||
' ', '\t' => continue :charloop,
|
|
||||||
',' => state = .want_map_key,
|
|
||||||
'}' => state = try popStack(&stack),
|
|
||||||
else => return error.BadToken,
|
|
||||||
},
|
|
||||||
// the root value was closed but there are characters remaining
|
|
||||||
// in the buffer
|
|
||||||
.done => return error.BadState,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// we ran out of characters while still in the middle of an object
|
|
||||||
if (state != .done) return error.BadState;
|
|
||||||
|
|
||||||
return root;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn appendListGetValue(list: *Value.List, value: Value) Error!*Value {
|
inline fn appendListGetValue(list: *Value.List, value: Value) Error!*Value {
|
||||||
@@ -1339,3 +1138,297 @@ pub const Parser = struct {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const FlowParser = struct {
|
||||||
|
const FlowStackItem = struct {
|
||||||
|
value: *Value,
|
||||||
|
// lists need this. maps do also for keys and values.
|
||||||
|
item_start: usize = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const FlowStack: type = std.ArrayList(FlowStackItem);
|
||||||
|
|
||||||
|
buffer: []const u8,
|
||||||
|
root: Value,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
stack: FlowStack,
|
||||||
|
state: ParseState,
|
||||||
|
|
||||||
|
// make this an ugly state machine parser
|
||||||
|
const ParseState = enum {
|
||||||
|
want_list_item,
|
||||||
|
consuming_list_item,
|
||||||
|
want_list_separator,
|
||||||
|
want_map_key,
|
||||||
|
consuming_map_key,
|
||||||
|
want_map_value,
|
||||||
|
consuming_map_value,
|
||||||
|
want_map_separator,
|
||||||
|
done,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Error = error{
|
||||||
|
BadState,
|
||||||
|
BadToken,
|
||||||
|
} || std.mem.Allocator.Error;
|
||||||
|
|
||||||
|
pub fn initList(alloc: std.mem.Allocator, buffer: []const u8) Error!FlowParser {
|
||||||
|
return .{
|
||||||
|
.buffer = buffer,
|
||||||
|
.root = undefined,
|
||||||
|
.alloc = alloc,
|
||||||
|
.stack = undefined,
|
||||||
|
.state = .want_list_item,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initMap(alloc: std.mem.Allocator, buffer: []const u8) Error!FlowParser {
|
||||||
|
return .{
|
||||||
|
.buffer = buffer,
|
||||||
|
.root = undefined,
|
||||||
|
.alloc = alloc,
|
||||||
|
.stack = undefined,
|
||||||
|
.state = .want_map_key,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *FlowParser) void {
|
||||||
|
self.stack.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn getStackTip(stack: FlowStack) Error!*FlowStackItem {
|
||||||
|
if (stack.items.len == 0) return error.BadState;
|
||||||
|
return &stack.items[stack.items.len - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn setStackItemStart(stack: FlowStack, start: usize) Error!void {
|
||||||
|
if (stack.items.len == 0) return error.BadState;
|
||||||
|
stack.items[stack.items.len - 1].item_start = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn popStack(self: *FlowParser) Parser.Error!ParseState {
|
||||||
|
if (self.stack.popOrNull() == null)
|
||||||
|
return error.BadState;
|
||||||
|
|
||||||
|
const parent = self.stack.getLastOrNull() orelse return .done;
|
||||||
|
|
||||||
|
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.newFlowList(self.alloc);
|
||||||
|
self.stack = try FlowStack.initCapacity(self.alloc, 1);
|
||||||
|
self.stack.appendAssumeCapacity(.{ .value = &self.root });
|
||||||
|
},
|
||||||
|
.want_map_key => {
|
||||||
|
self.root = Value.newFlowMap(self.alloc);
|
||||||
|
self.stack = try FlowStack.initCapacity(self.alloc, 1);
|
||||||
|
self.stack.appendAssumeCapacity(.{ .value = &self.root });
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
return error.BadState;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
.want_list_item => switch (char) {
|
||||||
|
' ', '\t' => continue :charloop,
|
||||||
|
',' => {
|
||||||
|
// empty value
|
||||||
|
const tip = try getStackTip(self.stack);
|
||||||
|
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.flow_list,
|
||||||
|
Value.newFlowMap(self.alloc),
|
||||||
|
);
|
||||||
|
|
||||||
|
tip.item_start = idx;
|
||||||
|
try self.stack.append(.{ .value = new_map });
|
||||||
|
self.state = .want_map_key;
|
||||||
|
},
|
||||||
|
'[' => {
|
||||||
|
const tip = try getStackTip(self.stack);
|
||||||
|
|
||||||
|
const new_list = try Parser.appendListGetValue(
|
||||||
|
&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;
|
||||||
|
},
|
||||||
|
']' => {
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.consuming_list_item => switch (char) {
|
||||||
|
',' => {
|
||||||
|
const tip = try getStackTip(self.stack);
|
||||||
|
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
']' => {
|
||||||
|
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) {
|
||||||
|
' ', '\t' => continue :charloop,
|
||||||
|
',' => {
|
||||||
|
try setStackItemStart(self.stack, idx);
|
||||||
|
self.state = .want_list_item;
|
||||||
|
},
|
||||||
|
']' => self.state = try self.popStack(),
|
||||||
|
else => return error.BadToken,
|
||||||
|
},
|
||||||
|
.want_map_key => switch (char) {
|
||||||
|
' ', '\t' => continue :charloop,
|
||||||
|
// forbid these characters so that flow dictionary keys cannot start
|
||||||
|
// with characters that regular dictionary keys cannot start with
|
||||||
|
// (even though they're unambiguous in this specific context).
|
||||||
|
'{', '[', '#', '>', '|', ',' => return error.BadToken,
|
||||||
|
':' => {
|
||||||
|
// we have an empty map key
|
||||||
|
dangling_key = "";
|
||||||
|
self.state = .want_map_value;
|
||||||
|
},
|
||||||
|
'}' => self.state = try self.popStack(),
|
||||||
|
else => {
|
||||||
|
try setStackItemStart(self.stack, idx);
|
||||||
|
self.state = .consuming_map_key;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.consuming_map_key => switch (char) {
|
||||||
|
':' => {
|
||||||
|
const tip = try getStackTip(self.stack);
|
||||||
|
dangling_key = try self.alloc.dupe(u8, self.buffer[tip.item_start..idx]);
|
||||||
|
|
||||||
|
self.state = .want_map_value;
|
||||||
|
},
|
||||||
|
else => continue :charloop,
|
||||||
|
},
|
||||||
|
.want_map_value => switch (char) {
|
||||||
|
' ', '\t' => continue :charloop,
|
||||||
|
',' => {
|
||||||
|
const tip = try getStackTip(self.stack);
|
||||||
|
try Parser.putMap(
|
||||||
|
&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.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.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.flow_map,
|
||||||
|
dangling_key.?,
|
||||||
|
Value.newScalar(self.alloc),
|
||||||
|
dupe_behavior,
|
||||||
|
);
|
||||||
|
|
||||||
|
dangling_key = null;
|
||||||
|
self.state = try self.popStack();
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
try setStackItemStart(self.stack, idx);
|
||||||
|
self.state = .consuming_map_value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.consuming_map_value => switch (char) {
|
||||||
|
',', '}' => |term| {
|
||||||
|
const tip = try getStackTip(self.stack);
|
||||||
|
try Parser.putMap(
|
||||||
|
&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 == '}') self.state = try self.popStack();
|
||||||
|
},
|
||||||
|
else => continue :charloop,
|
||||||
|
},
|
||||||
|
.want_map_separator => switch (char) {
|
||||||
|
' ', '\t' => continue :charloop,
|
||||||
|
',' => self.state = .want_map_key,
|
||||||
|
'}' => self.state = try self.popStack(),
|
||||||
|
else => return error.BadToken,
|
||||||
|
},
|
||||||
|
// the root value was closed but there are characters remaining
|
||||||
|
// in the buffer
|
||||||
|
.done => return error.BadState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we ran out of characters while still in the middle of an object
|
||||||
|
if (self.state != .done) return error.BadState;
|
||||||
|
|
||||||
|
return self.root;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user