Skip to documentation
SLOP

tiny.css.token

Reference tiny.css token

Defined in tiny.css.

The CSS Syntax Level 3 tokenizer.

API (19)

Actions

Public operations.

Types and contracts

Public types and contracts.

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

Source

Source: lib/css/src/token/scan.zig:35

zig
/// Whether a hash token could name an identifier, which decides whether `#a`/// is an id selector or a hex color.pub const HashKind = enum(u8) {    unrestricted,    id,};

Source: lib/css/src/token/scan.zig:5

zig
/// The token kinds CSS Syntax Level 3 section 4 produces. Comments are/// consumed rather than emitted, which is what the parsing algorithms expect.pub const Kind = enum(u8) {    eof,    whitespace,    ident,    function,    at_keyword,    hash,    string,    bad_string,    url,    bad_url,    delim,    number,    percentage,    dimension,    cdo,    cdc,    colon,    semicolon,    comma,    left_square,    right_square,    left_paren,    right_paren,    left_curly,    right_curly,};

Source: lib/css/src/token/scan.zig:41

zig
/// Whether a numeric token was written without a fraction or an exponent.pub const NumericKind = enum(u8) {    integer,    number,};

Source: lib/css/src/token/scan.zig:49

zig
/// One token as a span over the caller's source bytes. `value` narrows to the/// part a consumer reads: an identifier without its sigil, a string without/// its quotes, a dimension without its unit.pub const Token = struct {    kind: Kind,    start: u32,    end: u32,    value_start: u32,    value_end: u32,    unit_start: u32 = 0,    unit_end: u32 = 0,    number: f64 = 0,    numeric: NumericKind = .integer,    hash_kind: HashKind = .unrestricted,    escaped: bool = false,    /// The whole token including any sigil, quotes, or unit.    pub fn text(self: Token, source: []const u8) []const u8 {        return source[self.start..self.end];    }    /// The consumer facing span described above.    pub fn value(self: Token, source: []const u8) []const u8 {        return source[self.value_start..self.value_end];    }    /// The dimension unit, empty for every other kind.    pub fn unit(self: Token, source: []const u8) []const u8 {        return source[self.unit_start..self.unit_end];    }};

Source: lib/css/src/token/scan.zig:80

