Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 64 additions & 31 deletions src/browser/css/parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const PseudoClass = selector.PseudoClass;
const AttributeOP = selector.AttributeOP;
const Combinator = selector.Combinator;

const REPLACEMENT_CHARACTER = &.{ 239, 191, 189 };

pub const ParseError = error{
ExpectedSelector,
ExpectedIdentifier,
Expand Down Expand Up @@ -217,22 +219,31 @@ pub const Parser = struct {
// parseName parses a name (which is like an identifier, but doesn't have
// extra restrictions on the first character).
fn parseName(p: *Parser, w: anytype) ParseError!void {
const sel = p.s;
const sel_len = sel.len;

var i = p.i;
var ok = false;

while (i < p.s.len) {
const c = p.s[i];
while (i < sel_len) {
const c = sel[i];

if (nameChar(c)) {
const start = i;
while (i < p.s.len and nameChar(p.s[i])) i += 1;
w.writeAll(p.s[start..i]) catch return ParseError.WriteError;
while (i < sel_len and nameChar(sel[i])) i += 1;
w.writeAll(sel[start..i]) catch return ParseError.WriteError;
ok = true;
} else if (c == '\\') {
p.i = i;
try p.parseEscape(w);
i = p.i;
ok = true;
} else if (c == 0) {
w.writeAll(REPLACEMENT_CHARACTER) catch return ParseError.WriteError;
i += 1;
if (i == sel_len) {
ok = true;
}
} else {
// default:
break;
Expand All @@ -246,41 +257,60 @@ pub const Parser = struct {
// parseEscape parses a backslash escape.
// The returned string is owned by the caller.
fn parseEscape(p: *Parser, w: anytype) ParseError!void {
if (p.s.len < p.i + 2 or p.s[p.i] != '\\') {
return ParseError.InvalidEscape;
const sel = p.s;
const sel_len = sel.len;

if (sel_len < p.i + 2 or sel[p.i] != '\\') {
p.i += 1;
w.writeAll(REPLACEMENT_CHARACTER) catch return ParseError.WriteError;
return;
}

const start = p.i + 1;
const c = p.s[start];
if (ascii.isWhitespace(c)) return ParseError.EscapeLineEndingOutsideString;
const c = sel[start];

// unicode escape (hex)
if (ascii.isHex(c)) {
var i: usize = start;
while (i < start + 6 and i < p.s.len and ascii.isHex(p.s[i])) {
while (i < start + 6 and i < sel_len and ascii.isHex(sel[i])) {
i += 1;
}
const v = std.fmt.parseUnsigned(u21, p.s[start..i], 16) catch return ParseError.InvalidUnicode;
if (p.s.len > i) {
switch (p.s[i]) {
'\r' => {
i += 1;
if (p.s.len > i and p.s[i] == '\n') i += 1;
},
' ', '\t', '\n', std.ascii.control_code.ff => i += 1,
else => {},

const v = std.fmt.parseUnsigned(u21, sel[start..i], 16) catch {
p.i = i;
w.writeAll(REPLACEMENT_CHARACTER) catch return ParseError.WriteError;
return;
};

if (sel_len >= i) {
if (sel_len > i) {
switch (sel[i]) {
'\r' => {
i += 1;
if (sel_len > i and sel[i] == '\n') i += 1;
},
' ', '\t', '\n', std.ascii.control_code.ff => i += 1,
else => {},
}
}
p.i = i;
if (v == 0) {
w.writeAll(REPLACEMENT_CHARACTER) catch return ParseError.WriteError;
return;
}
var buf: [4]u8 = undefined;
const ln = std.unicode.utf8Encode(v, &buf) catch return ParseError.InvalidUnicode;
const ln = std.unicode.utf8Encode(v, &buf) catch {
w.writeAll(REPLACEMENT_CHARACTER) catch return ParseError.WriteError;
return;
};
w.writeAll(buf[0..ln]) catch return ParseError.WriteError;
return;
}
}

// Return the literal character after the backslash.
p.i += 2;
w.writeAll(p.s[start .. start + 1]) catch return ParseError.WriteError;
w.writeByte(sel[start]) catch return ParseError.WriteError;
}

// parseIDSelector parses a selector that matches by id attribute.
Expand Down Expand Up @@ -383,20 +413,23 @@ pub const Parser = struct {

// parseString parses a single- or double-quoted string.
fn parseString(p: *Parser, writer: anytype) ParseError!void {
const sel = p.s;
const sel_len = sel.len;

var i = p.i;
if (p.s.len < i + 2) return ParseError.ExpectedString;
if (sel_len < i + 2) return ParseError.ExpectedString;

const quote = p.s[i];
const quote = sel[i];
i += 1;

loop: while (i < p.s.len) {
switch (p.s[i]) {
loop: while (i < sel_len) {
switch (sel[i]) {
'\\' => {
if (p.s.len > i + 1) {
const c = p.s[i + 1];
if (sel_len > i + 1) {
const c = sel[i + 1];
switch (c) {
'\r' => {
if (p.s.len > i + 2 and p.s[i + 2] == '\n') {
if (sel_len > i + 2 and sel[i + 2] == '\n') {
i += 3;
continue :loop;
}
Expand All @@ -418,17 +451,17 @@ pub const Parser = struct {
else => |c| {
if (c == quote) break :loop;
const start = i;
while (i < p.s.len) {
const cc = p.s[i];
while (i < sel_len) {
const cc = sel[i];
if (cc == quote or cc == '\\' or c == '\r' or c == '\n' or c == std.ascii.control_code.ff) break;
i += 1;
}
writer.writeAll(p.s[start..i]) catch return ParseError.WriteError;
writer.writeAll(sel[start..i]) catch return ParseError.WriteError;
},
}
}

if (i >= p.s.len) return ParseError.InvalidString;
if (i >= sel_len) return ParseError.InvalidString;

// Consume the final quote.
i += 1;
Expand Down