Skip to documentation
SLOP

tiny.smt.term

Reference tiny.smt term

Defined in tiny.smt.

The formulas the package solves are trees of typed expressions over Booleans, integers, bit-vectors and arrays, with named constants and uninterpreted functions, and each expression has a sort.

API (17)

Types and contracts

Public types and contracts.

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

Source

Source: lib/smt/src/root.zig:100

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

Source: lib/smt/src/term.zig

zig
//! The formulas the package solves are trees of typed expressions over Booleans, integers,//! bit-vectors and arrays, with named constants and uninterpreted functions, and each expression//! has a sort.//!//! A caller builds a formula one operator at a time and needs each piece to be a small value it can//! reuse as an operand of larger pieces. The encoder and the SMT-LIB writer walk every expression//! and read its operator, its operands and its sort.//!//! Formulas share subexpressions, as `x` appears in both `x + 1` and `x < x + 1`, so the pieces//! refer to one another and need one owner. Each operator accepts operands of particular sorts, and//! a formula with a mismatched operand has no meaning.//!//! [SMT-LIB](https://smt-lib.org/) defines the sorts and operators of its theories: Booleans with//! equality, integers, fixed-size bit-vectors, arrays and uninterpreted functions. The term kinds//! here carry SMT-LIB's operator names, and the SMT-LIB writer and parser map each kind to its//! standard operator.//!//! Every term lives in one table that owns every term and function declaration it holds, a//! *context* (`Context`), and each term is its 32-bit index in that table (`Term`). Building a term//! appends one entry and returns its index, so an index stays valid until the table is freed, and//! building one expression twice gives two indexes.//!//! The builders copy the names and operand lists they receive, so a caller may free its own slices//! after the call. The builders check no sorts, except that `function` refuses a second declaration//! of one name with other sorts and `apply` checks its arguments against the declaration. `sortOf`//! computes a term's sort on demand and reports a mismatch. A builder stores operand indexes as//! given, so an index that names no earlier term leaves a term on which `sortOf` fails or recurses//! without end.//!//! Arrays map a bit-vector index to a bit-vector element, and their sort records the two widths//! (`ArraySort`). Integer terms can be built, sorted and written as SMT-LIB, and the bit-vector//! encoder refuses them. The SMT-LIB parser returns, and the writer reads, a logic name with the//! list of terms asserted over one table, a *script* (`Script`). Freeing the table frees every//! term, name and operand list at once, and nothing frees a single term.const std = @import("std");/// The shape of an array sort: the width of its bit-vector index and the width of its bit-vector/// element. Code that declares an array constant builds one to give the array's shape, as the/// encoder's array tests do. The bit-vector encoder accepts an index of at most eight bits.pub const ArraySort = struct {    /// The width of the index in bits. The index of every read or write of the array has this    /// width.    index_width: u32,    /// The width of each element in bits. A read of the array is a bit-vector of this width.    element_width: u32,};/// The sort of a term: Boolean, integer, a bit-vector of a given width, or an array from/// bit-vectors to bit-vectors. Code declares every constant and function with a sort, and `sortOf`/// returns one for every term.pub const Sort = union(enum) {    /// The Boolean sort. SMT-LIB writes it `Bool`.    bool,    /// The sort of mathematical integers. SMT-LIB writes it `Int`. The bit-vector encoder refuses    /// terms of this sort with `UnsupportedTerm`.    int,    /// A bit-vector sort, with its width in bits as the payload. SMT-LIB writes it `(_ BitVec n)`.    /// Any width is accepted, 0 and widths above 128 included. A bit-vector constant wider than 128    /// bits makes the encoder panic. A named constant wider than 128 bits encodes, and    /// `Encoder.model` returns `ModelValueTooWide` for it.    bitvec: u32,    /// An array sort, with its index and element widths as the payload. SMT-LIB writes it    /// `(Array (_ BitVec i) (_ BitVec e))`.    array: ArraySort,    /// Writes the sort in SMT-LIB syntax: `Bool`, `Int`, `(_ BitVec n)` or    /// `(Array (_ BitVec i) (_ BitVec e))`. The SMT-LIB writer calls it to print the sort of every    /// declaration. The function returns only the writer's errors.    pub fn write(self: Sort, writer: *std.Io.Writer) std.Io.Writer.Error!void {        switch (self) {            .bool => try writer.writeAll("Bool"),            .int => try writer.writeAll("Int"),            .bitvec => |width| try writer.print("(_ BitVec {d})", .{width}),            .array => |array| try writer.print(                "(Array (_ BitVec {d}) (_ BitVec {d}))",                .{ array.index_width, array.element_width },            ),        }    }    /// Returns true when the two sorts have the same kind and the same widths. `sortOf` and    /// `function` call it to compare the sort an operator needs with the sort it got.    pub fn eql(self: Sort, other: Sort) bool {        return std.meta.eql(self, other);    }};/// A term, named by its index in the table of the `Context` that built it. Every builder returns/// one and every operand is one, so callers pass terms around as plain integers. The index means/// nothing in another `Context`. The index stays valid until its `Context` is freed.pub const Term = u32;/// A function declaration, named by its index in the declaration table of the `Context` that holds/// it. `function` returns one, and `apply` takes one to build an application.pub const Function = u32;/// The payload of a named constant: its name and its sort. Code that walks terms reads it from a/// `.symbol` term to learn a constant's name and sort.pub const SymbolExpr = struct {    /// The constant's name, a copy the `Context` owns and frees. Two constants may share a name,    /// because every call to `symbol` builds a new term.    name: []const u8,    /// The constant's sort.    sort: Sort,};/// The payload of a function application: the declared function and its argument terms. Code that/// walks terms reads it from an `.apply` term to learn which function is applied to which/// arguments.pub const ApplyExpr = struct {    /// The applied function's index in the declaration table.    function: Function,    /// The argument terms in declaration order, a copy the `Context` owns and frees.    args: []const Term,};/// The payload of a bit-vector constant: its value and its width. Code that walks terms reads it/// from a `.bitvec` term to learn a constant's bits.pub const BitVecExpr = struct {    /// The constant's bits as an unsigned number: bit i of the number is bit i of the vector,    /// counted from the least significant. Nothing checks the value against the width, and the    /// encoder reads only the low `width` bits.    value: u128,    /// The constant's width in bits. A width above 128 makes the encoder panic.    width: u32,};/// The payload of a two-operand term: the left operand term `lhs` and the right operand term `rhs`./// Code that walks terms reads it from every two-operand term, such as `.eq`, `.bvadd` or `.bvult`.pub const BinaryOperands = struct {    lhs: Term,    rhs: Term,};/// The payload of an array read: the array term `array` and the index term `index`. Code that walks/// terms reads it from an `.array_select` term.pub const ArraySelectExpr = struct {    array: Term,    index: Term,};/// The payload of an array write: the array term `array`, the index term `index` and the element/// term `value` stored there. Code that walks terms reads it from an `.array_store` term.pub const ArrayStoreExpr = struct {    array: Term,    index: Term,    value: Term,};/// The payload of a bit range taken from the bit-vector term `operand`, between two bit positions./// Code that walks terms reads it from a `.bvextract` term.pub const BitVecExtractExpr = struct {    operand: Term,    /// The position of the highest bit taken, counted from 0 at the least significant bit. The    /// position has to be below the operand's width, or `sortOf` and the encoder report    /// `InvalidBitVectorRange`.    high: u32,    /// The position of the lowest bit taken, counted from 0 at the least significant bit. The    /// position has to be at most `high`, or `sortOf` and the encoder report    /// `InvalidBitVectorRange`.    low: u32,};/// The payload of a widening of the bit-vector term `operand`. Code that walks terms reads it from/// a `.bvzeroext` or `.bvsignext` term.pub const BitVecExtendExpr = struct {    operand: Term,    /// The number of bits added above the operand's most significant bit.    extra: u32,};/// The payload of a rotation of the bit-vector term `operand` by a fixed number of bits. Code that/// walks terms reads it from a `.bvrotl` or `.bvrotr` term.pub const BitVecRotateExpr = struct {    operand: Term,    /// The number of positions to rotate. The encoder takes it modulo the operand's width, so 5 on    /// a 4-bit operand rotates by 1.    amount: u32,};/// One declared function: its name, the sorts of its arguments and the sort of its result. Code/// that walks the declarations reads one per function, as the SMT-LIB writer does for each/// `declare-fun`.pub const FunctionDecl = struct {    /// The function's name, a copy the `Context` owns and frees. The name is unique among the    /// declarations of one `Context`.    name: []const u8,    /// The sorts of the arguments in order, a copy the `Context` owns and frees. The slice length    /// is the function's arity: 0 declares a constant function.    params: []const Sort,    /// The sort of every application's result.    result: Sort,};/// One term: a tag naming its operator and a payload holding its operands. Code that walks a/// formula switches on it, as the encoder and the SMT-LIB writer do for every term. The `Context`/// stores one per term, and a term's index is its position in that table. A tag named after an/// SMT-LIB operator reads and writes as that operator, and each tag whose name differs gives its/// SMT-LIB spelling. The bit-vector encoder refuses the integer tags (`int`, `add`, `mul`, `le`,/// `lt`, `ge`, `gt`) and `distinct` with `UnsupportedTerm`.pub const Expr = union(enum) {    /// A named constant, with its name and sort as the payload. The encoder gives it fresh    /// variables, one per bit.    symbol: SymbolExpr,    /// A function application, with the function and its arguments as the payload. Its sort is the    /// function's result sort.    apply: ApplyExpr,    /// A Boolean constant. SMT-LIB writes it `true` or `false`.    bool: bool,    /// An integer constant, a signed 128-bit value. The bit-vector encoder refuses it.    int: i128,    /// A bit-vector constant, with its value and width as the payload. SMT-LIB writes it    /// `(_ bvV W)`.    bitvec: BitVecExpr,    /// The negation of one Boolean operand.    not: Term,    /// The conjunction of any number of Boolean operands. With no operands it is true. SMT-LIB    /// writes it `and`.    and_: []const Term,    /// The disjunction of any number of Boolean operands. With no operands it is false. SMT-LIB    /// writes it `or`.    or_: []const Term,    /// `lhs` implies `rhs`, both Boolean. SMT-LIB writes it `=>`.    implies: BinaryOperands,    /// `lhs` equals `rhs`, a Boolean term over two operands of one sort. Two arrays are equal when    /// every cell is equal. SMT-LIB writes it `=`.    eq: BinaryOperands,    /// True when all operands differ from one another, over operands of one sort. The bit-vector    /// encoder refuses it.    distinct: []const Term,    /// The integer sum of any number of integer operands. SMT-LIB writes it `+`. The bit-vector    /// encoder refuses it.    add: []const Term,    /// The integer product of any number of integer operands. SMT-LIB writes it `*`. The bit-vector    /// encoder refuses it.    mul: []const Term,    /// `lhs` is at most `rhs`, as integers. SMT-LIB writes it `<=`. The bit-vector encoder refuses    /// it.    le: BinaryOperands,    /// `lhs` is less than `rhs`, as integers. SMT-LIB writes it `<`. The bit-vector encoder refuses    /// it.    lt: BinaryOperands,    /// `lhs` is at least `rhs`, as integers. SMT-LIB writes it `>=`. The bit-vector encoder refuses    /// it.    ge: BinaryOperands,    /// `lhs` is greater than `rhs`, as integers. SMT-LIB writes it `>`. The bit-vector encoder    /// refuses it.    gt: BinaryOperands,    /// `lhs` is at most `rhs`, as unsigned bit-vectors of one width.    bvule: BinaryOperands,    /// `lhs` is less than `rhs`, as unsigned bit-vectors of one width.    bvult: BinaryOperands,    /// `lhs` is at most `rhs`, as two's-complement bit-vectors of one width.    bvsle: BinaryOperands,    /// `lhs` is less than `rhs`, as two's-complement bit-vectors of one width.    bvslt: BinaryOperands,    /// True when the unsigned sum of `lhs` and `rhs` does not fit their width.    bvuaddo: BinaryOperands,    /// True when the two's-complement sum of `lhs` and `rhs` does not fit their width: both have    /// one sign and the sum has the other.    bvsaddo: BinaryOperands,    /// True when the two's-complement difference `lhs` minus `rhs` does not fit their width.    bvssubo: BinaryOperands,    /// True when the unsigned product of `lhs` and `rhs` does not fit their width.    bvumulo: BinaryOperands,    /// True when the two's-complement product of `lhs` and `rhs` does not fit their width.    bvsmulo: BinaryOperands,    /// The bitwise complement of one bit-vector operand, of the same width.    bvnot: Term,    /// The bitwise and of two bit-vectors of one width.    bvand: BinaryOperands,    /// The bitwise or of two bit-vectors of one width.    bvor: BinaryOperands,    /// The bitwise exclusive or of two bit-vectors of one width.    bvxor: BinaryOperands,    /// `lhs` shifted toward its most significant bit by the value of `rhs`, with zeros shifted in,    /// over two bit-vectors of one width. A shift amount at or above the width gives zero.    bvshl: BinaryOperands,    /// `lhs` shifted toward its least significant bit by the value of `rhs`, with zeros shifted in.    /// A shift amount at or above the width gives zero.    bvlshr: BinaryOperands,    /// `lhs` shifted toward its least significant bit by the value of `rhs`, with copies of its    /// sign bit shifted in. A shift amount at or above the width fills every bit with the sign bit.    bvashr: BinaryOperands,    /// The unsigned quotient of `lhs` by `rhs`, rounded down. Division by zero gives all ones.    bvudiv: BinaryOperands,    /// The unsigned remainder of `lhs` by `rhs`. The remainder by zero is `lhs`.    bvurem: BinaryOperands,    /// The two's-complement quotient of `lhs` by `rhs`, rounded toward zero. Division by zero gives    /// 1 for a negative `lhs` and all ones otherwise.    bvsdiv: BinaryOperands,    /// The two's-complement remainder of `lhs` by `rhs`, with the sign of `lhs`. The remainder by    /// zero is `lhs`.    bvsrem: BinaryOperands,    /// The two's-complement modulo of `lhs` by `rhs`, with the sign of `rhs`. The modulo by zero is    /// `lhs`.    bvsmod: BinaryOperands,    /// The element of an array at an index, a bit-vector of the element width. The index has the    /// array's index width. SMT-LIB writes it `select`.    array_select: ArraySelectExpr,    /// An array equal to its operand everywhere except at one index, which holds the new element.    /// The term's sort is the operand's array sort. SMT-LIB writes it `store`.    array_store: ArrayStoreExpr,    /// The bits of `lhs` above the bits of `rhs`, one bit-vector as wide as the two together.    /// SMT-LIB writes it `concat`.    bvconcat: BinaryOperands,    /// The bits from position `high` down to position `low` of one bit-vector, both included, so    /// the result has `high - low + 1` bits. SMT-LIB writes it `((_ extract high low) x)`.    bvextract: BitVecExtractExpr,    /// A bit-vector widened by `extra` zero bits above its most significant bit. SMT-LIB writes it    /// `((_ zero_extend extra) x)`.    bvzeroext: BitVecExtendExpr,    /// A bit-vector widened by `extra` copies of its sign bit. SMT-LIB writes it    /// `((_ sign_extend extra) x)`.    bvsignext: BitVecExtendExpr,    /// A bit-vector rotated toward its most significant bit by a fixed number of positions, with    /// the top bits wrapping to the bottom. SMT-LIB writes it `((_ rotate_left amount) x)`.    bvrotl: BitVecRotateExpr,    /// A bit-vector rotated toward its least significant bit by a fixed number of positions, with    /// the bottom bits wrapping to the top. SMT-LIB writes it `((_ rotate_right amount) x)`.    bvrotr: BitVecRotateExpr,    /// The sum of two bit-vectors of one width, modulo 2 to the power of the width.    bvadd: BinaryOperands,    /// The difference `lhs` minus `rhs` of two bit-vectors of one width, modulo 2 to the power of    /// the width.    bvsub: BinaryOperands,    /// The product of two bit-vectors of one width, modulo 2 to the power of the width.    bvmul: BinaryOperands,};/// One table that owns every term and function declaration it holds. Every formula starts here: a/// caller makes one, builds its terms with the builders, and frees them all at once with `deinit`./// Each builder appends one term and returns its index. The builders check no sorts, except/// `function` and `apply`, and `sortOf` checks a term's sort when a caller asks. Every builder can/// fail with `error.OutOfMemory`, and a failed builder leaves the table as it was. A `Script`, a/// parser and an encoder borrow the `Context` and have to be freed before it.pub const Context = struct {    /// The allocator for every term, name, operand list and declaration. A `Script` over this table    /// and the SMT-LIB parser allocate with it too.    allocator: std.mem.Allocator,    /// Every term in the order it was built: entry i is the term whose index is i. The encoder and    /// the SMT-LIB writer read it to walk every named constant.    nodes: std.ArrayList(Expr) = .empty,    /// Every function declaration in the order it was declared: entry i is the function whose index    /// is i.    functions: std.ArrayList(FunctionDecl) = .empty,    /// Returns an empty table that allocates with `allocator`, so a caller makes one before    /// building any term. The call allocates nothing.    pub fn init(allocator: std.mem.Allocator) Context {        return .{ .allocator = allocator };    }    /// Frees every term's name and operand list, every declaration and both tables, so the owner    /// calls it once after every `Script`, parser and encoder built over the table is done with its    /// terms. Every `Term` and `Function` index from this table is invalid afterward.    pub fn deinit(self: *Context) void {        for (self.nodes.items) |node| {            switch (node) {                .symbol => |sym| self.allocator.free(sym.name),                .apply => |item| self.allocator.free(item.args),                .and_, .or_, .distinct, .add, .mul => |items| self.allocator.free(items),                else => {},            }        }        for (self.functions.items) |decl| {            self.allocator.free(decl.name);            self.allocator.free(decl.params);        }        self.functions.deinit(self.allocator);        self.nodes.deinit(self.allocator);        self.* = undefined;    }    /// Appends a named constant of the given sort and returns its term, so code declares each free    /// variable of a formula with it, as the SMT-LIB parser does for every `declare-const`. The    /// builder copies `name`, so the caller may free its own copy. The builder builds a new term on    /// every call, even for a name used before.    pub fn symbol(self: *Context, name: []const u8, sort: Sort) !Term {        const owned_name = try self.allocator.dupe(u8, name);        errdefer self.allocator.free(owned_name);        return try self.append(.{ .symbol = .{ .name = owned_name, .sort = sort } });    }    /// Declares a function by name, argument sorts and result sort, and returns its index, so code    /// declares each uninterpreted function with it, as the SMT-LIB parser does for every    /// `declare-fun`. A second call with the same name and the same sorts returns the first index.    /// A second call with the same name and other sorts returns `error.DuplicateFunction`. The    /// builder copies `name` and `params`.    pub fn function(        self: *Context,        name: []const u8,        params: []const Sort,        result: Sort,    ) !Function {        for (self.functions.items, 0..) |decl, index| {            if (!std.mem.eql(u8, decl.name, name)) continue;            if (!sortListsEqual(decl.params, params) or !decl.result.eql(result)) {                return error.DuplicateFunction;            }            return @intCast(index);        }        const owned_name = try self.allocator.dupe(u8, name);        errdefer self.allocator.free(owned_name);        const owned_params = try self.allocator.dupe(Sort, params);        errdefer self.allocator.free(owned_params);        const id: Function = @intCast(self.functions.items.len);        try self.functions.append(self.allocator, .{            .name = owned_name,            .params = owned_params,            .result = result,        });        return id;    }    /// Appends the application of the function `function_id` to `args` and returns its term, so    /// code applies a declared function to arguments with it. The call returns    /// `error.UnknownFunction` for an index past the declarations, `error.FunctionArityMismatch`    /// for the wrong number of arguments, and `error.FunctionArgumentSortMismatch` for an argument    /// of the wrong sort. The builder computes each argument's sort with `sortOf`, so the call also    /// returns the errors of `sortOf` for an argument that has no sort. The builder copies `args`.    pub fn apply(self: *Context, function_id: Function, args: []const Term) !Term {        if (function_id >= self.functions.items.len) return error.UnknownFunction;        const decl = self.functions.items[function_id];        if (decl.params.len != args.len) return error.FunctionArityMismatch;        for (args, decl.params) |arg, expected| {            if (!(try self.sortOf(arg)).eql(expected)) return error.FunctionArgumentSortMismatch;        }        const owned = try self.allocator.dupe(Term, args);        errdefer self.allocator.free(owned);        return try self.append(.{ .apply = .{ .function = function_id, .args = owned } });    }    /// Returns the sort of term `id`, checking every operand below it against the sorts its    /// operator accepts, so code checks a formula's sorts before encoding or writing, and the    /// encoder reads each function argument's sort with it. The call returns `error.TermOutOfRange`    /// for an index past the table, `error.SortMismatch` for an operand of the wrong sort,    /// `error.InvalidBitVectorRange` for a bad extract range, and the errors of `apply` for a bad    /// application. The function allocates nothing, and the call walks the whole term below `id` on    /// every call. The recursion has no depth bound, and a term that refers to itself makes the    /// call recurse without end.    pub fn sortOf(self: *const Context, id: Term) anyerror!Sort {        if (id >= self.nodes.items.len) return error.TermOutOfRange;        return switch (self.nodes.items[id]) {            .symbol => |sym| sym.sort,            .apply => |item| try self.sortOfApply(item),            .bool => .bool,            .int => .int,            .bitvec => |value| .{ .bitvec = value.width },            .not => |operand| try self.expectBoolResult(operand),            .and_, .or_ => |operands| try self.expectBoolList(operands),            .implies => |pair| try self.expectBoolPair(pair.lhs, pair.rhs),            .eq => |pair| try self.expectSameSortPair(pair.lhs, pair.rhs),            .distinct => |operands| try self.expectSameSortList(operands),            .add, .mul => |operands| try self.expectIntList(operands),            .le => |pair| try self.expectIntPair(pair.lhs, pair.rhs),            .lt => |pair| try self.expectIntPair(pair.lhs, pair.rhs),            .ge => |pair| try self.expectIntPair(pair.lhs, pair.rhs),            .gt => |pair| try self.expectIntPair(pair.lhs, pair.rhs),            .bvule => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs),            .bvult => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs),            .bvsle => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs),            .bvslt => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs),            .bvuaddo => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs),            .bvsaddo => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs),            .bvssubo => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs),            .bvumulo => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs),            .bvsmulo => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs),            .bvnot => |operand| try self.expectBitVecResult(operand),            .bvand => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs),            .bvor => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs),            .bvxor => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs),            .bvshl => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs),            .bvlshr => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs),            .bvashr => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs),            .bvudiv => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs),            .bvurem => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs),            .bvsdiv => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs),            .bvsrem => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs),            .bvsmod => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs),            .bvadd => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs),            .bvsub => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs),            .bvmul => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs),            .array_select => |item| try self.sortOfArraySelect(item.array, item.index),            .array_store => |item| try self.sortOfArrayStore(item.array, item.index, item.value),            .bvconcat => |pair| try self.sortOfConcat(pair.lhs, pair.rhs),            .bvextract => |item| try self.sortOfExtract(item.operand, item.high, item.low),            .bvzeroext => |item| try self.sortOfExtend(item.operand, item.extra),            .bvsignext => |item| try self.sortOfExtend(item.operand, item.extra),            .bvrotl => |item| try self.expectBitVecResult(item.operand),            .bvrotr => |item| try self.expectBitVecResult(item.operand),        };    }    /// Appends the constant `value` and returns its term, so code builds the Boolean constants with    /// it.    pub fn boolValue(self: *Context, value: bool) !Term {        return try self.append(.{ .bool = value });    }    /// Appends the integer constant `value` and returns its term, so code builds integer constants    /// with it, as the SMT-LIB parser does for every numeral.    pub fn intValue(self: *Context, value: i128) !Term {        return try self.append(.{ .int = value });    }    /// Appends the constant with bits `value` and width `width` and returns its term, so code    /// builds bit-vector constants with it, as the SMT-LIB parser does for every `(_ bvV W)`. The    /// builder checks neither the value against the width nor the width. A width above 128 makes    /// the encoder panic.    pub fn bitvecValue(self: *Context, value: u128, width: u32) !Term {        return try self.append(.{ .bitvec = .{ .value = value, .width = width } });    }    /// Appends the negation of `operand` and returns its term, so code negates a Boolean term with    /// it.    pub fn not(self: *Context, operand: Term) !Term {        return try self.append(.{ .not = operand });    }    /// Appends the conjunction of `operands` and returns its term, so code calls it to join Boolean    /// terms that must all hold. The builder copies `operands`.    pub fn and_(self: *Context, operands: []const Term) !Term {        return try self.appendList(.and_, operands);    }    /// Appends the disjunction of `operands` and returns its term, so code calls it to join Boolean    /// terms of which one must hold. The builder copies `operands`.    pub fn or_(self: *Context, operands: []const Term) !Term {        return try self.appendList(.or_, operands);    }    /// Appends `lhs` implies `rhs` and returns its term, so code states that one Boolean term    /// implies another with it.    pub fn implies(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .implies = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends `lhs` equals `rhs` and returns its term, so code calls it to state that two terms    /// are equal.    pub fn eq(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .eq = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the claim that `operands` all differ and returns its term, so code states that terms    /// all differ with it. The builder copies `operands`. The bit-vector encoder refuses the term.    pub fn distinct(self: *Context, operands: []const Term) !Term {        return try self.appendList(.distinct, operands);    }    /// Appends the integer sum of `operands` and returns its term, so code adds integer terms with    /// it. The builder copies `operands`. The bit-vector encoder refuses the term.    pub fn add(self: *Context, operands: []const Term) !Term {        return try self.appendList(.add, operands);    }    /// Appends the integer product of `operands` and returns its term, so code multiplies integer    /// terms with it. The builder copies `operands`. The bit-vector encoder refuses the term.    pub fn mul(self: *Context, operands: []const Term) !Term {        return try self.appendList(.mul, operands);    }    /// Appends the two-operand term of kind `tag` over `lhs` and `rhs` and returns its term, so the    /// SMT-LIB parser calls it with the tag of each two-operand operator it reads, and one call    /// covers every such operator. The parameter `tag` names a kind whose payload is    /// `BinaryOperands`, or `.array_select`, which the builder builds with `arraySelect`. Any other    /// tag is a compile error.    pub fn binary(self: *Context, comptime tag: std.meta.Tag(Expr), lhs: Term, rhs: Term) !Term {        return switch (tag) {            .implies,            .eq,            .le,            .lt,            .ge,            .gt,            .bvule,            .bvult,            .bvsle,            .bvslt,            .bvuaddo,            .bvsaddo,            .bvssubo,            .bvumulo,            .bvsmulo,            .bvand,            .bvor,            .bvxor,            .bvshl,            .bvlshr,            .bvashr,            .bvudiv,            .bvurem,            .bvsdiv,            .bvsrem,            .bvsmod,            .bvconcat,            .bvadd,            .bvsub,            .bvmul,            => try self.appendPair(tag, lhs, rhs),            .array_select => try self.arraySelect(lhs, rhs),            else => unreachable,        };    }    /// Appends `lhs` at most `rhs`, as integers, and returns its term, so code compares integer    /// terms with it. The bit-vector encoder refuses the term.    pub fn le(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .le = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends `lhs` less than `rhs`, as integers, and returns its term, so code compares integer    /// terms with it. The bit-vector encoder refuses the term.    pub fn lt(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .lt = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends `lhs` at least `rhs`, as integers, and returns its term, so code compares integer    /// terms with it. The bit-vector encoder refuses the term.    pub fn ge(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .ge = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends `lhs` greater than `rhs`, as integers, and returns its term, so code compares    /// integer terms with it. The bit-vector encoder refuses the term.    pub fn gt(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .gt = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends `lhs` at most `rhs`, as unsigned bit-vectors, and returns its term, so code compares    /// bit-vectors as unsigned numbers with it.    pub fn bvule(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvule = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends `lhs` less than `rhs`, as unsigned bit-vectors, and returns its term, so code    /// compares bit-vectors as unsigned numbers with it, as the SMT-LIB round-trip test does.    pub fn bvult(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvult = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends `lhs` at most `rhs`, as two's-complement bit-vectors, and returns its term, so code    /// compares bit-vectors as two's-complement numbers with it.    pub fn bvsle(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvsle = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends `lhs` less than `rhs`, as two's-complement bit-vectors, and returns its term, so    /// code compares bit-vectors as two's-complement numbers with it.    pub fn bvslt(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvslt = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the claim that the unsigned sum of `lhs` and `rhs` overflows their width and returns    /// its term. Code asks with it whether an unsigned addition can overflow.    pub fn bvuaddo(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvuaddo = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the claim that the two's-complement sum of `lhs` and `rhs` overflows their width and    /// returns its term. Code asks with it whether a two's-complement addition can overflow.    pub fn bvsaddo(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvsaddo = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the claim that the two's-complement difference `lhs` minus `rhs` overflows their    /// width and returns its term. Code asks with it whether a two's-complement subtraction can    /// overflow.    pub fn bvssubo(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvssubo = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the claim that the unsigned product of `lhs` and `rhs` overflows their width and    /// returns its term. Code asks with it whether an unsigned multiplication can overflow.    pub fn bvumulo(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvumulo = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the claim that the two's-complement product of `lhs` and `rhs` overflows their width    /// and returns its term. Code asks with it whether a two's-complement multiplication can    /// overflow.    pub fn bvsmulo(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvsmulo = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the bitwise complement of `operand` and returns its term. Code complements every bit    /// of a bit-vector with it.    pub fn bvnot(self: *Context, operand: Term) !Term {        return try self.append(.{ .bvnot = operand });    }    /// Appends the bitwise and of `lhs` and `rhs` and returns its term. Code masks bit-vectors with    /// it.    pub fn bvand(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvand = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the bitwise or of `lhs` and `rhs` and returns its term. Code sets bits of a    /// bit-vector with it.    pub fn bvor(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvor = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the bitwise exclusive or of `lhs` and `rhs` and returns its term. Code flips bits of    /// a bit-vector with it.    pub fn bvxor(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvxor = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends `lhs` shifted toward its most significant bit by the value of `rhs` and returns its    /// term. Code shifts a bit-vector left with it.    pub fn bvshl(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvshl = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends `lhs` shifted toward its least significant bit by the value of `rhs`, with zeros    /// shifted in, and returns its term. Code shifts with it a bit-vector right with zero fill.    pub fn bvlshr(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvlshr = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends `lhs` shifted toward its least significant bit by the value of `rhs`, with copies of    /// its sign bit shifted in, and returns its term. Code shifts a two's-complement bit-vector    /// right with it.    pub fn bvashr(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvashr = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the unsigned quotient of `lhs` by `rhs` and returns its term. Code divides    /// bit-vectors as unsigned numbers with it.    pub fn bvudiv(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvudiv = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the unsigned remainder of `lhs` by `rhs` and returns its term. Code takes the    /// unsigned remainder of bit-vectors with it.    pub fn bvurem(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvurem = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the two's-complement quotient of `lhs` by `rhs`, rounded toward zero, and returns    /// its term. Code divides bit-vectors as two's-complement numbers with it.    pub fn bvsdiv(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvsdiv = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the remainder of `lhs` by `rhs` with the sign of `lhs` and returns its term. Code    /// takes the two's-complement remainder of bit-vectors with it.    pub fn bvsrem(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvsrem = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the modulo of `lhs` by `rhs` with the sign of `rhs` and returns its term. Code takes    /// the two's-complement modulo of bit-vectors with it.    pub fn bvsmod(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvsmod = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the element of `array` at `index` and returns its term. Code reads an array at an    /// index with it.    pub fn arraySelect(self: *Context, array: Term, index: Term) !Term {        return try self.append(.{ .array_select = .{ .array = array, .index = index } });    }    /// Appends the array equal to `array` except that `index` holds `value`, and returns its term.    /// Code writes an element into an array with it.    pub fn arrayStore(self: *Context, array: Term, index: Term, value: Term) !Term {        return try self.append(.{ .array_store = .{            .array = array,            .index = index,            .value = value,        } });    }    /// Appends the bits of `lhs` above the bits of `rhs` and returns its term. Code joins two    /// bit-vectors into a wider one with it.    pub fn bvconcat(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvconcat = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the bits from position `high` down to position `low` of `operand`, both included,    /// and returns its term. Code takes a range of bits from a bit-vector with it. It checks no    /// range: `sortOf` and the encoder report `InvalidBitVectorRange` when `low` is above `high` or    /// `high` is at or above the width.    pub fn bvextract(self: *Context, operand: Term, high: u32, low: u32) !Term {        return try self.append(.{ .bvextract = .{ .operand = operand, .high = high, .low = low } });    }    /// Appends `operand` widened by `extra` zero bits above its most significant bit and returns    /// its term. Code widens an unsigned bit-vector with it.    pub fn bvzeroext(self: *Context, operand: Term, extra: u32) !Term {        return try self.append(.{ .bvzeroext = .{ .operand = operand, .extra = extra } });    }    /// Appends `operand` widened by `extra` copies of its sign bit and returns its term. Code    /// widens a two's-complement bit-vector with it.    pub fn bvsignext(self: *Context, operand: Term, extra: u32) !Term {        return try self.append(.{ .bvsignext = .{ .operand = operand, .extra = extra } });    }    /// Appends `operand` rotated toward its most significant bit by `amount` positions and returns    /// its term. Code rotates a bit-vector left by a fixed amount with it.    pub fn bvrotl(self: *Context, operand: Term, amount: u32) !Term {        return try self.append(.{ .bvrotl = .{ .operand = operand, .amount = amount } });    }    /// Appends `operand` rotated toward its least significant bit by `amount` positions and returns    /// its term. Code rotates a bit-vector right by a fixed amount with it.    pub fn bvrotr(self: *Context, operand: Term, amount: u32) !Term {        return try self.append(.{ .bvrotr = .{ .operand = operand, .amount = amount } });    }    /// Appends the sum of `lhs` and `rhs`, modulo 2 to the power of their width, and returns its    /// term. Code adds bit-vectors with it.    pub fn bvadd(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvadd = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends `lhs` minus `rhs`, modulo 2 to the power of their width, and returns its term. Code    /// subtracts bit-vectors with it.    pub fn bvsub(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvsub = .{ .lhs = lhs, .rhs = rhs } });    }    /// Appends the product of `lhs` and `rhs`, modulo 2 to the power of their width, and returns    /// its term. Code multiplies bit-vectors with it.    pub fn bvmul(self: *Context, lhs: Term, rhs: Term) !Term {        return try self.append(.{ .bvmul = .{ .lhs = lhs, .rhs = rhs } });    }    fn sortOfApply(self: *const Context, item: ApplyExpr) !Sort {        if (item.function >= self.functions.items.len) return error.UnknownFunction;        const decl = self.functions.items[item.function];        if (decl.params.len != item.args.len) return error.FunctionArityMismatch;        for (item.args, decl.params) |arg, expected| {            if (!(try self.sortOf(arg)).eql(expected)) return error.FunctionArgumentSortMismatch;        }        return decl.result;    }    fn expectBoolResult(self: *const Context, operand: Term) !Sort {        if (!(try self.sortOf(operand)).eql(.bool)) return error.SortMismatch;        return .bool;    }    fn expectBoolPair(self: *const Context, lhs: Term, rhs: Term) !Sort {        if (!(try self.sortOf(lhs)).eql(.bool) or !(try self.sortOf(rhs)).eql(.bool)) {            return error.SortMismatch;        }        return .bool;    }    fn expectBoolList(self: *const Context, operands: []const Term) !Sort {        for (operands) |operand| {            if (!(try self.sortOf(operand)).eql(.bool)) return error.SortMismatch;        }        return .bool;    }    fn expectIntPair(self: *const Context, lhs: Term, rhs: Term) !Sort {        if (!(try self.sortOf(lhs)).eql(.int) or !(try self.sortOf(rhs)).eql(.int)) {            return error.SortMismatch;        }        return .bool;    }    fn expectIntList(self: *const Context, operands: []const Term) !Sort {        for (operands) |operand| {            if (!(try self.sortOf(operand)).eql(.int)) return error.SortMismatch;        }        return .int;    }    fn expectSameSortPair(self: *const Context, lhs: Term, rhs: Term) !Sort {        if (!(try self.sortOf(lhs)).eql(try self.sortOf(rhs))) return error.SortMismatch;        return .bool;    }    fn expectSameSortList(self: *const Context, operands: []const Term) !Sort {        if (operands.len == 0) return .bool;        const expected = try self.sortOf(operands[0]);        for (operands[1..]) |operand| {            if (!(try self.sortOf(operand)).eql(expected)) return error.SortMismatch;        }        return .bool;    }    fn expectBitVecPair(self: *const Context, lhs: Term, rhs: Term) !Sort {        _ = try self.expectBitVecPairResult(lhs, rhs);        return .bool;    }    fn expectBitVecResult(self: *const Context, operand: Term) !Sort {        return switch (try self.sortOf(operand)) {            .bitvec => |width| .{ .bitvec = width },            else => error.SortMismatch,        };    }    fn expectBitVecPairResult(self: *const Context, lhs: Term, rhs: Term) !Sort {        return switch (try self.sortOf(lhs)) {            .bitvec => |lhs_width| switch (try self.sortOf(rhs)) {                .bitvec => |rhs_width| {                    if (lhs_width != rhs_width) return error.SortMismatch;                    return .{ .bitvec = lhs_width };                },                else => error.SortMismatch,            },            else => error.SortMismatch,        };    }    fn sortOfArraySelect(self: *const Context, array: Term, index: Term) !Sort {        const array_sort = try self.sortOf(array);        const index_sort = try self.sortOf(index);        return switch (array_sort) {            .array => |shape| switch (index_sort) {                .bitvec => |width| {                    if (width != shape.index_width) return error.SortMismatch;                    return .{ .bitvec = shape.element_width };                },                else => error.SortMismatch,            },            else => error.SortMismatch,        };    }    fn sortOfArrayStore(self: *const Context, array: Term, index: Term, value: Term) !Sort {        const array_sort = try self.sortOf(array);        const index_sort = try self.sortOf(index);        const value_sort = try self.sortOf(value);        return switch (array_sort) {            .array => |shape| switch (index_sort) {                .bitvec => |index_width| switch (value_sort) {                    .bitvec => |element_width| {                        if (index_width != shape.index_width or                            element_width != shape.element_width)                        {                            return error.SortMismatch;                        }                        return array_sort;                    },                    else => error.SortMismatch,                },                else => error.SortMismatch,            },            else => error.SortMismatch,        };    }    fn sortOfConcat(self: *const Context, lhs: Term, rhs: Term) !Sort {        return switch (try self.sortOf(lhs)) {            .bitvec => |lhs_width| switch (try self.sortOf(rhs)) {                .bitvec => |rhs_width| .{ .bitvec = try std.math.add(u32, lhs_width, rhs_width) },                else => error.SortMismatch,            },            else => error.SortMismatch,        };    }    fn sortOfExtract(self: *const Context, operand: Term, high: u32, low: u32) !Sort {        return switch (try self.sortOf(operand)) {            .bitvec => |width| {                if (low > high or high >= width) return error.InvalidBitVectorRange;                return .{ .bitvec = high - low + 1 };            },            else => error.SortMismatch,        };    }    fn sortOfExtend(self: *const Context, operand: Term, extra: u32) !Sort {        return switch (try self.sortOf(operand)) {            .bitvec => |width| .{ .bitvec = try std.math.add(u32, width, extra) },            else => error.SortMismatch,        };    }    fn append(self: *Context, expr: Expr) !Term {        const id: Term = @intCast(self.nodes.items.len);        try self.nodes.append(self.allocator, expr);        return id;    }    fn appendPair(self: *Context, comptime tag: std.meta.Tag(Expr), lhs: Term, rhs: Term) !Term {        return try self.append(@unionInit(Expr, @tagName(tag), .{ .lhs = lhs, .rhs = rhs }));    }    fn appendList(self: *Context, comptime tag: std.meta.Tag(Expr), operands: []const Term) !Term {        const owned = try self.allocator.dupe(Term, operands);        errdefer self.allocator.free(owned);        return switch (tag) {            .and_ => try self.append(.{ .and_ = owned }),            .or_ => try self.append(.{ .or_ = owned }),            .distinct => try self.append(.{ .distinct = owned }),            .add => try self.append(.{ .add = owned }),            .mul => try self.append(.{ .mul = owned }),            else => unreachable,        };    }};fn sortListsEqual(lhs: []const Sort, rhs: []const Sort) bool {    if (lhs.len != rhs.len) return false;    for (lhs, rhs) |left, right| {        if (!left.eql(right)) return false;    }    return true;}/// A logic name and the list of terms asserted over one `Context`. `smtlib.parseScript` returns/// one, `smtlib.writeScript` prints one, and code that solves a script asserts each of its terms/// with the encoder. It borrows the `Context` and allocates its list with the `Context`'s/// allocator.pub const Script = struct {    /// The `Context` whose terms the script asserts.    ctx: *Context,    /// The logic name, such as `QF_BV`, that `set-logic` gives. The script borrows it: a script    /// from `smtlib.parseScript` points into the parsed text, which has to outlive the script.    logic: []const u8,    /// The asserted terms in the order they were asserted.    assertions: std.ArrayList(Term) = .empty,    /// Returns a script over `ctx` with logic `logic` and no assertions. Code makes an empty script    /// before asserting terms, as the SMT-LIB writer's tests do. It copies neither and allocates    /// nothing.    pub fn init(ctx: *Context, logic: []const u8) Script {        return .{ .ctx = ctx, .logic = logic };    }    /// Frees the list of assertions and leaves the terms to the `Context`. The owner calls it    /// before freeing the `Context`.    pub fn deinit(self: *Script) void {        self.assertions.deinit(self.ctx.allocator);        self.* = undefined;    }    /// Appends `assertion` to the list. Code adds one assertion to a script with it. It checks    /// nothing, including whether the term is Boolean.    pub fn assertTerm(self: *Script, assertion: Term) !void {        try self.assertions.append(self.ctx.allocator, assertion);    }};test "term context stores structured arithmetic expression" {    var ctx = Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .int);    const one = try ctx.intValue(1);    const sum = try ctx.add(&.{ x, one });    const zero = try ctx.intValue(0);    _ = try ctx.gt(sum, zero);    try std.testing.expectEqual(@as(usize, 5), ctx.nodes.items.len);}test "term context stores function applications" {    var ctx = Context.init(std.testing.allocator);    defer ctx.deinit();    const bv4 = Sort{ .bitvec = 4 };    const x = try ctx.symbol("x", bv4);    const y = try ctx.symbol("y", bv4);    const flag = try ctx.symbol("flag", .bool);    const f = try ctx.function("f", &.{bv4}, bv4);    const fx = try ctx.apply(f, &.{x});    _ = try ctx.apply(f, &.{y});    try std.testing.expectEqual(@as(usize, 1), ctx.functions.items.len);    try std.testing.expectEqual(@as(usize, 5), ctx.nodes.items.len);    try std.testing.expect((try ctx.sortOf(fx)).eql(bv4));    try std.testing.expectEqual(f, try ctx.function("f", &.{bv4}, bv4));    try std.testing.expectError(error.DuplicateFunction, ctx.function("f", &.{bv4}, .bool));    try std.testing.expectError(error.FunctionArgumentSortMismatch, ctx.apply(f, &.{flag}));}test "term context infers composite sorts" {    var ctx = Context.init(std.testing.allocator);    defer ctx.deinit();    const bv4 = Sort{ .bitvec = 4 };    const high = try ctx.symbol("high", bv4);    const low = try ctx.symbol("low", bv4);    const word = try ctx.bvconcat(high, low);    const upper = try ctx.bvextract(word, 7, 4);    const lower = try ctx.bvextract(word, 3, 0);    const memory = try ctx.symbol("memory", .{ .array = .{        .index_width = 2,        .element_width = 4,    } });    const index = try ctx.symbol("index", .{ .bitvec = 2 });    const read = try ctx.arraySelect(memory, index);    try std.testing.expect((try ctx.sortOf(word)).eql(.{ .bitvec = 8 }));    try std.testing.expect((try ctx.sortOf(upper)).eql(bv4));    try std.testing.expect((try ctx.sortOf(lower)).eql(bv4));    try std.testing.expect((try ctx.sortOf(try ctx.eq(upper, lower))).eql(.bool));    try std.testing.expect((try ctx.sortOf(read)).eql(bv4));}test "term context stores bit-vector overflow predicates" {    var ctx = Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 8 });    const y = try ctx.symbol("y", .{ .bitvec = 8 });    _ = try ctx.bvuaddo(x, y);    _ = try ctx.bvsaddo(x, y);    _ = try ctx.bvssubo(x, y);    _ = try ctx.bvumulo(x, y);    _ = try ctx.bvsmulo(x, y);    try std.testing.expectEqual(@as(usize, 7), ctx.nodes.items.len);}test "term context stores signed bit-vector comparisons" {    var ctx = Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 8 });    const y = try ctx.symbol("y", .{ .bitvec = 8 });    const difference = try ctx.bvsub(x, y);    _ = try ctx.bvslt(difference, y);    _ = try ctx.bvsle(x, y);    try std.testing.expect((try ctx.sortOf(difference)).eql(.{ .bitvec = 8 }));    try std.testing.expectEqual(@as(usize, 5), ctx.nodes.items.len);}test "term context stores bit-vector bitwise operators" {    var ctx = Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 8 });    const y = try ctx.symbol("y", .{ .bitvec = 8 });    _ = try ctx.bvnot(x);    _ = try ctx.bvand(x, y);    _ = try ctx.bvor(x, y);    _ = try ctx.bvxor(x, y);    try std.testing.expectEqual(@as(usize, 6), ctx.nodes.items.len);}test "term context stores bit-vector shift operators" {    var ctx = Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 8 });    const amount = try ctx.symbol("amount", .{ .bitvec = 8 });    _ = try ctx.bvshl(x, amount);    _ = try ctx.bvlshr(x, amount);    _ = try ctx.bvashr(x, amount);    _ = try ctx.bvudiv(x, amount);    _ = try ctx.bvurem(x, amount);    _ = try ctx.bvsdiv(x, amount);    _ = try ctx.bvsrem(x, amount);    _ = try ctx.bvsmod(x, amount);    try std.testing.expectEqual(@as(usize, 10), ctx.nodes.items.len);}test "term context stores bit-vector width-changing operators" {    var ctx = Context.init(std.testing.allocator);    defer ctx.deinit();    const high = try ctx.symbol("high", .{ .bitvec = 4 });    const low = try ctx.symbol("low", .{ .bitvec = 4 });    const word = try ctx.bvconcat(high, low);    _ = try ctx.bvextract(word, 7, 4);    _ = try ctx.bvzeroext(high, 4);    _ = try ctx.bvsignext(low, 4);    try std.testing.expectEqual(@as(usize, 6), ctx.nodes.items.len);}test "term context stores bit-vector rotate operators" {    var ctx = Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 8 });    _ = try ctx.bvrotl(x, 3);    _ = try ctx.bvrotr(x, 5);    try std.testing.expectEqual(@as(usize, 3), ctx.nodes.items.len);}test "term context stores finite bit-vector array operators" {    var ctx = Context.init(std.testing.allocator);    defer ctx.deinit();    const memory = try ctx.symbol("memory", .{ .array = .{        .index_width = 2,        .element_width = 4,    } });    const index = try ctx.symbol("index", .{ .bitvec = 2 });    const value = try ctx.symbol("value", .{ .bitvec = 4 });    const written = try ctx.arrayStore(memory, index, value);    _ = try ctx.arraySelect(written, index);    try std.testing.expectEqual(@as(usize, 5), ctx.nodes.items.len);}

Audit

Definitions11
Public names11
Members22
Version26.7.0
Revisiondaab053ee433