Skip to documentation
SLOP

tiny.python.syntax.parser

Reference tiny.python syntax parser

Defined in syntax.

The parser reads the token list of a Python program and builds its syntax tree: a list of statements whose expressions are trees of operators and operands.

API (2)

Actions

Public operations.

Types and contracts

Public types and contracts.

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

Source

Called byCallsNo direct callstest sourcelib.python.src.syntax.parsertest: parse assignments and precedencetest sourcelib.python.src.syntax.parsertest: parse attribute method callstest sourcelib.python.src.syntax.parsertest: parse call and list commas rema...test sourcelib.python.src.syntax.parsertest: parse chained comparisonstest sourcelib.python.src.syntax.parsertest: parse deletion statements+20 moresyntax.parserparse
Static calls · unresolved targets: 0 · external targets: 3.

Source: lib/python/src/syntax/parser.zig

zig
//! The parser reads the token list of a Python program and builds its syntax tree: a list of//! statements whose expressions are trees of operators and operands. The parser has to settle//! operator precedence and block nesting, so the compiler can emit instructions in one walk over//! the tree. For anything outside the package's subset, the parser has to return an error that//! names what it expected.//!//! An assignment's target is known only when the `=` arrives, after the expression before it has//! been read. A tree has one node per operator, name and literal, and the parser learns how many//! only as it reads. Every node stays in use until the caller is done with the whole tree.//!//! The parser's precedence levels follow the grammar of the [Python 3.14 language//! reference](https://docs.python.org/3.14/reference/), from loosest to tightest: `or`, `and`,//! `not`, comparisons, `+` and `-`, `*`, unary minus, and then calls, subscripts and attribute//! access. Comparisons chain as the reference defines them. `a < b <= c` becomes one node that//! keeps every operator with its right operand. That node lets the compiler test each pair and//! evaluate each operand once.//!//! The parser is written by hand as recursive descent, with one function per precedence level. The//! parser looks one token ahead. To tell `not in` from `not`, the parser looks two tokens ahead.//! The parser reads the left side of `=` as an ordinary expression. The parser accepts that//! expression as the target only when it is a name or a subscript with one index.//!//! Every node goes into one arena allocator. The returned tree, `Program`, owns the arena, so its//! `deinit` frees the whole tree at once. Names and string contents in the tree are slices of the//! source text, so the text has to outlive the tree. The tree keeps no pointer into the token list,//! so the caller can free the tokens as soon as the parser returns.//!//! A block's body starts on its own indented line after the colon, so `if x: pass` on one line//! fails. The body's first statement has to sit on the line right after the colon's line. Parsing//! fails on a blank or comment-only line in between. Nesting has no depth limit. Each level of//! nesting in the text takes stack frames of the calling thread. An error carries no position in//! the text.const std = @import("std");const source = @import("../source/root.zig");const ast = @import("ast.zig");/// The errors `parse` returns when the tokens do not form a program of the subset, besides running/// out of memory. A caller switches on it to report why the tokens do not form a program. Each/// names what the parser expected at the token where it stopped. None carries a position in the/// text.pub const Error = error{    /// An expression was due and the next token cannot start one, as in `x =` with nothing after    /// it. The parser also returns this error for an indent token where no block opens, as on an    /// indented first line.    ExpectedExpression,    /// A block header without its `:`, after the condition of `if`, `elif` or `while`, the iterable    /// of `for`, the parameters of `def`, or `else`. The parser also returns this error for a    /// dictionary entry whose key has no `:` after it, as in `{"a"}`.    ExpectedColon,    /// A `def` parameter or a call argument that neither `,` nor `)` follows.    ExpectedCommaOrRightParen,    /// A list item that neither `,` nor `]` follows.    ExpectedCommaOrRightBracket,    /// A dictionary entry that neither `,` nor `}` follows, as in `{"a": 1 "b": 2}`.    ExpectedCommaOrRightBrace,    /// A block's body that no dedent token closes. `tokenize` closes every open block before the    /// end-of-input token, so only a token list built another way produces this error.    ExpectedDedent,    /// A name was due after `def`, after `for`, in a parameter list, or after `.`, and another    /// token came.    ExpectedIdentifier,    /// A `for` loop's name that `in` does not follow.    ExpectedIn,    /// A block header's colon and line break that an indent token does not follow. A body indented    /// no deeper than its header gets this error. The parser also returns it for a blank or    /// comment-only line before the body's first statement.    ExpectedIndent,    /// A `def` name that `(` does not follow.    ExpectedLeftParen,    /// More tokens on the line after a complete simple statement, as with two statements on one    /// line. The parser also returns this error for more tokens on the line after a block header's    /// colon, as in `if x: pass`.    ExpectedNewline,    /// A parenthesized expression that `)` does not close.    ExpectedRightParen,    /// A subscript that `]` does not close. A comma inside a subscript, as in `xs[0, 1]`, fails    /// this way.    ExpectedRightBracket,    /// An integer literal too large for a signed 128-bit integer.    InvalidInteger,    /// An assignment whose left side is neither a name nor a subscript with one index. `[1] = 2`    /// and `xs[0:1] = [2]` fail this way. The parser also returns this error for a `del` whose    /// target is neither a name nor a subscript with one index. `del [1]` and `del xs[0:1]` fail    /// this way.    UnexpectedToken,};/// Builds the syntax tree of a program from its source text and the tokens made from that text. The/// package's `execute` calls `parse` on the tokens `tokenize` returned, and a caller that wants a/// program's tree calls it the same way. `parse` returns the tree as a `Program`. `bytes` has to be/// the text the tokens came from. The tokens have to end with the end-of-input token, as `tokenize`/// returns them. `parse` allocates every node from an arena on the given allocator. The returned/// `Program` owns the arena. The tree borrows `bytes`: names and string contents are slices of it,/// so `bytes` has to outlive the `Program`. The tree keeps no pointer to `tokens`, so the caller/// can free them once `parse` returns. `parse` returns one of the `Error` values when the tokens do/// not form a program of the subset. The function returns `error.OutOfMemory` when an allocation/// fails. On any error the function frees the arena. `parse` recurses once per level of nesting in/// the program. The recursion has no depth limit.pub fn parse(allocator: std.mem.Allocator, bytes: []const u8, tokens: []const source.Token) (Error || std.mem.Allocator.Error)!ast.Program {    var arena = std.heap.ArenaAllocator.init(allocator);    errdefer arena.deinit();    var parser = Parser{        .source = bytes,        .tokens = tokens,        .arena = arena.allocator(),    };    const statements = try parser.program();    return .{        .arena = arena,        .statements = statements,    };}const Parser = struct {    source: []const u8,    tokens: []const source.Token,    index: usize = 0,    arena: std.mem.Allocator,    fn program(self: *Parser) (Error || std.mem.Allocator.Error)![]const ast.Statement {        return try self.block(false);    }    fn block(self: *Parser, stop_on_dedent: bool) (Error || std.mem.Allocator.Error)![]const ast.Statement {        var statements = std.ArrayListUnmanaged(ast.Statement).empty;        while (self.match(.newline)) {}        while (!self.at(.eof) and !(stop_on_dedent and self.at(.dedent))) {            const parsed = try self.statement();            try statements.append(self.arena, parsed);            if (self.at(.eof) or (stop_on_dedent and self.at(.dedent))) break;            if (!compound(parsed) or self.at(.newline)) {                if (!self.match(.newline)) return Error.ExpectedNewline;            }            while (self.match(.newline)) {}        }        return try statements.toOwnedSlice(self.arena);    }    fn statement(self: *Parser) (Error || std.mem.Allocator.Error)!ast.Statement {        if (self.match(.break_kw)) return .break_stmt;        if (self.match(.continue_kw)) return .continue_stmt;        if (self.match(.del_kw)) return try self.deleteStatement();        if (self.match(.def_kw)) return try self.function();        if (self.match(.for_kw)) return try self.forStatement();        if (self.match(.if_kw)) return try self.ifStatement();        if (self.match(.pass_kw)) return .pass;        if (self.match(.return_kw)) return try self.returnStatement();        if (self.match(.while_kw)) return try self.whileStatement();        const target = try self.expression();        if (self.match(.equal)) {            const value = try self.expression();            return switch (target.*) {                .name => |name| .{ .assign = .{                    .name = name,                    .value = value,                } },                .subscript => |subscript| .{ .subscript_assign = .{                    .target = subscript.target,                    .index = switch (subscript.selector) {                        .index => |index| index,                        .slice => return Error.UnexpectedToken,                    },                    .value = value,                } },                else => Error.UnexpectedToken,            };        }        return .{ .expression = target };    }    fn deleteStatement(self: *Parser) (Error || std.mem.Allocator.Error)!ast.Statement {        const target = try self.expression();        return .{ .delete = switch (target.*) {            .name => |name| .{ .name = name },            .subscript => |subscript| .{ .subscript = .{                .target = subscript.target,                .index = switch (subscript.selector) {                    .index => |index| index,                    .slice => return Error.UnexpectedToken,                },            } },            else => return Error.UnexpectedToken,        } };    }    fn ifStatement(self: *Parser) (Error || std.mem.Allocator.Error)!ast.Statement {        return try self.ifTail();    }    fn ifTail(self: *Parser) (Error || std.mem.Allocator.Error)!ast.Statement {        const condition = try self.singleExpression();        const body = try self.suite();        const otherwise = try self.ifOtherwise();        return .{ .if_stmt = .{            .condition = condition,            .body = body,            .otherwise = otherwise,        } };    }    fn ifOtherwise(self: *Parser) (Error || std.mem.Allocator.Error)![]const ast.Statement {        if (self.match(.elif_kw)) {            const statement_node = try self.ifTail();            const statements = try self.arena.alloc(ast.Statement, 1);            statements[0] = statement_node;            return statements;        }        if (self.match(.else_kw)) return try self.suite();        return &.{};    }    fn function(self: *Parser) (Error || std.mem.Allocator.Error)!ast.Statement {        const name = try self.expectIdentifier();        if (!self.match(.lparen)) return Error.ExpectedLeftParen;        const params = try self.parameters();        const body = try self.suite();        return .{ .function = .{            .name = name,            .params = params,            .body = body,        } };    }    fn forStatement(self: *Parser) (Error || std.mem.Allocator.Error)!ast.Statement {        const name = try self.expectIdentifier();        if (!self.match(.in_kw)) return Error.ExpectedIn;        const iterable = try self.singleExpression();        const body = try self.suite();        const otherwise = if (self.match(.else_kw)) try self.suite() else &.{};        return .{ .for_stmt = .{            .name = name,            .iterable = iterable,            .body = body,            .otherwise = otherwise,        } };    }    fn parameters(self: *Parser) (Error || std.mem.Allocator.Error)![]const []const u8 {        var params = std.ArrayListUnmanaged([]const u8).empty;        if (self.match(.rparen)) return try params.toOwnedSlice(self.arena);        while (true) {            try params.append(self.arena, try self.expectIdentifier());            if (self.match(.rparen)) break;            if (!self.match(.comma)) return Error.ExpectedCommaOrRightParen;            if (self.match(.rparen)) break;        }        return try params.toOwnedSlice(self.arena);    }    fn returnStatement(self: *Parser) (Error || std.mem.Allocator.Error)!ast.Statement {        if (self.at(.newline) or self.at(.dedent) or self.at(.eof)) {            return .{ .return_stmt = .{ .value = null } };        }        return .{ .return_stmt = .{ .value = try self.expression() } };    }    fn whileStatement(self: *Parser) (Error || std.mem.Allocator.Error)!ast.Statement {        const condition = try self.singleExpression();        const body = try self.suite();        const otherwise = if (self.match(.else_kw)) try self.suite() else &.{};        return .{ .while_stmt = .{            .condition = condition,            .body = body,            .otherwise = otherwise,        } };    }    fn suite(self: *Parser) (Error || std.mem.Allocator.Error)![]const ast.Statement {        if (!self.match(.colon)) return Error.ExpectedColon;        if (!self.match(.newline)) return Error.ExpectedNewline;        if (!self.match(.indent)) return Error.ExpectedIndent;        const body = try self.block(true);        if (!self.match(.dedent)) return Error.ExpectedDedent;        return body;    }    fn expression(self: *Parser) (Error || std.mem.Allocator.Error)!*const ast.Expression {        return try self.tupleExpression();    }    fn tupleExpression(self: *Parser) (Error || std.mem.Allocator.Error)!*const ast.Expression {        const first = try self.singleExpression();        if (!self.match(.comma)) return first;        var items = std.ArrayListUnmanaged(*const ast.Expression).empty;        try items.append(self.arena, first);        while (!self.tupleTerminator()) {            try items.append(self.arena, try self.singleExpression());            if (!self.match(.comma)) break;        }        return try self.leaf(.{ .tuple = .{ .items = try items.toOwnedSlice(self.arena) } });    }    fn singleExpression(self: *Parser) (Error || std.mem.Allocator.Error)!*const ast.Expression {        return try self.disjunction();    }    fn disjunction(self: *Parser) (Error || std.mem.Allocator.Error)!*const ast.Expression {        var node = try self.conjunction();        while (self.match(.or_kw)) {            node = try self.logical(.or_op, node, try self.conjunction());        }        return node;    }    fn conjunction(self: *Parser) (Error || std.mem.Allocator.Error)!*const ast.Expression {        var node = try self.inversion();        while (self.match(.and_kw)) {            node = try self.logical(.and_op, node, try self.inversion());        }        return node;    }    fn inversion(self: *Parser) (Error || std.mem.Allocator.Error)!*const ast.Expression {        if (self.match(.not_kw)) {            const operand = try self.inversion();            const node = try self.arena.create(ast.Expression);            node.* = .{ .unary = .{ .op = .not, .operand = operand } };            return node;        }        return try self.comparison();    }    fn comparison(self: *Parser) (Error || std.mem.Allocator.Error)!*const ast.Expression {        const left = try self.term();        var terms = std.ArrayListUnmanaged(ast.ComparisonTerm).empty;        while (self.matchComparison()) |op| {            try terms.append(self.arena, .{                .op = op,                .right = try self.term(),            });        }        if (terms.items.len == 0) return left;        const node = try self.arena.create(ast.Expression);        node.* = .{ .comparison = .{            .left = left,            .terms = try terms.toOwnedSlice(self.arena),        } };        return node;    }    fn term(self: *Parser) (Error || std.mem.Allocator.Error)!*const ast.Expression {        var node = try self.factor();        while (self.match(.plus) or self.match(.minus)) {            const op: ast.BinaryOp = if (self.previous().tag == .plus) .add else .sub;            node = try self.binary(op, node, try self.factor());        }        return node;    }    fn factor(self: *Parser) (Error || std.mem.Allocator.Error)!*const ast.Expression {        var node = try self.unary();        while (self.match(.star)) {            node = try self.binary(.mul, node, try self.unary());        }        return node;    }    fn unary(self: *Parser) (Error || std.mem.Allocator.Error)!*const ast.Expression {        if (self.match(.minus)) {            const operand = try self.unary();            const node = try self.arena.create(ast.Expression);            node.* = .{ .unary = .{ .op = .negate, .operand = operand } };            return node;        }        return try self.call();    }    fn call(self: *Parser) (Error || std.mem.Allocator.Error)!*const ast.Expression {        var node = try self.primary();        while (true) {            if (self.match(.lparen)) {                const call_arguments = try self.arguments();                const call_node = try self.arena.create(ast.Expression);                call_node.* = .{ .call = .{                    .target = node,                    .arguments = call_arguments,                } };                node = call_node;            } else if (self.match(.lbracket)) {                const selector = try self.subscriptSelector();                if (!self.match(.rbracket)) return Error.ExpectedRightBracket;                const subscript_node = try self.arena.create(ast.Expression);                subscript_node.* = .{ .subscript = .{                    .target = node,                    .selector = selector,                } };                node = subscript_node;            } else if (self.match(.dot)) {                const name = try self.expectIdentifier();                const attribute_node = try self.arena.create(ast.Expression);                attribute_node.* = .{ .attribute = .{                    .target = node,                    .name = name,                } };                node = attribute_node;            } else {                break;            }        }        return node;    }    fn subscriptSelector(self: *Parser) (Error || std.mem.Allocator.Error)!ast.SubscriptSelector {        if (self.match(.colon)) {            const stop = try self.optionalSliceExpression();            const step = if (self.match(.colon)) try self.optionalSliceExpression() else null;            return .{ .slice = .{                .start = null,                .stop = stop,                .step = step,            } };        }        const first = try self.singleExpression();        if (!self.match(.colon)) return .{ .index = first };        const stop = try self.optionalSliceExpression();        const step = if (self.match(.colon)) try self.optionalSliceExpression() else null;        return .{ .slice = .{            .start = first,            .stop = stop,            .step = step,        } };    }    fn optionalSliceExpression(self: *Parser) (Error || std.mem.Allocator.Error)!?*const ast.Expression {        if (self.at(.colon) or self.at(.rbracket)) return null;        return try self.singleExpression();    }    fn arguments(self: *Parser) (Error || std.mem.Allocator.Error)![]const *const ast.Expression {        var args = std.ArrayListUnmanaged(*const ast.Expression).empty;        if (self.match(.rparen)) return try args.toOwnedSlice(self.arena);        while (true) {            try args.append(self.arena, try self.singleExpression());            if (self.match(.rparen)) break;            if (!self.match(.comma)) return Error.ExpectedCommaOrRightParen;            if (self.match(.rparen)) break;        }        return try args.toOwnedSlice(self.arena);    }    fn primary(self: *Parser) (Error || std.mem.Allocator.Error)!*const ast.Expression {        if (self.match(.integer)) {            const value = std.fmt.parseInt(i128, self.previous().span.text(self.source), 10) catch return Error.InvalidInteger;            return try self.leaf(.{ .integer = value });        }        if (self.match(.string)) {            const text = self.previous().span.text(self.source);            return try self.leaf(.{ .string = text[1 .. text.len - 1] });        }        if (self.match(.identifier)) {            return try self.leaf(.{ .name = self.previous().span.text(self.source) });        }        if (self.match(.true_kw)) return try self.leaf(.{ .boolean = true });        if (self.match(.false_kw)) return try self.leaf(.{ .boolean = false });        if (self.match(.none_kw)) return try self.leaf(.none);        if (self.match(.lbracket)) return try self.listDisplay();        if (self.match(.lbrace)) return try self.dictDisplay();        if (self.match(.lparen)) {            if (self.match(.rparen)) return try self.leaf(.{ .tuple = .{ .items = &.{} } });            const node = try self.expression();            if (!self.match(.rparen)) return Error.ExpectedRightParen;            return node;        }        return Error.ExpectedExpression;    }    fn listDisplay(self: *Parser) (Error || std.mem.Allocator.Error)!*const ast.Expression {        var items = std.ArrayListUnmanaged(*const ast.Expression).empty;        if (self.match(.rbracket)) return try self.leaf(.{ .list = .{ .items = try items.toOwnedSlice(self.arena) } });        while (true) {            try items.append(self.arena, try self.singleExpression());            if (self.match(.rbracket)) break;            if (!self.match(.comma)) return Error.ExpectedCommaOrRightBracket;            if (self.match(.rbracket)) break;        }        return try self.leaf(.{ .list = .{ .items = try items.toOwnedSlice(self.arena) } });    }    fn dictDisplay(self: *Parser) (Error || std.mem.Allocator.Error)!*const ast.Expression {        var items = std.ArrayListUnmanaged(ast.DictItem).empty;        if (self.match(.rbrace)) return try self.leaf(.{ .dict = .{ .items = try items.toOwnedSlice(self.arena) } });        while (true) {            const key = try self.singleExpression();            if (!self.match(.colon)) return Error.ExpectedColon;            const value = try self.singleExpression();            try items.append(self.arena, .{                .key = key,                .value = value,            });            if (self.match(.rbrace)) break;            if (!self.match(.comma)) return Error.ExpectedCommaOrRightBrace;            if (self.match(.rbrace)) break;        }        return try self.leaf(.{ .dict = .{ .items = try items.toOwnedSlice(self.arena) } });    }    fn leaf(self: *Parser, expression_node: ast.Expression) std.mem.Allocator.Error!*const ast.Expression {        const node = try self.arena.create(ast.Expression);        node.* = expression_node;        return node;    }    fn binary(self: *Parser, op: ast.BinaryOp, left: *const ast.Expression, right: *const ast.Expression) std.mem.Allocator.Error!*const ast.Expression {        const node = try self.arena.create(ast.Expression);        node.* = .{ .binary = .{ .op = op, .left = left, .right = right } };        return node;    }    fn logical(self: *Parser, op: ast.LogicalOp, left: *const ast.Expression, right: *const ast.Expression) std.mem.Allocator.Error!*const ast.Expression {        const node = try self.arena.create(ast.Expression);        node.* = .{ .logical = .{ .op = op, .left = left, .right = right } };        return node;    }    fn expectIdentifier(self: *Parser) Error![]const u8 {        if (!self.match(.identifier)) return Error.ExpectedIdentifier;        return self.previous().span.text(self.source);    }    fn matchComparison(self: *Parser) ?ast.ComparisonOp {        if (self.match(.equal_equal)) return .equal;        if (self.match(.bang_equal)) return .not_equal;        if (self.match(.less)) return .less;        if (self.match(.less_equal)) return .less_equal;        if (self.match(.greater)) return .greater;        if (self.match(.greater_equal)) return .greater_equal;        if (self.match(.in_kw)) return .contains;        if (self.match(.is_kw)) {            if (self.match(.not_kw)) return .not_identical;            return .identical;        }        if (self.at(.not_kw) and self.peek(1).tag == .in_kw) {            _ = self.advance();            _ = self.advance();            return .not_contains;        }        return null;    }    fn tupleTerminator(self: *const Parser) bool {        return switch (self.peek(0).tag) {            .rparen, .rbracket, .newline, .dedent, .eof, .colon => true,            else => false,        };    }    fn match(self: *Parser, tag: source.Tag) bool {        if (!self.at(tag)) return false;        self.index += 1;        return true;    }    fn at(self: *const Parser, tag: source.Tag) bool {        return self.peek(0).tag == tag;    }    fn peek(self: *const Parser, offset: usize) source.Token {        const target = self.index + offset;        if (target >= self.tokens.len) return self.tokens[self.tokens.len - 1];        return self.tokens[target];    }    fn advance(self: *Parser) source.Token {        const token = self.peek(0);        self.index += 1;        return token;    }    fn previous(self: *const Parser) source.Token {        return self.tokens[self.index - 1];    }};fn compound(statement_node: ast.Statement) bool {    return switch (statement_node) {        .function, .for_stmt, .if_stmt, .while_stmt => true,        else => false,    };}test "parse assignments and precedence" {    var stream = try source.tokenize(std.testing.allocator, "x = 1 + 2 * 3\nx");    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, "x = 1 + 2 * 3\nx", stream.tokens);    defer program_value.deinit();    try std.testing.expectEqual(@as(usize, 2), program_value.statements.len);    try std.testing.expectEqualStrings("x", program_value.statements[0].assign.name);    try std.testing.expect(program_value.statements[0].assign.value.* == .binary);}test "parse function with return and call" {    const bytes =        \\def add(a, b):        \\    return a + b        \\add(1, 2)    ;    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    try std.testing.expectEqual(@as(usize, 2), program_value.statements.len);    try std.testing.expectEqualStrings("add", program_value.statements[0].function.name);    try std.testing.expectEqual(@as(usize, 2), program_value.statements[0].function.params.len);    try std.testing.expect(program_value.statements[1].expression.* == .call);}test "parse if else and while" {    const bytes =        \\x = 0        \\while x < 3:        \\    if x == 1:        \\        x = x + 1        \\    else:        \\        pass        \\    x = x + 1        \\x    ;    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    try std.testing.expectEqual(@as(usize, 3), program_value.statements.len);    try std.testing.expect(program_value.statements[1] == .while_stmt);    try std.testing.expect(program_value.statements[1].while_stmt.condition.* == .comparison);    try std.testing.expect(program_value.statements[1].while_stmt.body[0] == .if_stmt);    try std.testing.expectEqual(@as(usize, 1), program_value.statements[1].while_stmt.body[0].if_stmt.otherwise.len);}test "parse loop control" {    const bytes =        \\for x in [1]:        \\    continue        \\    break    ;    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    try std.testing.expect(program_value.statements[0] == .for_stmt);    try std.testing.expectEqualStrings("x", program_value.statements[0].for_stmt.name);    try std.testing.expect(program_value.statements[0].for_stmt.iterable.* == .list);    try std.testing.expect(program_value.statements[0].for_stmt.body[0] == .continue_stmt);    try std.testing.expect(program_value.statements[0].for_stmt.body[1] == .break_stmt);}test "parse logical precedence" {    const bytes = "x = not 1 == 1 or 2 and 3";    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    const value = program_value.statements[0].assign.value;    try std.testing.expect(value.* == .logical);    try std.testing.expectEqual(ast.LogicalOp.or_op, value.logical.op);    try std.testing.expect(value.logical.left.* == .unary);    try std.testing.expectEqual(ast.UnaryOp.not, value.logical.left.unary.op);    try std.testing.expect(value.logical.left.unary.operand.* == .comparison);    try std.testing.expect(value.logical.right.* == .logical);    try std.testing.expectEqual(ast.LogicalOp.and_op, value.logical.right.logical.op);}test "parse chained comparisons" {    const bytes = "1 < x <= y != 4 is not z";    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    const value = program_value.statements[0].expression;    try std.testing.expect(value.* == .comparison);    try std.testing.expectEqual(@as(usize, 4), value.comparison.terms.len);    try std.testing.expectEqual(ast.ComparisonOp.less, value.comparison.terms[0].op);    try std.testing.expectEqual(ast.ComparisonOp.less_equal, value.comparison.terms[1].op);    try std.testing.expectEqual(ast.ComparisonOp.not_equal, value.comparison.terms[2].op);    try std.testing.expectEqual(ast.ComparisonOp.not_identical, value.comparison.terms[3].op);}test "parse membership comparisons" {    const bytes = "x in xs and y not in ys";    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    const value = program_value.statements[0].expression;    try std.testing.expect(value.* == .logical);    try std.testing.expect(value.logical.left.* == .comparison);    try std.testing.expectEqual(ast.ComparisonOp.contains, value.logical.left.comparison.terms[0].op);    try std.testing.expect(value.logical.right.* == .comparison);    try std.testing.expectEqual(ast.ComparisonOp.not_contains, value.logical.right.comparison.terms[0].op);}test "parse string literals" {    const bytes =        \\"alpha"        \\'beta'    ;    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    try std.testing.expectEqual(@as(usize, 2), program_value.statements.len);    try std.testing.expect(program_value.statements[0].expression.* == .string);    try std.testing.expectEqualStrings("alpha", program_value.statements[0].expression.string);    try std.testing.expectEqualStrings("beta", program_value.statements[1].expression.string);}test "parse tuple expressions" {    const bytes =        \\()        \\(1,)        \\(1, 2)        \\x = 1, 2    ;    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    try std.testing.expect(program_value.statements[0].expression.* == .tuple);    try std.testing.expectEqual(@as(usize, 0), program_value.statements[0].expression.tuple.items.len);    try std.testing.expect(program_value.statements[1].expression.* == .tuple);    try std.testing.expectEqual(@as(usize, 1), program_value.statements[1].expression.tuple.items.len);    try std.testing.expect(program_value.statements[2].expression.* == .tuple);    try std.testing.expectEqual(@as(usize, 2), program_value.statements[2].expression.tuple.items.len);    try std.testing.expect(program_value.statements[3].assign.value.* == .tuple);    try std.testing.expectEqual(@as(usize, 2), program_value.statements[3].assign.value.tuple.items.len);}test "parse list displays" {    const bytes =        \\[]        \\[1, x,]    ;    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    try std.testing.expect(program_value.statements[0].expression.* == .list);    try std.testing.expectEqual(@as(usize, 0), program_value.statements[0].expression.list.items.len);    try std.testing.expect(program_value.statements[1].expression.* == .list);    try std.testing.expectEqual(@as(usize, 2), program_value.statements[1].expression.list.items.len);}test "parse dictionary displays" {    const bytes =        \\{}        \\{"a": 1, "b": x,}    ;    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    try std.testing.expect(program_value.statements[0].expression.* == .dict);    try std.testing.expectEqual(@as(usize, 0), program_value.statements[0].expression.dict.items.len);    try std.testing.expect(program_value.statements[1].expression.* == .dict);    try std.testing.expectEqual(@as(usize, 2), program_value.statements[1].expression.dict.items.len);    try std.testing.expect(program_value.statements[1].expression.dict.items[0].key.* == .string);    try std.testing.expect(program_value.statements[1].expression.dict.items[1].value.* == .name);}test "parse rejects invalid dictionary displays" {    {        const bytes = "{\"a\"}";        var stream = try source.tokenize(std.testing.allocator, bytes);        defer stream.deinit(std.testing.allocator);        try std.testing.expectError(Error.ExpectedColon, parse(std.testing.allocator, bytes, stream.tokens));    }    {        const bytes = "{\"a\": 1 \"b\": 2}";        var stream = try source.tokenize(std.testing.allocator, bytes);        defer stream.deinit(std.testing.allocator);        try std.testing.expectError(Error.ExpectedCommaOrRightBrace, parse(std.testing.allocator, bytes, stream.tokens));    }}test "parse call and list commas remain separators" {    const bytes =        \\f(1, 2)        \\[1, 2]    ;    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    try std.testing.expect(program_value.statements[0].expression.* == .call);    try std.testing.expectEqual(@as(usize, 2), program_value.statements[0].expression.call.arguments.len);    try std.testing.expect(program_value.statements[1].expression.* == .list);    try std.testing.expectEqual(@as(usize, 2), program_value.statements[1].expression.list.items.len);}test "parse attribute method calls" {    const bytes = "xs.append(1)";    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    const value = program_value.statements[0].expression;    try std.testing.expect(value.* == .call);    try std.testing.expect(value.call.target.* == .attribute);    try std.testing.expectEqualStrings("append", value.call.target.attribute.name);    try std.testing.expect(value.call.target.attribute.target.* == .name);    try std.testing.expectEqualStrings("xs", value.call.target.attribute.target.name);}test "parse subscription expressions" {    const bytes = "[1, 2][0]";    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    const value = program_value.statements[0].expression;    try std.testing.expect(value.* == .subscript);    try std.testing.expect(value.subscript.target.* == .list);    try std.testing.expect(value.subscript.selector == .index);    try std.testing.expect(value.subscript.selector.index.* == .integer);}test "parse slice subscription expressions" {    const bytes =        \\xs[1:]        \\xs[:2]        \\xs[1:3:2]        \\xs[:]        \\xs[::2]    ;    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    const first = program_value.statements[0].expression.subscript.selector.slice;    try std.testing.expect(first.start.?.* == .integer);    try std.testing.expect(first.stop == null);    try std.testing.expect(first.step == null);    const second = program_value.statements[1].expression.subscript.selector.slice;    try std.testing.expect(second.start == null);    try std.testing.expect(second.stop.?.* == .integer);    try std.testing.expect(second.step == null);    const third = program_value.statements[2].expression.subscript.selector.slice;    try std.testing.expect(third.start.?.* == .integer);    try std.testing.expect(third.stop.?.* == .integer);    try std.testing.expect(third.step.?.* == .integer);    const fourth = program_value.statements[3].expression.subscript.selector.slice;    try std.testing.expect(fourth.start == null);    try std.testing.expect(fourth.stop == null);    try std.testing.expect(fourth.step == null);    const fifth = program_value.statements[4].expression.subscript.selector.slice;    try std.testing.expect(fifth.start == null);    try std.testing.expect(fifth.stop == null);    try std.testing.expect(fifth.step.?.* == .integer);}test "parse subscript assignment" {    const bytes =        \\xs = [1]        \\xs[0] = 2    ;    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    try std.testing.expect(program_value.statements[0] == .assign);    try std.testing.expect(program_value.statements[1] == .subscript_assign);    try std.testing.expect(program_value.statements[1].subscript_assign.target.* == .name);    try std.testing.expect(program_value.statements[1].subscript_assign.index.* == .integer);}test "parse deletion statements" {    const bytes =        \\del x        \\del xs[0]    ;    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    try std.testing.expect(program_value.statements[0] == .delete);    try std.testing.expectEqualStrings("x", program_value.statements[0].delete.name);    try std.testing.expect(program_value.statements[1] == .delete);    try std.testing.expect(program_value.statements[1].delete.subscript.target.* == .name);    try std.testing.expect(program_value.statements[1].delete.subscript.index.* == .integer);}test "parse rejects slice assignment" {    const bytes = "xs[0:1] = [2]";    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    try std.testing.expectError(Error.UnexpectedToken, parse(std.testing.allocator, bytes, stream.tokens));}test "parse rejects invalid deletion targets" {    {        const bytes = "del xs[0:1]";        var stream = try source.tokenize(std.testing.allocator, bytes);        defer stream.deinit(std.testing.allocator);        try std.testing.expectError(Error.UnexpectedToken, parse(std.testing.allocator, bytes, stream.tokens));    }    {        const bytes = "del [1]";        var stream = try source.tokenize(std.testing.allocator, bytes);        defer stream.deinit(std.testing.allocator);        try std.testing.expectError(Error.UnexpectedToken, parse(std.testing.allocator, bytes, stream.tokens));    }}test "parse rejects comma separated subscripts" {    const bytes = "xs[0, 1]";    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    try std.testing.expectError(Error.ExpectedRightBracket, parse(std.testing.allocator, bytes, stream.tokens));}test "parse rejects invalid assignment target" {    const bytes = "[1] = 2";    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    try std.testing.expectError(Error.UnexpectedToken, parse(std.testing.allocator, bytes, stream.tokens));}test "parse elif chains" {    const bytes =        \\if a:        \\    x = 1        \\elif b:        \\    x = 2        \\else:        \\    x = 3    ;    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    try std.testing.expect(program_value.statements[0] == .if_stmt);    try std.testing.expectEqual(@as(usize, 1), program_value.statements[0].if_stmt.otherwise.len);    try std.testing.expect(program_value.statements[0].if_stmt.otherwise[0] == .if_stmt);    try std.testing.expectEqual(@as(usize, 1), program_value.statements[0].if_stmt.otherwise[0].if_stmt.otherwise.len);}test "parse while else" {    const bytes =        \\while a:        \\    pass        \\else:        \\    x = 1    ;    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    try std.testing.expect(program_value.statements[0] == .while_stmt);    try std.testing.expectEqual(@as(usize, 1), program_value.statements[0].while_stmt.otherwise.len);    try std.testing.expect(program_value.statements[0].while_stmt.otherwise[0] == .assign);}test "parse for else" {    const bytes =        \\for x in [1, 2]:        \\    pass        \\else:        \\    y = x    ;    var stream = try source.tokenize(std.testing.allocator, bytes);    defer stream.deinit(std.testing.allocator);    var program_value = try parse(std.testing.allocator, bytes, stream.tokens);    defer program_value.deinit();    try std.testing.expect(program_value.statements[0] == .for_stmt);    try std.testing.expectEqualStrings("x", program_value.statements[0].for_stmt.name);    try std.testing.expect(program_value.statements[0].for_stmt.iterable.* == .list);    try std.testing.expectEqual(@as(usize, 1), program_value.statements[0].for_stmt.otherwise.len);    try std.testing.expect(program_value.statements[0].for_stmt.otherwise[0] == .assign);}

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

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

Complete caller list for syntax.parser.parse

25 direct callers.

Audit

Definitions3
Public names4
Members15
Version26.7.0
Revisiondaab053ee433