zig
/// A forward tokenizer over borrowed source bytes. It allocates nothing and/// every `next` either advances `index` or returns `eof`.pub const Tokenizer = struct {    source: []const u8,    index: u32 = 0,    pub fn init(source: []const u8) Tokenizer {        std.debug.assert(source.len <= std.math.maxInt(u32));        return .{ .source = source };    }    /// Consumes and returns the next token.    pub fn next(self: *Tokenizer) Token {        self.skipComments();        const start = self.index;        if (start >= self.source.len) return self.single(.eof, start);        const byte = self.source[start];        if (isWhitespace(byte)) return self.whitespace(start);        if (byte == '"' or byte == '\'') return self.string(start, byte);        if (byte == '#') return self.hash(start);        if (byte == '+' or byte == '.') {            if (startsNumber(self.source, start)) return self.numeric(start);            return self.delim(start);        }        if (byte == '-') return self.minus(start);        if (byte == '<') return self.lessThan(start);        if (byte == '@') return self.atKeyword(start);        if (byte == '\\') {            if (validEscape(self.source, start)) return self.identLike(start);            return self.delim(start);        }        if (isDigit(byte)) return self.numeric(start);        if (isIdentStart(byte)) return self.identLike(start);        if (punctuation(byte)) |kind| return self.single(kind, start);        return self.delim(start);    }    fn skipComments(self: *Tokenizer) void {        var guard: usize = 0;        while (guard <= self.source.len) : (guard += 1) {            const index = self.index;            if (index + 1 >= self.source.len) return;            if (self.source[index] != '/' or self.source[index + 1] != '*') return;            const rest = self.source[index + 2 ..];            const close = std.mem.indexOf(u8, rest, "*/") orelse {                self.index = @intCast(self.source.len);                return;            };            self.index = index + 2 + @as(u32, @intCast(close)) + 2;        }        unreachable;    }    fn single(self: *Tokenizer, kind: Kind, start: u32) Token {        const end = if (kind == .eof) start else start + 1;        self.index = end;        return .{ .kind = kind, .start = start, .end = end, .value_start = start, .value_end = end };    }    fn delim(self: *Tokenizer, start: u32) Token {        return self.single(.delim, start);    }    fn whitespace(self: *Tokenizer, start: u32) Token {        var index = start;        while (index < self.source.len and isWhitespace(self.source[index])) index += 1;        self.index = index;        return .{            .kind = .whitespace,            .start = start,            .end = index,            .value_start = start,            .value_end = index,        };    }    fn hash(self: *Tokenizer, start: u32) Token {        const after = start + 1;        if (after < self.source.len and            (isIdent(self.source[after]) or validEscape(self.source, after)))        {            const kind: HashKind = if (startsIdent(self.source, after)) .id else .unrestricted;            const end = consumeIdent(self.source, after);            self.index = end;            return .{                .kind = .hash,                .start = start,                .end = end,                .value_start = after,                .value_end = end,                .hash_kind = kind,                .escaped = hasEscape(self.source[after..end]),            };        }        return self.delim(start);    }    fn minus(self: *Tokenizer, start: u32) Token {        if (startsNumber(self.source, start)) return self.numeric(start);        if (start + 2 < self.source.len and std.mem.eql(u8, self.source[start..][0..3], "-->")) {            self.index = start + 3;            return .{                .kind = .cdc,                .start = start,                .end = start + 3,                .value_start = start,                .value_end = start + 3,            };        }        if (startsIdent(self.source, start)) return self.identLike(start);        return self.delim(start);    }    fn lessThan(self: *Tokenizer, start: u32) Token {        if (start + 3 < self.source.len and std.mem.eql(u8, self.source[start..][0..4], "<!--")) {            self.index = start + 4;            return .{                .kind = .cdo,                .start = start,                .end = start + 4,                .value_start = start,                .value_end = start + 4,            };        }        return self.delim(start);    }    fn atKeyword(self: *Tokenizer, start: u32) Token {        const after = start + 1;        if (!startsIdent(self.source, after)) return self.delim(start);        const end = consumeIdent(self.source, after);        self.index = end;        return .{            .kind = .at_keyword,            .start = start,            .end = end,            .value_start = after,            .value_end = end,            .escaped = hasEscape(self.source[after..end]),        };    }    fn identLike(self: *Tokenizer, start: u32) Token {        const end = consumeIdent(self.source, start);        const name = self.source[start..end];        if (end < self.source.len and self.source[end] == '(') {            if (std.ascii.eqlIgnoreCase(name, "url") and !quotedUrl(self.source, end + 1)) {                return self.url(start, end + 1);            }            self.index = end + 1;            return .{                .kind = .function,                .start = start,                .end = end + 1,                .value_start = start,                .value_end = end,                .escaped = hasEscape(name),            };        }        self.index = end;        return .{            .kind = .ident,            .start = start,            .end = end,            .value_start = start,            .value_end = end,            .escaped = hasEscape(name),        };    }    fn url(self: *Tokenizer, start: u32, body: u32) Token {        var index = body;        while (index < self.source.len and isWhitespace(self.source[index])) index += 1;        const value_start = index;        while (index < self.source.len and self.source[index] != ')') {            if (self.source[index] == '\\' and index + 1 < self.source.len) {                index += 2;                continue;            }            index += 1;        }        const value_end = trimTrailingSpace(self.source, value_start, index);        const closed = index < self.source.len;        self.index = if (closed) index + 1 else index;        return .{            .kind = if (closed) .url else .bad_url,            .start = start,            .end = self.index,            .value_start = value_start,            .value_end = value_end,            .escaped = hasEscape(self.source[value_start..value_end]),        };    }    fn string(self: *Tokenizer, start: u32, quote: u8) Token {        var index = start + 1;        while (index < self.source.len) {            const byte = self.source[index];            if (byte == quote) {                self.index = index + 1;                return .{                    .kind = .string,                    .start = start,                    .end = index + 1,                    .value_start = start + 1,                    .value_end = index,                    .escaped = hasEscape(self.source[start + 1 .. index]),                };            }            if (byte == '\n') break;            if (byte == '\\' and index + 1 < self.source.len) {                index += 2;                continue;            }            index += 1;        }        self.index = index;        return .{            .kind = .bad_string,            .start = start,            .end = index,            .value_start = start + 1,            .value_end = index,        };    }    fn numeric(self: *Tokenizer, start: u32) Token {        const scanned = consumeNumber(self.source, start);        var token = Token{            .kind = .number,            .start = start,            .end = scanned.end,            .value_start = start,            .value_end = scanned.end,            .number = std.fmt.parseFloat(f64, self.source[start..scanned.end]) catch 0,            .numeric = scanned.kind,        };        if (startsIdent(self.source, scanned.end)) {            const unit_end = consumeIdent(self.source, scanned.end);            token.kind = .dimension;            token.unit_start = scanned.end;            token.unit_end = unit_end;            token.end = unit_end;        } else if (scanned.end < self.source.len and self.source[scanned.end] == '%') {            token.kind = .percentage;            token.end = scanned.end + 1;        }        self.index = token.end;        return token;    }};
Called byCallsNo direct callsprivate sourcelib.css.src.token.scanexpectKindstest sourcelib.css.src.token.scantest: a hash token separates an ident...test sourcelib.css.src.token.scantest: a string keeps its interior and...test sourcelib.css.src.token.scantest: an escaped identifier stays one...test sourcelib.css.src.token.scantest: every token advances the cursor...+2 moretoken.Tokenizerinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.css.src.token.scanexpectKindstest sourcelib.css.src.token.scantest: a hash token separates an ident...test sourcelib.css.src.token.scantest: a string keeps its interior and...test sourcelib.css.src.token.scantest: an escaped identifier stays one...test sourcelib.css.src.token.scantest: every token advances the cursor...+2 moreprivate sourcelib.css.src.token.scan.TokenizeratKeywordprivate sourcelib.css.src.token.scan.Tokenizerdelimprivate sourcelib.css.src.token.scan.Tokenizerhashprivate sourcelib.css.src.token.scan.TokenizeridentLikeprivate sourcelib.css.src.token.scan.TokenizerlessThan+12 moretoken.Tokenizernext
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/css/src/token/scan.zig:474

