const c = @import("c.zig"); const fs = @import("std").fs; const std = @import("std"); const debug = @import("std").debug; pub const OutputConfig = struct { const Type = enum { window, }; const FilterMode = enum(c.GLuint) { nearest = c.GL_NEAREST, linear = c.GL_LINEAR, }; type: Type, width: i32, height: i32, filter: FilterMode, // only for 'window' monitor: []const u8, fullscreen: bool, }; pub const ParameterConfig = struct { name: []const u8, type: []const u8, }; pub const OSCConfig = union(enum) { const Protocol = enum { udp, tcp, unix, }; Manual: struct { protocol: Protocol = Protocol.udp, port: u16 = 9000, }, URL: []const u8, }; const defaultOutput = OutputConfig{ .type = .window, .width = 800, .height = 600, .filter = .linear, .monitor = "", .fullscreen = false, }; pub const Config = struct { width: i32, height: i32, outputs: []const OutputConfig, fragment: []const u8, parameters: []const ParameterConfig, project_root: fs.Dir, osc: OSCConfig, pub fn parse(allocator: *const std.mem.Allocator, filename: []const u8) !Config { var parser: c.yaml_parser_t = undefined; _ = c.yaml_parser_initialize(&parser); defer c.yaml_parser_delete(&parser); const file = try fs.cwd().openFile(filename, .{}); var buffer: [1024]u8 = undefined; const len: usize = try file.read(buffer[0..]); c.yaml_parser_set_input_string(&parser, buffer[0..], len); const dirname = fs.path.dirname(filename) orelse "."; debug.print("file/dirname is {s} / {s}\n", .{ filename, dirname }); var config: Config = .{ .width = 1920, .height = 1080, .outputs = ([_]OutputConfig{defaultOutput})[0..], .fragment = "", .parameters = ([0]ParameterConfig{})[0..], .project_root = try fs.cwd().openDir(dirname, .{}), .osc = .{ .URL = "osc.udp://:9000" }, }; try expectEvent(&parser, c.YAML_STREAM_START_EVENT); try expectEvent(&parser, c.YAML_DOCUMENT_START_EVENT); try expectEvent(&parser, c.YAML_MAPPING_START_EVENT); while (true) { var event: c.yaml_event_t = undefined; if (c.yaml_parser_parse(&parser, &event) != 1) return error.YAMLParserError; defer c.yaml_event_delete(&event); switch (event.type) { c.YAML_MAPPING_END_EVENT => break, c.YAML_SCALAR_EVENT => { const data = event.data.scalar; const key: []const u8 = data.value[0..data.length]; if (std.mem.eql(u8, key, "width")) { config.width = try parseInt(&parser, i32); } else if (std.mem.eql(u8, key, "height")) { config.height = try parseInt(&parser, i32); } else if (std.mem.eql(u8, key, "fragment")) { config.fragment = try parseString(&parser, allocator); } else if (std.mem.eql(u8, key, "outputs")) { config.outputs = try parseOutputs(&parser, allocator); } else if (std.mem.eql(u8, key, "parameters")) { config.parameters = try parseParameters(&parser, allocator); } else if (std.mem.eql(u8, key, "project_root")) { config.project_root = try fs.cwd().openDir(try parseString(&parser, allocator), .{}); } else if (std.mem.eql(u8, key, "osc")) { config.osc = try parseOSC(&parser, allocator); } else { debug.print("unknown key: '{s}'\n", .{key}); try skipAny(&parser); } }, else => { debug.print("unexpected event: {}\n", .{event.type}); return error.InvalidConfiguration; }, } } try expectEvent(&parser, c.YAML_DOCUMENT_END_EVENT); try expectEvent(&parser, c.YAML_STREAM_END_EVENT); return config; } }; fn expectEvent(parser: *c.yaml_parser_t, expectedType: c.yaml_event_type_t) !void { var event: c.yaml_event_t = undefined; if (c.yaml_parser_parse(parser, &event) != 1) return error.YAMLParserError; defer c.yaml_event_delete(&event); if (event.type != expectedType) { debug.print("unexpected event: {}\n", .{event.type}); return error.InvalidConfiguration; } } fn parseStringEvent(event: *c.yaml_event_t, allocator: *const std.mem.Allocator) ![]const u8 { if (event.type != c.YAML_SCALAR_EVENT) return error.InvalidType; const data = event.data.scalar; const value: []const u8 = data.value[0..data.length]; return try allocator.dupe(u8, value); } fn parseString(parser: *c.yaml_parser_t, allocator: *const std.mem.Allocator) ![]const u8 { var event: c.yaml_event_t = undefined; if (c.yaml_parser_parse(parser, &event) != 1) return error.YAMLParserError; defer c.yaml_event_delete(&event); return parseStringEvent(&event, allocator); } fn parseInt(parser: *c.yaml_parser_t, comptime T: type) !T { var event: c.yaml_event_t = undefined; if (c.yaml_parser_parse(parser, &event) != 1) return error.YAMLParserError; defer c.yaml_event_delete(&event); if (event.type != c.YAML_SCALAR_EVENT) return error.InvalidType; const data = event.data.scalar; const value: []const u8 = data.value[0..data.length]; return try std.fmt.parseInt(T, value, 10); } fn parseEnum(parser: *c.yaml_parser_t, comptime T: type) !T { var event: c.yaml_event_t = undefined; if (c.yaml_parser_parse(parser, &event) != 1) return error.YAMLParserError; defer c.yaml_event_delete(&event); if (event.type != c.YAML_SCALAR_EVENT) return error.InvalidType; const data = event.data.scalar; const value: []const u8 = data.value[0..data.length]; inline for (@typeInfo(T).@"enum".fields) |field| { if (std.mem.eql(u8, value, field.name)) return @field(T, field.name); } return error.InvalidValue; } fn parseBool(parser: *c.yaml_parser_t) !bool { var event: c.yaml_event_t = undefined; if (c.yaml_parser_parse(parser, &event) != 1) return error.YAMLParserError; defer c.yaml_event_delete(&event); if (event.type != c.YAML_SCALAR_EVENT) return error.InvalidType; const data = event.data.scalar; const value: []const u8 = data.value[0..data.length]; if (data.style != c.YAML_PLAIN_SCALAR_STYLE) return false; return std.mem.eql(u8, value, "true") or std.mem.eql(u8, value, "1"); } fn parseOutputs(parser: *c.yaml_parser_t, allocator: *const std.mem.Allocator) ![]OutputConfig { try expectEvent(parser, c.YAML_SEQUENCE_START_EVENT); var outputs = try allocator.alloc(OutputConfig, 1024); var output_count: usize = 0; while (true) { var seqEvent: c.yaml_event_t = undefined; if (c.yaml_parser_parse(parser, &seqEvent) != 1) return error.YAMLParserError; defer c.yaml_event_delete(&seqEvent); switch (seqEvent.type) { c.YAML_SEQUENCE_END_EVENT => break, c.YAML_MAPPING_START_EVENT => { const output = &outputs[output_count]; output.* = defaultOutput; while (true) { var event: c.yaml_event_t = undefined; if (c.yaml_parser_parse(parser, &event) != 1) return error.YAMLParserError; defer c.yaml_event_delete(&event); switch (event.type) { c.YAML_MAPPING_END_EVENT => break, c.YAML_SCALAR_EVENT => { const data = event.data.scalar; const key: []const u8 = data.value[0..data.length]; if (std.mem.eql(u8, key, "type")) { output.*.type = try parseEnum(parser, OutputConfig.Type); } else if (std.mem.eql(u8, key, "width")) { output.*.width = try parseInt(parser, i32); } else if (std.mem.eql(u8, key, "height")) { output.*.height = try parseInt(parser, i32); } else if (std.mem.eql(u8, key, "filter")) { output.*.filter = try parseEnum(parser, OutputConfig.FilterMode); } else if (std.mem.eql(u8, key, "monitor")) { output.*.monitor = try parseString(parser, allocator); } else if (std.mem.eql(u8, key, "fullscreen")) { output.*.fullscreen = try parseBool(parser); } else { debug.print("unknown key: '{s}'\n", .{key}); try skipAny(parser); } }, else => { debug.print("unexpected event: {}\n", .{event.type}); return error.InvalidConfiguration; }, } } output_count += 1; }, else => { debug.print("unexpected event: {}\n", .{seqEvent.type}); return error.InvalidConfiguration; }, } } _ = allocator.realloc(outputs, output_count) catch 0; return outputs[0..output_count]; } fn parseParameters(parser: *c.yaml_parser_t, allocator: *const std.mem.Allocator) ![]ParameterConfig { try expectEvent(parser, c.YAML_SEQUENCE_START_EVENT); var parameters = try allocator.alloc(ParameterConfig, 1024); var param_count: usize = 0; while (true) { var seqEvent: c.yaml_event_t = undefined; if (c.yaml_parser_parse(parser, &seqEvent) != 1) return error.YAMLParserError; defer c.yaml_event_delete(&seqEvent); switch (seqEvent.type) { c.YAML_SEQUENCE_END_EVENT => break, c.YAML_MAPPING_START_EVENT => { const param = ¶meters[param_count]; var have_name = false; var have_type = false; while (true) { var event: c.yaml_event_t = undefined; if (c.yaml_parser_parse(parser, &event) != 1) return error.YAMLParserError; defer c.yaml_event_delete(&event); switch (event.type) { c.YAML_MAPPING_END_EVENT => break, c.YAML_SCALAR_EVENT => { const data = event.data.scalar; const key: []const u8 = data.value[0..data.length]; if (std.mem.eql(u8, key, "name")) { param.*.name = try parseString(parser, allocator); have_name = true; } else if (std.mem.eql(u8, key, "type")) { param.*.type = try parseString(parser, allocator); have_type = true; } else { debug.print("unknown key: '{s}'\n", .{key}); try skipAny(parser); } }, else => { debug.print("unexpected event: {}\n", .{event.type}); return error.InvalidConfiguration; }, } } if (!(have_name and have_type)) { debug.print("name and type are mandatory for parameters.\n", .{}); return error.InvalidConfiguration; } param_count += 1; }, else => { debug.print("unexpected event: {}\n", .{seqEvent.type}); return error.InvalidConfiguration; }, } } _ = allocator.realloc(parameters, param_count) catch 0; return parameters[0..param_count]; } fn parseOSC(parser: *c.yaml_parser_t, allocator: *const std.mem.Allocator) !OSCConfig { var event: c.yaml_event_t = undefined; if (c.yaml_parser_parse(parser, &event) != 1) return error.YAMLParserError; defer c.yaml_event_delete(&event); if (event.type == c.YAML_SCALAR_EVENT) { const url = try parseStringEvent(&event, allocator); return OSCConfig{ .URL = url }; } else if (event.type != c.YAML_MAPPING_START_EVENT) { debug.print("unexpected event: {}\n", .{event.type}); return error.InvalidConfiguration; } var config: OSCConfig = .{ .Manual = .{} }; while (true) { if (c.yaml_parser_parse(parser, &event) != 1) return error.YAMLParserError; defer c.yaml_event_delete(&event); switch (event.type) { c.YAML_MAPPING_END_EVENT => break, c.YAML_SCALAR_EVENT => { const data = event.data.scalar; const key: []const u8 = data.value[0..data.length]; if (std.mem.eql(u8, key, "protocol")) { config.Manual.protocol = try parseEnum(parser, OSCConfig.Protocol); } else if (std.mem.eql(u8, key, "port")) { config.Manual.port = try parseInt(parser, u16); } else { debug.print("unknown key: '{s}'\n", .{key}); try skipAny(parser); } }, else => { debug.print("unexpected event: {}\n", .{event.type}); return error.InvalidConfiguration; }, } } return config; } fn skipAny(parser: *c.yaml_parser_t) !void { var event: c.yaml_event_t = undefined; var depth: u32 = 0; while (true) { if (c.yaml_parser_parse(parser, &event) != 1) return error.YAMLParserError; defer c.yaml_event_delete(&event); switch (event.type) { c.YAML_SCALAR_EVENT => { if (depth == 0) break; }, c.YAML_SEQUENCE_START_EVENT, c.YAML_MAPPING_START_EVENT, => depth += 1, c.YAML_SEQUENCE_END_EVENT, c.YAML_MAPPING_END_EVENT, => { depth -= 1; if (depth == 0) break; }, else => { debug.print("unexpected event: {}\n", .{event.type}); return error.InvalidConfiguration; }, } } }