const std = @import("std"); const bun = @import("bun"); pub const css = @import("../css_parser.zig"); const Result = css.Result; const ArrayList = std.ArrayListUnmanaged; const Printer = css.Printer; const PrintErr = css.PrintErr; const CSSNumber = css.css_values.number.CSSNumber; const CSSNumberFns = css.css_values.number.CSSNumberFns; const LengthPercentage = css.css_values.length.LengthPercentage; const Length = css.css_values.length.Length; const Percentage = css.css_values.percentage.Percentage; const CssColor = css.css_values.color.CssColor; const Image = css.css_values.image.Image; const Url = css.css_values.url.Url; const CSSInteger = css.css_values.number.CSSInteger; const CSSIntegerFns = css.css_values.number.CSSIntegerFns; const Angle = css.css_values.angle.Angle; const Time = css.css_values.time.Time; const Resolution = css.css_values.resolution.Resolution; const CustomIdent = css.css_values.ident.CustomIdent; const CustomIdentFns = css.css_values.ident.CustomIdentFns; const Ident = css.css_values.ident.Ident; // https://drafts.csswg.org/css-syntax-3/#whitespace const SPACE_CHARACTERS: []const u8 = &.{ 0x20, 0x09 }; /// A CSS [syntax string](https://drafts.css-houdini.org/css-properties-values-api/#syntax-strings) /// used to define the grammar for a registered custom property. pub const SyntaxString = union(enum) { /// A list of syntax components. components: ArrayList(SyntaxComponent), /// The universal syntax definition. universal, const This = @This(); pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } pub fn toCss(this: *const This, comptime W: type, dest: *Printer(W)) PrintErr!void { try dest.writeChar('"'); switch (this.*) { .universal => try dest.writeChar('*'), .components => |*components| { var first = true; for (components.items) |*component| { if (first) { first = false; } else { try dest.delim('|', true); } try component.toCss(W, dest); } }, } return dest.writeChar('"'); } pub fn parse(input: *css.Parser) Result(SyntaxString) { const string = switch (input.expectString()) { .result => |v| v, .err => |e| return .{ .err = e }, }; const result = SyntaxString.parseString(input.allocator(), string); if (result.isErr()) return .{ .err = input.newCustomError(css.ParserError.invalid_value) }; return .{ .result = result.result }; } /// Parses a syntax string. pub fn parseString(allocator: std.mem.Allocator, input: []const u8) css.Maybe(SyntaxString, void) { // https://drafts.css-houdini.org/css-properties-values-api/#parsing-syntax var trimmed_input = std.mem.trimLeft(u8, input, SPACE_CHARACTERS); if (trimmed_input.len == 0) { return .{ .err = {} }; } if (bun.strings.eqlComptime(trimmed_input, "*")) { return .{ .result = SyntaxString.universal }; } var components = ArrayList(SyntaxComponent){}; // PERF(alloc): count first? while (true) { const component = switch (SyntaxComponent.parseString(&trimmed_input)) { .result => |v| v, .err => |e| return .{ .err = e }, }; components.append( allocator, component, ) catch bun.outOfMemory(); trimmed_input = std.mem.trimLeft(u8, trimmed_input, SPACE_CHARACTERS); if (trimmed_input.len == 0) { break; } if (bun.strings.startsWithChar(trimmed_input, '|')) { trimmed_input = trimmed_input[1..]; continue; } return .{ .err = {} }; } return .{ .result = SyntaxString{ .components = components } }; } /// Parses a value according to the syntax grammar. pub fn parseValue(this: *const SyntaxString, input: *css.Parser) Result(ParsedComponent) { switch (this.*) { .universal => return .{ .result = ParsedComponent{ .token_list = switch (css.css_properties.custom.TokenList.parse( input, &css.ParserOptions.default(input.allocator(), null), 0, )) { .result => |v| v, .err => |e| return .{ .err = e }, }, } }, .components => |components| { // Loop through each component, and return the first one that parses successfully. for (components.items) |component| { const state = input.state(); // PERF: deinit this on error var parsed = ArrayList(ParsedComponent){}; while (true) { const value_result = input.tryParse(struct { fn parse( i: *css.Parser, comp: SyntaxComponent, ) Result(ParsedComponent) { const value = switch (comp.kind) { .length => ParsedComponent{ .length = switch (Length.parse(i)) { .result => |vv| vv, .err => |e| return .{ .err = e }, } }, .number => ParsedComponent{ .number = switch (CSSNumberFns.parse(i)) { .result => |vv| vv, .err => |e| return .{ .err = e }, } }, .percentage => ParsedComponent{ .percentage = switch (Percentage.parse(i)) { .result => |vv| vv, .err => |e| return .{ .err = e }, } }, .length_percentage => ParsedComponent{ .length_percentage = switch (LengthPercentage.parse(i)) { .result => |vv| vv, .err => |e| return .{ .err = e }, } }, .color => ParsedComponent{ .color = switch (CssColor.parse(i)) { .result => |vv| vv, .err => |e| return .{ .err = e }, } }, .image => ParsedComponent{ .image = switch (Image.parse(i)) { .result => |vv| vv, .err => |e| return .{ .err = e }, } }, .url => ParsedComponent{ .url = switch (Url.parse(i)) { .result => |vv| vv, .err => |e| return .{ .err = e }, } }, .integer => ParsedComponent{ .integer = switch (CSSIntegerFns.parse(i)) { .result => |vv| vv, .err => |e| return .{ .err = e }, } }, .angle => ParsedComponent{ .angle = switch (Angle.parse(i)) { .result => |vv| vv, .err => |e| return .{ .err = e }, } }, .time => ParsedComponent{ .time = switch (Time.parse(i)) { .result => |vv| vv, .err => |e| return .{ .err = e }, } }, .resolution => ParsedComponent{ .resolution = switch (Resolution.parse(i)) { .result => |vv| vv, .err => |e| return .{ .err = e }, } }, .transform_function => ParsedComponent{ .transform_function = switch (css.css_properties.transform.Transform.parse(i)) { .result => |vv| vv, .err => |e| return .{ .err = e }, } }, .transform_list => ParsedComponent{ .transform_list = switch (css.css_properties.transform.TransformList.parse(i)) { .result => |vv| vv, .err => |e| return .{ .err = e }, } }, .custom_ident => ParsedComponent{ .custom_ident = switch (CustomIdentFns.parse(i)) { .result => |vv| vv, .err => |e| return .{ .err = e }, } }, .literal => |value| blk: { const location = i.currentSourceLocation(); const ident = switch (i.expectIdent()) { .result => |v| v, .err => |e| return .{ .err = e }, }; if (!bun.strings.eql(ident, value)) { return .{ .err = location.newUnexpectedTokenError(.{ .ident = ident }) }; } break :blk ParsedComponent{ .literal = .{ .v = ident } }; }, }; return .{ .result = value }; } }.parse, .{component}); if (value_result.asValue()) |value| { switch (component.multiplier) { .none => return .{ .result = value }, .space => { parsed.append(input.allocator(), value) catch bun.outOfMemory(); if (input.isExhausted()) { return .{ .result = ParsedComponent{ .repeated = .{ .components = parsed, .multiplier = component.multiplier, } } }; } }, .comma => { parsed.append(input.allocator(), value) catch bun.outOfMemory(); if (input.next().asValue()) |token| { if (token.* == .comma) continue; break; } else { return .{ .result = ParsedComponent{ .repeated = .{ .components = parsed, .multiplier = component.multiplier, } } }; } }, } } else { break; } } input.reset(&state); } return .{ .err = input.newErrorForNextToken() }; }, } } }; /// A [syntax component](https://drafts.css-houdini.org/css-properties-values-api/#syntax-component) /// within a [SyntaxString](SyntaxString). /// /// A syntax component consists of a component kind an a multiplier, which indicates how the component /// may repeat during parsing. pub const SyntaxComponent = struct { kind: SyntaxComponentKind, multiplier: Multiplier, pub fn parseString(input: *[]const u8) css.Maybe(SyntaxComponent, void) { const kind = switch (SyntaxComponentKind.parseString(input)) { .result => |vv| vv, .err => |e| return .{ .err = e }, }; // Pre-multiplied types cannot have multipliers. if (kind == .transform_list) { return .{ .result = SyntaxComponent{ .kind = kind, .multiplier = .none, } }; } var multiplier: Multiplier = .none; if (bun.strings.startsWithChar(input.*, '+')) { input.* = input.*[1..]; multiplier = .space; } else if (bun.strings.startsWithChar(input.*, '#')) { input.* = input.*[1..]; multiplier = .comma; } return .{ .result = SyntaxComponent{ .kind = kind, .multiplier = multiplier } }; } pub fn toCss(this: *const SyntaxComponent, comptime W: type, dest: *Printer(W)) PrintErr!void { try this.kind.toCss(W, dest); return switch (this.multiplier) { .none => {}, .comma => dest.writeChar('#'), .space => dest.writeChar('+'), }; } pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } }; /// A [syntax component component name](https://drafts.css-houdini.org/css-properties-values-api/#supported-names). pub const SyntaxComponentKind = union(enum) { /// A `` component. length, /// A `` component. number, /// A `` component. percentage, /// A `` component. length_percentage, /// A `` component. color, /// An `` component. image, /// A `` component. url, /// An `` component. integer, /// An `` component. angle, /// A `