zig
/// Consumes an identifier starting at `index` and returns its end.pub fn consumeIdent(source: []const u8, index: u32) u32 {    var cursor = index;    var guard: usize = 0;    while (cursor < source.len and guard <= source.len) : (guard += 1) {        if (isIdent(source[cursor])) {            cursor += 1;            continue;        }        if (validEscape(source, cursor)) {            cursor += 2;            if (isHex(source[cursor - 1])) {                var digits: usize = 1;                while (cursor < source.len and digits < 6 and isHex(source[cursor])) : (digits += 1) {                    cursor += 1;                }                if (cursor < source.len and isWhitespace(source[cursor])) cursor += 1;            }            continue;        }        break;    }    return @min(cursor, @as(u32, @intCast(source.len)));}
Called byCallsprivate sourcelib.css.src.token.scan.TokenizeratKeywordprivate sourcelib.css.src.token.scan.Tokenizerhashprivate sourcelib.css.src.token.scan.TokenizeridentLikeprivate sourcelib.css.src.token.scan.Tokenizernumericprivate sourcelib.css.src.token.scanisHextokenisIdenttokenisWhitespacetokenvalidEscapetokenconsumeIdent
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/css/src/token/scan.zig:383

zig
/// Whether `text` carries an escape and therefore needs `unescape`.pub fn hasEscape(text: []const u8) bool {    return std.mem.indexOfScalar(u8, text, '\\') != null;}
Called byCallsNo direct callsprivate sourcelib.css.src.token.scan.TokenizeratKeywordprivate sourcelib.css.src.token.scan.Tokenizerhashprivate sourcelib.css.src.token.scan.TokenizeridentLikeprivate sourcelib.css.src.token.scan.Tokenizerstringprivate sourcelib.css.src.token.scan.TokenizerurltokenhasEscape
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/css/src/token/scan.zig:393

