tiny.python.runtime.vm
Defined in runtime.
The virtual machine runs a compiled Python program one instruction at a time, with one stack of values shared by every call, a list of call frames, a table of global variables and a heap.
API (8)
Actions
Public operations.
Vm.deinit: Frees the frames, the stack, the globals and the heap, and leaves the machine undefined.Vm.init: Returns a machine for the given chunk, with an empty heap, stack, frame list and table of globals.Vm.run: Runs the chunk from its first instruction in a new top-level frame and returns the program's value.Vm.takeHeap: Returns the machine's heap and leaves an empty heap in its place.execute: Tokenizes, parses, compiles and runs the source text, then returns the program's value together with the heap that owns the value's objects.
Types and contracts
Public types and contracts.
Error: The errors the virtual machine returns when a program fails, besideserror.OutOfMemory.Result: A finished run's value together with the heap that owns every object the value can point to.Vm: The state of one run: the chunk it runs, its heap, its call frames, its value stack and its global variables.
Source
Source: lib/python/src/runtime/root.zig:7
zig
pub const vm = @import("vm.zig");Source: lib/python/src/runtime/vm.zig
zig
//! The virtual machine runs a compiled Python program one instruction at a time, with one stack of//! values shared by every call, a list of call frames, a table of global variables and a heap.//!//! A caller hands over source text and needs back the program's value, with memory it can free in//! one step, or an error that says why the program failed. The package accepts part of Python 3.14,//! and a run has to follow Python's rules for every name, call, loop and builtin type a program can//! use.//!//! A run creates objects whose lifetimes the program decides, and the program's value may point at//! any of them, so the objects have to outlive the machine that made them. The package accepts no//! `try` statement, so a program cannot handle an error, and every error ends the run.//!//! The package keeps the stack-machine design of [CPython](https://github.com/python/cpython), with//! Python's names for the builtins it provides (`len`, `range`, `iter`, `next` and five more) and//! for the errors it reports (`TypeError`, `KeyError`, `IndexError`, `ValueError`,//! `AttributeError`, `StopIteration`).//!//! Each Python exception that a run can raise becomes a member of `Error`. A member of `Error` ends//! the run and carries no message, value or position. A name resolves in three places: the current//! call's local variables, then the global variables, then the nine builtin functions, so a global//! named `len` hides the builtin. Assignment at top level writes a global variable, and assignment//! inside a function writes a local variable of that call, because the package accepts no `global`//! statement. `execute` compiles and runs a program and moves the heap into its `Result`, so the//! value it returns stays valid after the machine is gone. The returned value can point into the//! heap and into the source text, because a string literal and its slices with step 1 borrow the//! source. A run has no bound on its steps or its call depth. Each call adds a frame, so a program//! that recurses without end runs until an allocation fails and the run returns//! `error.OutOfMemory`. A loop without end that allocates nothing never returns.const std = @import("std");const source = @import("../source/root.zig");const syntax = @import("../syntax/root.zig");const compiler = @import("../compile/root.zig");const code = @import("../code/root.zig");const object = @import("../object/root.zig");/// The errors the virtual machine returns when a program fails, besides `error.OutOfMemory`. A/// caller switches on it to report why a program failed while it ran. Most tags carry the name of/// the Python exception that the same failure raises. An error carries no message, value or/// position. The package accepts no `try` statement, so every error ends the run.pub const Error = error{ /// A call with the wrong number of arguments, to a Python function, a builtin or a bound /// method. ArityMismatch, /// A read of a method the list or dictionary lacks, or of any attribute of another type. AttributeError, /// A list or tuple index outside the sequence, including `pop` on an empty list. IndexError, /// A call to a function value whose position names no function in the top-level chunk. The same /// error also reports a frame whose position has passed the end of its instructions. Only a /// chunk built outside `compile` reaches that second case. InvalidFunction, /// Integer arithmetic outside the signed 128-bit range, the negation of the smallest such /// integer, or a length or count that overflows while joining, repeating, slicing or building a /// dictionary. The error also reports a range element or an `enumerate` counter that passes the /// largest such integer while items remain. IntegerOverflow, /// A missing dictionary key in a lookup, a `del`, or `pop` without a default, or `popitem` on /// an empty dictionary. KeyError, /// An operation that needs more values than the stack holds. StackUnderflow, /// `next` on an exhausted iterator with no default. StopIteration, /// An operation given a value of a type it does not support: arithmetic, ordering, calls, /// iteration, subscripts and slices, an unhashable dictionary key, and builtins given an /// unsupported argument. TypeError, /// A read or `del` of a name that is bound nowhere the lookup searches. UndefinedName, /// A step of zero in `range` or a slice, a string of invalid UTF-8 given to an operation that /// counts its codepoints, or an item of the wrong length given to `dict` or `update` as a key /// and value pair. ValueError,};/// A finished run's value together with the heap that owns every object the value can point to./// `execute` returns one, and the caller reads `value` and then frees everything with `deinit`./// `value` stays valid until `deinit`. Strings in `value` can borrow the source text passed to/// `execute`, so that text has to stay alive while `value` is in use.pub const Result = struct { /// The program's value: the value of its last top-level expression statement, or `None` when no /// expression statement ran. value: object.Value, /// The heap that owns every object `value` can point to. heap: object.Heap, /// Frees the heap of the returned value, and every value derived from it becomes invalid. The /// caller of `execute` defers it right after the call, as the README's example does. The call /// leaves the result undefined. pub fn deinit(self: *Result) void { self.heap.deinit(); self.* = undefined; }};/// Tokenizes, parses, compiles and runs the source text, then returns the program's value together/// with the heap that owns the value's objects. The README's example and nearly every test of the/// package call it with a short program. The call borrows the source text. Before it returns, the/// call frees the tokens, the syntax tree, the chunk and the machine's own state, so the result's/// heap is the only memory it hands back. The returned value borrows the result's heap and stays/// valid only until `Result.deinit`. The returned value can also point into the source text,/// through string literals and their slices, so the text has to stay alive while the value is used./// `Result.deinit` frees the heap with the allocator passed to this call. The call returns the/// errors of `tokenize`, `parse`, `compile` and `Vm.run`, and `error.OutOfMemory`, as one inferred/// error set. On any error the call frees everything it allocated, the heap included.pub fn execute(allocator: std.mem.Allocator, bytes: []const u8) !Result { var stream = try source.tokenize(allocator, bytes); defer stream.deinit(allocator); var program = try syntax.parse(allocator, bytes, stream.tokens); defer program.deinit(); var chunk_value = try compiler.compile(allocator, &program); defer chunk_value.deinit(allocator); var vm = Vm.init(allocator, &chunk_value); defer vm.deinit(); const value = try vm.run(); return .{ .value = value, .heap = vm.takeHeap(), };}/// The state of one run: the chunk it runs, its heap, its call frames, its value stack and its/// global variables. `execute` drives one, and a caller that already holds a compiled chunk can run/// it directly. A machine is made with `init`, run with `run`, and freed with `deinit`. The machine/// borrows the chunk, which has to outlive it. The machine checks stack depth and function/// positions, and it trusts every other position in the chunk, so a chunk built outside `compile`/// has to keep them in range.pub const Vm = struct { /// The allocator given to `init`. The frames, the stack, the globals and the heap all allocate /// from it. allocator: std.mem.Allocator, /// The top-level chunk, which the machine borrows. Every call finds its function in this /// chunk's function table. module: *const code.Chunk, /// The heap that owns every object the run creates. `takeHeap` moves this heap out and leaves /// an empty one in its place. heap: object.Heap, /// One frame per call in progress, the top-level frame first. Each frame holds the chunk it /// runs, its position in that chunk, its local variables and the value `save` last recorded. /// The list grows by one frame per call, with no bound. frames: std.ArrayListUnmanaged(Frame) = .empty, /// The value stack shared by every frame: each operation pops its inputs from the end and /// pushes its result there. stack: std.ArrayListUnmanaged(object.Value) = .empty, /// The global variables, keyed by names borrowed from the chunk. globals: std.StringHashMapUnmanaged(object.Value) = .empty, /// Returns a machine for the given chunk, with an empty heap, stack, frame list and table of /// globals. `execute` calls it with the chunk from `compile`, and a caller that holds its own /// chunk does the same. The call allocates nothing, so it cannot fail. The machine borrows the /// chunk for its whole life. pub fn init(allocator: std.mem.Allocator, chunk_value: *const code.Chunk) Vm { return .{ .allocator = allocator, .module = chunk_value, .heap = object.Heap.init(allocator), }; } /// Frees the frames, the stack, the globals and the heap, and leaves the machine undefined. /// `execute` defers it right after `init`, so it runs whether `run` succeeds or fails. Values /// from `run` point into the heap freed here, unless `takeHeap` moved the heap out first. pub fn deinit(self: *Vm) void { while (self.frames.items.len > 0) self.popFrame(); self.frames.deinit(self.allocator); self.stack.deinit(self.allocator); self.globals.deinit(self.allocator); self.heap.deinit(); self.* = undefined; } /// Returns the machine's heap and leaves an empty heap in its place. `execute` calls it after /// `run`, so the objects of the result outlive the machine. The caller owns the returned heap /// and frees it with `Heap.deinit`. pub fn takeHeap(self: *Vm) object.Heap { const heap = self.heap; self.heap = object.Heap.init(self.allocator); return heap; } /// Runs the chunk from its first instruction in a new top-level frame and returns the program's /// value. `execute` calls it once per program. The value is the one that `ret` or a top-level /// `return_value` returns. Returned values point into the machine's heap. The call returns a /// member of `Error` when the program fails, and `error.OutOfMemory` when an allocation fails. /// After an error, the frames, the stack and the heap keep the state at the failure until /// `deinit` frees them. A run has no bound on its steps or its call depth. pub fn run(self: *Vm) (Error || std.mem.Allocator.Error)!object.Value { try self.pushFrame(self.module, .{}); while (self.frames.items.len > 0) { const frame = self.currentFrame(); if (frame.ip >= frame.chunk.instructions.items.len) return Error.InvalidFunction; const instruction = frame.chunk.instructions.items[frame.ip]; frame.ip += 1; switch (instruction.op) { .constant => try self.push(frame.chunk.constants.items[instruction.operand]), .load => try self.load(instruction.operand), .store => try self.store(instruction.operand), .delete => try self.delete(instruction.operand), .pop => _ = try self.pop(), .save => frame.last = try self.pop(), .dup => try self.dup(), .swap => try self.swap(), .rotate_three => try self.rotateThree(), .jump => frame.ip = instruction.operand, .jump_if_false => { if (!(try self.peek()).truthy()) frame.ip = instruction.operand; }, .add => try self.binary(.add), .sub => try self.binary(.sub), .mul => try self.binary(.mul), .neg => try self.negate(), .not => try self.logicalNot(), .equal => try self.binary(.equal), .not_equal => try self.binary(.not_equal), .less => try self.binary(.less), .less_equal => try self.binary(.less_equal), .greater => try self.binary(.greater), .greater_equal => try self.binary(.greater_equal), .contains => try self.binary(.contains), .not_contains => try self.binary(.not_contains), .identical => try self.binary(.identical), .not_identical => try self.binary(.not_identical), .call => try self.call(instruction.operand), .attribute => try self.attribute(instruction.operand), .build_list => try self.buildList(instruction.operand), .build_tuple => try self.buildTuple(instruction.operand), .build_dict => try self.buildDict(instruction.operand), .iter => try self.iter(), .for_next => try self.forNext(instruction.operand), .subscript => try self.subscript(), .slice => try self.slice(), .store_subscript => try self.storeSubscript(), .delete_subscript => try self.deleteSubscript(), .return_value => if (try self.returnValue(try self.pop())) |value| return value, .ret => if (try self.returnValue(frame.last)) |value| return value, } } return .none; } fn currentFrame(self: *Vm) *Frame { return &self.frames.items[self.frames.items.len - 1]; } fn pushFrame(self: *Vm, chunk_value: *const code.Chunk, locals: std.StringHashMapUnmanaged(object.Value)) std.mem.Allocator.Error!void { try self.frames.append(self.allocator, .{ .chunk = chunk_value, .locals = locals, }); } fn popFrame(self: *Vm) void { var frame = self.frames.items[self.frames.items.len - 1]; self.frames.items.len -= 1; frame.deinit(self.allocator); } fn push(self: *Vm, value: object.Value) std.mem.Allocator.Error!void { try self.stack.append(self.allocator, value); } fn pop(self: *Vm) Error!object.Value { if (self.stack.items.len == 0) return Error.StackUnderflow; const value = self.stack.items[self.stack.items.len - 1]; self.stack.items.len -= 1; return value; } fn peek(self: *const Vm) Error!object.Value { if (self.stack.items.len == 0) return Error.StackUnderflow; return self.stack.items[self.stack.items.len - 1]; } fn dup(self: *Vm) (Error || std.mem.Allocator.Error)!void { try self.push(try self.peek()); } fn swap(self: *Vm) Error!void { if (self.stack.items.len < 2) return Error.StackUnderflow; const top = self.stack.items.len - 1; std.mem.swap(object.Value, &self.stack.items[top], &self.stack.items[top - 1]); } fn rotateThree(self: *Vm) Error!void { if (self.stack.items.len < 3) return Error.StackUnderflow; const base = self.stack.items.len - 3; const first = self.stack.items[base]; const second = self.stack.items[base + 1]; const third = self.stack.items[base + 2]; self.stack.items[base] = third; self.stack.items[base + 1] = first; self.stack.items[base + 2] = second; } fn load(self: *Vm, name_index: usize) (Error || std.mem.Allocator.Error)!void { const frame = self.currentFrame(); const name = frame.chunk.names.items[name_index]; if (frame.locals.get(name)) |value| { try self.push(value); return; } if (self.globals.get(name)) |value| { try self.push(value); return; } if (builtin(name)) |value| { try self.push(value); return; } return Error.UndefinedName; } fn store(self: *Vm, name_index: usize) (Error || std.mem.Allocator.Error)!void { const value = try self.pop(); const frame = self.currentFrame(); const name = frame.chunk.names.items[name_index]; if (self.frames.items.len == 1) { try self.globals.put(self.allocator, name, value); } else { try frame.locals.put(self.allocator, name, value); } } fn delete(self: *Vm, name_index: usize) Error!void { const frame = self.currentFrame(); const name = frame.chunk.names.items[name_index]; if (self.frames.items.len == 1) { if (!self.globals.remove(name)) return Error.UndefinedName; } else { if (!frame.locals.remove(name)) return Error.UndefinedName; } } fn call(self: *Vm, argument_count: usize) (Error || std.mem.Allocator.Error)!void { if (self.stack.items.len < argument_count + 1) return Error.StackUnderflow; const callee_index = self.stack.items.len - argument_count - 1; const callee = self.stack.items[callee_index]; switch (callee) { .function => |index| try self.callFunction(callee_index, argument_count, index), .builtin => |value| try self.callBuiltin(callee_index, argument_count, value), .method => |value| try self.callNativeMethod(callee_index, argument_count, value), else => return Error.TypeError, } } fn callFunction(self: *Vm, callee_index: usize, argument_count: usize, function_index: usize) (Error || std.mem.Allocator.Error)!void { if (function_index >= self.module.functions.items.len) return Error.InvalidFunction; const function = &self.module.functions.items[function_index]; if (function.params.len != argument_count) return Error.ArityMismatch; var locals = std.StringHashMapUnmanaged(object.Value).empty; errdefer locals.deinit(self.allocator); for (function.params, 0..) |param, index| { try locals.put(self.allocator, param, self.stack.items[callee_index + 1 + index]); } self.stack.items.len = callee_index; try self.pushFrame(&function.chunk, locals); } fn callBuiltin(self: *Vm, callee_index: usize, argument_count: usize, value: object.Builtin) (Error || std.mem.Allocator.Error)!void { const arguments = self.stack.items[callee_index + 1 ..][0..argument_count]; const result = switch (value) { .dict => try self.dictBuiltin(arguments), .enumerate => try self.enumerateBuiltin(arguments), .iter => try self.iterBuiltin(arguments), .len => try self.lenBuiltin(arguments), .list => try self.listBuiltin(arguments), .next => try self.nextBuiltin(arguments), .range => try self.rangeBuiltin(arguments), .reversed => try self.reversedBuiltin(arguments), .tuple => try self.tupleBuiltin(arguments), }; self.stack.items.len = callee_index; try self.push(result); } fn callNativeMethod(self: *Vm, callee_index: usize, argument_count: usize, method: *object.NativeMethod) (Error || std.mem.Allocator.Error)!void { const arguments = self.stack.items[callee_index + 1 ..][0..argument_count]; const result = switch (method.*) { .dict_clear => |dict| try self.dictClearMethod(dict, arguments), .dict_copy => |dict| try self.dictCopyMethod(dict, arguments), .dict_get => |dict| try self.dictGetMethod(dict, arguments), .dict_items => |dict| try self.dictItemsMethod(dict, arguments), .dict_keys => |dict| try self.dictKeysMethod(dict, arguments), .dict_pop => |dict| try self.dictPopMethod(dict, arguments), .dict_popitem => |dict| try self.dictPopitemMethod(dict, arguments), .dict_setdefault => |dict| try self.dictSetdefaultMethod(dict, arguments), .dict_update => |dict| try self.dictUpdateMethod(dict, arguments), .dict_values => |dict| try self.dictValuesMethod(dict, arguments), .list_append => |list| try self.listAppendMethod(list, arguments), .list_clear => |list| try self.listClearMethod(list, arguments), .list_copy => |list| try self.listCopyMethod(list, arguments), .list_pop => |list| try self.listPopMethod(list, arguments), }; self.stack.items.len = callee_index; try self.push(result); } fn attribute(self: *Vm, name_index: usize) (Error || std.mem.Allocator.Error)!void { const target = try self.pop(); const name = self.currentFrame().chunk.names.items[name_index]; const result = switch (target) { .dict => |dict| try self.dictAttribute(dict, name), .list => |list| try self.listAttribute(list, name), else => return Error.AttributeError, }; try self.push(result); } fn dictAttribute(self: *Vm, dict: *object.Dict, name: []const u8) (Error || std.mem.Allocator.Error)!object.Value { if (std.mem.eql(u8, name, "clear")) return try self.heap.createNativeMethod(.{ .dict_clear = dict }); if (std.mem.eql(u8, name, "copy")) return try self.heap.createNativeMethod(.{ .dict_copy = dict }); if (std.mem.eql(u8, name, "get")) return try self.heap.createNativeMethod(.{ .dict_get = dict }); if (std.mem.eql(u8, name, "items")) return try self.heap.createNativeMethod(.{ .dict_items = dict }); if (std.mem.eql(u8, name, "keys")) return try self.heap.createNativeMethod(.{ .dict_keys = dict }); if (std.mem.eql(u8, name, "pop")) return try self.heap.createNativeMethod(.{ .dict_pop = dict }); if (std.mem.eql(u8, name, "popitem")) return try self.heap.createNativeMethod(.{ .dict_popitem = dict }); if (std.mem.eql(u8, name, "setdefault")) return try self.heap.createNativeMethod(.{ .dict_setdefault = dict }); if (std.mem.eql(u8, name, "update")) return try self.heap.createNativeMethod(.{ .dict_update = dict }); if (std.mem.eql(u8, name, "values")) return try self.heap.createNativeMethod(.{ .dict_values = dict }); return Error.AttributeError; } fn listAttribute(self: *Vm, list: *object.List, name: []const u8) (Error || std.mem.Allocator.Error)!object.Value { if (std.mem.eql(u8, name, "append")) return try self.heap.createNativeMethod(.{ .list_append = list }); if (std.mem.eql(u8, name, "clear")) return try self.heap.createNativeMethod(.{ .list_clear = list }); if (std.mem.eql(u8, name, "copy")) return try self.heap.createNativeMethod(.{ .list_copy = list }); if (std.mem.eql(u8, name, "pop")) return try self.heap.createNativeMethod(.{ .list_pop = list }); return Error.AttributeError; } fn dictClearMethod(self: *Vm, dict: *object.Dict, arguments: []const object.Value) Error!object.Value { _ = self; if (arguments.len != 0) return Error.ArityMismatch; dict.clearRetainingCapacity(); return .none; } fn dictCopyMethod(self: *Vm, dict: *object.Dict, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len != 0) return Error.ArityMismatch; return try self.heap.createDict(dict.entries.items); } fn dictGetMethod(self: *Vm, dict: *object.Dict, arguments: []const object.Value) Error!object.Value { _ = self; if (arguments.len == 0 or arguments.len > 2) return Error.ArityMismatch; if (try dictEntryIndex(dict, arguments[0])) |index| return dict.entries.items[index].value; if (arguments.len == 2) return arguments[1]; return .none; } fn dictItemsMethod(self: *Vm, dict: *object.Dict, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len != 0) return Error.ArityMismatch; return try self.heap.createDictView(dict, .items); } fn dictKeysMethod(self: *Vm, dict: *object.Dict, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len != 0) return Error.ArityMismatch; return try self.heap.createDictView(dict, .keys); } fn dictPopMethod(self: *Vm, dict: *object.Dict, arguments: []const object.Value) Error!object.Value { _ = self; if (arguments.len == 0 or arguments.len > 2) return Error.ArityMismatch; if (try dictEntryIndex(dict, arguments[0])) |index| return dict.removeAt(index); if (arguments.len == 2) return arguments[1]; return Error.KeyError; } fn dictPopitemMethod(self: *Vm, dict: *object.Dict, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len != 0) return Error.ArityMismatch; if (dict.entries.items.len == 0) return Error.KeyError; const entry = dict.popLast().?; const pair = [_]object.Value{ entry.key, entry.value }; return try self.heap.createTuple(&pair); } fn dictSetdefaultMethod(self: *Vm, dict: *object.Dict, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len == 0 or arguments.len > 2) return Error.ArityMismatch; if (try dictEntryIndex(dict, arguments[0])) |index| return dict.entries.items[index].value; const value = if (arguments.len == 2) arguments[1] else object.Value.none; try self.dictSet(dict, arguments[0], value); return value; } fn dictUpdateMethod(self: *Vm, dict: *object.Dict, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len > 1) return Error.ArityMismatch; if (arguments.len == 1) try self.updateDictFromValue(dict, arguments[0]); return .none; } fn dictValuesMethod(self: *Vm, dict: *object.Dict, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len != 0) return Error.ArityMismatch; return try self.heap.createDictView(dict, .values); } fn listAppendMethod(self: *Vm, list: *object.List, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len != 1) return Error.ArityMismatch; try self.listAppend(list, arguments[0]); return .none; } fn listClearMethod(self: *Vm, list: *object.List, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len != 0) return Error.ArityMismatch; self.listClear(list); return .none; } fn listCopyMethod(self: *Vm, list: *object.List, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len != 0) return Error.ArityMismatch; return try self.heap.createList(list.items); } fn listPopMethod(self: *Vm, list: *object.List, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len > 1) return Error.ArityMismatch; const index = if (arguments.len == 1) arguments[0] else object.Value{ .integer = -1 }; return try self.listRemoveAt(list, index); } fn dictBuiltin(self: *Vm, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len > 1) return Error.ArityMismatch; if (arguments.len == 0) return try self.heap.createDict(&.{}); return switch (arguments[0]) { .dict => |value| try self.heap.createDict(value.entries.items), .iterator => |value| try self.dictFromIterator(value), .string => |value| try self.dictFromIterator((try self.heap.createStringIterator(value)).iterator), .list => |value| try self.dictFromIterator((try self.heap.createListIterator(value)).iterator), .tuple => |value| try self.dictFromIterator((try self.heap.createTupleIterator(value)).iterator), .view => |value| try self.dictFromIterator((try self.heap.createDictViewIterator(value)).iterator), .range => |value| try self.dictFromIterator((try self.heap.createRangeIterator(value)).iterator), else => Error.TypeError, }; } fn enumerateBuiltin(self: *Vm, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len == 0 or arguments.len > 2) return Error.ArityMismatch; const start = if (arguments.len == 2) arguments[1].integerLike() orelse return Error.TypeError else 0; return try self.heap.createEnumerateIterator((try self.iteratorValue(arguments[0])).iterator, start); } fn iterBuiltin(self: *Vm, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len != 1) return Error.ArityMismatch; return try self.iteratorValue(arguments[0]); } fn lenBuiltin(self: *Vm, arguments: []const object.Value) Error!object.Value { _ = self; if (arguments.len != 1) return Error.ArityMismatch; const length = switch (arguments[0]) { .string => |value| std.unicode.utf8CountCodepoints(value) catch return Error.ValueError, .list => |value| value.items.len, .tuple => |value| value.items.len, .dict => |value| value.entries.items.len, .view => |value| value.dict.entries.items.len, .range => |value| value.length, else => return Error.TypeError, }; return .{ .integer = @intCast(length) }; } fn listBuiltin(self: *Vm, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len > 1) return Error.ArityMismatch; if (arguments.len == 0) return try self.heap.createList(&.{}); return switch (arguments[0]) { .iterator => |value| try self.listFromIterator(value), .string => |value| try self.listFromIterator((try self.heap.createStringIterator(value)).iterator), .list => |value| try self.heap.createList(value.items), .tuple => |value| try self.heap.createList(value.items), .dict => |value| try self.listFromIterator((try self.heap.createDictIterator(value)).iterator), .view => |value| try self.listFromIterator((try self.heap.createDictViewIterator(value)).iterator), .range => |value| try self.listFromIterator((try self.heap.createRangeIterator(value)).iterator), else => Error.TypeError, }; } fn nextBuiltin(self: *Vm, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len == 0 or arguments.len > 2) return Error.ArityMismatch; const iterator = switch (arguments[0]) { .iterator => |value| value, else => return Error.TypeError, }; if (try self.iteratorNext(iterator)) |value| return value; if (arguments.len == 2) return arguments[1]; return Error.StopIteration; } fn rangeBuiltin(self: *Vm, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len == 0 or arguments.len > 3) return Error.ArityMismatch; const stop = arguments[if (arguments.len == 1) 0 else 1].integerLike() orelse return Error.TypeError; const start = if (arguments.len == 1) 0 else arguments[0].integerLike() orelse return Error.TypeError; const step = if (arguments.len == 3) arguments[2].integerLike() orelse return Error.TypeError else 1; if (step == 0) return Error.ValueError; return try self.heap.createRange(start, stop, step, try rangeLength(start, stop, step)); } fn reversedBuiltin(self: *Vm, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len != 1) return Error.ArityMismatch; return try self.reverseIteratorValue(arguments[0]); } fn tupleBuiltin(self: *Vm, arguments: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { if (arguments.len > 1) return Error.ArityMismatch; if (arguments.len == 0) return try self.heap.createTuple(&.{}); return switch (arguments[0]) { .iterator => |value| try self.tupleFromIterator(value), .string => |value| try self.tupleFromIterator((try self.heap.createStringIterator(value)).iterator), .list => |value| try self.heap.createTuple(value.items), .tuple => arguments[0], .view => |value| try self.tupleFromIterator((try self.heap.createDictViewIterator(value)).iterator), .range => |value| try self.tupleFromIterator((try self.heap.createRangeIterator(value)).iterator), else => Error.TypeError, }; } fn listFromIterator(self: *Vm, iterator: *object.value.Iterator) (Error || std.mem.Allocator.Error)!object.Value { var items = std.ArrayListUnmanaged(object.Value).empty; defer items.deinit(self.allocator); while (try self.iteratorNext(iterator)) |item| { try items.append(self.allocator, item); } return try self.heap.createList(items.items); } fn tupleFromIterator(self: *Vm, iterator: *object.value.Iterator) (Error || std.mem.Allocator.Error)!object.Value { var items = std.ArrayListUnmanaged(object.Value).empty; defer items.deinit(self.allocator); while (try self.iteratorNext(iterator)) |item| { try items.append(self.allocator, item); } return try self.heap.createTuple(items.items); } fn dictFromIterator(self: *Vm, iterator: *object.value.Iterator) (Error || std.mem.Allocator.Error)!object.Value { const dict = (try self.heap.createDict(&.{})).dict; while (try self.iteratorNext(iterator)) |item| { const pair = try pairFromValue(item); try self.dictSet(dict, pair.key, pair.value); } return .{ .dict = dict }; } fn updateDictFromValue(self: *Vm, dict: *object.Dict, value: object.Value) (Error || std.mem.Allocator.Error)!void { switch (value) { .dict => |other| try self.updateDictFromEntries(dict, other.entries.items), .iterator => |iterator| try self.updateDictFromIterator(dict, iterator), .string => |string| try self.updateDictFromIterator(dict, (try self.heap.createStringIterator(string)).iterator), .list => |list| try self.updateDictFromIterator(dict, (try self.heap.createListIterator(list)).iterator), .tuple => |tuple| try self.updateDictFromIterator(dict, (try self.heap.createTupleIterator(tuple)).iterator), .view => |view| try self.updateDictFromIterator(dict, (try self.heap.createDictViewIterator(view)).iterator), .range => |range| try self.updateDictFromIterator(dict, (try self.heap.createRangeIterator(range)).iterator), else => return Error.TypeError, } } fn updateDictFromEntries(self: *Vm, dict: *object.Dict, entries: []const object.DictEntry) (Error || std.mem.Allocator.Error)!void { for (entries) |entry| try self.dictSet(dict, entry.key, entry.value); } fn updateDictFromIterator(self: *Vm, dict: *object.Dict, iterator: *object.value.Iterator) (Error || std.mem.Allocator.Error)!void { while (try self.iteratorNext(iterator)) |item| { const pair = try pairFromValue(item); try self.dictSet(dict, pair.key, pair.value); } } fn buildList(self: *Vm, item_count: usize) (Error || std.mem.Allocator.Error)!void { if (self.stack.items.len < item_count) return Error.StackUnderflow; const start = self.stack.items.len - item_count; const list = try self.heap.createList(self.stack.items[start..]); self.stack.items.len = start; try self.push(list); } fn buildTuple(self: *Vm, item_count: usize) (Error || std.mem.Allocator.Error)!void { if (self.stack.items.len < item_count) return Error.StackUnderflow; const start = self.stack.items.len - item_count; const tuple = try self.heap.createTuple(self.stack.items[start..]); self.stack.items.len = start; try self.push(tuple); } fn buildDict(self: *Vm, item_count: usize) (Error || std.mem.Allocator.Error)!void { const stack_count = std.math.mul(usize, item_count, 2) catch return Error.IntegerOverflow; if (self.stack.items.len < stack_count) return Error.StackUnderflow; const start = self.stack.items.len - stack_count; const dict = (try self.heap.createDict(&.{})).dict; try dict.ensureTotalCapacity(self.allocator, item_count); for (0..item_count) |index| { const key_index = start + index * 2; try self.dictSet(dict, self.stack.items[key_index], self.stack.items[key_index + 1]); } self.stack.items.len = start; try self.push(.{ .dict = dict }); } fn iter(self: *Vm) (Error || std.mem.Allocator.Error)!void { const iterable = try self.pop(); try self.push(try self.iteratorValue(iterable)); } fn iteratorValue(self: *Vm, iterable: object.Value) (Error || std.mem.Allocator.Error)!object.Value { return switch (iterable) { .iterator => iterable, .list => |list| try self.heap.createListIterator(list), .dict => |dict| try self.heap.createDictIterator(dict), .view => |view| try self.heap.createDictViewIterator(view), .tuple => |tuple| try self.heap.createTupleIterator(tuple), .range => |range| try self.heap.createRangeIterator(range), .string => |string| try self.heap.createStringIterator(string), else => Error.TypeError, }; } fn reverseIteratorValue(self: *Vm, iterable: object.Value) (Error || std.mem.Allocator.Error)!object.Value { return switch (iterable) { .list => |list| try self.heap.createListReverseIterator(list), .dict => |dict| try self.heap.createDictReverseIterator(dict), .view => |view| try self.heap.createDictViewReverseIterator(view), .tuple => |tuple| try self.heap.createTupleReverseIterator(tuple), .range => |range| try self.heap.createRangeReverseIterator(range), .string => |string| try self.heap.createStringReverseIterator(string), else => Error.TypeError, }; } fn forNext(self: *Vm, exit: usize) (Error || std.mem.Allocator.Error)!void { const value = try self.peek(); switch (value) { .iterator => |iterator| { if (try self.iteratorNext(iterator)) |item| { try self.push(item); } else { _ = try self.pop(); self.currentFrame().ip = exit; } }, else => return Error.TypeError, } } fn iteratorNext(self: *Vm, iterator: *object.value.Iterator) (Error || std.mem.Allocator.Error)!?object.Value { return switch (iterator.*) { .dict => |*dict| dictIteratorNext(dict), .enumerate => |*enumerate| try self.enumerateIteratorNext(enumerate), .view => |*view| try self.dictViewIteratorNext(view), .list => |*list| listIteratorNext(list), .tuple => |*tuple| tupleIteratorNext(tuple), .range => |*range| rangeIteratorNext(range), .string => |*string| stringIteratorNext(string), }; } fn enumerateIteratorNext(self: *Vm, iterator: *object.value.EnumerateIterator) (Error || std.mem.Allocator.Error)!?object.Value { if (iterator.overflowed) { if (try self.iteratorNext(iterator.iterator)) |_| return Error.IntegerOverflow; return null; } const item = (try self.iteratorNext(iterator.iterator)) orelse return null; const pair = [_]object.Value{ .{ .integer = iterator.index }, item, }; iterator.index = std.math.add(i128, iterator.index, 1) catch blk: { iterator.overflowed = true; break :blk iterator.index; }; return try self.heap.createTuple(&pair); } fn listIteratorNext(iterator: *object.value.ListIterator) ?object.Value { if (iterator.index >= iterator.list.items.len) return null; const index = if (iterator.reverse) iterator.list.items.len - 1 - iterator.index else iterator.index; const item = iterator.list.items[index]; iterator.index += 1; return item; } fn dictIteratorNext(iterator: *object.value.DictIterator) ?object.Value { if (iterator.index >= iterator.dict.entries.items.len) return null; const index = if (iterator.reverse) iterator.dict.entries.items.len - 1 - iterator.index else iterator.index; const item = iterator.dict.entries.items[index].key; iterator.index += 1; return item; } fn dictViewIteratorNext(self: *Vm, iterator: *object.value.DictViewIterator) (Error || std.mem.Allocator.Error)!?object.Value { if (iterator.index >= iterator.view.dict.entries.items.len) return null; const index = if (iterator.reverse) iterator.view.dict.entries.items.len - 1 - iterator.index else iterator.index; const entry = iterator.view.dict.entries.items[index]; iterator.index += 1; return switch (iterator.view.kind) { .keys => entry.key, .values => entry.value, .items => blk: { const pair = [_]object.Value{ entry.key, entry.value }; break :blk try self.heap.createTuple(&pair); }, }; } fn tupleIteratorNext(iterator: *object.value.TupleIterator) ?object.Value { if (iterator.index >= iterator.tuple.items.len) return null; const index = if (iterator.reverse) iterator.tuple.items.len - 1 - iterator.index else iterator.index; const item = iterator.tuple.items[index]; iterator.index += 1; return item; } fn rangeIteratorNext(iterator: *object.value.RangeIterator) Error!?object.Value { if (iterator.index >= iterator.range.length) { return null; } if (iterator.reverse) { const index: i128 = @intCast(iterator.range.length - 1 - iterator.index); const item = try rangeValueAt(iterator.range, index); iterator.index += 1; return .{ .integer = item }; } const item = iterator.next; const next_index = iterator.index + 1; const next = if (next_index < iterator.range.length) std.math.add(i128, iterator.next, iterator.range.step) catch return Error.IntegerOverflow else undefined; iterator.index = next_index; if (iterator.index < iterator.range.length) iterator.next = next; return .{ .integer = item }; } fn stringIteratorNext(iterator: *object.value.StringIterator) Error!?object.Value { if (iterator.reverse) return stringReverseIteratorNext(iterator); if (iterator.index >= iterator.value.len) return null; var view = std.unicode.Utf8View.init(iterator.value[iterator.index..]) catch return Error.ValueError; var utf8 = view.iterator(); const codepoint = utf8.nextCodepointSlice() orelse return null; const start = iterator.index; iterator.index += codepoint.len; return .{ .string = iterator.value[start..iterator.index] }; } fn stringReverseIteratorNext(iterator: *object.value.StringIterator) Error!?object.Value { if (iterator.index == 0) return null; _ = std.unicode.Utf8View.init(iterator.value) catch return Error.ValueError; var start = iterator.index - 1; while (start > 0 and (iterator.value[start] & 0xc0) == 0x80) start -= 1; const end = iterator.index; iterator.index = start; return .{ .string = iterator.value[start..end] }; } fn subscript(self: *Vm) (Error || std.mem.Allocator.Error)!void { const index = try self.pop(); const target = try self.pop(); switch (target) { .list => |list| try self.push(try listItem(list, index)), .tuple => |tuple| try self.push(try tupleItem(tuple, index)), .dict => |dict| try self.push(try dictItem(dict, index)), else => return Error.TypeError, } } fn slice(self: *Vm) (Error || std.mem.Allocator.Error)!void { const step = try self.pop(); const stop = try self.pop(); const start = try self.pop(); const target = try self.pop(); const result = switch (target) { .list => |list| try self.listSlice(list, start, stop, step), .tuple => |tuple| try self.tupleSlice(tuple, start, stop, step), .range => |range| try self.rangeSlice(range, start, stop, step), .string => |string| try self.stringSlice(string, start, stop, step), else => return Error.TypeError, }; try self.push(result); } fn storeSubscript(self: *Vm) (Error || std.mem.Allocator.Error)!void { const value = try self.pop(); const index = try self.pop(); const target = try self.pop(); switch (target) { .list => |list| list.items[try listIndex(list, index)] = value, .dict => |dict| try self.dictSet(dict, index, value), else => return Error.TypeError, } } fn deleteSubscript(self: *Vm) (Error || std.mem.Allocator.Error)!void { const index = try self.pop(); const target = try self.pop(); switch (target) { .list => |list| try self.listDelete(list, index), .dict => |dict| try dictDelete(dict, index), else => return Error.TypeError, } } fn listItem(list: *const object.List, index_value: object.Value) Error!object.Value { return list.items[try sequenceIndex(list.items.len, index_value)]; } fn listIndex(list: *const object.List, index_value: object.Value) Error!usize { return sequenceIndex(list.items.len, index_value); } fn tupleItem(tuple: *const object.Tuple, index_value: object.Value) Error!object.Value { return tuple.items[try sequenceIndex(tuple.items.len, index_value)]; } fn dictItem(dict: *const object.Dict, key: object.Value) Error!object.Value { const index = try dictEntryIndex(dict, key) orelse return Error.KeyError; return dict.entries.items[index].value; } fn dictSet(self: *Vm, dict: *object.Dict, key: object.Value, value: object.Value) (Error || std.mem.Allocator.Error)!void { if (!key.hashable()) return Error.TypeError; try dict.put(self.allocator, key, value); } fn listDelete(self: *Vm, list: *object.List, index_value: object.Value) (Error || std.mem.Allocator.Error)!void { _ = try self.listRemoveAt(list, index_value); } fn listAppend(self: *Vm, list: *object.List, value: object.Value) (Error || std.mem.Allocator.Error)!void { _ = try sequenceConcatLength(list.items.len, 1); try list.append(self.allocator, value); } fn listClear(self: *Vm, list: *object.List) void { list.clearAndFree(self.allocator); } fn listRemoveAt(self: *Vm, list: *object.List, index_value: object.Value) Error!object.Value { const index = try listIndex(list, index_value); const removed = list.orderedRemove(index); if (list.items.len < list.capacity / 2) list.shrinkAndFree(self.allocator, list.items.len); return removed; } fn dictDelete(dict: *object.Dict, key: object.Value) Error!void { const index = try dictEntryIndex(dict, key) orelse return Error.KeyError; _ = dict.removeAt(index); } fn listSlice(self: *Vm, list: *const object.List, start_value: object.Value, stop_value: object.Value, step_value: object.Value) (Error || std.mem.Allocator.Error)!object.Value { const bounds = try sliceBounds(list.items.len, start_value, stop_value, step_value); var items = std.ArrayListUnmanaged(object.Value).empty; defer items.deinit(self.allocator); try items.ensureTotalCapacity(self.allocator, sliceCount(bounds)); var index = bounds.start; while (sliceIncludes(index, bounds)) { try items.append(self.allocator, list.items[@intCast(index)]); index = std.math.add(i128, index, bounds.step) catch break; } return try self.heap.createList(items.items); } fn tupleSlice(self: *Vm, tuple: *const object.Tuple, start_value: object.Value, stop_value: object.Value, step_value: object.Value) (Error || std.mem.Allocator.Error)!object.Value { const bounds = try sliceBounds(tuple.items.len, start_value, stop_value, step_value); var items = std.ArrayListUnmanaged(object.Value).empty; defer items.deinit(self.allocator); try items.ensureTotalCapacity(self.allocator, sliceCount(bounds)); var index = bounds.start; while (sliceIncludes(index, bounds)) { try items.append(self.allocator, tuple.items[@intCast(index)]); index = std.math.add(i128, index, bounds.step) catch break; } return try self.heap.createTuple(items.items); } fn rangeSlice(self: *Vm, range: *const object.Range, start_value: object.Value, stop_value: object.Value, step_value: object.Value) (Error || std.mem.Allocator.Error)!object.Value { const bounds = try sliceBounds(range.length, start_value, stop_value, step_value); const length = sliceCount(bounds); if (length == 0) return try self.heap.createRange(0, 0, 1, 0); const start = try rangeValueAt(range, bounds.start); const step = std.math.mul(i128, range.step, bounds.step) catch return Error.IntegerOverflow; const length_value: i128 = @intCast(length); const span = std.math.mul(i128, step, length_value) catch return Error.IntegerOverflow; const stop = std.math.add(i128, start, span) catch return Error.IntegerOverflow; return try self.heap.createRange(start, stop, step, length); } fn stringSlice(self: *Vm, value: []const u8, start_value: object.Value, stop_value: object.Value, step_value: object.Value) (Error || std.mem.Allocator.Error)!object.Value { var offsets = std.ArrayListUnmanaged(usize).empty; defer offsets.deinit(self.allocator); var view = std.unicode.Utf8View.init(value) catch return Error.ValueError; var iterator = view.iterator(); var byte_index: usize = 0; while (iterator.nextCodepointSlice()) |codepoint| { try offsets.append(self.allocator, byte_index); byte_index += codepoint.len; } try offsets.append(self.allocator, value.len); const bounds = try sliceBounds(offsets.items.len - 1, start_value, stop_value, step_value); if (sliceCount(bounds) == 0) return .{ .string = value[0..0] }; if (bounds.step == 1) { const start: usize = @intCast(bounds.start); const stop: usize = @intCast(bounds.stop); return .{ .string = value[offsets.items[start]..offsets.items[stop]] }; } var bytes = std.ArrayListUnmanaged(u8).empty; defer bytes.deinit(self.allocator); var index = bounds.start; while (sliceIncludes(index, bounds)) { const item: usize = @intCast(index); try bytes.appendSlice(self.allocator, value[offsets.items[item]..offsets.items[item + 1]]); index = std.math.add(i128, index, bounds.step) catch break; } return try self.heap.createString(bytes.items); } fn sequenceIndex(length: usize, index_value: object.Value) Error!usize { var index = index_value.integerLike() orelse return Error.TypeError; const len: i128 = @intCast(length); if (index < 0) index += len; if (index < 0 or index >= len) return Error.IndexError; return @intCast(index); } fn returnValue(self: *Vm, value: object.Value) std.mem.Allocator.Error!?object.Value { self.popFrame(); if (self.frames.items.len == 0) { return value; } else { try self.push(value); } return null; } fn negate(self: *Vm) (Error || std.mem.Allocator.Error)!void { const value = try self.pop(); const integer = value.integerLike() orelse return Error.TypeError; if (integer == std.math.minInt(i128)) return Error.IntegerOverflow; try self.push(.{ .integer = -integer }); } fn logicalNot(self: *Vm) (Error || std.mem.Allocator.Error)!void { const value = try self.pop(); try self.push(.{ .boolean = !value.truthy() }); } fn binary(self: *Vm, op: BinaryOp) (Error || std.mem.Allocator.Error)!void { const right = try self.pop(); const left = try self.pop(); switch (op) { .equal => { try self.push(.{ .boolean = left.eql(right) }); return; }, .not_equal => { try self.push(.{ .boolean = !left.eql(right) }); return; }, .identical => { try self.push(.{ .boolean = identical(left, right) }); return; }, .not_identical => { try self.push(.{ .boolean = !identical(left, right) }); return; }, .contains => { try self.push(.{ .boolean = try self.contains(left, right) }); return; }, .not_contains => { try self.push(.{ .boolean = !(try self.contains(left, right)) }); return; }, .add => { try self.push(try self.add(left, right)); return; }, .mul => { try self.push(try self.multiply(left, right)); return; }, .less, .less_equal, .greater, .greater_equal => { try self.push(.{ .boolean = try orderValues(left, right, op) }); return; }, else => {}, } const a = left.integerLike() orelse return Error.TypeError; const b = right.integerLike() orelse return Error.TypeError; switch (op) { .add => try self.push(.{ .integer = std.math.add(i128, a, b) catch return Error.IntegerOverflow }), .sub => try self.push(.{ .integer = std.math.sub(i128, a, b) catch return Error.IntegerOverflow }), .mul => try self.push(.{ .integer = std.math.mul(i128, a, b) catch return Error.IntegerOverflow }), .less => try self.push(.{ .boolean = a < b }), .less_equal => try self.push(.{ .boolean = a <= b }), .greater => try self.push(.{ .boolean = a > b }), .greater_equal => try self.push(.{ .boolean = a >= b }), .equal, .not_equal, .identical, .not_identical, .contains, .not_contains => unreachable, } } fn add(self: *Vm, left: object.Value, right: object.Value) (Error || std.mem.Allocator.Error)!object.Value { return switch (left) { .list => |list| switch (right) { .list => |other| try self.listConcat(list.items, other.items), else => Error.TypeError, }, .tuple => |tuple| switch (right) { .tuple => |other| try self.tupleConcat(tuple.items, other.items), else => Error.TypeError, }, .string => |string| switch (right) { .string => |other| try self.stringConcat(string, other), else => Error.TypeError, }, else => { const a = left.integerLike() orelse return Error.TypeError; const b = right.integerLike() orelse return Error.TypeError; return .{ .integer = std.math.add(i128, a, b) catch return Error.IntegerOverflow }; }, }; } fn multiply(self: *Vm, left: object.Value, right: object.Value) (Error || std.mem.Allocator.Error)!object.Value { return switch (left) { .list => |list| try self.listRepeat(list.items, right), .tuple => |tuple| try self.tupleRepeat(tuple.items, right), .string => |string| try self.stringRepeat(string, right), else => switch (right) { .list => |list| try self.listRepeat(list.items, left), .tuple => |tuple| try self.tupleRepeat(tuple.items, left), .string => |string| try self.stringRepeat(string, left), else => { const a = left.integerLike() orelse return Error.TypeError; const b = right.integerLike() orelse return Error.TypeError; return .{ .integer = std.math.mul(i128, a, b) catch return Error.IntegerOverflow }; }, }, }; } fn listConcat(self: *Vm, left: []const object.Value, right: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { var items = std.ArrayListUnmanaged(object.Value).empty; defer items.deinit(self.allocator); try items.ensureTotalCapacity(self.allocator, try sequenceConcatLength(left.len, right.len)); try items.appendSlice(self.allocator, left); try items.appendSlice(self.allocator, right); return try self.heap.createList(items.items); } fn tupleConcat(self: *Vm, left: []const object.Value, right: []const object.Value) (Error || std.mem.Allocator.Error)!object.Value { var items = std.ArrayListUnmanaged(object.Value).empty; defer items.deinit(self.allocator); try items.ensureTotalCapacity(self.allocator, try sequenceConcatLength(left.len, right.len)); try items.appendSlice(self.allocator, left); try items.appendSlice(self.allocator, right); return try self.heap.createTuple(items.items); } fn stringConcat(self: *Vm, left: []const u8, right: []const u8) (Error || std.mem.Allocator.Error)!object.Value { var bytes = std.ArrayListUnmanaged(u8).empty; defer bytes.deinit(self.allocator); try bytes.ensureTotalCapacity(self.allocator, try sequenceConcatLength(left.len, right.len)); try bytes.appendSlice(self.allocator, left); try bytes.appendSlice(self.allocator, right); return try self.heap.createString(bytes.items); } fn listRepeat(self: *Vm, items: []const object.Value, count_value: object.Value) (Error || std.mem.Allocator.Error)!object.Value { const count = try repeatCount(count_value); const length = try repeatLength(items.len, count); if (length == 0) return try self.heap.createList(&.{}); var repeated = std.ArrayListUnmanaged(object.Value).empty; defer repeated.deinit(self.allocator); try repeated.ensureTotalCapacity(self.allocator, length); for (0..count) |_| try repeated.appendSlice(self.allocator, items); return try self.heap.createList(repeated.items); } fn tupleRepeat(self: *Vm, items: []const object.Value, count_value: object.Value) (Error || std.mem.Allocator.Error)!object.Value { const count = try repeatCount(count_value); const length = try repeatLength(items.len, count); if (length == 0) return try self.heap.createTuple(&.{}); var repeated = std.ArrayListUnmanaged(object.Value).empty; defer repeated.deinit(self.allocator); try repeated.ensureTotalCapacity(self.allocator, length); for (0..count) |_| try repeated.appendSlice(self.allocator, items); return try self.heap.createTuple(repeated.items); } fn stringRepeat(self: *Vm, bytes: []const u8, count_value: object.Value) (Error || std.mem.Allocator.Error)!object.Value { const count = try repeatCount(count_value); const length = try repeatLength(bytes.len, count); if (length == 0) return try self.heap.createString(""); var repeated = std.ArrayListUnmanaged(u8).empty; defer repeated.deinit(self.allocator); try repeated.ensureTotalCapacity(self.allocator, length); for (0..count) |_| try repeated.appendSlice(self.allocator, bytes); return try self.heap.createString(repeated.items); } fn contains(self: *Vm, item: object.Value, container: object.Value) (Error || std.mem.Allocator.Error)!bool { return switch (container) { .list => |list| listContains(item, list), .tuple => |tuple| sequenceContains(item, tuple.items), .dict => |dict| try dictContains(item, dict), .view => |view| try self.dictViewContains(item, view), .range => |range| rangeContains(item, range), .string => |string| stringContains(item, string), .iterator => |iterator| try self.iteratorContains(item, iterator), else => Error.TypeError, }; } fn iteratorContains(self: *Vm, item: object.Value, iterator: *object.value.Iterator) (Error || std.mem.Allocator.Error)!bool { while (try self.iteratorNext(iterator)) |candidate| { if (identical(item, candidate) or item.eql(candidate)) return true; } return false; } fn dictViewContains(self: *Vm, item: object.Value, view: *const object.DictView) (Error || std.mem.Allocator.Error)!bool { _ = self; return switch (view.kind) { .keys => try dictContains(item, view.dict), .values => dictValuesContain(item, view.dict), .items => try dictItemsContain(item, view.dict), }; }};const BinaryOp = enum { add, sub, mul, equal, not_equal, less, less_equal, greater, greater_equal, contains, not_contains, identical, not_identical,};fn sequenceConcatLength(left: usize, right: usize) Error!usize { return std.math.add(usize, left, right) catch Error.IntegerOverflow;}fn repeatCount(value: object.Value) Error!usize { const count = value.integerLike() orelse return Error.TypeError; if (count <= 0) return 0; const max: i128 = @intCast(std.math.maxInt(usize)); if (count > max) return Error.IntegerOverflow; return @intCast(count);}fn repeatLength(length: usize, count: usize) Error!usize { return std.math.mul(usize, length, count) catch Error.IntegerOverflow;}const ValueOrder = enum { lt, eq, gt,};fn orderValues(left: object.Value, right: object.Value, op: BinaryOp) Error!bool { return orderResult(try compareValues(left, right), op);}fn compareValues(left: object.Value, right: object.Value) Error!ValueOrder { if (left.integerLike()) |a| { if (right.integerLike()) |b| return compareInteger(a, b); } return switch (left) { .string => |value| switch (right) { .string => |other| compareString(value, other), else => Error.TypeError, }, .list => |value| switch (right) { .list => |other| try compareSequences(value.items, other.items), else => Error.TypeError, }, .tuple => |value| switch (right) { .tuple => |other| try compareSequences(value.items, other.items), else => Error.TypeError, }, else => Error.TypeError, };}fn compareSequences(left: []const object.Value, right: []const object.Value) Error!ValueOrder { const count = @min(left.len, right.len); for (left[0..count], right[0..count]) |a, b| { if (identical(a, b) or a.eql(b)) continue; const item_order = try compareValues(a, b); if (item_order != .eq) return item_order; } return compareLength(left.len, right.len);}fn compareInteger(left: i128, right: i128) ValueOrder { if (left < right) return .lt; if (left > right) return .gt; return .eq;}fn compareLength(left: usize, right: usize) ValueOrder { if (left < right) return .lt; if (left > right) return .gt; return .eq;}fn compareString(left: []const u8, right: []const u8) ValueOrder { return switch (std.mem.order(u8, left, right)) { .lt => .lt, .eq => .eq, .gt => .gt, };}fn orderResult(order: ValueOrder, op: BinaryOp) bool { return switch (op) { .less => order == .lt, .less_equal => order != .gt, .greater => order == .gt, .greater_equal => order != .lt, else => unreachable, };}const SliceBounds = struct { start: i128, stop: i128, step: i128,};fn sliceBounds(length: usize, start_value: object.Value, stop_value: object.Value, step_value: object.Value) Error!SliceBounds { const step = (try optionalSliceInteger(step_value)) orelse 1; if (step == 0) return Error.ValueError; const len: i128 = @intCast(length); if (step > 0) { return .{ .start = if (try optionalSliceInteger(start_value)) |value| positiveSliceBound(value, len) else 0, .stop = if (try optionalSliceInteger(stop_value)) |value| positiveSliceBound(value, len) else len, .step = step, }; } return .{ .start = if (try optionalSliceInteger(start_value)) |value| negativeSliceBound(value, len) else len - 1, .stop = if (try optionalSliceInteger(stop_value)) |value| negativeSliceBound(value, len) else -1, .step = step, };}fn optionalSliceInteger(value: object.Value) Error!?i128 { return switch (value) { .none => null, else => value.integerLike() orelse Error.TypeError, };}fn positiveSliceBound(value: i128, length: i128) i128 { var index = value; if (index < 0) index += length; if (index < 0) return 0; if (index > length) return length; return index;}fn negativeSliceBound(value: i128, length: i128) i128 { var index = value; if (index < 0) index += length; if (index < 0) return -1; if (index >= length) return length - 1; return index;}fn sliceIncludes(index: i128, bounds: SliceBounds) bool { if (bounds.step > 0) return index < bounds.stop; return index > bounds.stop;}fn sliceCount(bounds: SliceBounds) usize { var count: usize = 0; var index = bounds.start; while (sliceIncludes(index, bounds)) { count += 1; index = std.math.add(i128, index, bounds.step) catch break; } return count;}fn rangeValueAt(range: *const object.Range, index: i128) Error!i128 { const offset = std.math.mul(i128, range.step, index) catch return Error.IntegerOverflow; return std.math.add(i128, range.start, offset) catch return Error.IntegerOverflow;}fn identical(left: object.Value, right: object.Value) bool { return switch (left) { .none => right == .none, .boolean => |value| switch (right) { .boolean => |other| value == other, else => false, }, .integer => |value| switch (right) { .integer => |other| value == other, else => false, }, .string => |value| switch (right) { .string => |other| value.ptr == other.ptr and value.len == other.len, else => false, }, .function => |value| switch (right) { .function => |other| value == other, else => false, }, .builtin => |value| switch (right) { .builtin => |other| value == other, else => false, }, .method => |value| switch (right) { .method => |other| value == other, else => false, }, .view => |value| switch (right) { .view => |other| value == other, else => false, }, .list => |value| switch (right) { .list => |other| value == other, else => false, }, .tuple => |value| switch (right) { .tuple => |other| value == other, else => false, }, .dict => |value| switch (right) { .dict => |other| value == other, else => false, }, .range => |value| switch (right) { .range => |other| value == other, else => false, }, .iterator => |value| switch (right) { .iterator => |other| value == other, else => false, }, };}fn listContains(item: object.Value, list: *const object.List) bool { return sequenceContains(item, list.items);}fn sequenceContains(item: object.Value, items: []const object.Value) bool { for (items) |candidate| { if (identical(item, candidate) or item.eql(candidate)) return true; } return false;}fn dictContains(item: object.Value, dict: *const object.Dict) Error!bool { return (try dictEntryIndex(dict, item)) != null;}fn dictValuesContain(item: object.Value, dict: *const object.Dict) bool { for (dict.entries.items) |entry| { if (identical(item, entry.value) or item.eql(entry.value)) return true; } return false;}fn dictItemsContain(item: object.Value, dict: *const object.Dict) Error!bool { return switch (item) { .tuple => |tuple| { if (tuple.items.len != 2) return false; const index = try dictEntryIndex(dict, tuple.items[0]) orelse return false; const value = dict.entries.items[index].value; return identical(tuple.items[1], value) or tuple.items[1].eql(value); }, else => false, };}fn dictEntryIndex(dict: *const object.Dict, key: object.Value) Error!?usize { if (!key.hashable()) return Error.TypeError; return dict.indexOf(key);}const Pair = struct { key: object.Value, value: object.Value,};fn pairFromValue(value: object.Value) Error!Pair { return switch (value) { .list => |list| pairFromSlice(list.items), .tuple => |tuple| pairFromSlice(tuple.items), else => Error.TypeError, };}fn pairFromSlice(items: []const object.Value) Error!Pair { if (items.len != 2) return Error.ValueError; return .{ .key = items[0], .value = items[1], };}fn rangeContains(item: object.Value, range: *const object.Range) bool { if (range.length == 0) return false; const value = item.integerLike() orelse return false; if (range.step > 0) { if (value < range.start or value >= range.stop) return false; return orderedDistance(range.start, value) % @as(u128, @intCast(range.step)) == 0; } if (value > range.start or value <= range.stop) return false; return orderedDistance(value, range.start) % negativeMagnitude(range.step) == 0;}fn stringContains(item: object.Value, string: []const u8) Error!bool { const needle = switch (item) { .string => |value| value, else => return Error.TypeError, }; return std.mem.indexOf(u8, string, needle) != null;}fn orderedDistance(start: i128, stop: i128) u128 { std.debug.assert(start <= stop); if (start == stop) return 0; if (start >= 0) return @intCast(stop - start); if (stop <= 0) return nonPositiveMagnitude(start) - nonPositiveMagnitude(stop); return nonPositiveMagnitude(start) + @as(u128, @intCast(stop));}fn builtin(name: []const u8) ?object.Value { if (std.mem.eql(u8, name, "dict")) return .{ .builtin = .dict }; if (std.mem.eql(u8, name, "enumerate")) return .{ .builtin = .enumerate }; if (std.mem.eql(u8, name, "iter")) return .{ .builtin = .iter }; if (std.mem.eql(u8, name, "len")) return .{ .builtin = .len }; if (std.mem.eql(u8, name, "list")) return .{ .builtin = .list }; if (std.mem.eql(u8, name, "next")) return .{ .builtin = .next }; if (std.mem.eql(u8, name, "range")) return .{ .builtin = .range }; if (std.mem.eql(u8, name, "reversed")) return .{ .builtin = .reversed }; if (std.mem.eql(u8, name, "tuple")) return .{ .builtin = .tuple }; return null;}fn rangeLength(start: i128, stop: i128, step: i128) Error!usize { if (step > 0) { if (start >= stop) return 0; return try rangeCount(distanceAscending(start, stop), @intCast(step)); } if (start <= stop) return 0; return try rangeCount(distanceAscending(stop, start), negativeMagnitude(step));}fn rangeCount(distance: u128, step: u128) Error!usize { const count = (distance - 1) / step + 1; if (count > @as(u128, std.math.maxInt(usize))) return Error.IntegerOverflow; return @intCast(count);}fn distanceAscending(start: i128, stop: i128) u128 { std.debug.assert(start < stop); if (start >= 0) return @intCast(stop - start); if (stop <= 0) return nonPositiveMagnitude(start) - nonPositiveMagnitude(stop); return nonPositiveMagnitude(start) + @as(u128, @intCast(stop));}fn nonPositiveMagnitude(value: i128) u128 { if (value == 0) return 0; return negativeMagnitude(value);}fn negativeMagnitude(value: i128) u128 { std.debug.assert(value < 0); return @as(u128, @intCast(-(value + 1))) + 1;}const Frame = struct { chunk: *const code.Chunk, ip: usize = 0, locals: std.StringHashMapUnmanaged(object.Value) = .empty, last: object.Value = .none, fn deinit(self: *Frame, allocator: std.mem.Allocator) void { self.locals.deinit(allocator); self.* = undefined; }};fn executeValue(allocator: std.mem.Allocator, bytes: []const u8) !object.Value { var result = try execute(allocator, bytes); defer result.deinit(); return result.value;}test "execute arithmetic with precedence" { try std.testing.expectEqual(object.Value{ .integer = 7 }, try executeValue(std.testing.allocator, "1 + 2 * 3"));}test "execute assignments" { try std.testing.expectEqual(object.Value{ .integer = 13 }, try executeValue(std.testing.allocator, \\x = 5 \\y = x * 2 \\y + 3 ));}test "execute name deletion" { try std.testing.expectEqual(object.Value{ .integer = 2 }, try executeValue(std.testing.allocator, \\x = 1 \\del x \\x = 2 \\x )); try std.testing.expectError(Error.UndefinedName, executeValue(std.testing.allocator, \\x = 1 \\del x \\x )); try std.testing.expectError(Error.UndefinedName, executeValue(std.testing.allocator, \\def f(): \\ x = 1 \\ del x \\ return x \\f() )); try std.testing.expectError(Error.UndefinedName, executeValue(std.testing.allocator, "del missing"));}test "execute booleans as integers" { try std.testing.expectEqual(object.Value{ .integer = 2 }, try executeValue(std.testing.allocator, "True + True"));}test "execute simple function call" { try std.testing.expectEqual(object.Value{ .integer = 42 }, try executeValue(std.testing.allocator, \\def add(a, b): \\ return a + b \\add(20, 22) ));}test "execute function local frame with global fallback" { try std.testing.expectEqual(object.Value{ .integer = 17 }, try executeValue(std.testing.allocator, \\x = 10 \\def f(x): \\ y = x + 2 \\ return y \\f(5) + x ));}test "execute function without return yields none" { try std.testing.expectEqual(object.Value.none, try executeValue(std.testing.allocator, \\def f(): \\ 1 + 2 \\f() ));}test "execute comparisons" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "1 < 2")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "True == 1")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "None != 0"));}test "execute chained comparisons" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "1 < 2 <= 2 != 3")); try std.testing.expectEqual(object.Value{ .boolean = false }, try executeValue(std.testing.allocator, "1 < 2 < 2")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "1 < 3 > 2"));}test "execute chained comparisons short circuit" { try std.testing.expectEqual(object.Value{ .boolean = false }, try executeValue(std.testing.allocator, "3 < 2 < missing"));}test "execute chained sequence comparisons" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[1] < [2] < [3]")); try std.testing.expectEqual(object.Value{ .boolean = false }, try executeValue(std.testing.allocator, "\"b\" < \"a\" < missing"));}test "execute identity comparisons" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "None is None")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "True is not False")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\xs = [] \\ys = xs \\xs is ys )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\xs = [] \\ys = [] \\xs is not ys ));}test "execute membership comparisons over supported containers" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "2 in [1, 2, 3]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "4 not in [1, 2, 3]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "2 in (1, 2, 3)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "4 not in (1, 2, 3)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "2 in range(0, 5, 2)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "3 not in range(0, 5, 2)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "5 in range(7, 3, -1)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "\"bc\" in \"abcd\"")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "\"\" in \"abcd\"")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "\"z\" not in \"abcd\""));}test "execute membership consumes iterators" { try std.testing.expectEqual(object.Value{ .integer = 2 }, try executeValue(std.testing.allocator, \\it = iter([1, 2, 3]) \\first = 1 in it \\second = next(it) \\third = 3 in it \\if first and third: \\ value = second \\else: \\ value = 0 \\value )); try std.testing.expectEqual(object.Value{ .integer = 9 }, try executeValue(std.testing.allocator, \\it = iter([1]) \\missing = 2 in it \\next(it, 9) ));}test "execute membership errors" { try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "1 in 2")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "1 in \"123\""));}test "execute membership in chained comparisons" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\xs = [1, 2] \\1 in xs == [1, 2] )); try std.testing.expectEqual(object.Value{ .boolean = false }, try executeValue(std.testing.allocator, "1 in [] < missing"));}test "execute if else branches" { try std.testing.expectEqual(object.Value{ .integer = 7 }, try executeValue(std.testing.allocator, \\x = 3 \\if x > 5: \\ y = 1 \\else: \\ y = 7 \\y ));}test "execute if without else true branch" { try std.testing.expectEqual(object.Value{ .integer = 3 }, try executeValue(std.testing.allocator, \\x = 1 \\if x: \\ x = x + 2 \\x ));}test "execute while loop" { try std.testing.expectEqual(object.Value{ .integer = 6 }, try executeValue(std.testing.allocator, \\x = 0 \\sum = 0 \\while x < 4: \\ sum = sum + x \\ x = x + 1 \\sum ));}test "execute control flow inside function" { try std.testing.expectEqual(object.Value{ .integer = 3 }, try executeValue(std.testing.allocator, \\def first_three(limit): \\ x = 0 \\ while x < limit: \\ if x == 3: \\ return x \\ x = x + 1 \\ return -1 \\first_three(5) ));}test "execute break" { try std.testing.expectEqual(object.Value{ .integer = 4 }, try executeValue(std.testing.allocator, \\x = 0 \\while True: \\ x = x + 1 \\ if x == 4: \\ break \\x ));}test "execute continue" { try std.testing.expectEqual(object.Value{ .integer = 12 }, try executeValue(std.testing.allocator, \\x = 0 \\sum = 0 \\while x < 5: \\ x = x + 1 \\ if x == 3: \\ continue \\ sum = sum + x \\sum ));}test "execute loop control inside function" { try std.testing.expectEqual(object.Value{ .integer = 7 }, try executeValue(std.testing.allocator, \\def stop_at(limit): \\ x = 0 \\ while True: \\ x = x + 1 \\ if x == limit: \\ break \\ if x < 3: \\ continue \\ return x \\stop_at(7) ));}test "execute logical not" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "not None")); try std.testing.expectEqual(object.Value{ .boolean = false }, try executeValue(std.testing.allocator, "not 1 == 1"));}test "execute logical and returns selected operand" { try std.testing.expectEqual(object.Value{ .integer = 0 }, try executeValue(std.testing.allocator, "0 and missing")); try std.testing.expectEqual(object.Value{ .integer = 7 }, try executeValue(std.testing.allocator, "1 and 7"));}test "execute logical or returns selected operand" { try std.testing.expectEqual(object.Value{ .integer = 5 }, try executeValue(std.testing.allocator, "5 or missing")); try std.testing.expectEqual(object.Value{ .integer = 9 }, try executeValue(std.testing.allocator, "None or 9"));}test "execute logical operators in control flow" { try std.testing.expectEqual(object.Value{ .integer = 3 }, try executeValue(std.testing.allocator, \\x = 0 \\while x < 5: \\ x = x + 1 \\ if x > 1 and not x == 3: \\ continue \\ if x == 3 or False: \\ break \\x ));}test "execute string literals" { try expectString("alpha", try executeValue(std.testing.allocator, "\"alpha\"")); try expectString("beta", try executeValue(std.testing.allocator, "'beta'"));}test "execute string assignment and return" { try expectString("value", try executeValue(std.testing.allocator, \\x = "value" \\x )); try expectString("done", try executeValue(std.testing.allocator, \\def f(): \\ return "done" \\f() ));}test "execute string equality" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "\"a\" == 'a'")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "\"a\" != \"b\""));}test "execute string truthiness" { try std.testing.expectEqual(object.Value{ .integer = 1 }, try executeValue(std.testing.allocator, \\if "": \\ x = 0 \\else: \\ x = 1 \\x )); try expectString("fallback", try executeValue(std.testing.allocator, "\"\" or \"fallback\"")); try expectString("omega", try executeValue(std.testing.allocator, "\"alpha\" and \"omega\" or \"alpha\""));}test "execute string concatenation and repetition" { try expectExecutedString("abcd", "\"ab\" + \"cd\""); try expectExecutedString("ababab", "\"ab\" * 3"); try expectExecutedString("abab", "2 * \"ab\""); try expectExecutedString("", "\"ab\" * -1"); try expectExecutedString("", "\"ab\" * False"); try expectExecutedString("ab", "\"ab\" * True"); try expectExecutedString("\xc3\xab\xc3\xab", "\"" ++ "\xc3\xab" ++ "\" * 2");}test "execute string ordering" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "\"abc\" < \"abd\"")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "\"abc\" <= \"abc\"")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "\"abd\" > \"abc\"")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "\"" ++ "\xc3\xa9" ++ "\" > \"z\""));}test "execute tuple literals" { { var result = try execute(std.testing.allocator, "()"); defer result.deinit(); try std.testing.expect(result.value == .tuple); try std.testing.expectEqual(@as(usize, 0), result.value.tuple.items.len); } { var result = try execute(std.testing.allocator, "(1,)"); defer result.deinit(); try std.testing.expect(result.value == .tuple); try std.testing.expectEqual(@as(usize, 1), result.value.tuple.items.len); try std.testing.expectEqual(object.Value{ .integer = 1 }, result.value.tuple.items[0]); } { var result = try execute(std.testing.allocator, "1, 2"); defer result.deinit(); try std.testing.expect(result.value == .tuple); try std.testing.expectEqual(@as(usize, 2), result.value.tuple.items.len); try std.testing.expectEqual(object.Value{ .integer = 2 }, result.value.tuple.items[1]); }}test "execute tuple indexing truthiness and equality" { try std.testing.expectEqual(object.Value{ .integer = 2 }, try executeValue(std.testing.allocator, "(1, 2, 3)[1]")); try std.testing.expectEqual(object.Value{ .integer = 3 }, try executeValue(std.testing.allocator, "(1, 2, 3)[-1]")); try std.testing.expectEqual(object.Value{ .integer = 2 }, try executeValue(std.testing.allocator, \\if (): \\ x = 1 \\else: \\ x = 2 \\x )); try std.testing.expectEqual(object.Value{ .integer = 1 }, try executeValue(std.testing.allocator, \\if (0,): \\ x = 1 \\else: \\ x = 2 \\x )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "(1, 2) == (1, 2)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "(1, 2) != (1, 3)"));}test "execute tuple concatenation and repetition" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "(1,) + (2, 3) == (1, 2, 3)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "(1, 2) * 2 == (1, 2, 1, 2)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "2 * (1,) == (1, 1)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "(1,) * -1 == ()"));}test "execute tuple ordering" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "(1, 2) < (1, 3)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "(1, 2) <= (1, 2)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "(1, 3) > (1, 2)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "(1, 2) < (1, 2, 0)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "((1,),) < ((2,),)"));}test "execute tuple indexing and assignment errors" { try std.testing.expectError(Error.IndexError, executeValue(std.testing.allocator, "(1,)[1]")); try std.testing.expectError(Error.IndexError, executeValue(std.testing.allocator, "(1,)[-2]")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "(1,)[None]")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\xs = (1, 2) \\xs[0] = 3 ));}test "execute list displays" { var result = try execute(std.testing.allocator, "[1, 2, 3]"); defer result.deinit(); try std.testing.expect(result.value == .list); try std.testing.expectEqual(@as(usize, 3), result.value.list.items.len); try std.testing.expectEqual(object.Value{ .integer = 1 }, result.value.list.items[0]); try std.testing.expectEqual(object.Value{ .integer = 3 }, result.value.list.items[2]);}test "execute list indexing" { try std.testing.expectEqual(object.Value{ .integer = 2 }, try executeValue(std.testing.allocator, "[1, 2, 3][1]")); try std.testing.expectEqual(object.Value{ .integer = 3 }, try executeValue(std.testing.allocator, "[1, 2, 3][-1]")); try std.testing.expectEqual(object.Value{ .integer = 7 }, try executeValue(std.testing.allocator, "[[7]][0][0]"));}test "execute list truthiness and equality" { try std.testing.expectEqual(object.Value{ .integer = 2 }, try executeValue(std.testing.allocator, \\if []: \\ x = 1 \\else: \\ x = 2 \\x )); try std.testing.expectEqual(object.Value{ .integer = 1 }, try executeValue(std.testing.allocator, \\if [0]: \\ x = 1 \\else: \\ x = 2 \\x )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[1, 2] == [1, 2]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[1, 2] != [1, 3]"));}test "execute self-containing list equality" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\xs = [] \\xs.append(xs) \\xs == xs )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\xs = [] \\xs.append((xs,)) \\xs == xs ));}test "execute list concatenation and repetition" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[1] + [2, 3] == [1, 2, 3]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[1, 2] * 2 == [1, 2, 1, 2]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "2 * [1] == [1, 1]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[1] * -1 == []")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[1] * False == []")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[1] * True == [1]")); try std.testing.expectEqual(object.Value{ .integer = 9 }, try executeValue(std.testing.allocator, \\inner = [1] \\xs = [inner] * 2 \\xs[0][0] = 9 \\xs[1][0] )); try std.testing.expectEqual(object.Value{ .integer = 7 }, try executeValue(std.testing.allocator, \\inner = [1] \\xs = [inner] + [] \\xs[0][0] = 7 \\inner[0] ));}test "execute list ordering" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[1, 2] < [1, 3]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[1, 2] <= [1, 2]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[1, 3] > [1, 2]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[1, 2] < [1, 2, 0]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[[1]] < [[2]]"));}test "execute list index errors" { try std.testing.expectError(Error.IndexError, executeValue(std.testing.allocator, "[1][1]")); try std.testing.expectError(Error.IndexError, executeValue(std.testing.allocator, "[1][-2]")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "[1][None]"));}test "execute list subscript assignment" { try std.testing.expectEqual(object.Value{ .integer = 7 }, try executeValue(std.testing.allocator, \\xs = [1, 2] \\xs[0] = 7 \\xs[0] )); try std.testing.expectEqual(object.Value{ .integer = 9 }, try executeValue(std.testing.allocator, \\xs = [1, 2] \\xs[-1] = 9 \\xs[1] ));}test "execute nested list subscript assignment" { try std.testing.expectEqual(object.Value{ .integer = 5 }, try executeValue(std.testing.allocator, \\xs = [[1]] \\xs[0][0] = 5 \\xs[0][0] ));}test "execute list subscript assignment errors" { try std.testing.expectError(Error.IndexError, executeValue(std.testing.allocator, \\xs = [1] \\xs[1] = 2 )); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\xs = [1] \\xs[None] = 2 )); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "1[0] = 2"));}test "execute list item deletion" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\xs = [1, 2, 3] \\del xs[1] \\xs == [1, 3] )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\xs = [1, 2, 3] \\del xs[-1] \\xs == [1, 2] )); try std.testing.expectEqual(object.Value{ .integer = 12 }, try executeValue(std.testing.allocator, \\xs = [0, 1, 2] \\del xs[0] \\xs[0] * 10 + len(xs) ));}test "execute list item deletion errors" { try std.testing.expectError(Error.IndexError, executeValue(std.testing.allocator, \\xs = [1] \\del xs[1] )); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\xs = [1] \\del xs[None] )); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "del (1,)[0]")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "del 1[0]"));}test "execute list append method" { try std.testing.expectEqual(object.Value{ .integer = 31 }, try executeValue(std.testing.allocator, \\xs = [] \\result = xs.append(2) \\xs.append(1) \\if result is None: \\ marker = 10 \\else: \\ marker = 0 \\xs[0] * 10 + xs[1] + marker ));}test "execute stored bound list methods" { try std.testing.expectEqual(object.Value{ .integer = 3 }, try executeValue(std.testing.allocator, \\xs = [] \\push = xs.append \\push(3) \\xs[0] )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\xs = [] \\first = xs.append \\second = xs.append \\first == second and not first is second )); try std.testing.expectEqual(object.Value{ .integer = 7 }, try executeValue(std.testing.allocator, \\xs = [] \\d = {xs.append: 7} \\d[xs.append] ));}test "execute list pop method" { try std.testing.expectEqual(object.Value{ .integer = 131 }, try executeValue(std.testing.allocator, \\xs = [1, 2, 3] \\last = xs.pop() \\first = xs.pop(0) \\last * 10 + first * 100 + len(xs) )); try std.testing.expectEqual(object.Value{ .integer = 21 }, try executeValue(std.testing.allocator, \\xs = [1, 2] \\xs.pop(-1) * 10 + len(xs) ));}test "execute list clear and copy methods" { try std.testing.expectEqual(object.Value{ .integer = 19 }, try executeValue(std.testing.allocator, \\xs = [1, 2] \\ys = xs.copy() \\ys[0] = 9 \\xs[0] * 10 + ys[0] )); try std.testing.expectEqual(object.Value{ .integer = 0 }, try executeValue(std.testing.allocator, \\xs = [1] \\result = xs.clear() \\if result is None: \\ len(xs) \\else: \\ 9 )); var result = try execute(std.testing.allocator, \\xs = [0, 1, 2, 3, 4, 5, 6, 7] \\xs.clear() \\xs ); defer result.deinit(); try std.testing.expectEqual(@as(usize, 0), result.value.list.items.len); try std.testing.expectEqual(@as(usize, 0), result.value.list.capacity);}test "execute list method errors" { try std.testing.expectError(Error.AttributeError, executeValue(std.testing.allocator, "[1].missing")); try std.testing.expectError(Error.AttributeError, executeValue(std.testing.allocator, "1.append")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "[].append()")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "[].append(1, 2)")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "[].clear(1)")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "[].copy(1)")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "[].pop(0, 1)")); try std.testing.expectError(Error.IndexError, executeValue(std.testing.allocator, "[].pop()")); try std.testing.expectError(Error.IndexError, executeValue(std.testing.allocator, "[1].pop(1)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "[1].pop(None)"));}test "execute list slices" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[0, 1, 2, 3, 4][1:4] == [1, 2, 3]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[0, 1, 2, 3, 4][-4:-1:2] == [1, 3]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[0, 1, 2, 3][::-1] == [3, 2, 1, 0]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[1, 2][None:None:None] == [1, 2]")); try std.testing.expectEqual(object.Value{ .integer = 193 }, try executeValue(std.testing.allocator, \\xs = [0, 1, 2, 3, 4] \\ys = xs[1:4] \\ys[0] = 9 \\xs[1] * 100 + ys[0] * 10 + len(ys) ));}test "execute tuple slices" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "(0, 1, 2, 3)[1:3] == (1, 2)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "(0, 1, 2, 3)[::-1] == (3, 2, 1, 0)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "(0, 1, 2, 3)[10:] == ()"));}test "execute range slices" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "list(range(10)[2:8:2]) == [2, 4, 6]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "list(range(9, 0, -2)[1:3]) == [7, 5]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "list(range(10)[::-3]) == [9, 6, 3, 0]")); try std.testing.expectEqual(object.Value{ .integer = 0 }, try executeValue(std.testing.allocator, "len(range(10)[20:])"));}test "execute string slices" { { var result = try execute(std.testing.allocator, "\"abcd\"[1:3]"); defer result.deinit(); try expectString("bc", result.value); } { var result = try execute(std.testing.allocator, "\"abcd\"[::2]"); defer result.deinit(); try expectString("ac", result.value); } { var result = try execute(std.testing.allocator, "\"abcd\"[::-1]"); defer result.deinit(); try expectString("dcba", result.value); } { var result = try execute(std.testing.allocator, "\"no" ++ "\xc3\xabl" ++ "\"[2:3]"); defer result.deinit(); try expectString("\xc3\xab", result.value); }}test "execute slice errors" { try std.testing.expectError(Error.ValueError, executeValue(std.testing.allocator, "[1][::0]")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "[1][\"a\":]")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "1[0:1]"));}test "execute sequence operator errors" { try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "[1] + (2,)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "\"a\" + 1")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "[1] * None")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "None * [1]")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "range(3) + range(3)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "range(3) * 2"));}test "execute sequence ordering errors" { try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "[1] < (1,)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "\"a\" < 1")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "[None] < [0]")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "range(3) < range(4)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "[range(1)] < [range(2)]"));}test "execute for loop over list" { try std.testing.expectEqual(object.Value{ .integer = 6 }, try executeValue(std.testing.allocator, \\total = 0 \\for x in [1, 2, 3]: \\ total = total + x \\total ));}test "execute for loop variable persists" { try std.testing.expectEqual(object.Value{ .integer = 3 }, try executeValue(std.testing.allocator, \\for x in [1, 2, 3]: \\ pass \\x ));}test "execute empty for loop leaves target unassigned" { try std.testing.expectError(Error.UndefinedName, executeValue(std.testing.allocator, \\for x in []: \\ pass \\x ));}test "execute for else on natural exhaustion" { try expectString("done", try executeValue(std.testing.allocator, \\for x in [1, 2]: \\ y = "body" \\else: \\ y = "done" \\y ));}test "execute for else skipped by break" { try expectString("break", try executeValue(std.testing.allocator, \\y = "start" \\for x in [1, 2, 3]: \\ if x == 2: \\ y = "break" \\ break \\else: \\ y = "else" \\y ));}test "execute for else after continue" { try expectString("done", try executeValue(std.testing.allocator, \\for x in [1, 2, 3]: \\ if x < 3: \\ continue \\else: \\ y = "done" \\y ));}test "execute return from for loop" { try std.testing.expectEqual(object.Value{ .integer = 5 }, try executeValue(std.testing.allocator, \\def first(): \\ for x in [1, 2]: \\ return x \\ return 9 \\first() + 4 ));}test "execute rejects non-iterable for loop" { try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\for x in 1: \\ pass ));}test "execute range builtin creates range objects" { var result = try execute(std.testing.allocator, "range(1, 6, 2)"); defer result.deinit(); try std.testing.expect(result.value == .range); try std.testing.expectEqual(@as(i128, 1), result.value.range.start); try std.testing.expectEqual(@as(i128, 6), result.value.range.stop); try std.testing.expectEqual(@as(i128, 2), result.value.range.step); try std.testing.expectEqual(@as(usize, 3), result.value.range.length);}test "execute for loop over range" { try std.testing.expectEqual(object.Value{ .integer = 6 }, try executeValue(std.testing.allocator, \\total = 0 \\for x in range(4): \\ total = total + x \\total )); try std.testing.expectEqual(object.Value{ .integer = 9 }, try executeValue(std.testing.allocator, \\total = 0 \\for x in range(1, 6, 2): \\ total = total + x \\total )); try std.testing.expectEqual(object.Value{ .integer = 9 }, try executeValue(std.testing.allocator, \\total = 0 \\for x in range(5, 0, -2): \\ total = total + x \\total ));}test "execute range loop supports else and empty target behavior" { try expectString("done", try executeValue(std.testing.allocator, \\for x in range(0): \\ y = "body" \\else: \\ y = "done" \\y )); try std.testing.expectError(Error.UndefinedName, executeValue(std.testing.allocator, \\for x in range(0): \\ pass \\x ));}test "execute range truthiness and equality" { try std.testing.expectEqual(object.Value{ .integer = 2 }, try executeValue(std.testing.allocator, \\if range(0): \\ x = 1 \\else: \\ x = 2 \\x )); try std.testing.expectEqual(object.Value{ .integer = 1 }, try executeValue(std.testing.allocator, \\if range(1): \\ x = 1 \\else: \\ x = 2 \\x )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "range(0, 3, 2) == range(0, 4, 2)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "range(0) == range(1, 1, 3)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "range(0, 5, 2) != range(0, 4, 2)"));}test "execute range builtin errors" { try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "range()")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "range(1, 2, 3, 4)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "range(None)")); try std.testing.expectError(Error.ValueError, executeValue(std.testing.allocator, "range(1, 2, 0)"));}test "execute globals shadow range builtin" { try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\range = 3 \\range(1) ));}test "execute len builtin for supported sequences" { try std.testing.expectEqual(object.Value{ .integer = 0 }, try executeValue(std.testing.allocator, "len([])")); try std.testing.expectEqual(object.Value{ .integer = 3 }, try executeValue(std.testing.allocator, "len([1, 2, 3])")); try std.testing.expectEqual(object.Value{ .integer = 2 }, try executeValue(std.testing.allocator, "len((1, 2))")); try std.testing.expectEqual(object.Value{ .integer = 3 }, try executeValue(std.testing.allocator, "len(range(1, 6, 2))")); try std.testing.expectEqual(object.Value{ .integer = 4 }, try executeValue(std.testing.allocator, "len(\"abcd\")")); try std.testing.expectEqual(object.Value{ .integer = 4 }, try executeValue(std.testing.allocator, "len(\"no" ++ "\xc3\xabl" ++ "\")"));}test "execute len builtin errors and shadowing" { try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "len()")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "len([], [])")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "len(1)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\len = 4 \\len([]) ));}test "execute list builtin creates lists" { { var result = try execute(std.testing.allocator, "list()"); defer result.deinit(); try std.testing.expect(result.value == .list); try std.testing.expectEqual(@as(usize, 0), result.value.list.items.len); } { var result = try execute(std.testing.allocator, "list(range(1, 6, 2))"); defer result.deinit(); try std.testing.expect(result.value == .list); try std.testing.expectEqual(@as(usize, 3), result.value.list.items.len); try std.testing.expectEqual(object.Value{ .integer = 1 }, result.value.list.items[0]); try std.testing.expectEqual(object.Value{ .integer = 3 }, result.value.list.items[1]); try std.testing.expectEqual(object.Value{ .integer = 5 }, result.value.list.items[2]); } { var result = try execute(std.testing.allocator, "list(\"ab\")"); defer result.deinit(); try std.testing.expect(result.value == .list); try std.testing.expectEqual(@as(usize, 2), result.value.list.items.len); try expectString("a", result.value.list.items[0]); try expectString("b", result.value.list.items[1]); } { var result = try execute(std.testing.allocator, "list((1, 2))"); defer result.deinit(); try std.testing.expect(result.value == .list); try std.testing.expectEqual(@as(usize, 2), result.value.list.items.len); try std.testing.expectEqual(object.Value{ .integer = 1 }, result.value.list.items[0]); try std.testing.expectEqual(object.Value{ .integer = 2 }, result.value.list.items[1]); }}test "execute list builtin copies list values" { try std.testing.expectEqual(object.Value{ .integer = 17 }, try executeValue(std.testing.allocator, \\xs = [1, 2] \\ys = list(xs) \\ys[0] = 7 \\xs[0] * 10 + ys[0] ));}test "execute list builtin errors and shadowing" { try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "list(1, 2)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "list(1)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\list = 4 \\list() ));}test "execute iter and next builtins over ranges and lists" { try std.testing.expectEqual(object.Value{ .integer = 12 }, try executeValue(std.testing.allocator, \\it = iter(range(1, 4)) \\a = next(it) \\b = next(it) \\a * 10 + b )); try std.testing.expectEqual(object.Value{ .integer = 12 }, try executeValue(std.testing.allocator, \\it = iter([1, 2]) \\same = iter(it) \\next(same) * 10 + next(it) ));}test "execute next builtin exhaustion" { try std.testing.expectError(Error.StopIteration, executeValue(std.testing.allocator, "next(iter([]))")); try std.testing.expectEqual(object.Value{ .integer = 9 }, try executeValue(std.testing.allocator, "next(iter([]), 9)")); try std.testing.expectEqual(object.Value.none, try executeValue(std.testing.allocator, "next(iter([]), None)"));}test "execute iter and next builtin errors and shadowing" { try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "iter()")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "iter([], None)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "iter(1)")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "next()")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "next(iter([]), 1, 2)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "next([])")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\iter = 4 \\iter([]) )); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\next = 4 \\next(iter([])) ));}test "execute for loop over iterators and strings" { try std.testing.expectEqual(object.Value{ .integer = 3 }, try executeValue(std.testing.allocator, \\total = 0 \\it = iter(range(3)) \\for x in it: \\ total = total + x \\total )); try std.testing.expectEqual(object.Value{ .integer = 2 }, try executeValue(std.testing.allocator, \\total = 0 \\for ch in "ab": \\ total = total + len(ch) \\total ));}test "execute for loop over tuples" { try std.testing.expectEqual(object.Value{ .integer = 6 }, try executeValue(std.testing.allocator, \\total = 0 \\for x in (1, 2, 3): \\ total = total + x \\total )); try std.testing.expectEqual(object.Value{ .integer = 12 }, try executeValue(std.testing.allocator, \\it = iter((1, 2)) \\next(it) * 10 + next(it) ));}test "execute string iterator yields codepoint slices" { try expectString("a", try executeValue(std.testing.allocator, "next(iter(\"ab\"))")); try expectString("\xc3\xab", try executeValue(std.testing.allocator, "next(iter(\"" ++ "\xc3\xab" ++ "\"))"));}test "execute enumerate builtin over iterable values" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "list(enumerate([\"a\", \"b\"])) == [(0, \"a\"), (1, \"b\")]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "tuple(enumerate((3, 4), -1)) == ((-1, 3), (0, 4))")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "list(enumerate(\"a" ++ "\xc3\xab" ++ "\")) == [(0, \"a\"), (1, \"" ++ "\xc3\xab" ++ "\")]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "list(enumerate({\"a\": 1, \"b\": 2}.items(), True)) == [(1, (\"a\", 1)), (2, (\"b\", 2))]"));}test "execute enumerate builtin consumes iterators with next and for loops" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\it = iter([10, 20, 30]) \\first = next(it) \\first == 10 and list(enumerate(it, 5)) == [(5, 20), (6, 30)] )); try std.testing.expectEqual(object.Value{ .integer = 468 }, try executeValue(std.testing.allocator, \\total = 0 \\for pair in enumerate(range(3), 4): \\ total = total * 10 + pair[0] + pair[1] \\total )); try std.testing.expectEqual(object.Value{ .integer = 52 }, try executeValue(std.testing.allocator, \\pair = next(enumerate(reversed([1, 2]), 5)) \\pair[0] * 10 + pair[1] ));}test "execute enumerate builtin errors and shadowing" { try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "enumerate()")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "enumerate([], 1, 2)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "enumerate(1)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "enumerate([], None)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\enumerate = 4 \\enumerate([]) ));}test "execute reversed builtin over sequences" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "list(reversed([1, 2, 3])) == [3, 2, 1]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "tuple(reversed((1, 2, 3))) == (3, 2, 1)")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "list(reversed(range(1, 6, 2))) == [5, 3, 1]")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "list(reversed(\"a" ++ "\xc3\xab" ++ "\")) == [\"" ++ "\xc3\xab" ++ "\", \"a\"]"));}test "execute reversed builtin over dictionaries and views" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1, "b": 2} \\list(reversed(d)) == ["b", "a"] )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1, "b": 2} \\list(reversed(d.keys())) == ["b", "a"] )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1, "b": 2} \\list(reversed(d.values())) == [2, 1] )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1, "b": 2} \\list(reversed(d.items())) == [("b", 2), ("a", 1)] ));}test "execute reversed builtin consumes with next and for loops" { try std.testing.expectEqual(object.Value{ .integer = 21 }, try executeValue(std.testing.allocator, \\it = reversed([1, 2]) \\next(it) * 10 + next(it) )); try std.testing.expectEqual(object.Value{ .integer = 210 }, try executeValue(std.testing.allocator, \\total = 0 \\for value in reversed(range(3)): \\ total = total * 10 + value \\total ));}test "execute reversed builtin errors and shadowing" { try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "reversed()")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "reversed([], [])")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "reversed(1)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "reversed(iter([1]))")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\reversed = 4 \\reversed([]) ));}test "execute list builtin consumes iterators" { try std.testing.expectEqual(object.Value{ .integer = 13 }, try executeValue(std.testing.allocator, \\it = iter(range(4)) \\first = next(it) \\xs = list(it) \\first * 100 + xs[0] * 10 + len(xs) ));}test "execute dictionary displays" { { var result = try execute(std.testing.allocator, "{}"); defer result.deinit(); try std.testing.expect(result.value == .dict); try std.testing.expectEqual(@as(usize, 0), result.value.dict.entries.items.len); } { var result = try execute(std.testing.allocator, "{\"a\": 1, \"b\": 2}"); defer result.deinit(); try std.testing.expect(result.value == .dict); try std.testing.expectEqual(@as(usize, 2), result.value.dict.entries.items.len); try expectString("a", result.value.dict.entries.items[0].key); try std.testing.expectEqual(object.Value{ .integer = 1 }, result.value.dict.entries.items[0].value); try expectString("b", result.value.dict.entries.items[1].key); try std.testing.expectEqual(object.Value{ .integer = 2 }, result.value.dict.entries.items[1].value); }}test "execute dictionary duplicate keys replace values" { try std.testing.expectEqual(object.Value{ .integer = 12 }, try executeValue(std.testing.allocator, \\d = {"a": 1, "a": 2} \\len(d) * 10 + d["a"] )); try std.testing.expectEqual(object.Value{ .integer = 12 }, try executeValue(std.testing.allocator, \\d = {True: 1, 1: 2} \\len(d) * 10 + d[True] ));}test "execute dictionary lookup assignment truthiness and equality" { try std.testing.expectEqual(object.Value{ .integer = 2 }, try executeValue(std.testing.allocator, "{\"a\": 2}[\"a\"]")); try std.testing.expectEqual(object.Value{ .integer = 12 }, try executeValue(std.testing.allocator, \\d = {} \\if d: \\ empty = 1 \\else: \\ empty = 2 \\d["a"] = 7 \\if d: \\ full = 1 \\else: \\ full = 2 \\full * 10 + empty )); try std.testing.expectEqual(object.Value{ .integer = 37 }, try executeValue(std.testing.allocator, \\d = {"a": 1, "b": 2} \\d["a"] = 3 \\keys = list(d) \\if keys == ["a", "b"]: \\ order = 30 \\else: \\ order = 0 \\order + d["a"] + d["b"] * 2 )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "{\"a\": 1, \"b\": 2} == {\"b\": 2, \"a\": 1}")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "{\"a\": 1} != {\"a\": 2}"));}test "execute dictionary iteration membership and builtins" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "\"a\" in {\"a\": 1}")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "1 not in {\"a\": 1}")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "list({\"a\": 1, \"b\": 2}) == [\"a\", \"b\"]")); try expectString("a", try executeValue(std.testing.allocator, "next(iter({\"a\": 1}))")); try std.testing.expectEqual(object.Value{ .integer = 2 }, try executeValue(std.testing.allocator, \\total = 0 \\for key in {"a": 1, "b": 2}: \\ total = total + len(key) \\total )); try std.testing.expectEqual(object.Value{ .integer = 7 }, try executeValue(std.testing.allocator, \\d = {"a": 1} \\copy = dict(d) \\d["a"] = 7 \\copy["a"] + d["a"] - 1 )); try std.testing.expectEqual(object.Value{ .integer = 12 }, try executeValue(std.testing.allocator, \\d = dict([("a", 1), ["b", 2]]) \\d["a"] * 10 + d["b"] )); try std.testing.expectEqual(object.Value{ .integer = 0 }, try executeValue(std.testing.allocator, "len(dict())"));}test "execute dictionary key errors" { try std.testing.expectError(Error.KeyError, executeValue(std.testing.allocator, "{\"a\": 1}[\"b\"]")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "{[]: 1}")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "{([],): 1}")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\d = {} \\d[[]] = 1 )); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "[] in {}"));}test "execute dictionary key deletion" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1, "b": 2, "c": 3} \\del d["b"] \\list(d) == ["a", "c"] )); try std.testing.expectEqual(object.Value{ .integer = 31 }, try executeValue(std.testing.allocator, \\d = {"a": 1, "b": 2} \\del d["a"] \\len(d) * 10 + d["b"] * 10 + ("a" not in d) )); try std.testing.expectEqual(object.Value{ .integer = 0 }, try executeValue(std.testing.allocator, \\d = {True: 1, 1: 2} \\del d[True] \\len(d) ));}test "execute dictionary key deletion errors" { try std.testing.expectError(Error.KeyError, executeValue(std.testing.allocator, \\d = {"a": 1} \\del d["b"] )); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\d = {} \\del d[[]] ));}test "execute dictionary get method" { try std.testing.expectEqual(object.Value{ .integer = 121 }, try executeValue(std.testing.allocator, \\d = {"a": 1} \\d.get("a") * 100 + d.get("b", 2) * 10 + (d.get("b") is None) ));}test "execute stored bound dictionary methods" { try std.testing.expectEqual(object.Value{ .integer = 3 }, try executeValue(std.testing.allocator, \\d = {} \\get = d.get \\d["a"] = 3 \\get("a") )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {} \\first = d.get \\second = d.get \\first == second and not first is second )); try std.testing.expectEqual(object.Value{ .integer = 7 }, try executeValue(std.testing.allocator, \\d = {} \\outer = {d.get: 7} \\outer[d.get] )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {} \\update = d.update \\setdefault = d.setdefault \\popitem = d.popitem \\update([("a", 1)]) \\setdefault("b", 2) \\popitem() == ("b", 2) and d.update == d.update and d.setdefault == d.setdefault and d.popitem == d.popitem ));}test "execute dictionary pop method" { try std.testing.expectEqual(object.Value{ .integer = 124 }, try executeValue(std.testing.allocator, \\d = {"a": 1, "b": 2, "c": 3} \\value = d.pop("b") \\missing = d.pop("z", 4) \\keys = list(d) \\if keys == ["a", "c"]: \\ order = 100 \\else: \\ order = 0 \\order + value * 10 + missing ));}test "execute dictionary setdefault method" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1} \\first = d.setdefault("a", 9) \\second = d.setdefault("b", 2) \\third = d.setdefault("c") \\first == 1 and second == 2 and third is None and d["a"] == 1 and d["b"] == 2 and d["c"] is None and list(d) == ["a", "b", "c"] ));}test "execute dictionary update method" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1, "b": 2} \\result = d.update({"b": 20, "c": 3}) \\result is None and list(d.items()) == [("a", 1), ("b", 20), ("c", 3)] )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1} \\d.update([("a", 10), ["b", 2]]) \\d.update({"c": 3}.items()) \\d.update(iter([("d", 4)])) \\list(d.items()) == [("a", 10), ("b", 2), ("c", 3), ("d", 4)] )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1} \\result = d.update() \\result is None and list(d.items()) == [("a", 1)] ));}test "execute dictionary popitem method" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1, "b": 2, "c": 3} \\first = d.popitem() \\second = d.popitem() \\first == ("c", 3) and second == ("b", 2) and list(d.items()) == [("a", 1)] ));}test "execute dictionary clear and copy methods" { try std.testing.expectEqual(object.Value{ .integer = 17 }, try executeValue(std.testing.allocator, \\d = {"a": 1} \\copy = d.copy() \\d["a"] = 7 \\copy["a"] * 10 + d["a"] )); try std.testing.expectEqual(object.Value{ .integer = 5 }, try executeValue(std.testing.allocator, \\inner = [] \\d = {"a": inner} \\copy = d.copy() \\copy["a"].append(5) \\d["a"][0] )); try std.testing.expectEqual(object.Value{ .integer = 0 }, try executeValue(std.testing.allocator, \\d = {"a": 1} \\result = d.clear() \\if result is None: \\ len(d) \\else: \\ 9 ));}test "execute dictionary method errors" { try std.testing.expectError(Error.AttributeError, executeValue(std.testing.allocator, "{}.missing")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "{}.get()")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "{}.get(1, 2, 3)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "{}.get([])")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "{}.get([], 1)")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "{}.pop()")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "{}.pop(1, 2, 3)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "{}.pop([])")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "{}.pop([], 1)")); try std.testing.expectError(Error.KeyError, executeValue(std.testing.allocator, "{}.pop(\"a\")")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "{}.setdefault()")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "{}.setdefault(1, 2, 3)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "{}.setdefault([])")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "{}.update(1, 2)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "{}.update(1)")); try std.testing.expectError(Error.ValueError, executeValue(std.testing.allocator, "{}.update([[1]])")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "{}.update([1])")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "{}.update([([], 1)])")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "{}.popitem(1)")); try std.testing.expectError(Error.KeyError, executeValue(std.testing.allocator, "{}.popitem()")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "{}.clear(1)")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "{}.copy(1)"));}test "execute dictionary view iteration and length" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1, "b": 2} \\list(d.keys()) == ["a", "b"] and list(d.values()) == [1, 2] and list(d.items()) == [("a", 1), ("b", 2)] )); try std.testing.expectEqual(object.Value{ .integer = 222 }, try executeValue(std.testing.allocator, \\d = {"a": 1, "b": 2} \\len(d.keys()) * 100 + len(d.values()) * 10 + len(d.items()) )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\tuple({"a": 1}.items()) == (("a", 1),) )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1} \\dict(d.items()) == d ));}test "execute dictionary views reflect mutation" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1} \\keys = d.keys() \\values = d.values() \\items = d.items() \\d["b"] = 2 \\list(keys) == ["a", "b"] and list(values) == [1, 2] and list(items) == [("a", 1), ("b", 2)] ));}test "execute dictionary views in loops and membership" { try std.testing.expectEqual(object.Value{ .integer = 3 }, try executeValue(std.testing.allocator, \\total = 0 \\for value in {"a": 1, "b": 2}.values(): \\ total = total + value \\total )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1, "b": 2} \\"a" in d.keys() and 2 in d.values() and ("a", 1) in d.items() and ("a", 2) not in d.items() and ["a", 1] not in d.items() ));}test "execute dictionary view equality and truthiness" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\{"a": 1, "b": 2}.keys() == {"b": 9, "a": 8}.keys() )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\{"a": 1}.items() == {"a": 1}.items() )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1} \\values = d.values() \\values == values and d.values() != d.values() )); try std.testing.expectEqual(object.Value{ .integer = 3 }, try executeValue(std.testing.allocator, \\if {}.keys(): \\ total = 9 \\else: \\ total = 1 \\if {"a": 1}.values(): \\ total = total + 2 \\total ));}test "execute stored bound dictionary view methods" { try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\d = {"a": 1} \\keys = d.keys \\list(keys()) == ["a"] ));}test "execute dictionary view errors" { try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "{}.keys(1)")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "{}.values(1)")); try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "{}.items(1)")); try std.testing.expectError(Error.AttributeError, executeValue(std.testing.allocator, "{}.keys().missing")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\d = {} \\outer = {d.keys(): 1} )); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "([], 1) in {\"a\": 1}.items()"));}test "execute dictionary builtin errors and ordering errors" { try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "dict(1, 2)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "dict(1)")); try std.testing.expectError(Error.ValueError, executeValue(std.testing.allocator, "dict([[1]])")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "dict([1])")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\dict = 4 \\dict() )); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "{} < {}")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "[{}] <= [{}]"));}test "execute tuple builtin creates tuples" { { var result = try execute(std.testing.allocator, "tuple()"); defer result.deinit(); try std.testing.expect(result.value == .tuple); try std.testing.expectEqual(@as(usize, 0), result.value.tuple.items.len); } { var result = try execute(std.testing.allocator, "tuple([1, 2])"); defer result.deinit(); try std.testing.expect(result.value == .tuple); try std.testing.expectEqual(@as(usize, 2), result.value.tuple.items.len); try std.testing.expectEqual(object.Value{ .integer = 1 }, result.value.tuple.items[0]); try std.testing.expectEqual(object.Value{ .integer = 2 }, result.value.tuple.items[1]); } try std.testing.expectEqual(object.Value{ .integer = 3 }, try executeValue(std.testing.allocator, "len(tuple(range(3)))")); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, "tuple(\"ab\")[1] == \"b\""));}test "execute tuple builtin consumes iterators and preserves tuples" { try std.testing.expectEqual(object.Value{ .integer = 12 }, try executeValue(std.testing.allocator, \\it = iter([1, 2]) \\first = next(it) \\xs = tuple(it) \\first * 10 + xs[0] )); try std.testing.expectEqual(object.Value{ .boolean = true }, try executeValue(std.testing.allocator, \\xs = (1, 2) \\tuple(xs) is xs ));}test "execute tuple builtin errors and shadowing" { try std.testing.expectError(Error.ArityMismatch, executeValue(std.testing.allocator, "tuple(1, 2)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, "tuple(1)")); try std.testing.expectError(Error.TypeError, executeValue(std.testing.allocator, \\tuple = 4 \\tuple() ));}test "execute elif chains" { try expectString("middle", try executeValue(std.testing.allocator, \\x = 2 \\if x == 1: \\ y = "first" \\elif x == 2: \\ y = "middle" \\else: \\ y = "last" \\y ));}test "execute while else on natural exhaustion" { try expectString("done", try executeValue(std.testing.allocator, \\x = 0 \\while x < 3: \\ x = x + 1 \\else: \\ y = "done" \\y ));}test "execute while else skipped by break" { try expectString("break", try executeValue(std.testing.allocator, \\x = 0 \\y = "start" \\while x < 5: \\ x = x + 1 \\ if x == 3: \\ y = "break" \\ break \\else: \\ y = "else" \\y ));}test "execute while else after continue" { try expectString("done", try executeValue(std.testing.allocator, \\x = 0 \\while x < 3: \\ x = x + 1 \\ if x < 3: \\ continue \\else: \\ y = "done" \\y ));}fn expectString(expected: []const u8, actual: object.Value) !void { try std.testing.expect(actual == .string); try std.testing.expectEqualStrings(expected, actual.string);}fn expectExecutedString(expected: []const u8, bytes: []const u8) !void { var result = try execute(std.testing.allocator, bytes); defer result.deinit(); try expectString(expected, result.value);}Complete call list for runtime.Vm.run
26 direct calls.
lib.python.src.runtime.vm.Vm.attribute[method] — private source atlib/python/src/runtime/vm.zig:398in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.binary[method] — private source atlib/python/src/runtime/vm.zig:1040in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.buildDict[method] — private source atlib/python/src/runtime/vm.zig:681in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.buildList[method] — private source atlib/python/src/runtime/vm.zig:665in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.buildTuple[method] — private source atlib/python/src/runtime/vm.zig:673in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.call[method] — private source atlib/python/src/runtime/vm.zig:334in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.currentFrame[method] — private source atlib/python/src/runtime/vm.zig:241in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.delete[method] — private source atlib/python/src/runtime/vm.zig:324in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.deleteSubscript[method] — private source atlib/python/src/runtime/vm.zig:884in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.dup[method] — private source atlib/python/src/runtime/vm.zig:274in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.forNext[method] — private source atlib/python/src/runtime/vm.zig:725in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.iter[method] — private source atlib/python/src/runtime/vm.zig:695in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.load[method] — private source atlib/python/src/runtime/vm.zig:295in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.logicalNot[method] — private source atlib/python/src/runtime/vm.zig:1035in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.negate[method] — private source atlib/python/src/runtime/vm.zig:1028in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.peek[method] — private source atlib/python/src/runtime/vm.zig:269in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.pop[method] — private source atlib/python/src/runtime/vm.zig:262in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.push[method] — private source atlib/python/src/runtime/vm.zig:258in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.pushFrame[method] — private source atlib/python/src/runtime/vm.zig:245in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.returnValue[method] — private source atlib/python/src/runtime/vm.zig:1018in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.rotateThree[method] — private source atlib/python/src/runtime/vm.zig:284in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.slice[method] — private source atlib/python/src/runtime/vm.zig:858in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.store[method] — private source atlib/python/src/runtime/vm.zig:313in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.storeSubscript[method] — private source atlib/python/src/runtime/vm.zig:873in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.subscript[method] — private source atlib/python/src/runtime/vm.zig:847in nearest public ownertiny.python.runtime.vmlib.python.src.runtime.vm.Vm.swap[method] — private source atlib/python/src/runtime/vm.zig:278in nearest public ownertiny.python.runtime.vm
Audit
| Definitions | 7 |
|---|---|
| Public names | 13 |
| Members | 17 |
| Version | 26.7.0 |
| Revision | daab053ee433 |