Skip to documentation
SLOP

tiny.zen.diagram.spec

Reference tiny.zen diagram spec

Defined in diagram.

API (45)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callsdiagramspec
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callersprivate sourcelib.zen.src.diagram.specfreeNonEmptydiagram.Bardeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.zen.src.diagram.specfreeNonEmptydiagram.Boxdeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.zen.src.diagram.specfreeNonEmptydiagram.ChannelMarkdeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.zen.src.diagram.specfreeNonEmptydiagram.DataRowdeinit
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsdiagram.Fielddeinitprivate sourcelib.zen.src.diagram.specfreeNonEmptydiagram.DataValuedeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.zen.src.diagram.specfreeNonEmptydiagram.Edgedeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersdiagram.DataValuedeinitprivate sourcelib.zen.src.diagram.specfreeNonEmptydiagram.Fielddeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsDiagramDocumentdeinitprivate sourcelib.zen.src.diagram.specfreeNonEmptydiagram.Framedeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsdiagram.limitsmeasurediagram.limitssurveyDiagramDocumentparsediagram.spec.LineIteratorinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsdiagram.limitsmeasurediagram.limitssurveyDiagramDocumentparsediagram.spec.LineIteratornext
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callersdiagram.XValuedeinitprivate sourcelib.zen.src.diagram.specfreeNonEmptydiagram.Pointdeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.zen.src.diagram.specfreeNonEmptydiagram.Ruledeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsdiagram.Scalesdeinitprivate sourcelib.zen.src.diagram.specfreeNonEmptydiagram.Scaledeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsDiagramDocumentdeinitdiagram.Scaledeinitdiagram.Scalesdeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.zen.src.diagram.specfreeNonEmptydiagram.Stackdeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersdiagram.XValuedeinitprivate sourcelib.zen.src.diagram.specfreeNonEmptydiagram.Textdeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsdiagram.Pointdeinitdiagram.Textdeinitprivate sourcelib.zen.src.diagram.specfreeNonEmptydiagram.XValuedeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsdiagram.specrecordKindOfdiagram.specrecordKind
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsdiagram.limitssurveyprivate sourcelib.zen.src.diagram.spec.DocumentapplyRecordprivate sourcelib.zen.src.diagram.specobjectStringdiagram.specrecordKinddiagram.specrecordKindOf
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/zen/src/diagram/root.zig:11

zig
pub const spec = @import("spec.zig");

Source: lib/zen/src/diagram/spec.zig