zig
/// Whether `byte` may continue an identifier.pub fn isIdent(byte: u8) bool {    return isIdentStart(byte) or std.ascii.isDigit(byte) or byte == '-';}
Called byCallsprivate sourcelib.css.src.token.scan.TokenizerhashtokenconsumeIdenttokenisIdentStarttokenisIdent
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/css/src/token/scan.zig:388

zig
/// Whether `byte` may start an identifier.pub fn isIdentStart(byte: u8) bool {    return std.ascii.isAlphabetic(byte) or byte == '_' or byte >= 0x80;}
Called byCallsNo direct callstoken.TokenizernexttokenisIdenttokenstartsIdenttokenisIdentStart
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/css/src/token/scan.zig:398

zig
/// Whether `byte` is one of the five CSS whitespace bytes.pub fn isWhitespace(byte: u8) bool {    return byte == ' ' or byte == '\t' or byte == '\n' or byte == '\r' or byte == 0x0C;}
Called byCallsNo direct callstoken.Tokenizernextprivate sourcelib.css.src.token.scan.Tokenizerurlprivate sourcelib.css.src.token.scan.TokenizerwhitespacetokenconsumeIdentprivate sourcelib.css.src.token.scanquotedUrl+2 moretokenisWhitespace
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/css/src/token/scan.zig:443

zig
/// Whether an identifier starts at `index`, per section 4.3.9.pub fn startsIdent(source: []const u8, index: u32) bool {    if (index >= source.len) return false;    const byte = source[index];    if (isIdentStart(byte)) return true;    if (byte == '-') {        if (index + 1 >= source.len) return false;        const after = source[index + 1];        return isIdentStart(after) or after == '-' or validEscape(source, index + 1);    }    return validEscape(source, index);}
Called byCallsprivate sourcelib.css.src.token.scan.TokenizeratKeywordprivate sourcelib.css.src.token.scan.Tokenizerhashprivate sourcelib.css.src.token.scan.Tokenizerminusprivate sourcelib.css.src.token.scan.TokenizernumerictokenisIdentStarttokenvalidEscapetokenstartsIdent
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/css/src/token/scan.zig:456

zig
/// Whether a number starts at `index`, per section 4.3.10.pub fn startsNumber(source: []const u8, index: u32) bool {    if (index >= source.len) return false;    const byte = source[index];    if (isDigit(byte)) return true;    if (byte == '.') return index + 1 < source.len and isDigit(source[index + 1]);    if (byte != '+' and byte != '-') return false;    if (index + 1 >= source.len) return false;    if (isDigit(source[index + 1])) return true;    return source[index + 1] == '.' and index + 2 < source.len and isDigit(source[index + 2]);}
Called byCallsprivate sourcelib.css.src.token.scan.Tokenizerminustoken.Tokenizernextprivate sourcelib.css.src.token.scanisDigittokenstartsNumber
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/css/src/token/scan.zig:337

