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.
parse: Builds the syntax tree of a program from its source text and the tokens made from that text.
Types and contracts
Public types and contracts.
Error: The errorsparsereturns when the tokens do not form a program of the subset, besides running out of memory.
Source
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.
lib.python.src.syntax.parser.test_parse_assignments_and_precedence[function] — test source atlib/python/src/syntax/parser.zig:582in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_attribute_method_calls[function] — test source atlib/python/src/syntax/parser.zig:802in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_call_and_list_commas_remain_separators[function] — test source atlib/python/src/syntax/parser.zig:786in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_chained_comparisons[function] — test source atlib/python/src/syntax/parser.zig:668in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_deletion_statements[function] — test source atlib/python/src/syntax/parser.zig:886in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_dictionary_displays[function] — test source atlib/python/src/syntax/parser.zig:753in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_elif_chains[function] — test source atlib/python/src/syntax/parser.zig:942in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_for_else[function] — test source atlib/python/src/syntax/parser.zig:979in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_function_with_return_and_call[function] — test source atlib/python/src/syntax/parser.zig:593in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_if_else_and_while[function] — test source atlib/python/src/syntax/parser.zig:610in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_list_displays[function] — test source atlib/python/src/syntax/parser.zig:737in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_logical_precedence[function] — test source atlib/python/src/syntax/parser.zig:651in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_loop_control[function] — test source atlib/python/src/syntax/parser.zig:633in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_membership_comparisons[function] — test source atlib/python/src/syntax/parser.zig:684in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_rejects_comma_separated_subscripts[function] — test source atlib/python/src/syntax/parser.zig:926in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_rejects_invalid_assignment_target[function] — test source atlib/python/src/syntax/parser.zig:934in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_rejects_invalid_deletion_targets[function] — test source atlib/python/src/syntax/parser.zig:911in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_rejects_invalid_dictionary_displays[function] — test source atlib/python/src/syntax/parser.zig:771in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_rejects_slice_assignment[function] — test source atlib/python/src/syntax/parser.zig:903in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_slice_subscription_expressions[function] — test source atlib/python/src/syntax/parser.zig:831in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_string_literals[function] — test source atlib/python/src/syntax/parser.zig:699in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_subscript_assignment[function] — test source atlib/python/src/syntax/parser.zig:870in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_subscription_expressions[function] — test source atlib/python/src/syntax/parser.zig:817in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_tuple_expressions[function] — test source atlib/python/src/syntax/parser.zig:715in nearest public ownertiny.python.syntax.parserlib.python.src.syntax.parser.test_parse_while_else[function] — test source atlib/python/src/syntax/parser.zig:962in nearest public ownertiny.python.syntax.parser
Audit
| Definitions | 3 |
|---|---|
| Public names | 4 |
| Members | 15 |
| Version | 26.7.0 |
| Revision | daab053ee433 |