Skip to documentation
SLOP

tiny.python.source.lexer

Reference tiny.python source lexer

Defined in source.

The lexer reads the text of a Python program once, from start to end, and turns it into a list of tokens that ends with an end-of-input token.

API (4)

Actions

Public operations.

Types and contracts

Public types and contracts.

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

Source

Called byCallsNo direct callstest sourcelib.python.src.source.lexertest: reject unterminated stringstest sourcelib.python.src.source.lexertest: tokenize arithmetic assignmenttest sourcelib.python.src.source.lexertest: tokenize attribute referencestest sourcelib.python.src.source.lexertest: tokenize brackets and bracestest sourcelib.python.src.source.lexertest: tokenize control flow and compa...+8 moresource.lexertokenize
Static calls · unresolved targets: 2 · external targets: 3.

Source: lib/python/src/source/lexer.zig

zig
//! The lexer reads the text of a Python program once, from start to end, and turns it into a list//! of tokens that ends with an end-of-input token. The lexer has to recover the program's block//! structure from indentation. The lexer has to drop comments. For text outside the package's//! subset, the lexer has to return an error that names the problem.//!//! Python marks blocks by indentation alone. A block starts where lines become more indented and//! ends where they return to an outer level. The lexer therefore has to remember the indentation of//! every block still open.//!//! The lexical analysis in the [Python 3.14 language//! reference](https://docs.python.org/3.14/reference/) tracks indentation with a stack of levels.//! The lexer keeps the same stack, and the stack starts with the single level zero. A line indented//! deeper than the top of the stack pushes its level and yields an indent token. A line indented//! less pops every deeper level and yields a dedent token for each. The line's indentation has to//! match a level still on the stack. At the end of the text, every level still open is popped with//! a dedent token of its own.//!//! Indentation counts spaces only, and a tab in a line's indentation fails with//! `InvalidIndentation`. A newline token marks every line break, including the breaks of blank and//! comment-only lines and breaks inside parentheses, brackets and braces. The lexer knows 19//! keywords, the ones the subset uses. The lexer reads every other word as a name, even a word//! Python reserves, such as `class` or `import`. An integer is a run of decimal digits. A string//! sits on one line between single or double quotes. A string's bytes stay as written, backslashes//! included. A name starts with an ASCII letter or underscore and continues with ASCII letters,//! digits and underscores. A byte that starts no token of the subset can appear only inside a//! string or a comment.//!//! The tokens record byte offsets into the text and keep no pointer to it, so reading a token's//! characters takes the text again. The lexer's entry point, `tokenize`, frees its stack of//! indentation levels before it returns. The only memory the caller receives is the token list//! (`Stream`).const std = @import("std");const token = @import("token.zig");/// The errors `tokenize` returns for text the subset cannot read, besides running out of memory. A/// caller switches on this error set to report why the text could not be read. An error carries no/// position in the text.pub const Error = error{    /// A byte that starts no token of the subset, such as `/`, `%`, `;`, a backslash, or a byte    /// above 127 outside a string or comment, or a `!` that no `=` follows.    InvalidCharacter,    /// A tab in a line's indentation, or a line whose indentation drops below the current block's    /// and matches no enclosing block's level.    InvalidIndentation,    /// A string literal that reaches a line break or the end of the text before its closing quote.    UnterminatedString,};/// The token list that `tokenize` returns, for the caller to pass to `parse`. A stream owns the/// memory of its tokens. The caller frees a stream with `deinit` and the allocator it gave/// `tokenize`. The syntax tree that `parse` builds keeps no pointer into the token list, so the/// stream can be freed once `parse` returns.pub const Stream = struct {    /// The tokens in source order, ending with the end-of-input token. The slice is one allocation    /// from the allocator passed to `tokenize`.    tokens: []const token.Token,    /// Frees the token list with the given allocator. The package's `execute` calls this function    /// as it returns, after the program has run. The allocator has to be the one passed to    /// `tokenize`. The call leaves the stream undefined.    pub fn deinit(self: *Stream, allocator: std.mem.Allocator) void {        allocator.free(self.tokens);        self.* = undefined;    }};/// Reads the source text and returns its tokens as a `Stream`. The package's `execute` calls this/// function first, and a caller that wants the tokens of a program calls it directly. The function/// borrows the text for the call alone: the tokens record byte offsets and keep no pointer to it./// The function allocates the token list from the given allocator and returns it as one slice. The/// function also allocates a stack of indentation levels, which it frees before returning. The/// caller owns the returned stream and frees it with `Stream.deinit` and the same allocator. The/// function returns `InvalidCharacter`, `InvalidIndentation` or `UnterminatedString` for text it/// cannot read. The function returns `error.OutOfMemory` when an allocation fails. On any error it/// frees everything it allocated. The list ends with a dedent token for each block still open and/// then the end-of-input token. Empty text yields the end-of-input token alone.pub fn tokenize(allocator: std.mem.Allocator, source: []const u8) (Error || std.mem.Allocator.Error)!Stream {    var lexer = Lexer{        .source = source,        .tokens = .empty,        .indents = .empty,    };    errdefer lexer.tokens.deinit(allocator);    errdefer lexer.indents.deinit(allocator);    try lexer.indents.append(allocator, 0);    try lexer.run(allocator);    const tokens = try lexer.tokens.toOwnedSlice(allocator);    lexer.indents.deinit(allocator);    return .{ .tokens = tokens };}const Lexer = struct {    source: []const u8,    index: usize = 0,    tokens: std.ArrayListUnmanaged(token.Token),    indents: std.ArrayListUnmanaged(usize),    line_start: bool = true,    fn run(self: *Lexer, allocator: std.mem.Allocator) (Error || std.mem.Allocator.Error)!void {        while (self.index < self.source.len) {            if (self.line_start) try self.indentation(allocator);            if (self.index >= self.source.len) break;            const c = self.source[self.index];            switch (c) {                ' ', '\t' => self.index += 1,                '\n' => try self.newline(allocator, 1),                '\r' => try self.carriage(allocator),                '#' => self.comment(),                '+' => try self.simple(allocator, .plus, 1),                '-' => try self.simple(allocator, .minus, 1),                '*' => try self.simple(allocator, .star, 1),                '=' => try self.singleOrEqual(allocator, .equal, .equal_equal),                '!' => try self.bang(allocator),                '<' => try self.singleOrEqual(allocator, .less, .less_equal),                '>' => try self.singleOrEqual(allocator, .greater, .greater_equal),                ',' => try self.simple(allocator, .comma, 1),                ':' => try self.simple(allocator, .colon, 1),                '.' => try self.simple(allocator, .dot, 1),                '(' => try self.simple(allocator, .lparen, 1),                ')' => try self.simple(allocator, .rparen, 1),                '[' => try self.simple(allocator, .lbracket, 1),                ']' => try self.simple(allocator, .rbracket, 1),                '{' => try self.simple(allocator, .lbrace, 1),                '}' => try self.simple(allocator, .rbrace, 1),                '\'', '"' => try self.string(allocator),                '0'...'9' => try self.integer(allocator),                'A'...'Z', 'a'...'z', '_' => try self.identifier(allocator),                else => return Error.InvalidCharacter,            }        }        while (self.indents.items.len > 1) {            _ = self.indents.pop();            try self.tokens.append(allocator, .{                .tag = .dedent,                .span = .{ .start = self.source.len, .end = self.source.len },            });        }        try self.tokens.append(allocator, .{            .tag = .eof,            .span = .{ .start = self.source.len, .end = self.source.len },        });    }    fn indentation(self: *Lexer, allocator: std.mem.Allocator) (Error || std.mem.Allocator.Error)!void {        const start = self.index;        var width: usize = 0;        while (self.index < self.source.len and self.source[self.index] == ' ') {            self.index += 1;            width += 1;        }        if (self.index < self.source.len and self.source[self.index] == '\t') return Error.InvalidIndentation;        if (self.index >= self.source.len or self.source[self.index] == '\n' or self.source[self.index] == '\r' or self.source[self.index] == '#') return;        const current = self.indents.items[self.indents.items.len - 1];        if (width > current) {            try self.indents.append(allocator, width);            try self.tokens.append(allocator, .{                .tag = .indent,                .span = .{ .start = start, .end = self.index },            });        } else if (width < current) {            while (self.indents.items.len > 1 and width < self.indents.items[self.indents.items.len - 1]) {                _ = self.indents.pop();                try self.tokens.append(allocator, .{                    .tag = .dedent,                    .span = .{ .start = start, .end = self.index },                });            }            if (width != self.indents.items[self.indents.items.len - 1]) return Error.InvalidIndentation;        }        self.line_start = false;    }    fn simple(self: *Lexer, allocator: std.mem.Allocator, tag: token.Tag, width: usize) std.mem.Allocator.Error!void {        const start = self.index;        self.index += width;        self.line_start = false;        try self.tokens.append(allocator, .{            .tag = tag,            .span = .{ .start = start, .end = self.index },        });    }    fn singleOrEqual(self: *Lexer, allocator: std.mem.Allocator, single: token.Tag, equal: token.Tag) std.mem.Allocator.Error!void {        const start = self.index;        self.index += 1;        const tag = if (self.index < self.source.len and self.source[self.index] == '=') blk: {            self.index += 1;            break :blk equal;        } else single;        self.line_start = false;        try self.tokens.append(allocator, .{            .tag = tag,            .span = .{ .start = start, .end = self.index },        });    }    fn bang(self: *Lexer, allocator: std.mem.Allocator) (Error || std.mem.Allocator.Error)!void {        const start = self.index;        self.index += 1;        if (self.index >= self.source.len or self.source[self.index] != '=') return Error.InvalidCharacter;        self.index += 1;        self.line_start = false;        try self.tokens.append(allocator, .{            .tag = .bang_equal,            .span = .{ .start = start, .end = self.index },        });    }    fn carriage(self: *Lexer, allocator: std.mem.Allocator) std.mem.Allocator.Error!void {        const start = self.index;        self.index += 1;        if (self.index < self.source.len and self.source[self.index] == '\n') {            self.index += 1;        }        try self.tokens.append(allocator, .{            .tag = .newline,            .span = .{ .start = start, .end = self.index },        });        self.line_start = true;    }    fn newline(self: *Lexer, allocator: std.mem.Allocator, width: usize) std.mem.Allocator.Error!void {        const start = self.index;        self.index += width;        try self.tokens.append(allocator, .{            .tag = .newline,            .span = .{ .start = start, .end = self.index },        });        self.line_start = true;    }    fn comment(self: *Lexer) void {        while (self.index < self.source.len and self.source[self.index] != '\n' and self.source[self.index] != '\r') {            self.index += 1;        }    }    fn integer(self: *Lexer, allocator: std.mem.Allocator) std.mem.Allocator.Error!void {        const start = self.index;        while (self.index < self.source.len and isDigit(self.source[self.index])) {            self.index += 1;        }        self.line_start = false;        try self.tokens.append(allocator, .{            .tag = .integer,            .span = .{ .start = start, .end = self.index },        });    }    fn string(self: *Lexer, allocator: std.mem.Allocator) (Error || std.mem.Allocator.Error)!void {        const quote = self.source[self.index];        const start = self.index;        self.index += 1;        while (self.index < self.source.len and self.source[self.index] != quote) {            if (self.source[self.index] == '\n' or self.source[self.index] == '\r') return Error.UnterminatedString;            self.index += 1;        }        if (self.index >= self.source.len) return Error.UnterminatedString;        self.index += 1;        self.line_start = false;        try self.tokens.append(allocator, .{            .tag = .string,            .span = .{ .start = start, .end = self.index },        });    }    fn identifier(self: *Lexer, allocator: std.mem.Allocator) std.mem.Allocator.Error!void {        const start = self.index;        while (self.index < self.source.len and isIdentifierContinue(self.source[self.index])) {            self.index += 1;        }        const text = self.source[start..self.index];        self.line_start = false;        try self.tokens.append(allocator, .{            .tag = keyword(text),            .span = .{ .start = start, .end = self.index },        });    }};fn keyword(text: []const u8) token.Tag {    if (std.mem.eql(u8, text, "and")) return .and_kw;    if (std.mem.eql(u8, text, "break")) return .break_kw;    if (std.mem.eql(u8, text, "continue")) return .continue_kw;    if (std.mem.eql(u8, text, "del")) return .del_kw;    if (std.mem.eql(u8, text, "def")) return .def_kw;    if (std.mem.eql(u8, text, "elif")) return .elif_kw;    if (std.mem.eql(u8, text, "else")) return .else_kw;    if (std.mem.eql(u8, text, "for")) return .for_kw;    if (std.mem.eql(u8, text, "if")) return .if_kw;    if (std.mem.eql(u8, text, "in")) return .in_kw;    if (std.mem.eql(u8, text, "is")) return .is_kw;    if (std.mem.eql(u8, text, "not")) return .not_kw;    if (std.mem.eql(u8, text, "or")) return .or_kw;    if (std.mem.eql(u8, text, "pass")) return .pass_kw;    if (std.mem.eql(u8, text, "return")) return .return_kw;    if (std.mem.eql(u8, text, "while")) return .while_kw;    if (std.mem.eql(u8, text, "True")) return .true_kw;    if (std.mem.eql(u8, text, "False")) return .false_kw;    if (std.mem.eql(u8, text, "None")) return .none_kw;    return .identifier;}fn isDigit(c: u8) bool {    return c >= '0' and c <= '9';}fn isIdentifierContinue(c: u8) bool {    return isDigit(c) or (c >= 'A' and c <= 'Z') or (c >= 'a' and c <= 'z') or c == '_';}test "tokenize arithmetic assignment" {    var stream = try tokenize(std.testing.allocator, "x = 1 + 2\n");    defer stream.deinit(std.testing.allocator);    try std.testing.expectEqual(token.Tag.identifier, stream.tokens[0].tag);    try std.testing.expectEqual(token.Tag.equal, stream.tokens[1].tag);    try std.testing.expectEqual(token.Tag.integer, stream.tokens[2].tag);    try std.testing.expectEqual(token.Tag.plus, stream.tokens[3].tag);    try std.testing.expectEqual(token.Tag.integer, stream.tokens[4].tag);    try std.testing.expectEqual(token.Tag.newline, stream.tokens[5].tag);    try std.testing.expectEqual(token.Tag.eof, stream.tokens[6].tag);}test "tokenize keywords and comments" {    var stream = try tokenize(std.testing.allocator, "True # hidden\nFalse None");    defer stream.deinit(std.testing.allocator);    try std.testing.expectEqual(token.Tag.true_kw, stream.tokens[0].tag);    try std.testing.expectEqual(token.Tag.newline, stream.tokens[1].tag);    try std.testing.expectEqual(token.Tag.false_kw, stream.tokens[2].tag);    try std.testing.expectEqual(token.Tag.none_kw, stream.tokens[3].tag);}test "tokenize function indentation" {    var stream = try tokenize(std.testing.allocator,        \\def f(x):        \\    return x        \\f(1)    );    defer stream.deinit(std.testing.allocator);    try std.testing.expectEqual(token.Tag.def_kw, stream.tokens[0].tag);    try std.testing.expectEqual(token.Tag.identifier, stream.tokens[1].tag);    try std.testing.expectEqual(token.Tag.lparen, stream.tokens[2].tag);    try std.testing.expectEqual(token.Tag.identifier, stream.tokens[3].tag);    try std.testing.expectEqual(token.Tag.rparen, stream.tokens[4].tag);    try std.testing.expectEqual(token.Tag.colon, stream.tokens[5].tag);    try std.testing.expectEqual(token.Tag.newline, stream.tokens[6].tag);    try std.testing.expectEqual(token.Tag.indent, stream.tokens[7].tag);    try std.testing.expectEqual(token.Tag.return_kw, stream.tokens[8].tag);    try std.testing.expectEqual(token.Tag.dedent, stream.tokens[11].tag);}test "tokenize control flow and comparisons" {    var stream = try tokenize(std.testing.allocator,        \\if x <= 3:        \\    pass        \\else:        \\    y = x != 4    );    defer stream.deinit(std.testing.allocator);    try std.testing.expectEqual(token.Tag.if_kw, stream.tokens[0].tag);    try std.testing.expectEqual(token.Tag.less_equal, stream.tokens[2].tag);    try std.testing.expectEqual(token.Tag.pass_kw, stream.tokens[7].tag);    try std.testing.expectEqual(token.Tag.else_kw, stream.tokens[10].tag);    try std.testing.expectEqual(token.Tag.bang_equal, stream.tokens[17].tag);}test "tokenize loop control" {    var stream = try tokenize(std.testing.allocator,        \\for x in [1]:        \\    break        \\    continue    );    defer stream.deinit(std.testing.allocator);    try std.testing.expectEqual(token.Tag.for_kw, stream.tokens[0].tag);    try std.testing.expectEqual(token.Tag.identifier, stream.tokens[1].tag);    try std.testing.expectEqual(token.Tag.in_kw, stream.tokens[2].tag);    try std.testing.expectEqual(token.Tag.break_kw, stream.tokens[9].tag);    try std.testing.expectEqual(token.Tag.continue_kw, stream.tokens[11].tag);}test "tokenize deletion keyword" {    var stream = try tokenize(std.testing.allocator, "del x");    defer stream.deinit(std.testing.allocator);    try std.testing.expectEqual(token.Tag.del_kw, stream.tokens[0].tag);    try std.testing.expectEqual(token.Tag.identifier, stream.tokens[1].tag);}test "tokenize attribute references" {    var stream = try tokenize(std.testing.allocator, "xs.append");    defer stream.deinit(std.testing.allocator);    try std.testing.expectEqual(token.Tag.identifier, stream.tokens[0].tag);    try std.testing.expectEqual(token.Tag.dot, stream.tokens[1].tag);    try std.testing.expectEqual(token.Tag.identifier, stream.tokens[2].tag);}test "tokenize logical operators" {    var stream = try tokenize(std.testing.allocator, "not True and False or None is");    defer stream.deinit(std.testing.allocator);    try std.testing.expectEqual(token.Tag.not_kw, stream.tokens[0].tag);    try std.testing.expectEqual(token.Tag.true_kw, stream.tokens[1].tag);    try std.testing.expectEqual(token.Tag.and_kw, stream.tokens[2].tag);    try std.testing.expectEqual(token.Tag.false_kw, stream.tokens[3].tag);    try std.testing.expectEqual(token.Tag.or_kw, stream.tokens[4].tag);    try std.testing.expectEqual(token.Tag.none_kw, stream.tokens[5].tag);    try std.testing.expectEqual(token.Tag.is_kw, stream.tokens[6].tag);}test "tokenize strings" {    var stream = try tokenize(std.testing.allocator,        \\"alpha" 'beta'    );    defer stream.deinit(std.testing.allocator);    try std.testing.expectEqual(token.Tag.string, stream.tokens[0].tag);    try std.testing.expectEqualStrings("\"alpha\"", stream.tokens[0].span.text("\"alpha\" 'beta'"));    try std.testing.expectEqual(token.Tag.string, stream.tokens[1].tag);}test "tokenize brackets and braces" {    var stream = try tokenize(std.testing.allocator, "[1, 2][0] {\"a\": 1}");    defer stream.deinit(std.testing.allocator);    try std.testing.expectEqual(token.Tag.lbracket, stream.tokens[0].tag);    try std.testing.expectEqual(token.Tag.integer, stream.tokens[1].tag);    try std.testing.expectEqual(token.Tag.comma, stream.tokens[2].tag);    try std.testing.expectEqual(token.Tag.integer, stream.tokens[3].tag);    try std.testing.expectEqual(token.Tag.rbracket, stream.tokens[4].tag);    try std.testing.expectEqual(token.Tag.lbracket, stream.tokens[5].tag);    try std.testing.expectEqual(token.Tag.integer, stream.tokens[6].tag);    try std.testing.expectEqual(token.Tag.rbracket, stream.tokens[7].tag);    try std.testing.expectEqual(token.Tag.lbrace, stream.tokens[8].tag);    try std.testing.expectEqual(token.Tag.string, stream.tokens[9].tag);    try std.testing.expectEqual(token.Tag.colon, stream.tokens[10].tag);    try std.testing.expectEqual(token.Tag.integer, stream.tokens[11].tag);    try std.testing.expectEqual(token.Tag.rbrace, stream.tokens[12].tag);}test "reject unterminated strings" {    try std.testing.expectError(Error.UnterminatedString, tokenize(std.testing.allocator, "\"alpha"));}test "tokenize releases indentation storage once when token transfer fails" {    const source = "if x:\n    pass";    var counting = std.testing.FailingAllocator.init(std.testing.allocator, .{        .resize_fail_index = 0,    });    const counting_allocator = counting.allocator();    var stream = try tokenize(counting_allocator, source);    stream.deinit(counting_allocator);    try std.testing.expect(counting.alloc_index > 0);    var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{        .fail_index = counting.alloc_index - 1,        .resize_fail_index = 0,    });    try std.testing.expectError(error.OutOfMemory, tokenize(failing.allocator(), source));    try std.testing.expect(failing.has_induced_failure);}test "tokenize elif" {    var stream = try tokenize(std.testing.allocator,        \\if a:        \\    pass        \\elif b:        \\    pass    );    defer stream.deinit(std.testing.allocator);    try std.testing.expectEqual(token.Tag.if_kw, stream.tokens[0].tag);    try std.testing.expectEqual(token.Tag.elif_kw, stream.tokens[8].tag);}

Source: lib/python/src/source/root.zig:11

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

Complete caller list for source.lexer.tokenize

13 direct callers.

Audit

Definitions5
Public names8
Members4
Version26.7.0
Revisiondaab053ee433