zig
/// Decodes CSS escapes from `text` into `out`, dropping escaped newlines./// Returns null when the decoded identifier does not fit.pub fn unescape(text: []const u8, out: []u8) ?[]const u8 {    var read: usize = 0;    var written: usize = 0;    while (read < text.len) {        const byte = text[read];        if (byte != '\\') {            if (written >= out.len) return null;            out[written] = byte;            written += 1;            read += 1;            continue;        }        read += 1;        if (read >= text.len) {            if (written + 3 > out.len) return null;            written += std.unicode.utf8Encode(0xFFFD, out[written..]) catch return null;            break;        }        if (text[read] == '\n') {            read += 1;            continue;        }        if (!isHex(text[read])) {            const start = read;            read += std.unicode.utf8ByteSequenceLength(text[read]) catch 1;            const end = @min(read, text.len);            if (written + (end - start) > out.len) return null;            @memcpy(out[written..][0 .. end - start], text[start..end]);            written += end - start;            continue;        }        var point: u32 = 0;        var digits: usize = 0;        while (read < text.len and digits < 6 and isHex(text[read])) : (digits += 1) {            point = point * 16 + hexValue(text[read]);            read += 1;        }        if (read < text.len and isWhitespace(text[read])) read += 1;        const scalar = if (point == 0 or point > 0x10FFFF or            (point >= 0xD800 and point <= 0xDFFF)) 0xFFFD else point;        written += std.unicode.utf8Encode(@intCast(scalar), out[written..]) catch return null;    }    return out[0..written];}
Called byCallstest sourcelib.css.src.token.scantest: an escape decodes to its code p...test sourcelib.css.src.token.scantest: an escaped identifier stays one...private sourcelib.css.src.token.scanhexValueprivate sourcelib.css.src.token.scanisHextokenisWhitespacetokenunescape
Static calls · unresolved targets: 1 · external targets: 0.

Source: lib/css/src/token/scan.zig:468

zig
/// Whether a valid escape starts at `index`, per section 4.3.8.pub fn validEscape(source: []const u8, index: u32) bool {    if (index >= source.len or source[index] != '\\') return false;    return index + 1 < source.len and source[index + 1] != '\n';}
Called byCallsNo direct callsprivate sourcelib.css.src.token.scan.Tokenizerhashtoken.TokenizernexttokenconsumeIdenttokenstartsIdenttokenvalidEscape
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/css/src/root.zig:44

zig
pub const token = @import("token/root.zig");

Source: lib/css/src/token/root.zig

zig
//! The CSS Syntax Level 3 tokenizer.//!//! Every selector, declaration value, and at-rule prelude in this package is//! read through `Tokenizer`, so escapes, strings, comments, and numeric units//! are handled once instead of at each byte scanning site.//!//! The tokenizer borrows the caller's source bytes and allocates nothing. A//! token is a pair of spans over that source, so a consumer that needs decoded//! text calls `unescape` with its own buffer.//!//! Identifiers need no Unicode tables. Section 4.2 makes every byte at or above//! 0x80 an identifier code point, so UTF-8 identifiers scan byte wise.const scan = @import("scan.zig");pub const HashKind = scan.HashKind;pub const Kind = scan.Kind;pub const NumericKind = scan.NumericKind;pub const Token = scan.Token;pub const Tokenizer = scan.Tokenizer;pub const consumeIdent = scan.consumeIdent;pub const hasEscape = scan.hasEscape;pub const isIdent = scan.isIdent;pub const isIdentStart = scan.isIdentStart;pub const isWhitespace = scan.isWhitespace;pub const startsIdent = scan.startsIdent;pub const startsNumber = scan.startsNumber;pub const unescape = scan.unescape;pub const validEscape = scan.validEscape;

Complete caller list for token.Tokenizer.init

7 direct callers.

Complete caller list for token.Tokenizer.next

7 direct callers.

Complete call list for token.Tokenizer.next

17 direct calls.

Complete caller list for token.isWhitespace

7 direct callers.

Audit

Definitions20
Public names20
Members42
Version26.7.0
Revisiondaab053ee433