Accept popover-over pseudo selector

Optimize pseudo-selector parsing. Make comparison case insensitive, bucket
comparisons by length, and process input as integers.
This commit is contained in:
Karl Seguin
2025-07-09 18:45:28 +08:00
parent bdc49a65aa
commit 98cad6bf8d
2 changed files with 99 additions and 43 deletions

View File

@@ -605,7 +605,7 @@ pub const Parser = struct {
.after, .backdrop, .before, .cue, .first_letter => return .{ .pseudo_element = pseudo_class }, .after, .backdrop, .before, .cue, .first_letter => return .{ .pseudo_element = pseudo_class },
.first_line, .grammar_error, .marker, .placeholder => return .{ .pseudo_element = pseudo_class }, .first_line, .grammar_error, .marker, .placeholder => return .{ .pseudo_element = pseudo_class },
.selection, .spelling_error => return .{ .pseudo_element = pseudo_class }, .selection, .spelling_error => return .{ .pseudo_element = pseudo_class },
.modal => return .{ .pseudo_element = pseudo_class }, .modal, .popover_open => return .{ .pseudo_element = pseudo_class },
} }
} }

View File

@@ -99,6 +99,7 @@ pub const PseudoClass = enum {
selection, selection,
spelling_error, spelling_error,
modal, modal,
popover_open,
pub const Error = error{ pub const Error = error{
InvalidPseudoClass, InvalidPseudoClass,
@@ -114,52 +115,107 @@ pub const PseudoClass = enum {
} }
pub fn parse(s: []const u8) Error!PseudoClass { pub fn parse(s: []const u8) Error!PseudoClass {
if (std.ascii.eqlIgnoreCase(s, "not")) return .not; const longest_selector = "nth-last-of-type";
if (std.ascii.eqlIgnoreCase(s, "has")) return .has; if (s.len > longest_selector.len) {
if (std.ascii.eqlIgnoreCase(s, "haschild")) return .haschild; return Error.InvalidPseudoClass;
if (std.ascii.eqlIgnoreCase(s, "contains")) return .contains; }
if (std.ascii.eqlIgnoreCase(s, "containsown")) return .containsown;
if (std.ascii.eqlIgnoreCase(s, "matches")) return .matches; var buf: [longest_selector.len]u8 = undefined;
if (std.ascii.eqlIgnoreCase(s, "matchesown")) return .matchesown; const selector = std.ascii.lowerString(&buf, s);
if (std.ascii.eqlIgnoreCase(s, "nth-child")) return .nth_child;
if (std.ascii.eqlIgnoreCase(s, "nth-last-child")) return .nth_last_child; switch (selector.len) {
if (std.ascii.eqlIgnoreCase(s, "nth-of-type")) return .nth_of_type; 3 => switch (@as(u24, @bitCast(selector[0..3].*))) {
if (std.ascii.eqlIgnoreCase(s, "nth-last-of-type")) return .nth_last_of_type; asUint(u24, "cue") => return .cue,
if (std.ascii.eqlIgnoreCase(s, "first-child")) return .first_child; asUint(u24, "has") => return .has,
if (std.ascii.eqlIgnoreCase(s, "last-child")) return .last_child; asUint(u24, "not") => return .not,
if (std.ascii.eqlIgnoreCase(s, "first-of-type")) return .first_of_type; else => {},
if (std.ascii.eqlIgnoreCase(s, "last-of-type")) return .last_of_type; },
if (std.ascii.eqlIgnoreCase(s, "only-child")) return .only_child; 4 => switch (@as(u32, @bitCast(selector[0..4].*))) {
if (std.ascii.eqlIgnoreCase(s, "only-of-type")) return .only_of_type; asUint(u32, "lang") => return .lang,
if (std.ascii.eqlIgnoreCase(s, "input")) return .input; asUint(u32, "link") => return .link,
if (std.ascii.eqlIgnoreCase(s, "empty")) return .empty; asUint(u32, "root") => return .root,
if (std.ascii.eqlIgnoreCase(s, "root")) return .root; else => {},
if (std.ascii.eqlIgnoreCase(s, "link")) return .link; },
if (std.ascii.eqlIgnoreCase(s, "lang")) return .lang; 5 => switch (@as(u40, @bitCast(selector[0..5].*))) {
if (std.ascii.eqlIgnoreCase(s, "enabled")) return .enabled; asUint(u40, "after") => return .after,
if (std.ascii.eqlIgnoreCase(s, "disabled")) return .disabled; asUint(u40, "empty") => return .empty,
if (std.ascii.eqlIgnoreCase(s, "checked")) return .checked; asUint(u40, "focus") => return .focus,
if (std.ascii.eqlIgnoreCase(s, "visited")) return .visited; asUint(u40, "hover") => return .hover,
if (std.ascii.eqlIgnoreCase(s, "hover")) return .hover; asUint(u40, "input") => return .input,
if (std.ascii.eqlIgnoreCase(s, "active")) return .active; asUint(u40, "modal") => return .modal,
if (std.ascii.eqlIgnoreCase(s, "focus")) return .focus; else => {},
if (std.ascii.eqlIgnoreCase(s, "target")) return .target; },
if (std.ascii.eqlIgnoreCase(s, "after")) return .after; 6 => switch (@as(u48, @bitCast(selector[0..6].*))) {
if (std.ascii.eqlIgnoreCase(s, "backdrop")) return .backdrop; asUint(u48, "active") => return .active,
if (std.ascii.eqlIgnoreCase(s, "before")) return .before; asUint(u48, "before") => return .before,
if (std.ascii.eqlIgnoreCase(s, "cue")) return .cue; asUint(u48, "marker") => return .marker,
if (std.ascii.eqlIgnoreCase(s, "first-letter")) return .first_letter; asUint(u48, "target") => return .target,
if (std.ascii.eqlIgnoreCase(s, "first-line")) return .first_line; else => {},
if (std.ascii.eqlIgnoreCase(s, "grammar-error")) return .grammar_error; },
if (std.ascii.eqlIgnoreCase(s, "marker")) return .marker; 7 => switch (@as(u56, @bitCast(selector[0..7].*))) {
if (std.ascii.eqlIgnoreCase(s, "placeholder")) return .placeholder; asUint(u56, "checked") => return .checked,
if (std.ascii.eqlIgnoreCase(s, "selection")) return .selection; asUint(u56, "enabled") => return .enabled,
if (std.ascii.eqlIgnoreCase(s, "spelling-error")) return .spelling_error; asUint(u56, "matches") => return .matches,
if (std.ascii.eqlIgnoreCase(s, "modal")) return .modal; asUint(u56, "visited") => return .visited,
else => {},
},
8 => switch (@as(u64, @bitCast(selector[0..8].*))) {
asUint(u64, "backdrop") => return .backdrop,
asUint(u64, "contains") => return .contains,
asUint(u64, "disabled") => return .disabled,
asUint(u64, "haschild") => return .haschild,
else => {},
},
9 => switch (@as(u72, @bitCast(selector[0..9].*))) {
asUint(u72, "nth-child") => return .nth_child,
asUint(u72, "selection") => return .selection,
else => {},
},
10 => switch (@as(u80, @bitCast(selector[0..10].*))) {
asUint(u80, "first-line") => return .first_line,
asUint(u80, "last-child") => return .last_child,
asUint(u80, "matchesown") => return .matchesown,
asUint(u80, "only-child") => return .only_child,
else => {},
},
11 => switch (@as(u88, @bitCast(selector[0..11].*))) {
asUint(u88, "containsown") => return .containsown,
asUint(u88, "first-child") => return .first_child,
asUint(u88, "nth-of-type") => return .nth_of_type,
asUint(u88, "placeholder") => return .placeholder,
else => {},
},
12 => switch (@as(u96, @bitCast(selector[0..12].*))) {
asUint(u96, "first-letter") => return .first_letter,
asUint(u96, "last-of-type") => return .last_of_type,
asUint(u96, "only-of-type") => return .only_of_type,
asUint(u96, "popover-open") => return .popover_open,
else => {},
},
13 => switch (@as(u104, @bitCast(selector[0..13].*))) {
asUint(u104, "first-of-type") => return .first_of_type,
asUint(u104, "grammar-error") => return .grammar_error,
else => {},
},
14 => switch (@as(u112, @bitCast(selector[0..14].*))) {
asUint(u112, "nth-last-child") => return .nth_last_child,
asUint(u112, "spelling-error") => return .spelling_error,
else => {},
},
16 => switch (@as(u128, @bitCast(selector[0..16].*))) {
asUint(u128, "nth-last-of-type") => return .nth_last_of_type,
else => {},
},
else => {},
}
return Error.InvalidPseudoClass; return Error.InvalidPseudoClass;
} }
}; };
fn asUint(comptime T: type, comptime string: []const u8) T {
return @bitCast(string[0..string.len].*);
}
pub const Selector = union(enum) { pub const Selector = union(enum) {
pub const Error = error{ pub const Error = error{
UnknownCombinedCombinator, UnknownCombinedCombinator,