zig
const std = @import("std");pub const XValue = union(enum) {    number: f64,    text: []u8,    pub fn deinit(self: *XValue, allocator: std.mem.Allocator) void {        switch (self.*) {            .number => {},            .text => |text| freeNonEmpty(allocator, text),        }    }};pub const Axis = enum {    x,    y,};pub const ScaleKind = enum {    linear,    log,};pub const Scale = struct {    kind: ScaleKind = .linear,    min: ?f64 = null,    max: ?f64 = null,    base: f64 = 10,    label: []u8 = &.{},    pub fn deinit(self: *Scale, allocator: std.mem.Allocator) void {        freeNonEmpty(allocator, self.label);    }};pub const Scales = struct {    x: Scale = .{},    y: Scale = .{},    pub fn deinit(self: *Scales, allocator: std.mem.Allocator) void {        self.x.deinit(allocator);        self.y.deinit(allocator);    }};pub const Frame = struct {    width: f64 = 640,    height: f64 = 360,    inset: f64 = 44,    axes: bool = true,    title: []u8 = &.{},    x_label: []u8 = &.{},    y_label: []u8 = &.{},    background: []u8 = &.{},    y_min: ?f64 = null,    y_max: ?f64 = null,    x_min: ?f64 = null,    x_max: ?f64 = null,    pub fn deinit(self: *Frame, allocator: std.mem.Allocator) void {        freeNonEmpty(allocator, self.title);        freeNonEmpty(allocator, self.x_label);        freeNonEmpty(allocator, self.y_label);        freeNonEmpty(allocator, self.background);    }};pub const DataValue = union(enum) {    number: f64,    text: []u8,    boolean: bool,    pub fn deinit(self: *DataValue, allocator: std.mem.Allocator) void {        switch (self.*) {            .number => {},            .text => |text| freeNonEmpty(allocator, text),            .boolean => {},        }    }};pub const Field = struct {    name: []u8,    value: DataValue,    pub fn deinit(self: *Field, allocator: std.mem.Allocator) void {        freeNonEmpty(allocator, self.name);        self.value.deinit(allocator);    }};pub const DataRow = struct {    name: []u8,    fields: std.ArrayList(Field) = .empty,    pub fn deinit(self: *DataRow, allocator: std.mem.Allocator) void {        freeNonEmpty(allocator, self.name);        for (self.fields.items) |*field| field.deinit(allocator);        self.fields.deinit(allocator);    }    pub fn lookup(self: *const DataRow, name: []const u8) ?DataValue {        for (self.fields.items) |item| {            if (std.mem.eql(u8, item.name, name)) return item.value;        }        return null;    }};pub const Bar = struct {    x: []u8,    y: f64,    series: []u8 = &.{},    label: []u8 = &.{},    fill: []u8 = &.{},    pub fn deinit(self: *Bar, allocator: std.mem.Allocator) void {        freeNonEmpty(allocator, self.x);        freeNonEmpty(allocator, self.series);        freeNonEmpty(allocator, self.label);        freeNonEmpty(allocator, self.fill);    }};pub const Point = struct {    x: XValue,    y: f64,    label: []u8 = &.{},    fill: []u8 = &.{},    radius: f64 = 4,    pub fn deinit(self: *Point, allocator: std.mem.Allocator) void {        self.x.deinit(allocator);        freeNonEmpty(allocator, self.label);        freeNonEmpty(allocator, self.fill);    }};pub const Rule = struct {    x1: f64,    y1: f64,    x2: f64,    y2: f64,    stroke: []u8 = &.{},    pub fn deinit(self: *Rule, allocator: std.mem.Allocator) void {        freeNonEmpty(allocator, self.stroke);    }};pub const Text = struct {    x: XValue,    y: f64,    text: []u8,    fill: []u8 = &.{},    pub fn deinit(self: *Text, allocator: std.mem.Allocator) void {        self.x.deinit(allocator);        freeNonEmpty(allocator, self.text);        freeNonEmpty(allocator, self.fill);    }};pub const Box = struct {    x: f64,    y: f64,    width: f64,    height: f64,    text: []u8 = &.{},    fill: []u8 = &.{},    stroke: []u8 = &.{},    pub fn deinit(self: *Box, allocator: std.mem.Allocator) void {        freeNonEmpty(allocator, self.text);        freeNonEmpty(allocator, self.fill);        freeNonEmpty(allocator, self.stroke);    }};pub const Edge = struct {    x1: f64,    y1: f64,    x2: f64,    y2: f64,    label: []u8 = &.{},    stroke: []u8 = &.{},    arrow: bool = true,    pub fn deinit(self: *Edge, allocator: std.mem.Allocator) void {        freeNonEmpty(allocator, self.label);        freeNonEmpty(allocator, self.stroke);    }};pub const Stack = struct {    mark: []u8 = &.{},    pub fn deinit(self: *Stack, allocator: std.mem.Allocator) void {        freeNonEmpty(allocator, self.mark);    }};pub const Transform = union(enum) {    stack: Stack,    pub fn deinit(self: *Transform, allocator: std.mem.Allocator) void {        switch (self.*) {            .stack => |*transform| transform.deinit(allocator),        }    }};pub const ChannelMarkKind = enum {    bar,    point,    text,};pub const ChannelMark = struct {    kind: ChannelMarkKind,    data: []u8,    x: []u8,    y: []u8,    label: []u8 = &.{},    fill: []u8 = &.{},    series: []u8 = &.{},    text: []u8 = &.{},    radius: []u8 = &.{},    pub fn deinit(self: *ChannelMark, allocator: std.mem.Allocator) void {        freeNonEmpty(allocator, self.data);        freeNonEmpty(allocator, self.x);        freeNonEmpty(allocator, self.y);        freeNonEmpty(allocator, self.label);        freeNonEmpty(allocator, self.fill);        freeNonEmpty(allocator, self.series);        freeNonEmpty(allocator, self.text);        freeNonEmpty(allocator, self.radius);    }};pub const Mark = union(enum) {    bar: Bar,    point: Point,    rule: Rule,    text: Text,    box: Box,    edge: Edge,    pub fn deinit(self: *Mark, allocator: std.mem.Allocator) void {        switch (self.*) {            .bar => |*mark| mark.deinit(allocator),            .point => |*mark| mark.deinit(allocator),            .rule => |*mark| mark.deinit(allocator),            .text => |*mark| mark.deinit(allocator),            .box => |*mark| mark.deinit(allocator),            .edge => |*mark| mark.deinit(allocator),        }    }};pub const RecordKind = enum {    frame,    scale,    transform,    data,    mark,    bar,    point,    rule,    text,    box,    edge,};pub fn recordKind(name: []const u8) ?RecordKind {    inline for (@typeInfo(RecordKind).@"enum".field_names, std.meta.tags(RecordKind)) |field_name, value| {        if (std.mem.eql(u8, name, field_name)) return value;    }    return null;}pub fn recordKindOf(value: std.json.Value) !RecordKind {    const object = switch (value) {        .object => |object| object,        else => return error.InvalidRecord,    };    const kind = objectString(object, "kind") orelse return error.MissingKind;    return recordKind(kind) orelse return error.UnknownRecordKind;}pub const LineIterator = struct {    lines: std.mem.SplitIterator(u8, .scalar),    pub fn init(jsonl: []const u8) LineIterator {        return .{ .lines = std.mem.splitScalar(u8, jsonl, '\n') };    }    pub fn next(self: *LineIterator) ?[]const u8 {        while (self.lines.next()) |raw_line| {            const line = std.mem.trim(u8, raw_line, " \t\r\n");            if (line.len == 0) continue;            return line;        }        return null;    }};pub const Document = struct {    allocator: std.mem.Allocator,    frame: Frame = .{},    scales: Scales = .{},    transforms: std.ArrayList(Transform) = .empty,    data: std.ArrayList(DataRow) = .empty,    marks: std.ArrayList(Mark) = .empty,    scratch_high_water: usize = 0,    pub fn parse(allocator: std.mem.Allocator, line_scratch: []u8, jsonl: []const u8) !Document {        var document = Document{ .allocator = allocator };        errdefer document.deinit();        var scratch = std.heap.FixedBufferAllocator.init(line_scratch);        var lines = LineIterator.init(jsonl);        while (lines.next()) |line| {            scratch.reset();            var parsed = std.json.parseFromSlice(std.json.Value, scratch.allocator(), line, .{}) catch |err| switch (err) {                error.OutOfMemory => return error.OutOfMemory,                else => return error.InvalidJsonl,            };            defer parsed.deinit();            try document.applyRecord(parsed.value);            document.scratch_high_water = @max(document.scratch_high_water, scratch.end_index);        }        try validateDocument(&document);        return document;    }    pub fn deinit(self: *Document) void {        self.frame.deinit(self.allocator);        self.scales.deinit(self.allocator);        for (self.transforms.items) |*transform| transform.deinit(self.allocator);        self.transforms.deinit(self.allocator);        for (self.data.items) |*row| row.deinit(self.allocator);        self.data.deinit(self.allocator);        for (self.marks.items) |*mark| mark.deinit(self.allocator);        self.marks.deinit(self.allocator);    }    fn applyRecord(self: *Document, value: std.json.Value) !void {        const object = switch (value) {            .object => |object| object,            else => return error.InvalidRecord,        };        switch (try recordKindOf(value)) {            .frame => try self.applyFrame(object),            .scale => try self.applyScale(object),            .transform => try self.applyTransform(object),            .data => try self.applyData(object),            .mark => try self.applyChannelMark(object),            .bar => try self.marks.append(self.allocator, .{ .bar = try parseBar(self.allocator, object) }),            .point => try self.marks.append(self.allocator, .{ .point = try parsePoint(self.allocator, object) }),            .rule => try self.marks.append(self.allocator, .{ .rule = try parseRule(self.allocator, object) }),            .text => try self.marks.append(self.allocator, .{ .text = try parseText(self.allocator, object) }),            .box => try self.marks.append(self.allocator, .{ .box = try parseBox(self.allocator, object) }),            .edge => try self.marks.append(self.allocator, .{ .edge = try parseEdge(self.allocator, object) }),        }    }    fn applyFrame(self: *Document, object: std.json.ObjectMap) !void {        if (objectNumber(object, "width")) |value| self.frame.width = value;        if (objectNumber(object, "height")) |value| self.frame.height = value;        if (objectNumber(object, "inset")) |value| self.frame.inset = value;        if (objectBool(object, "axes")) |value| self.frame.axes = value;        if (objectNumber(object, "y_min")) |value| self.frame.y_min = value;        if (objectNumber(object, "y_max")) |value| self.frame.y_max = value;        if (objectNumber(object, "x_min")) |value| self.frame.x_min = value;        if (objectNumber(object, "x_max")) |value| self.frame.x_max = value;        try replaceString(self.allocator, &self.frame.title, objectString(object, "title"));        try replaceString(self.allocator, &self.frame.x_label, objectString(object, "x_label"));        try replaceString(self.allocator, &self.frame.y_label, objectString(object, "y_label"));        if (objectString(object, "background")) |background| {            if (!validPaint(background)) return error.InvalidColor;            try replaceString(self.allocator, &self.frame.background, background);        }    }    fn applyScale(self: *Document, object: std.json.ObjectMap) !void {        const axis = try parseAxis(object);        const scale = switch (axis) {            .x => &self.scales.x,            .y => &self.scales.y,        };        if (objectString(object, "type")) |value| scale.kind = try parseScaleKind(value);        if (objectNumber(object, "min")) |value| scale.min = value;        if (objectNumber(object, "max")) |value| scale.max = value;        if (objectNumber(object, "base")) |value| scale.base = value;        try replaceString(self.allocator, &scale.label, objectString(object, "label"));    }    fn applyTransform(self: *Document, object: std.json.ObjectMap) !void {        var transform = try parseTransform(self.allocator, object);        errdefer transform.deinit(self.allocator);        try self.transforms.append(self.allocator, transform);    }    fn applyData(self: *Document, object: std.json.ObjectMap) !void {        var row = try parseDataRow(self.allocator, object);        errdefer row.deinit(self.allocator);        try self.data.append(self.allocator, row);    }    fn applyChannelMark(self: *Document, object: std.json.ObjectMap) !void {        var channel = try parseChannelMark(self.allocator, object);        defer channel.deinit(self.allocator);        var matched = false;        for (self.data.items) |*row| {            if (!std.mem.eql(u8, row.name, channel.data)) continue;            matched = true;            var mark = try markFromChannel(self.allocator, channel, row);            var appended = false;            errdefer if (!appended) mark.deinit(self.allocator);            try self.marks.append(self.allocator, mark);            appended = true;        }        if (!matched) return error.UnknownData;    }};fn parseDataRow(allocator: std.mem.Allocator, object: std.json.ObjectMap) !DataRow {    var row = DataRow{ .name = try dupeDatasetName(allocator, object) };    errdefer row.deinit(allocator);    var iter = object.iterator();    while (iter.next()) |entry| {        const key = entry.key_ptr.*;        if (dataMetaKey(key)) continue;        const name = try allocator.dupe(u8, key);        var name_owned = true;        errdefer if (name_owned) allocator.free(name);        var value = try parseDataValue(allocator, entry.value_ptr.*);        var value_owned = true;        errdefer if (value_owned) value.deinit(allocator);        try row.fields.append(allocator, .{            .name = name,            .value = value,        });        name_owned = false;        value_owned = false;    }    if (row.fields.items.len == 0) return error.InvalidData;    return row;}fn parseDataValue(allocator: std.mem.Allocator, value: std.json.Value) !DataValue {    return switch (value) {        .string => |string| .{ .text = try allocator.dupe(u8, string) },        .integer => |integer| .{ .number = @floatFromInt(integer) },        .float => |float| .{ .number = float },        .number_string => |string| .{ .number = try std.fmt.parseFloat(f64, string) },        .bool => |boolean| .{ .boolean = boolean },        else => error.InvalidField,    };}fn parseChannelMark(allocator: std.mem.Allocator, object: std.json.ObjectMap) !ChannelMark {    var channel = ChannelMark{        .kind = try parseChannelMarkKind(objectString(object, "type") orelse return error.MissingField),        .data = &.{},        .x = &.{},        .y = &.{},    };    errdefer channel.deinit(allocator);    channel.data = try dupeRequiredString(allocator, object, "data");    channel.x = try dupeRequiredString(allocator, object, "x");    channel.y = try dupeRequiredString(allocator, object, "y");    channel.label = try dupeOptionalString(allocator, object, "label");    channel.fill = try dupeOptionalString(allocator, object, "fill");    channel.series = try dupeOptionalString(allocator, object, "series");    channel.text = try dupeOptionalString(allocator, object, "text");    channel.radius = try dupeOptionalString(allocator, object, "radius");    if (channel.kind == .text and channel.text.len == 0) return error.MissingField;    return channel;}fn markFromChannel(allocator: std.mem.Allocator, channel: ChannelMark, row: *const DataRow) !Mark {    return switch (channel.kind) {        .bar => .{ .bar = try barFromChannel(allocator, channel, row) },        .point => .{ .point = try pointFromChannel(allocator, channel, row) },        .text => .{ .text = try textFromChannel(allocator, channel, row) },    };}fn barFromChannel(allocator: std.mem.Allocator, channel: ChannelMark, row: *const DataRow) !Bar {    var bar = Bar{ .x = &.{}, .y = 0 };    errdefer bar.deinit(allocator);    bar.x = try requiredChannelText(allocator, row, channel.x);    bar.y = try requiredChannelNumber(row, channel.y);    bar.series = try optionalChannelText(allocator, row, channel.series);    bar.label = try optionalChannelText(allocator, row, channel.label);    bar.fill = try optionalChannelPaint(allocator, row, channel.fill);    return bar;}fn pointFromChannel(allocator: std.mem.Allocator, channel: ChannelMark, row: *const DataRow) !Point {    var point = Point{ .x = .{ .number = 0 }, .y = 0 };    errdefer point.deinit(allocator);    point.x = try requiredChannelX(allocator, row, channel.x);    point.y = try requiredChannelNumber(row, channel.y);    point.label = try optionalChannelText(allocator, row, channel.label);    point.fill = try optionalChannelPaint(allocator, row, channel.fill);    point.radius = if (channel.radius.len == 0) 4 else try requiredChannelNumber(row, channel.radius);    return point;}fn textFromChannel(allocator: std.mem.Allocator, channel: ChannelMark, row: *const DataRow) !Text {    var text = Text{ .x = .{ .number = 0 }, .y = 0, .text = &.{} };    errdefer text.deinit(allocator);    text.x = try requiredChannelX(allocator, row, channel.x);    text.y = try requiredChannelNumber(row, channel.y);    text.text = try requiredChannelText(allocator, row, channel.text);    text.fill = try optionalChannelPaint(allocator, row, channel.fill);    return text;}fn parseBar(allocator: std.mem.Allocator, object: std.json.ObjectMap) !Bar {    var bar = Bar{ .x = &.{}, .y = 0 };    errdefer bar.deinit(allocator);    bar.x = try dupeRequiredString(allocator, object, "x");    bar.y = objectNumber(object, "y") orelse return error.MissingField;    bar.series = try dupeOptionalString(allocator, object, "series");    bar.label = try dupeOptionalString(allocator, object, "label");    bar.fill = try dupePaint(allocator, object, "fill");    return bar;}fn parsePoint(allocator: std.mem.Allocator, object: std.json.ObjectMap) !Point {    var point = Point{ .x = .{ .number = 0 }, .y = 0 };    errdefer point.deinit(allocator);    point.x = try parseX(allocator, object, "x");    point.y = objectNumber(object, "y") orelse return error.MissingField;    point.label = try dupeOptionalString(allocator, object, "label");    point.fill = try dupePaint(allocator, object, "fill");    point.radius = objectNumber(object, "radius") orelse 4;    return point;}fn parseRule(allocator: std.mem.Allocator, object: std.json.ObjectMap) !Rule {    var rule = Rule{ .x1 = 0, .y1 = 0, .x2 = 0, .y2 = 0 };    errdefer rule.deinit(allocator);    rule.x1 = objectNumber(object, "x1") orelse return error.MissingField;    rule.y1 = objectNumber(object, "y1") orelse return error.MissingField;    rule.x2 = objectNumber(object, "x2") orelse return error.MissingField;    rule.y2 = objectNumber(object, "y2") orelse return error.MissingField;    rule.stroke = try dupePaint(allocator, object, "stroke");    return rule;}fn parseText(allocator: std.mem.Allocator, object: std.json.ObjectMap) !Text {    var text = Text{ .x = .{ .number = 0 }, .y = 0, .text = &.{} };    errdefer text.deinit(allocator);    text.x = try parseX(allocator, object, "x");    text.y = objectNumber(object, "y") orelse return error.MissingField;    text.text = try dupeRequiredString(allocator, object, "text");    text.fill = try dupePaint(allocator, object, "fill");    return text;}fn parseBox(allocator: std.mem.Allocator, object: std.json.ObjectMap) !Box {    var box = Box{ .x = 0, .y = 0, .width = 0, .height = 0 };    errdefer box.deinit(allocator);    box.x = objectNumber(object, "x") orelse return error.MissingField;    box.y = objectNumber(object, "y") orelse return error.MissingField;    box.width = objectNumber(object, "width") orelse return error.MissingField;    box.height = objectNumber(object, "height") orelse return error.MissingField;    if (box.width <= 0 or box.height <= 0) return error.InvalidField;    box.text = try dupeOptionalString(allocator, object, "text");    box.fill = try dupePaint(allocator, object, "fill");    box.stroke = try dupePaint(allocator, object, "stroke");    return box;}fn parseEdge(allocator: std.mem.Allocator, object: std.json.ObjectMap) !Edge {    var edge = Edge{ .x1 = 0, .y1 = 0, .x2 = 0, .y2 = 0 };    errdefer edge.deinit(allocator);    edge.x1 = objectNumber(object, "x1") orelse return error.MissingField;    edge.y1 = objectNumber(object, "y1") orelse return error.MissingField;    edge.x2 = objectNumber(object, "x2") orelse return error.MissingField;    edge.y2 = objectNumber(object, "y2") orelse return error.MissingField;    edge.label = try dupeOptionalString(allocator, object, "label");    edge.stroke = try dupePaint(allocator, object, "stroke");    edge.arrow = objectBool(object, "arrow") orelse true;    return edge;}fn parseTransform(allocator: std.mem.Allocator, object: std.json.ObjectMap) !Transform {    const op = objectString(object, "op") orelse return error.MissingField;    if (std.mem.eql(u8, op, "stack")) {        var stack = Stack{ .mark = &.{} };        errdefer stack.deinit(allocator);        stack.mark = try dupeOptionalString(allocator, object, "mark");        if (stack.mark.len != 0 and !std.mem.eql(u8, stack.mark, "bar")) return error.InvalidTransform;        return .{ .stack = stack };    }    return error.UnknownTransform;}fn parseAxis(object: std.json.ObjectMap) !Axis {    const axis = objectString(object, "axis") orelse return error.MissingField;    if (std.mem.eql(u8, axis, "x")) return .x;    if (std.mem.eql(u8, axis, "y")) return .y;    return error.InvalidScale;}fn parseScaleKind(value: []const u8) !ScaleKind {    if (std.mem.eql(u8, value, "linear")) return .linear;    if (std.mem.eql(u8, value, "log")) return .log;    return error.InvalidScale;}fn parseChannelMarkKind(value: []const u8) !ChannelMarkKind {    if (std.mem.eql(u8, value, "bar")) return .bar;    if (std.mem.eql(u8, value, "point")) return .point;    if (std.mem.eql(u8, value, "text")) return .text;    return error.UnknownMark;}fn parseX(allocator: std.mem.Allocator, object: std.json.ObjectMap, key: []const u8) !XValue {    const value = object.get(key) orelse return error.MissingField;    return switch (value) {        .string => |string| .{ .text = try allocator.dupe(u8, string) },        .integer => |integer| .{ .number = @floatFromInt(integer) },        .float => |float| .{ .number = float },        .number_string => |string| .{ .number = try std.fmt.parseFloat(f64, string) },        else => error.InvalidField,    };}fn requiredChannelValue(row: *const DataRow, field: []const u8) !DataValue {    if (field.len == 0) return error.MissingField;    return row.lookup(field) orelse error.MissingField;}fn requiredChannelNumber(row: *const DataRow, field: []const u8) !f64 {    return try dataValueNumber(try requiredChannelValue(row, field));}fn requiredChannelText(allocator: std.mem.Allocator, row: *const DataRow, field: []const u8) ![]u8 {    return try dataValueText(allocator, try requiredChannelValue(row, field));}fn requiredChannelX(allocator: std.mem.Allocator, row: *const DataRow, field: []const u8) !XValue {    return try dataValueX(allocator, try requiredChannelValue(row, field));}fn optionalChannelText(allocator: std.mem.Allocator, row: *const DataRow, field: []const u8) ![]u8 {    if (field.len == 0) return try allocator.dupe(u8, "");    return try requiredChannelText(allocator, row, field);}fn optionalChannelPaint(allocator: std.mem.Allocator, row: *const DataRow, field: []const u8) ![]u8 {    const value = try optionalChannelText(allocator, row, field);    errdefer allocator.free(value);    if (!validPaint(value)) return error.InvalidColor;    return value;}fn dataValueNumber(value: DataValue) !f64 {    return switch (value) {        .number => |number| number,        .text => |text| std.fmt.parseFloat(f64, text) catch error.InvalidField,        .boolean => error.InvalidField,    };}fn dataValueText(allocator: std.mem.Allocator, value: DataValue) ![]u8 {    return switch (value) {        .number => |number| try std.fmt.allocPrint(allocator, "{d}", .{number}),        .text => |text| try allocator.dupe(u8, text),        .boolean => |boolean| try allocator.dupe(u8, if (boolean) "true" else "false"),    };}fn dataValueX(allocator: std.mem.Allocator, value: DataValue) !XValue {    return switch (value) {        .number => |number| .{ .number = number },        .text => |text| .{ .text = try allocator.dupe(u8, text) },        .boolean => |boolean| .{ .text = try allocator.dupe(u8, if (boolean) "true" else "false") },    };}fn objectBool(object: std.json.ObjectMap, key: []const u8) ?bool {    const value = object.get(key) orelse return null;    return switch (value) {        .bool => |boolean| boolean,        else => null,    };}fn objectString(object: std.json.ObjectMap, key: []const u8) ?[]const u8 {    const value = object.get(key) orelse return null;    return switch (value) {        .string => |string| string,        else => null,    };}fn objectNumber(object: std.json.ObjectMap, key: []const u8) ?f64 {    const value = object.get(key) orelse return null;    return switch (value) {        .integer => |integer| @floatFromInt(integer),        .float => |float| float,        .number_string => |string| std.fmt.parseFloat(f64, string) catch null,        else => null,    };}fn dupeDatasetName(allocator: std.mem.Allocator, object: std.json.ObjectMap) ![]u8 {    if (objectString(object, "name")) |name| return try allocator.dupe(u8, name);    if (objectString(object, "id")) |id| return try allocator.dupe(u8, id);    return error.MissingField;}fn dupeRequiredString(allocator: std.mem.Allocator, object: std.json.ObjectMap, key: []const u8) ![]u8 {    const value = objectString(object, key) orelse return error.MissingField;    return try allocator.dupe(u8, value);}fn dupeOptionalString(allocator: std.mem.Allocator, object: std.json.ObjectMap, key: []const u8) ![]u8 {    const value = objectString(object, key) orelse return try allocator.dupe(u8, "");    return try allocator.dupe(u8, value);}fn dupePaint(allocator: std.mem.Allocator, object: std.json.ObjectMap, key: []const u8) ![]u8 {    const value = objectString(object, key) orelse return try allocator.dupe(u8, "");    if (!validPaint(value)) return error.InvalidColor;    return try allocator.dupe(u8, value);}fn replaceString(allocator: std.mem.Allocator, target: *[]u8, value: ?[]const u8) !void {    const source = value orelse return;    freeNonEmpty(allocator, target.*);    target.* = try allocator.dupe(u8, source);}fn validateDocument(document: *const Document) !void {    try validateFrame(document.frame);    try validateScale(document.scales.x);    try validateScale(document.scales.y);}fn validateFrame(frame: Frame) !void {    if (frame.width < 160 or frame.width > 4000) return error.InvalidFrame;    if (frame.height < 120 or frame.height > 3000) return error.InvalidFrame;    if (frame.inset < 8 or frame.inset > 400) return error.InvalidFrame;    if (frame.y_min != null and frame.y_max != null and frame.y_min.? >= frame.y_max.?) return error.InvalidFrame;    if (frame.x_min != null and frame.x_max != null and frame.x_min.? >= frame.x_max.?) return error.InvalidFrame;}fn validateScale(scale: Scale) !void {    if (scale.base <= 1) return error.InvalidScale;    if (scale.min != null and scale.max != null and scale.min.? >= scale.max.?) return error.InvalidScale;    if (scale.kind == .log) {        if (scale.min != null and scale.min.? <= 0) return error.InvalidScale;        if (scale.max != null and scale.max.? <= 0) return error.InvalidScale;    }}fn validPaint(value: []const u8) bool {    if (value.len == 0) return true;    if (value.len != 4 and value.len != 7) return false;    if (value[0] != '#') return false;    for (value[1..]) |byte| {        if (!std.ascii.isHex(byte)) return false;    }    return true;}fn dataMetaKey(key: []const u8) bool {    return std.mem.eql(u8, key, "kind") or std.mem.eql(u8, key, "name") or std.mem.eql(u8, key, "id");}fn freeNonEmpty(allocator: std.mem.Allocator, bytes: []u8) void {    if (bytes.len > 0) allocator.free(bytes);}test "diagram spec parses frame and bars" {    const allocator = std.testing.allocator;    const input =        \\{"kind":"frame","width":500,"height":280,"title":"Coin","x_label":"outcome","y_label":"p","y_min":0,"y_max":1}        \\{"kind":"bar","x":"heads","y":0.6,"fill":"#245f8d"}        \\{"kind":"bar","x":"tails","y":0.4,"fill":"#8f3a32"}        \\    ;    var document = try parseForTest(allocator, input);    defer document.deinit();    try std.testing.expectEqual(@as(usize, 2), document.marks.items.len);    try std.testing.expectEqualStrings("Coin", document.frame.title);    try std.testing.expectEqualStrings("heads", document.marks.items[0].bar.x);}test "diagram spec parses scales transforms and series" {    const allocator = std.testing.allocator;    const input =        \\{"kind":"frame","width":500,"height":280,"title":"Counts"}        \\{"kind":"scale","axis":"y","type":"log","min":1,"max":1000,"label":"events","base":10}        \\{"kind":"transform","op":"stack","mark":"bar"}        \\{"kind":"bar","x":"a","y":10,"series":"first","fill":"#245f8d"}        \\{"kind":"bar","x":"a","y":40,"series":"second","fill":"#8f3a32"}        \\    ;    var document = try parseForTest(allocator, input);    defer document.deinit();    try std.testing.expectEqual(ScaleKind.log, document.scales.y.kind);    try std.testing.expectEqual(@as(f64, 1), document.scales.y.min.?);    try std.testing.expectEqual(@as(f64, 1000), document.scales.y.max.?);    try std.testing.expectEqualStrings("events", document.scales.y.label);    try std.testing.expectEqual(@as(usize, 1), document.transforms.items.len);    try std.testing.expectEqualStrings("bar", document.transforms.items[0].stack.mark);    try std.testing.expectEqualStrings("second", document.marks.items[1].bar.series);}test "diagram spec expands data marks" {    const allocator = std.testing.allocator;    const input =        \\{"kind":"frame","width":500,"height":280,"title":"Coin","y_min":0,"y_max":1}        \\{"kind":"data","name":"coin","outcome":"heads","probability":0.62,"paint":"#245f8d","label":"heads"}        \\{"kind":"data","name":"coin","outcome":"tails","probability":0.38,"paint":"#8f3a32","label":"tails"}        \\{"kind":"mark","type":"bar","data":"coin","x":"outcome","y":"probability","fill":"paint","label":"label"}        \\    ;    var document = try parseForTest(allocator, input);    defer document.deinit();    try std.testing.expectEqual(@as(usize, 2), document.data.items.len);    try std.testing.expectEqual(@as(usize, 2), document.marks.items.len);    try std.testing.expectEqualStrings("heads", document.marks.items[0].bar.x);    try std.testing.expectEqual(@as(f64, 0.62), document.marks.items[0].bar.y);    try std.testing.expectEqualStrings("#8f3a32", document.marks.items[1].bar.fill);}test "diagram spec expands point and text channels" {    const allocator = std.testing.allocator;    const input =        \\{"kind":"frame","width":500,"height":280,"title":"Latency"}        \\{"kind":"data","name":"latency","events":10,"micros":120,"label":"p50","paint":"#245f8d","size":5}        \\{"kind":"mark","type":"point","data":"latency","x":"events","y":"micros","label":"label","fill":"paint","radius":"size"}        \\{"kind":"mark","type":"text","data":"latency","x":"events","y":"micros","text":"label","fill":"paint"}        \\    ;    var document = try parseForTest(allocator, input);    defer document.deinit();    try std.testing.expectEqual(@as(usize, 2), document.marks.items.len);    try std.testing.expectEqual(@as(f64, 5), document.marks.items[0].point.radius);    try std.testing.expectEqualStrings("p50", document.marks.items[0].point.label);    try std.testing.expectEqualStrings("p50", document.marks.items[1].text.text);}test "diagram spec parses boxes and edges" {    const allocator = std.testing.allocator;    const input =        \\{"kind":"frame","width":640,"height":360,"title":"Flow","axes":false,"x_min":0,"x_max":10,"y_min":0,"y_max":10}        \\{"kind":"edge","x1":3,"y1":7,"x2":7,"y2":7,"label":"message"}        \\{"kind":"box","x":3,"y":7,"width":2.4,"height":1.2,"text":"actor <A>","fill":"#eef5f1","stroke":"#245f8d"}        \\{"kind":"box","x":7,"y":7,"width":2.4,"height":1.2,"text":"actor B"}        \\    ;    var document = try parseForTest(allocator, input);    defer document.deinit();    try std.testing.expect(!document.frame.axes);    try std.testing.expectEqual(@as(usize, 3), document.marks.items.len);    try std.testing.expectEqualStrings("actor <A>", document.marks.items[1].box.text);    try std.testing.expect(document.marks.items[0].edge.arrow);}test "diagram spec rejects unknown marks and unsafe colors" {    const allocator = std.testing.allocator;    try std.testing.expectError(error.UnknownRecordKind, parseForTest(allocator, "{\"kind\":\"arc\"}\n"));    try std.testing.expectError(error.InvalidColor, parseForTest(allocator, "{\"kind\":\"bar\",\"x\":\"a\",\"y\":1,\"fill\":\"url(x)\"}\n"));    try std.testing.expectError(error.InvalidField, parseForTest(allocator, "{\"kind\":\"box\",\"x\":1,\"y\":1,\"width\":0,\"height\":1}\n"));    try std.testing.expectError(error.InvalidScale, parseForTest(allocator, "{\"kind\":\"scale\",\"axis\":\"y\",\"type\":\"log\",\"min\":0}\n"));    try std.testing.expectError(error.InvalidTransform, parseForTest(allocator, "{\"kind\":\"transform\",\"op\":\"stack\",\"mark\":\"point\"}\n"));    try std.testing.expectError(error.UnknownData, parseForTest(allocator, "{\"kind\":\"mark\",\"type\":\"bar\",\"data\":\"missing\",\"x\":\"x\",\"y\":\"y\"}\n"));    try std.testing.expectError(error.InvalidColor, parseForTest(allocator, "{\"kind\":\"data\",\"name\":\"d\",\"x\":\"a\",\"y\":1,\"paint\":\"url(x)\"}\n{\"kind\":\"mark\",\"type\":\"bar\",\"data\":\"d\",\"x\":\"x\",\"y\":\"y\",\"fill\":\"paint\"}\n"));}fn parseForTest(allocator: std.mem.Allocator, jsonl: []const u8) !Document {    var scratch: [16 * 1024]u8 = undefined;    return Document.parse(allocator, &scratch, jsonl);}

Audit

Definitions45
Public names82
Members97
Version26.7.0
Revisiondaab053ee433