mirror of
https://github.com/Z3Prover/z3
synced 2026-02-21 15:57:35 +00:00
5046 lines
162 KiB
TypeScript
5046 lines
162 KiB
TypeScript
// TODO(ritave): Add typing for Context Options
|
|
// https://github.com/Z3Prover/z3/pull/6048#discussion_r883391669
|
|
// TODO(ritave): Add an error handler
|
|
// TODO(ritave): Add support for building faster floats without support for Safari
|
|
// TODO(ritave): Use Z3_DECLARE_CLOSURE macro to generate code https://github.com/Z3Prover/z3/pull/6048#discussion_r884155462
|
|
// TODO(ritave): Add pretty printing
|
|
// TODO(ritave): Make Z3 multi-threaded
|
|
// TODO(ritave): If a test times out, jest kills it, and the global state of Z3 is left in an unexpected state.
|
|
// This occurs specifically during longer check(). Afterwards, all next tests will fail to run
|
|
// thinking the previous call was not finished. Find a way to stop execution and clean up the global state
|
|
import { Mutex } from 'async-mutex';
|
|
import {
|
|
Z3Core,
|
|
Z3_ast,
|
|
Z3_ast_kind,
|
|
Z3_ast_map,
|
|
Z3_ast_print_mode,
|
|
Z3_ast_vector,
|
|
Z3_context,
|
|
Z3_constructor,
|
|
Z3_constructor_list,
|
|
Z3_decl_kind,
|
|
Z3_error_code,
|
|
Z3_func_decl,
|
|
Z3_func_interp,
|
|
Z3_lbool,
|
|
Z3_model,
|
|
Z3_parameter_kind,
|
|
Z3_probe,
|
|
Z3_solver,
|
|
Z3_sort,
|
|
Z3_sort_kind,
|
|
Z3_stats,
|
|
Z3_symbol,
|
|
Z3_symbol_kind,
|
|
Z3_tactic,
|
|
Z3_pattern,
|
|
Z3_app,
|
|
Z3_params,
|
|
Z3_func_entry,
|
|
Z3_optimize,
|
|
Z3_fixedpoint,
|
|
Z3_goal,
|
|
Z3_apply_result,
|
|
Z3_goal_prec,
|
|
Z3_param_descrs,
|
|
Z3_simplifier,
|
|
Z3_rcf_num,
|
|
} from '../low-level';
|
|
import {
|
|
AnyAst,
|
|
AnyExpr,
|
|
AnySort,
|
|
Arith,
|
|
ArithSort,
|
|
ArrayIndexType,
|
|
CoercibleToArrayIndexType,
|
|
Ast,
|
|
AstMap,
|
|
AstMapCtor,
|
|
AstVector,
|
|
AstVectorCtor,
|
|
BitVec,
|
|
BitVecNum,
|
|
BitVecSort,
|
|
Bool,
|
|
BoolSort,
|
|
CheckSatResult,
|
|
CoercibleToMap,
|
|
CoercibleRational,
|
|
CoercibleToBitVec,
|
|
CoercibleToExpr,
|
|
CoercibleFromMap,
|
|
CoercibleToFP,
|
|
Context,
|
|
ContextCtor,
|
|
Expr,
|
|
FP,
|
|
FPNum,
|
|
FPSort,
|
|
FPRM,
|
|
FPRMSort,
|
|
Fixedpoint,
|
|
FuncDecl,
|
|
FuncDeclSignature,
|
|
FuncInterp,
|
|
IntNum,
|
|
Model,
|
|
Optimize,
|
|
Params,
|
|
ParamDescrs,
|
|
Pattern,
|
|
Probe,
|
|
Quantifier,
|
|
BodyT,
|
|
RatNum,
|
|
RCFNum,
|
|
RCFNumCreation,
|
|
Re,
|
|
ReSort,
|
|
ReCreation,
|
|
Seq,
|
|
SeqSort,
|
|
Simplifier,
|
|
SMTArray,
|
|
SMTArraySort,
|
|
Solver,
|
|
Sort,
|
|
SortToExprMap,
|
|
Statistics,
|
|
StatisticsEntry,
|
|
Tactic,
|
|
Goal,
|
|
ApplyResult,
|
|
Z3Error,
|
|
Z3HighLevel,
|
|
CoercibleToArith,
|
|
NonEmptySortArray,
|
|
FuncEntry,
|
|
SMTSetSort,
|
|
SMTSet,
|
|
Datatype,
|
|
DatatypeSort,
|
|
DatatypeExpr,
|
|
DatatypeCreation,
|
|
} from './types';
|
|
import { allSatisfy, assert, assertExhaustive } from './utils';
|
|
|
|
const FALLBACK_PRECISION = 17;
|
|
|
|
const asyncMutex = new Mutex();
|
|
|
|
function isCoercibleRational(obj: any): obj is CoercibleRational {
|
|
// prettier-ignore
|
|
const r = (
|
|
(obj !== null &&
|
|
(typeof obj === 'object' || typeof obj === 'function')) &&
|
|
(obj.numerator !== null &&
|
|
(typeof obj.numerator === 'number' || typeof obj.numerator === 'bigint')) &&
|
|
(obj.denominator !== null &&
|
|
(typeof obj.denominator === 'number' || typeof obj.denominator === 'bigint'))
|
|
);
|
|
r &&
|
|
assert(
|
|
(typeof obj!.numerator !== 'number' || Number.isSafeInteger(obj!.numerator)) &&
|
|
(typeof obj!.denominator !== 'number' || Number.isSafeInteger(obj!.denominator)),
|
|
'Fraction numerator and denominator must be integers',
|
|
);
|
|
return r;
|
|
}
|
|
|
|
export function createApi(Z3: Z3Core): Z3HighLevel {
|
|
// TODO(ritave): Create a custom linting rule that checks if the provided callbacks to cleanup
|
|
// Don't capture `this`
|
|
const cleanup = new FinalizationRegistry<() => void>(callback => callback());
|
|
|
|
function enableTrace(tag: string) {
|
|
Z3.enable_trace(tag);
|
|
}
|
|
|
|
function disableTrace(tag: string) {
|
|
Z3.disable_trace(tag);
|
|
}
|
|
|
|
function getVersion() {
|
|
return Z3.get_version();
|
|
}
|
|
|
|
function getVersionString() {
|
|
const { major, minor, build_number } = Z3.get_version();
|
|
return `${major}.${minor}.${build_number}`;
|
|
}
|
|
|
|
function getFullVersion() {
|
|
return Z3.get_full_version();
|
|
}
|
|
|
|
function openLog(filename: string) {
|
|
return Z3.open_log(filename);
|
|
}
|
|
|
|
function appendLog(s: string) {
|
|
Z3.append_log(s);
|
|
}
|
|
|
|
function setParam(key: string, value: any): void;
|
|
function setParam(params: Record<string, any>): void;
|
|
function setParam(key: string | Record<string, any>, value?: any) {
|
|
if (typeof key === 'string') {
|
|
Z3.global_param_set(key, value.toString());
|
|
} else {
|
|
assert(value === undefined, "Can't provide a Record and second parameter to set_param at the same time");
|
|
Object.entries(key).forEach(([key, value]) => setParam(key, value));
|
|
}
|
|
}
|
|
|
|
function resetParams() {
|
|
Z3.global_param_reset_all();
|
|
}
|
|
|
|
function getParam(name: string) {
|
|
return Z3.global_param_get(name);
|
|
}
|
|
|
|
function createContext<Name extends string>(name: Name, options?: Record<string, any>): Context<Name> {
|
|
const cfg = Z3.mk_config();
|
|
if (options != null) {
|
|
Object.entries(options).forEach(([key, value]) => check(Z3.set_param_value(cfg, key, value.toString())));
|
|
}
|
|
const contextPtr = Z3.mk_context_rc(cfg);
|
|
Z3.set_ast_print_mode(contextPtr, Z3_ast_print_mode.Z3_PRINT_SMTLIB2_COMPLIANT);
|
|
Z3.del_config(cfg);
|
|
|
|
function _assertContext(...ctxs: (Context<Name> | { ctx: Context<Name> })[]) {
|
|
ctxs.forEach(other => assert('ctx' in other ? ctx === other.ctx : ctx === other, 'Context mismatch'));
|
|
}
|
|
|
|
function _assertPtr<T extends Number>(ptr: T | null): asserts ptr is T {
|
|
if (ptr == null) throw new TypeError('Expected non-null pointer');
|
|
}
|
|
|
|
// call this after every nontrivial call to the underlying API
|
|
function throwIfError() {
|
|
if (Z3.get_error_code(contextPtr) !== Z3_error_code.Z3_OK) {
|
|
throw new Error(Z3.get_error_msg(ctx.ptr, Z3.get_error_code(ctx.ptr)));
|
|
}
|
|
}
|
|
|
|
function check<T>(val: T): T {
|
|
throwIfError();
|
|
return val;
|
|
}
|
|
|
|
/////////////
|
|
// Private //
|
|
/////////////
|
|
function _toSymbol(s: string | number) {
|
|
if (typeof s === 'number') {
|
|
return check(Z3.mk_int_symbol(contextPtr, s));
|
|
} else {
|
|
return check(Z3.mk_string_symbol(contextPtr, s));
|
|
}
|
|
}
|
|
|
|
function _fromSymbol(sym: Z3_symbol) {
|
|
const kind = check(Z3.get_symbol_kind(contextPtr, sym));
|
|
switch (kind) {
|
|
case Z3_symbol_kind.Z3_INT_SYMBOL:
|
|
return Z3.get_symbol_int(contextPtr, sym);
|
|
case Z3_symbol_kind.Z3_STRING_SYMBOL:
|
|
return Z3.get_symbol_string(contextPtr, sym);
|
|
default:
|
|
assertExhaustive(kind);
|
|
}
|
|
}
|
|
|
|
function _toParams(key: string, value: any): Z3_params {
|
|
const params = Z3.mk_params(contextPtr);
|
|
Z3.params_inc_ref(contextPtr, params);
|
|
// If value is a boolean
|
|
if (typeof value === 'boolean') {
|
|
Z3.params_set_bool(contextPtr, params, _toSymbol(key), value);
|
|
} else if (typeof value === 'number') {
|
|
// If value is a uint
|
|
if (Number.isInteger(value)) {
|
|
check(Z3.params_set_uint(contextPtr, params, _toSymbol(key), value));
|
|
} else {
|
|
// If value is a double
|
|
check(Z3.params_set_double(contextPtr, params, _toSymbol(key), value));
|
|
}
|
|
} else if (typeof value === 'string') {
|
|
check(Z3.params_set_symbol(contextPtr, params, _toSymbol(key), _toSymbol(value)));
|
|
}
|
|
return params;
|
|
}
|
|
|
|
function _toAst(ast: Z3_ast): AnyAst<Name> {
|
|
switch (check(Z3.get_ast_kind(contextPtr, ast))) {
|
|
case Z3_ast_kind.Z3_SORT_AST:
|
|
return _toSort(ast as Z3_sort);
|
|
case Z3_ast_kind.Z3_FUNC_DECL_AST:
|
|
return new FuncDeclImpl(ast as Z3_func_decl);
|
|
default:
|
|
return _toExpr(ast);
|
|
}
|
|
}
|
|
|
|
function _toSort(ast: Z3_sort): AnySort<Name> {
|
|
switch (check(Z3.get_sort_kind(contextPtr, ast))) {
|
|
case Z3_sort_kind.Z3_BOOL_SORT:
|
|
return new BoolSortImpl(ast);
|
|
case Z3_sort_kind.Z3_INT_SORT:
|
|
case Z3_sort_kind.Z3_REAL_SORT:
|
|
return new ArithSortImpl(ast);
|
|
case Z3_sort_kind.Z3_BV_SORT:
|
|
return new BitVecSortImpl(ast);
|
|
case Z3_sort_kind.Z3_FLOATING_POINT_SORT:
|
|
return new FPSortImpl(ast);
|
|
case Z3_sort_kind.Z3_ROUNDING_MODE_SORT:
|
|
return new FPRMSortImpl(ast);
|
|
case Z3_sort_kind.Z3_SEQ_SORT:
|
|
return new SeqSortImpl(ast);
|
|
case Z3_sort_kind.Z3_RE_SORT:
|
|
return new ReSortImpl(ast);
|
|
case Z3_sort_kind.Z3_ARRAY_SORT:
|
|
return new ArraySortImpl(ast);
|
|
default:
|
|
return new SortImpl(ast);
|
|
}
|
|
}
|
|
|
|
function _toExpr(ast: Z3_ast): AnyExpr<Name> {
|
|
const kind = check(Z3.get_ast_kind(contextPtr, ast));
|
|
if (kind === Z3_ast_kind.Z3_QUANTIFIER_AST) {
|
|
if (Z3.is_lambda(contextPtr, ast)) {
|
|
return new LambdaImpl(ast);
|
|
}
|
|
return new NonLambdaQuantifierImpl(ast);
|
|
}
|
|
const sortKind = check(Z3.get_sort_kind(contextPtr, Z3.get_sort(contextPtr, ast)));
|
|
switch (sortKind) {
|
|
case Z3_sort_kind.Z3_BOOL_SORT:
|
|
return new BoolImpl(ast);
|
|
case Z3_sort_kind.Z3_INT_SORT:
|
|
if (kind === Z3_ast_kind.Z3_NUMERAL_AST) {
|
|
return new IntNumImpl(ast);
|
|
}
|
|
return new ArithImpl(ast);
|
|
case Z3_sort_kind.Z3_REAL_SORT:
|
|
if (kind === Z3_ast_kind.Z3_NUMERAL_AST) {
|
|
return new RatNumImpl(ast);
|
|
}
|
|
return new ArithImpl(ast);
|
|
case Z3_sort_kind.Z3_BV_SORT:
|
|
if (kind === Z3_ast_kind.Z3_NUMERAL_AST) {
|
|
return new BitVecNumImpl(ast);
|
|
}
|
|
return new BitVecImpl(ast);
|
|
case Z3_sort_kind.Z3_FLOATING_POINT_SORT:
|
|
if (kind === Z3_ast_kind.Z3_NUMERAL_AST || kind === Z3_ast_kind.Z3_APP_AST) {
|
|
return new FPNumImpl(ast);
|
|
}
|
|
return new FPImpl(ast);
|
|
case Z3_sort_kind.Z3_ROUNDING_MODE_SORT:
|
|
return new FPRMImpl(ast);
|
|
case Z3_sort_kind.Z3_SEQ_SORT:
|
|
return new SeqImpl(ast);
|
|
case Z3_sort_kind.Z3_RE_SORT:
|
|
return new ReImpl(ast);
|
|
case Z3_sort_kind.Z3_ARRAY_SORT:
|
|
return new ArrayImpl(ast);
|
|
default:
|
|
return new ExprImpl(ast);
|
|
}
|
|
}
|
|
|
|
function _flattenArgs<T extends AnyAst<Name> = AnyAst<Name>>(args: (T | AstVector<Name, T>)[]): T[] {
|
|
const result: T[] = [];
|
|
for (const arg of args) {
|
|
if (isAstVector(arg)) {
|
|
result.push(...arg.values());
|
|
} else {
|
|
result.push(arg);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function _toProbe(p: Probe<Name> | Z3_probe): Probe<Name> {
|
|
if (isProbe(p)) {
|
|
return p;
|
|
}
|
|
return new ProbeImpl(p);
|
|
}
|
|
|
|
function _probeNary(
|
|
f: (ctx: Z3_context, left: Z3_probe, right: Z3_probe) => Z3_probe,
|
|
args: [Probe<Name> | Z3_probe, ...(Probe<Name> | Z3_probe)[]],
|
|
) {
|
|
assert(args.length > 0, 'At least one argument expected');
|
|
let r = _toProbe(args[0]);
|
|
for (let i = 1; i < args.length; i++) {
|
|
r = new ProbeImpl(check(f(contextPtr, r.ptr, _toProbe(args[i]).ptr)));
|
|
}
|
|
return r;
|
|
}
|
|
|
|
///////////////
|
|
// Functions //
|
|
///////////////
|
|
function interrupt(): void {
|
|
check(Z3.interrupt(contextPtr));
|
|
}
|
|
|
|
function setPrintMode(mode: Z3_ast_print_mode): void {
|
|
Z3.set_ast_print_mode(contextPtr, mode);
|
|
}
|
|
|
|
function isModel(obj: unknown): obj is Model<Name> {
|
|
const r = obj instanceof ModelImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isAst(obj: unknown): obj is Ast<Name> {
|
|
const r = obj instanceof AstImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isSort(obj: unknown): obj is Sort<Name> {
|
|
const r = obj instanceof SortImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isFuncDecl(obj: unknown): obj is FuncDecl<Name> {
|
|
const r = obj instanceof FuncDeclImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isFuncInterp(obj: unknown): obj is FuncInterp<Name> {
|
|
const r = obj instanceof FuncInterpImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isApp(obj: unknown): boolean {
|
|
if (!isExpr(obj)) {
|
|
return false;
|
|
}
|
|
const kind = check(Z3.get_ast_kind(contextPtr, obj.ast));
|
|
return kind === Z3_ast_kind.Z3_NUMERAL_AST || kind === Z3_ast_kind.Z3_APP_AST;
|
|
}
|
|
|
|
function isConst(obj: unknown): boolean {
|
|
return isExpr(obj) && isApp(obj) && obj.numArgs() === 0;
|
|
}
|
|
|
|
function isExpr(obj: unknown): obj is Expr<Name> {
|
|
const r = obj instanceof ExprImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isVar(obj: unknown): boolean {
|
|
return isExpr(obj) && check(Z3.get_ast_kind(contextPtr, obj.ast)) === Z3_ast_kind.Z3_VAR_AST;
|
|
}
|
|
|
|
function isAppOf(obj: unknown, kind: Z3_decl_kind): boolean {
|
|
return isExpr(obj) && isApp(obj) && obj.decl().kind() === kind;
|
|
}
|
|
|
|
function isBool(obj: unknown): obj is Bool<Name> {
|
|
const r = obj instanceof ExprImpl && obj.sort.kind() === Z3_sort_kind.Z3_BOOL_SORT;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isTrue(obj: unknown): boolean {
|
|
return isAppOf(obj, Z3_decl_kind.Z3_OP_TRUE);
|
|
}
|
|
|
|
function isFalse(obj: unknown): boolean {
|
|
return isAppOf(obj, Z3_decl_kind.Z3_OP_FALSE);
|
|
}
|
|
|
|
function isAnd(obj: unknown): boolean {
|
|
return isAppOf(obj, Z3_decl_kind.Z3_OP_AND);
|
|
}
|
|
|
|
function isOr(obj: unknown): boolean {
|
|
return isAppOf(obj, Z3_decl_kind.Z3_OP_OR);
|
|
}
|
|
|
|
function isImplies(obj: unknown): boolean {
|
|
return isAppOf(obj, Z3_decl_kind.Z3_OP_IMPLIES);
|
|
}
|
|
|
|
function isNot(obj: unknown): boolean {
|
|
return isAppOf(obj, Z3_decl_kind.Z3_OP_NOT);
|
|
}
|
|
|
|
function isEq(obj: unknown): boolean {
|
|
return isAppOf(obj, Z3_decl_kind.Z3_OP_EQ);
|
|
}
|
|
|
|
function isDistinct(obj: unknown): boolean {
|
|
return isAppOf(obj, Z3_decl_kind.Z3_OP_DISTINCT);
|
|
}
|
|
|
|
function isQuantifier(obj: unknown): obj is Quantifier<Name> {
|
|
const r = obj instanceof QuantifierImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isArith(obj: unknown): obj is Arith<Name> {
|
|
const r = obj instanceof ArithImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isArithSort(obj: unknown): obj is ArithSort<Name> {
|
|
const r = obj instanceof ArithSortImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isInt(obj: unknown): boolean {
|
|
return isArith(obj) && isIntSort(obj.sort);
|
|
}
|
|
|
|
function isIntVal(obj: unknown): obj is IntNum<Name> {
|
|
const r = obj instanceof IntNumImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isIntSort(obj: unknown): boolean {
|
|
return isSort(obj) && obj.kind() === Z3_sort_kind.Z3_INT_SORT;
|
|
}
|
|
|
|
function isReal(obj: unknown): boolean {
|
|
return isArith(obj) && isRealSort(obj.sort);
|
|
}
|
|
|
|
function isRealVal(obj: unknown): obj is RatNum<Name> {
|
|
const r = obj instanceof RatNumImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isRealSort(obj: unknown): boolean {
|
|
return isSort(obj) && obj.kind() === Z3_sort_kind.Z3_REAL_SORT;
|
|
}
|
|
|
|
function isRCFNum(obj: unknown): obj is RCFNum<Name> {
|
|
const r = obj instanceof RCFNumImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isBitVecSort(obj: unknown): obj is BitVecSort<number, Name> {
|
|
const r = obj instanceof BitVecSortImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isBitVec(obj: unknown): obj is BitVec<number, Name> {
|
|
const r = obj instanceof BitVecImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isBitVecVal(obj: unknown): obj is BitVecNum<number, Name> {
|
|
const r = obj instanceof BitVecNumImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isArraySort(obj: unknown): obj is SMTArraySort<Name> {
|
|
const r = obj instanceof ArraySortImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isArray(obj: unknown): obj is SMTArray<Name> {
|
|
const r = obj instanceof ArrayImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isConstArray(obj: unknown): boolean {
|
|
return isAppOf(obj, Z3_decl_kind.Z3_OP_CONST_ARRAY);
|
|
}
|
|
|
|
function isFPRMSort(obj: unknown): obj is FPRMSort<Name> {
|
|
const r = obj instanceof FPRMSortImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isFPRM(obj: unknown): obj is FPRM<Name> {
|
|
const r = obj instanceof FPRMImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isFPSort(obj: unknown): obj is FPSort<Name> {
|
|
const r = obj instanceof FPSortImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isFP(obj: unknown): obj is FP<Name> {
|
|
const r = obj instanceof FPImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isFPVal(obj: unknown): obj is FPNum<Name> {
|
|
const r = obj instanceof FPNumImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isSeqSort(obj: unknown): obj is SeqSort<Name> {
|
|
const r = obj instanceof SeqSortImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isSeq(obj: unknown): obj is Seq<Name> {
|
|
const r = obj instanceof SeqImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isReSort(obj: unknown): obj is ReSort<Name> {
|
|
const r = obj instanceof ReSortImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isRe(obj: unknown): obj is Re<Name> {
|
|
const r = obj instanceof ReImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isStringSort(obj: unknown): obj is SeqSort<Name> {
|
|
return isSeqSort(obj) && obj.isString();
|
|
}
|
|
|
|
function isString(obj: unknown): obj is Seq<Name> {
|
|
return isSeq(obj) && obj.isString();
|
|
}
|
|
|
|
function isProbe(obj: unknown): obj is Probe<Name> {
|
|
const r = obj instanceof ProbeImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isTactic(obj: unknown): obj is Tactic<Name> {
|
|
const r = obj instanceof TacticImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isGoal(obj: unknown): obj is Goal<Name> {
|
|
const r = obj instanceof GoalImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function isAstVector(obj: unknown): obj is AstVector<Name> {
|
|
const r = obj instanceof AstVectorImpl;
|
|
r && _assertContext(obj);
|
|
return r;
|
|
}
|
|
|
|
function eqIdentity(a: Ast<Name>, b: Ast<Name>): boolean {
|
|
return a.eqIdentity(b);
|
|
}
|
|
|
|
function getVarIndex(obj: Expr<Name>): number {
|
|
assert(isVar(obj), 'Z3 bound variable expected');
|
|
return Z3.get_index_value(contextPtr, obj.ast);
|
|
}
|
|
|
|
function from(primitive: boolean): Bool<Name>;
|
|
function from(primitive: number): IntNum<Name> | RatNum<Name>;
|
|
function from(primitive: CoercibleRational): RatNum<Name>;
|
|
function from(primitive: bigint): IntNum<Name>;
|
|
function from<T extends Expr<Name>>(expr: T): T;
|
|
function from(expr: CoercibleToExpr<Name>): AnyExpr<Name>;
|
|
function from(value: CoercibleToExpr<Name>): AnyExpr<Name> {
|
|
if (typeof value === 'boolean') {
|
|
return Bool.val(value);
|
|
} else if (typeof value === 'number') {
|
|
if (!Number.isFinite(value)) {
|
|
throw new Error(`cannot represent infinity/NaN (got ${value})`);
|
|
}
|
|
if (Math.floor(value) === value) {
|
|
return Int.val(value);
|
|
}
|
|
return Real.val(value);
|
|
} else if (isCoercibleRational(value)) {
|
|
return Real.val(value);
|
|
} else if (typeof value === 'bigint') {
|
|
return Int.val(value);
|
|
} else if (isExpr(value)) {
|
|
return value;
|
|
}
|
|
assert(false);
|
|
}
|
|
|
|
async function solve(...assertions: Bool<Name>[]): Promise<Model<Name> | 'unsat' | 'unknown'> {
|
|
const solver = new ctx.Solver();
|
|
solver.add(...assertions);
|
|
const result = await solver.check();
|
|
if (result === 'sat') {
|
|
return solver.model();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// expression simplification //
|
|
///////////////////////////////
|
|
|
|
async function simplify(e: Expr<Name>): Promise<Expr<Name>> {
|
|
const result = await Z3.simplify(contextPtr, e.ast);
|
|
return _toExpr(check(result));
|
|
}
|
|
|
|
/////////////
|
|
// Objects //
|
|
/////////////
|
|
const Sort = {
|
|
declare: (name: string) => new SortImpl(Z3.mk_uninterpreted_sort(contextPtr, _toSymbol(name))),
|
|
};
|
|
const Function = {
|
|
declare: <DomainSort extends Sort<Name>[], RangeSort extends Sort<Name>>(
|
|
name: string,
|
|
...signature: [...DomainSort, RangeSort]
|
|
): FuncDecl<Name, DomainSort, RangeSort> => {
|
|
const arity = signature.length - 1;
|
|
const rng = signature[arity];
|
|
_assertContext(rng);
|
|
const dom = [];
|
|
for (let i = 0; i < arity; i++) {
|
|
_assertContext(signature[i]);
|
|
dom.push(signature[i].ptr);
|
|
}
|
|
return new FuncDeclImpl<DomainSort, RangeSort>(Z3.mk_func_decl(contextPtr, _toSymbol(name), dom, rng.ptr));
|
|
},
|
|
fresh: <DomainSort extends Sort<Name>[], RangeSort extends Sort<Name>>(
|
|
...signature: [...DomainSort, RangeSort]
|
|
): FuncDecl<Name, DomainSort, RangeSort> => {
|
|
const arity = signature.length - 1;
|
|
const rng = signature[arity];
|
|
_assertContext(rng);
|
|
const dom = [];
|
|
for (let i = 0; i < arity; i++) {
|
|
_assertContext(signature[i]);
|
|
dom.push(signature[i].ptr);
|
|
}
|
|
return new FuncDeclImpl<DomainSort, RangeSort>(Z3.mk_fresh_func_decl(contextPtr, 'f', dom, rng.ptr));
|
|
},
|
|
};
|
|
const RecFunc = {
|
|
declare: (name: string, ...signature: FuncDeclSignature<Name>) => {
|
|
const arity = signature.length - 1;
|
|
const rng = signature[arity];
|
|
_assertContext(rng);
|
|
const dom = [];
|
|
for (let i = 0; i < arity; i++) {
|
|
_assertContext(signature[i]);
|
|
dom.push(signature[i].ptr);
|
|
}
|
|
return new FuncDeclImpl(Z3.mk_rec_func_decl(contextPtr, _toSymbol(name), dom, rng.ptr));
|
|
},
|
|
|
|
addDefinition: (f: FuncDecl<Name>, args: Expr<Name>[], body: Expr<Name>) => {
|
|
_assertContext(f, ...args, body);
|
|
check(
|
|
Z3.add_rec_def(
|
|
contextPtr,
|
|
f.ptr,
|
|
args.map(arg => arg.ast),
|
|
body.ast,
|
|
),
|
|
);
|
|
},
|
|
};
|
|
const Bool = {
|
|
sort: () => new BoolSortImpl(Z3.mk_bool_sort(contextPtr)),
|
|
|
|
const: (name: string) => new BoolImpl(Z3.mk_const(contextPtr, _toSymbol(name), Bool.sort().ptr)),
|
|
consts: (names: string | string[]) => {
|
|
if (typeof names === 'string') {
|
|
names = names.split(' ');
|
|
}
|
|
return names.map(name => Bool.const(name));
|
|
},
|
|
vector: (prefix: string, count: number) => {
|
|
const result = [];
|
|
for (let i = 0; i < count; i++) {
|
|
result.push(Bool.const(`${prefix}__${i}`));
|
|
}
|
|
return result;
|
|
},
|
|
fresh: (prefix = 'b') => new BoolImpl(Z3.mk_fresh_const(contextPtr, prefix, Bool.sort().ptr)),
|
|
|
|
val: (value: boolean) => {
|
|
if (value) {
|
|
return new BoolImpl(Z3.mk_true(contextPtr));
|
|
}
|
|
return new BoolImpl(Z3.mk_false(contextPtr));
|
|
},
|
|
};
|
|
const Int = {
|
|
sort: () => new ArithSortImpl(Z3.mk_int_sort(contextPtr)),
|
|
|
|
const: (name: string) => new ArithImpl(Z3.mk_const(contextPtr, _toSymbol(name), Int.sort().ptr)),
|
|
consts: (names: string | string[]) => {
|
|
if (typeof names === 'string') {
|
|
names = names.split(' ');
|
|
}
|
|
return names.map(name => Int.const(name));
|
|
},
|
|
vector: (prefix: string, count: number) => {
|
|
const result = [];
|
|
for (let i = 0; i < count; i++) {
|
|
result.push(Int.const(`${prefix}__${i}`));
|
|
}
|
|
return result;
|
|
},
|
|
fresh: (prefix = 'x') => new ArithImpl(Z3.mk_fresh_const(contextPtr, prefix, Int.sort().ptr)),
|
|
|
|
val: (value: number | bigint | string) => {
|
|
assert(typeof value === 'bigint' || typeof value === 'string' || Number.isSafeInteger(value));
|
|
return new IntNumImpl(check(Z3.mk_numeral(contextPtr, value.toString(), Int.sort().ptr)));
|
|
},
|
|
};
|
|
const Real = {
|
|
sort: () => new ArithSortImpl(Z3.mk_real_sort(contextPtr)),
|
|
|
|
const: (name: string) => new ArithImpl(check(Z3.mk_const(contextPtr, _toSymbol(name), Real.sort().ptr))),
|
|
consts: (names: string | string[]) => {
|
|
if (typeof names === 'string') {
|
|
names = names.split(' ');
|
|
}
|
|
return names.map(name => Real.const(name));
|
|
},
|
|
vector: (prefix: string, count: number) => {
|
|
const result = [];
|
|
for (let i = 0; i < count; i++) {
|
|
result.push(Real.const(`${prefix}__${i}`));
|
|
}
|
|
return result;
|
|
},
|
|
fresh: (prefix = 'b') => new ArithImpl(Z3.mk_fresh_const(contextPtr, prefix, Real.sort().ptr)),
|
|
|
|
val: (value: number | bigint | string | CoercibleRational) => {
|
|
if (isCoercibleRational(value)) {
|
|
value = `${value.numerator}/${value.denominator}`;
|
|
}
|
|
return new RatNumImpl(Z3.mk_numeral(contextPtr, value.toString(), Real.sort().ptr));
|
|
},
|
|
};
|
|
|
|
const RCFNum = Object.assign((value: string | number) => new RCFNumImpl(value), {
|
|
pi: () => new RCFNumImpl(check(Z3.rcf_mk_pi(contextPtr))),
|
|
|
|
e: () => new RCFNumImpl(check(Z3.rcf_mk_e(contextPtr))),
|
|
|
|
infinitesimal: () => new RCFNumImpl(check(Z3.rcf_mk_infinitesimal(contextPtr))),
|
|
|
|
roots: (coefficients: RCFNum<Name>[]) => {
|
|
assert(coefficients.length > 0, 'Polynomial coefficients cannot be empty');
|
|
const coeffPtrs = coefficients.map(c => (c as RCFNumImpl).ptr);
|
|
const { rv: numRoots, roots: rootPtrs } = Z3.rcf_mk_roots(contextPtr, coeffPtrs);
|
|
const result: RCFNum<Name>[] = [];
|
|
for (let i = 0; i < numRoots; i++) {
|
|
result.push(new RCFNumImpl(rootPtrs[i]));
|
|
}
|
|
return result;
|
|
},
|
|
}) as RCFNumCreation<Name>;
|
|
|
|
const BitVec = {
|
|
sort<Bits extends number>(bits: Bits): BitVecSort<Bits, Name> {
|
|
assert(Number.isSafeInteger(bits), 'number of bits must be an integer');
|
|
return new BitVecSortImpl(Z3.mk_bv_sort(contextPtr, bits));
|
|
},
|
|
|
|
const<Bits extends number>(name: string, bits: Bits | BitVecSort<Bits, Name>): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(
|
|
check(Z3.mk_const(contextPtr, _toSymbol(name), isBitVecSort(bits) ? bits.ptr : BitVec.sort(bits).ptr)),
|
|
);
|
|
},
|
|
|
|
consts<Bits extends number>(names: string | string[], bits: Bits | BitVecSort<Bits, Name>): BitVec<Bits, Name>[] {
|
|
if (typeof names === 'string') {
|
|
names = names.split(' ');
|
|
}
|
|
return names.map(name => BitVec.const(name, bits));
|
|
},
|
|
|
|
val<Bits extends number>(
|
|
value: bigint | number | boolean | string,
|
|
bits: Bits | BitVecSort<Bits, Name>,
|
|
): BitVecNum<Bits, Name> {
|
|
if (value === true) {
|
|
return BitVec.val(1, bits);
|
|
} else if (value === false) {
|
|
return BitVec.val(0, bits);
|
|
}
|
|
return new BitVecNumImpl<Bits>(
|
|
check(Z3.mk_numeral(contextPtr, value.toString(), isBitVecSort(bits) ? bits.ptr : BitVec.sort(bits).ptr)),
|
|
);
|
|
},
|
|
};
|
|
|
|
const Float = {
|
|
sort(ebits: number, sbits: number): FPSort<Name> {
|
|
assert(Number.isSafeInteger(ebits) && ebits > 0, 'ebits must be a positive integer');
|
|
assert(Number.isSafeInteger(sbits) && sbits > 0, 'sbits must be a positive integer');
|
|
return new FPSortImpl(Z3.mk_fpa_sort(contextPtr, ebits, sbits));
|
|
},
|
|
|
|
sort16(): FPSort<Name> {
|
|
return new FPSortImpl(Z3.mk_fpa_sort_16(contextPtr));
|
|
},
|
|
|
|
sort32(): FPSort<Name> {
|
|
return new FPSortImpl(Z3.mk_fpa_sort_32(contextPtr));
|
|
},
|
|
|
|
sort64(): FPSort<Name> {
|
|
return new FPSortImpl(Z3.mk_fpa_sort_64(contextPtr));
|
|
},
|
|
|
|
sort128(): FPSort<Name> {
|
|
return new FPSortImpl(Z3.mk_fpa_sort_128(contextPtr));
|
|
},
|
|
|
|
const(name: string, sort: FPSort<Name>): FP<Name> {
|
|
return new FPImpl(check(Z3.mk_const(contextPtr, _toSymbol(name), sort.ptr)));
|
|
},
|
|
|
|
consts(names: string | string[], sort: FPSort<Name>): FP<Name>[] {
|
|
if (typeof names === 'string') {
|
|
names = names.split(' ');
|
|
}
|
|
return names.map(name => Float.const(name, sort));
|
|
},
|
|
|
|
val(value: number, sort: FPSort<Name>): FPNum<Name> {
|
|
return new FPNumImpl(check(Z3.mk_fpa_numeral_double(contextPtr, value, sort.ptr)));
|
|
},
|
|
|
|
NaN(sort: FPSort<Name>): FPNum<Name> {
|
|
return new FPNumImpl(check(Z3.mk_fpa_nan(contextPtr, sort.ptr)));
|
|
},
|
|
|
|
inf(sort: FPSort<Name>, negative: boolean = false): FPNum<Name> {
|
|
return new FPNumImpl(check(Z3.mk_fpa_inf(contextPtr, sort.ptr, negative)));
|
|
},
|
|
|
|
zero(sort: FPSort<Name>, negative: boolean = false): FPNum<Name> {
|
|
return new FPNumImpl(check(Z3.mk_fpa_zero(contextPtr, sort.ptr, negative)));
|
|
},
|
|
};
|
|
|
|
const FloatRM = {
|
|
sort(): FPRMSort<Name> {
|
|
return new FPRMSortImpl(Z3.mk_fpa_rounding_mode_sort(contextPtr));
|
|
},
|
|
|
|
RNE(): FPRM<Name> {
|
|
return new FPRMImpl(check(Z3.mk_fpa_rne(contextPtr)));
|
|
},
|
|
|
|
RNA(): FPRM<Name> {
|
|
return new FPRMImpl(check(Z3.mk_fpa_rna(contextPtr)));
|
|
},
|
|
|
|
RTP(): FPRM<Name> {
|
|
return new FPRMImpl(check(Z3.mk_fpa_rtp(contextPtr)));
|
|
},
|
|
|
|
RTN(): FPRM<Name> {
|
|
return new FPRMImpl(check(Z3.mk_fpa_rtn(contextPtr)));
|
|
},
|
|
|
|
RTZ(): FPRM<Name> {
|
|
return new FPRMImpl(check(Z3.mk_fpa_rtz(contextPtr)));
|
|
},
|
|
};
|
|
|
|
const String = {
|
|
sort(): SeqSort<Name> {
|
|
return new SeqSortImpl(Z3.mk_string_sort(contextPtr));
|
|
},
|
|
|
|
const(name: string): Seq<Name> {
|
|
return new SeqImpl(check(Z3.mk_const(contextPtr, _toSymbol(name), String.sort().ptr)));
|
|
},
|
|
|
|
consts(names: string | string[]): Seq<Name>[] {
|
|
if (typeof names === 'string') {
|
|
names = names.split(' ');
|
|
}
|
|
return names.map(name => String.const(name));
|
|
},
|
|
|
|
val(value: string): Seq<Name> {
|
|
return new SeqImpl(check(Z3.mk_string(contextPtr, value)));
|
|
},
|
|
};
|
|
|
|
const Seq = {
|
|
sort<ElemSort extends Sort<Name>>(elemSort: ElemSort): SeqSort<Name, ElemSort> {
|
|
return new SeqSortImpl<ElemSort>(Z3.mk_seq_sort(contextPtr, elemSort.ptr));
|
|
},
|
|
|
|
empty<ElemSort extends Sort<Name>>(elemSort: ElemSort): Seq<Name, ElemSort> {
|
|
return new SeqImpl<ElemSort>(check(Z3.mk_seq_empty(contextPtr, Seq.sort(elemSort).ptr)));
|
|
},
|
|
|
|
unit<ElemSort extends Sort<Name>>(elem: Expr<Name>): Seq<Name, ElemSort> {
|
|
return new SeqImpl<ElemSort>(check(Z3.mk_seq_unit(contextPtr, elem.ast)));
|
|
},
|
|
};
|
|
|
|
const Re = {
|
|
sort<SeqSortRef extends SeqSort<Name>>(seqSort: SeqSortRef): ReSort<Name, SeqSortRef> {
|
|
return new ReSortImpl<SeqSortRef>(Z3.mk_re_sort(contextPtr, seqSort.ptr));
|
|
},
|
|
|
|
toRe(seq: Seq<Name> | string): Re<Name> {
|
|
const seqExpr = isSeq(seq) ? seq : String.val(seq);
|
|
return new ReImpl(check(Z3.mk_seq_to_re(contextPtr, seqExpr.ast)));
|
|
},
|
|
};
|
|
|
|
const Array = {
|
|
sort<DomainSort extends NonEmptySortArray<Name>, RangeSort extends AnySort<Name>>(
|
|
...sig: [...DomainSort, RangeSort]
|
|
): SMTArraySort<Name, DomainSort, RangeSort> {
|
|
const arity = sig.length - 1;
|
|
const r = sig[arity];
|
|
const d = sig[0];
|
|
if (arity === 1) {
|
|
return new ArraySortImpl(Z3.mk_array_sort(contextPtr, d.ptr, r.ptr));
|
|
}
|
|
const dom = sig.slice(0, arity);
|
|
return new ArraySortImpl(
|
|
Z3.mk_array_sort_n(
|
|
contextPtr,
|
|
dom.map(s => s.ptr),
|
|
r.ptr,
|
|
),
|
|
);
|
|
},
|
|
const<DomainSort extends NonEmptySortArray<Name>, RangeSort extends AnySort<Name>>(
|
|
name: string,
|
|
...sig: [...DomainSort, RangeSort]
|
|
): SMTArray<Name, DomainSort, RangeSort> {
|
|
return new ArrayImpl<DomainSort, RangeSort>(
|
|
check(Z3.mk_const(contextPtr, _toSymbol(name), Array.sort(...sig).ptr)),
|
|
);
|
|
},
|
|
consts<DomainSort extends NonEmptySortArray<Name>, RangeSort extends AnySort<Name>>(
|
|
names: string | string[],
|
|
...sig: [...DomainSort, RangeSort]
|
|
): SMTArray<Name, DomainSort, RangeSort>[] {
|
|
if (typeof names === 'string') {
|
|
names = names.split(' ');
|
|
}
|
|
return names.map(name => Array.const(name, ...sig));
|
|
},
|
|
K<DomainSort extends AnySort<Name>, RangeSort extends AnySort<Name>>(
|
|
domain: DomainSort,
|
|
value: SortToExprMap<RangeSort, Name>,
|
|
): SMTArray<Name, [DomainSort], RangeSort> {
|
|
return new ArrayImpl<[DomainSort], RangeSort>(check(Z3.mk_const_array(contextPtr, domain.ptr, value.ptr)));
|
|
},
|
|
};
|
|
const Set = {
|
|
// reference: https://z3prover.github.io/api/html/namespacez3py.html#a545f894afeb24caa1b88b7f2a324ee7e
|
|
sort<ElemSort extends AnySort<Name>>(sort: ElemSort): SMTSetSort<Name, ElemSort> {
|
|
return Array.sort(sort, Bool.sort());
|
|
},
|
|
const<ElemSort extends AnySort<Name>>(name: string, sort: ElemSort): SMTSet<Name, ElemSort> {
|
|
return new SetImpl<ElemSort>(
|
|
check(Z3.mk_const(contextPtr, _toSymbol(name), Array.sort(sort, Bool.sort()).ptr)),
|
|
);
|
|
},
|
|
consts<ElemSort extends AnySort<Name>>(names: string | string[], sort: ElemSort): SMTSet<Name, ElemSort>[] {
|
|
if (typeof names === 'string') {
|
|
names = names.split(' ');
|
|
}
|
|
return names.map(name => Set.const(name, sort));
|
|
},
|
|
empty<ElemSort extends AnySort<Name>>(sort: ElemSort): SMTSet<Name, ElemSort> {
|
|
return EmptySet(sort);
|
|
},
|
|
val<ElemSort extends AnySort<Name>>(
|
|
values: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>[],
|
|
sort: ElemSort,
|
|
): SMTSet<Name, ElemSort> {
|
|
var result = EmptySet(sort);
|
|
for (const value of values) {
|
|
result = SetAdd(result, value);
|
|
}
|
|
return result;
|
|
},
|
|
};
|
|
|
|
const Datatype = Object.assign(
|
|
(name: string): DatatypeImpl => {
|
|
return new DatatypeImpl(ctx, name);
|
|
},
|
|
{
|
|
createDatatypes(...datatypes: DatatypeImpl[]): DatatypeSortImpl[] {
|
|
return createDatatypes(...datatypes);
|
|
},
|
|
},
|
|
);
|
|
|
|
////////////////
|
|
// Operations //
|
|
////////////////
|
|
function If(condition: Probe<Name>, onTrue: Tactic<Name>, onFalse: Tactic<Name>): Tactic<Name>;
|
|
function If<OnTrueRef extends CoercibleToExpr<Name>, OnFalseRef extends CoercibleToExpr<Name>>(
|
|
condition: Bool<Name> | boolean,
|
|
onTrue: OnTrueRef,
|
|
onFalse: OnFalseRef,
|
|
): CoercibleFromMap<OnTrueRef | OnFalseRef, Name>;
|
|
function If(
|
|
condition: Bool<Name> | Probe<Name> | boolean,
|
|
onTrue: CoercibleToExpr<Name> | Tactic<Name>,
|
|
onFalse: CoercibleToExpr<Name> | Tactic<Name>,
|
|
): Expr<Name> | Tactic<Name> {
|
|
if (isProbe(condition) && isTactic(onTrue) && isTactic(onFalse)) {
|
|
return Cond(condition, onTrue, onFalse);
|
|
}
|
|
assert(!isProbe(condition) && !isTactic(onTrue) && !isTactic(onFalse), 'Mixed expressions and goals');
|
|
if (typeof condition === 'boolean') {
|
|
condition = Bool.val(condition);
|
|
}
|
|
onTrue = from(onTrue);
|
|
onFalse = from(onFalse);
|
|
return _toExpr(check(Z3.mk_ite(contextPtr, condition.ptr, onTrue.ast, onFalse.ast)));
|
|
}
|
|
|
|
function Distinct(...exprs: CoercibleToExpr<Name>[]): Bool<Name> {
|
|
assert(exprs.length > 0, "Can't make Distinct ouf of nothing");
|
|
|
|
return new BoolImpl(
|
|
check(
|
|
Z3.mk_distinct(
|
|
contextPtr,
|
|
exprs.map(expr => {
|
|
expr = from(expr);
|
|
_assertContext(expr);
|
|
return expr.ast;
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
function Const<S extends Sort<Name>>(name: string, sort: S): SortToExprMap<S, Name> {
|
|
_assertContext(sort);
|
|
return _toExpr(check(Z3.mk_const(contextPtr, _toSymbol(name), sort.ptr))) as SortToExprMap<S, Name>;
|
|
}
|
|
|
|
function Consts<S extends Sort<Name>>(names: string | string[], sort: S): SortToExprMap<S, Name>[] {
|
|
_assertContext(sort);
|
|
if (typeof names === 'string') {
|
|
names = names.split(' ');
|
|
}
|
|
return names.map(name => Const(name, sort));
|
|
}
|
|
|
|
function FreshConst<S extends Sort<Name>>(sort: S, prefix: string = 'c'): SortToExprMap<S, Name> {
|
|
_assertContext(sort);
|
|
return _toExpr(Z3.mk_fresh_const(sort.ctx.ptr, prefix, sort.ptr)) as SortToExprMap<S, Name>;
|
|
}
|
|
|
|
function Var<S extends Sort<Name>>(idx: number, sort: S): SortToExprMap<S, Name> {
|
|
_assertContext(sort);
|
|
return _toExpr(Z3.mk_bound(sort.ctx.ptr, idx, sort.ptr)) as SortToExprMap<S, Name>;
|
|
}
|
|
|
|
function Implies(a: Bool<Name> | boolean, b: Bool<Name> | boolean): Bool<Name> {
|
|
a = from(a) as Bool<Name>;
|
|
b = from(b) as Bool<Name>;
|
|
_assertContext(a, b);
|
|
return new BoolImpl(check(Z3.mk_implies(contextPtr, a.ptr, b.ptr)));
|
|
}
|
|
|
|
function Iff(a: Bool<Name> | boolean, b: Bool<Name> | boolean): Bool<Name> {
|
|
a = from(a) as Bool<Name>;
|
|
b = from(b) as Bool<Name>;
|
|
_assertContext(a, b);
|
|
return new BoolImpl(check(Z3.mk_iff(contextPtr, a.ptr, b.ptr)));
|
|
}
|
|
|
|
function Eq(a: CoercibleToExpr<Name>, b: CoercibleToExpr<Name>): Bool<Name> {
|
|
a = from(a);
|
|
b = from(b);
|
|
_assertContext(a, b);
|
|
return a.eq(b);
|
|
}
|
|
|
|
function Xor(a: Bool<Name> | boolean, b: Bool<Name> | boolean): Bool<Name> {
|
|
a = from(a) as Bool<Name>;
|
|
b = from(b) as Bool<Name>;
|
|
_assertContext(a, b);
|
|
return new BoolImpl(check(Z3.mk_xor(contextPtr, a.ptr, b.ptr)));
|
|
}
|
|
|
|
function Not(a: Probe<Name>): Probe<Name>;
|
|
function Not(a: Bool<Name> | boolean): Bool<Name>;
|
|
function Not(a: Bool<Name> | boolean | Probe<Name>): Bool<Name> | Probe<Name> {
|
|
if (typeof a === 'boolean') {
|
|
a = from(a);
|
|
}
|
|
_assertContext(a);
|
|
if (isProbe(a)) {
|
|
return new ProbeImpl(check(Z3.probe_not(contextPtr, a.ptr)));
|
|
}
|
|
return new BoolImpl(check(Z3.mk_not(contextPtr, a.ptr)));
|
|
}
|
|
|
|
function And(): Bool<Name>;
|
|
function And(vector: AstVector<Name, Bool<Name>>): Bool<Name>;
|
|
function And(...args: (Bool<Name> | boolean)[]): Bool<Name>;
|
|
function And(...args: Probe<Name>[]): Probe<Name>;
|
|
function And(
|
|
...args: (AstVector<Name, Bool<Name>> | Probe<Name> | Bool<Name> | boolean)[]
|
|
): Bool<Name> | Probe<Name> {
|
|
if (args.length == 1 && args[0] instanceof ctx.AstVector) {
|
|
args = [...args[0].values()];
|
|
assert(allSatisfy(args, isBool) ?? true, 'AstVector containing not bools');
|
|
}
|
|
|
|
const allProbes = allSatisfy(args, isProbe) ?? false;
|
|
if (allProbes) {
|
|
return _probeNary(Z3.probe_and, args as [Probe<Name>, ...Probe<Name>[]]);
|
|
} else {
|
|
const castArgs = args.map(from) as Bool<Name>[];
|
|
_assertContext(...castArgs);
|
|
return new BoolImpl(
|
|
check(
|
|
Z3.mk_and(
|
|
contextPtr,
|
|
castArgs.map(arg => arg.ptr),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
function Or(): Bool<Name>;
|
|
function Or(vector: AstVector<Name, Bool<Name>>): Bool<Name>;
|
|
function Or(...args: (Bool<Name> | boolean)[]): Bool<Name>;
|
|
function Or(...args: Probe<Name>[]): Probe<Name>;
|
|
function Or(
|
|
...args: (AstVector<Name, Bool<Name>> | Probe<Name> | Bool<Name> | boolean)[]
|
|
): Bool<Name> | Probe<Name> {
|
|
if (args.length == 1 && args[0] instanceof ctx.AstVector) {
|
|
args = [...args[0].values()];
|
|
assert(allSatisfy(args, isBool) ?? true, 'AstVector containing not bools');
|
|
}
|
|
|
|
const allProbes = allSatisfy(args, isProbe) ?? false;
|
|
if (allProbes) {
|
|
return _probeNary(Z3.probe_or, args as [Probe<Name>, ...Probe<Name>[]]);
|
|
} else {
|
|
const castArgs = args.map(from) as Bool<Name>[];
|
|
_assertContext(...castArgs);
|
|
return new BoolImpl(
|
|
check(
|
|
Z3.mk_or(
|
|
contextPtr,
|
|
castArgs.map(arg => arg.ptr),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
function PbEq(args: [Bool<Name>, ...Bool<Name>[]], coeffs: [number, ...number[]], k: number): Bool<Name> {
|
|
_assertContext(...args);
|
|
if (args.length !== coeffs.length) {
|
|
throw new Error('Number of arguments and coefficients must match');
|
|
}
|
|
return new BoolImpl(
|
|
check(
|
|
Z3.mk_pbeq(
|
|
contextPtr,
|
|
args.map(arg => arg.ast),
|
|
coeffs,
|
|
k,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
function PbGe(args: [Bool<Name>, ...Bool<Name>[]], coeffs: [number, ...number[]], k: number): Bool<Name> {
|
|
_assertContext(...args);
|
|
if (args.length !== coeffs.length) {
|
|
throw new Error('Number of arguments and coefficients must match');
|
|
}
|
|
return new BoolImpl(
|
|
check(
|
|
Z3.mk_pbge(
|
|
contextPtr,
|
|
args.map(arg => arg.ast),
|
|
coeffs,
|
|
k,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
function PbLe(args: [Bool<Name>, ...Bool<Name>[]], coeffs: [number, ...number[]], k: number): Bool<Name> {
|
|
_assertContext(...args);
|
|
if (args.length !== coeffs.length) {
|
|
throw new Error('Number of arguments and coefficients must match');
|
|
}
|
|
return new BoolImpl(
|
|
check(
|
|
Z3.mk_pble(
|
|
contextPtr,
|
|
args.map(arg => arg.ast),
|
|
coeffs,
|
|
k,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
function AtMost(args: [Bool<Name>, ...Bool<Name>[]], k: number): Bool<Name> {
|
|
_assertContext(...args);
|
|
return new BoolImpl(
|
|
check(
|
|
Z3.mk_atmost(
|
|
contextPtr,
|
|
args.map(arg => arg.ast),
|
|
k,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
function AtLeast(args: [Bool<Name>, ...Bool<Name>[]], k: number): Bool<Name> {
|
|
_assertContext(...args);
|
|
return new BoolImpl(
|
|
check(
|
|
Z3.mk_atleast(
|
|
contextPtr,
|
|
args.map(arg => arg.ast),
|
|
k,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
function ForAll<QVarSorts extends NonEmptySortArray<Name>>(
|
|
quantifiers: ArrayIndexType<Name, QVarSorts>,
|
|
body: Bool<Name>,
|
|
weight: number = 1,
|
|
): NonLambdaQuantifierImpl<QVarSorts> {
|
|
// Verify all quantifiers are constants
|
|
if (!allSatisfy(quantifiers, isConst)) {
|
|
throw new Error('Quantifier variables must be constants');
|
|
}
|
|
|
|
return new NonLambdaQuantifierImpl<QVarSorts>(
|
|
check(
|
|
Z3.mk_quantifier_const_ex(
|
|
contextPtr,
|
|
true,
|
|
weight,
|
|
_toSymbol(''),
|
|
_toSymbol(''),
|
|
quantifiers.map(q => q.ptr as unknown as Z3_app), // The earlier check verifies these are all apps
|
|
[],
|
|
[],
|
|
body.ptr,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
function Exists<QVarSorts extends NonEmptySortArray<Name>>(
|
|
quantifiers: ArrayIndexType<Name, QVarSorts>,
|
|
body: Bool<Name>,
|
|
weight: number = 1,
|
|
): NonLambdaQuantifierImpl<QVarSorts> {
|
|
// Verify all quantifiers are constants
|
|
if (!allSatisfy(quantifiers, isConst)) {
|
|
throw new Error('Quantifier variables must be constants');
|
|
}
|
|
|
|
return new NonLambdaQuantifierImpl<QVarSorts>(
|
|
check(
|
|
Z3.mk_quantifier_const_ex(
|
|
contextPtr,
|
|
false,
|
|
weight,
|
|
_toSymbol(''),
|
|
_toSymbol(''),
|
|
quantifiers.map(q => q.ptr as unknown as Z3_app), // The earlier check verifies these are all apps
|
|
[],
|
|
[],
|
|
body.ptr,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
function Lambda<DomainSort extends NonEmptySortArray<Name>, RangeSort extends Sort<Name>>(
|
|
quantifiers: ArrayIndexType<Name, DomainSort>,
|
|
expr: SortToExprMap<RangeSort, Name>,
|
|
): LambdaImpl<any, RangeSort> {
|
|
// TODO(walden): For some reason LambdaImpl<DomainSort, RangeSort> leads to type issues
|
|
// and Typescript won't build. I'm not sure why since the types seem to all match
|
|
// up. For now, we just use any for the domain sort
|
|
|
|
// Verify all quantifiers are constants
|
|
if (!allSatisfy(quantifiers, isConst)) {
|
|
throw new Error('Quantifier variables must be constants');
|
|
}
|
|
|
|
return new LambdaImpl<DomainSort, RangeSort>(
|
|
check(
|
|
Z3.mk_lambda_const(
|
|
contextPtr,
|
|
quantifiers.map(q => q.ptr as unknown as Z3_app),
|
|
expr.ptr,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
function ToReal(expr: Arith<Name> | bigint): Arith<Name> {
|
|
expr = from(expr) as Arith<Name>;
|
|
_assertContext(expr);
|
|
assert(isInt(expr), 'Int expression expected');
|
|
return new ArithImpl(check(Z3.mk_int2real(contextPtr, expr.ast)));
|
|
}
|
|
|
|
function ToInt(expr: Arith<Name> | number | CoercibleRational | string): Arith<Name> {
|
|
if (!isExpr(expr)) {
|
|
expr = Real.val(expr);
|
|
}
|
|
_assertContext(expr);
|
|
assert(isReal(expr), 'Real expression expected');
|
|
return new ArithImpl(check(Z3.mk_real2int(contextPtr, expr.ast)));
|
|
}
|
|
|
|
function IsInt(expr: Arith<Name> | number | CoercibleRational | string): Bool<Name> {
|
|
if (!isExpr(expr)) {
|
|
expr = Real.val(expr);
|
|
}
|
|
_assertContext(expr);
|
|
assert(isReal(expr), 'Real expression expected');
|
|
return new BoolImpl(check(Z3.mk_is_int(contextPtr, expr.ast)));
|
|
}
|
|
|
|
function Sqrt(a: Arith<Name> | number | bigint | string | CoercibleRational): Arith<Name> {
|
|
if (!isExpr(a)) {
|
|
a = Real.val(a);
|
|
}
|
|
return a.pow('1/2');
|
|
}
|
|
|
|
function Cbrt(a: Arith<Name> | number | bigint | string | CoercibleRational): Arith<Name> {
|
|
if (!isExpr(a)) {
|
|
a = Real.val(a);
|
|
}
|
|
return a.pow('1/3');
|
|
}
|
|
|
|
function BV2Int<Bits extends number>(a: BitVec<Bits, Name>, isSigned: boolean): Arith<Name> {
|
|
_assertContext(a);
|
|
return new ArithImpl(check(Z3.mk_bv2int(contextPtr, a.ast, isSigned)));
|
|
}
|
|
|
|
function Int2BV<Bits extends number>(a: Arith<Name> | bigint | number, bits: Bits): BitVec<Bits, Name> {
|
|
if (isArith(a)) {
|
|
assert(isInt(a), 'parameter must be an integer');
|
|
} else {
|
|
assert(typeof a !== 'number' || Number.isSafeInteger(a), 'parameter must not have decimal places');
|
|
a = Int.val(a);
|
|
}
|
|
return new BitVecImpl<Bits>(check(Z3.mk_int2bv(contextPtr, bits, a.ast)));
|
|
}
|
|
|
|
function Concat<Bits extends number>(...bitvecs: BitVec<Bits, Name>[]): BitVec<Bits, Name> {
|
|
_assertContext(...bitvecs);
|
|
return bitvecs.reduce((prev, curr) => new BitVecImpl<Bits>(check(Z3.mk_concat(contextPtr, prev.ast, curr.ast))));
|
|
}
|
|
|
|
function Cond(probe: Probe<Name>, onTrue: Tactic<Name>, onFalse: Tactic<Name>): Tactic<Name> {
|
|
_assertContext(probe, onTrue, onFalse);
|
|
return new TacticImpl(check(Z3.tactic_cond(contextPtr, probe.ptr, onTrue.ptr, onFalse.ptr)));
|
|
}
|
|
|
|
function _toTactic(t: Tactic<Name> | string): Tactic<Name> {
|
|
return typeof t === 'string' ? new TacticImpl(t) : t;
|
|
}
|
|
|
|
function AndThen(
|
|
t1: Tactic<Name> | string,
|
|
t2: Tactic<Name> | string,
|
|
...ts: (Tactic<Name> | string)[]
|
|
): Tactic<Name> {
|
|
let result = _toTactic(t1);
|
|
let current = _toTactic(t2);
|
|
_assertContext(result, current);
|
|
result = new TacticImpl(check(Z3.tactic_and_then(contextPtr, result.ptr, current.ptr)));
|
|
|
|
for (const t of ts) {
|
|
current = _toTactic(t);
|
|
_assertContext(result, current);
|
|
result = new TacticImpl(check(Z3.tactic_and_then(contextPtr, result.ptr, current.ptr)));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function OrElse(
|
|
t1: Tactic<Name> | string,
|
|
t2: Tactic<Name> | string,
|
|
...ts: (Tactic<Name> | string)[]
|
|
): Tactic<Name> {
|
|
let result = _toTactic(t1);
|
|
let current = _toTactic(t2);
|
|
_assertContext(result, current);
|
|
result = new TacticImpl(check(Z3.tactic_or_else(contextPtr, result.ptr, current.ptr)));
|
|
|
|
for (const t of ts) {
|
|
current = _toTactic(t);
|
|
_assertContext(result, current);
|
|
result = new TacticImpl(check(Z3.tactic_or_else(contextPtr, result.ptr, current.ptr)));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
const UINT_MAX = 4294967295;
|
|
|
|
function Repeat(t: Tactic<Name> | string, max?: number): Tactic<Name> {
|
|
const tactic = _toTactic(t);
|
|
_assertContext(tactic);
|
|
const maxVal = max !== undefined ? max : UINT_MAX;
|
|
return new TacticImpl(check(Z3.tactic_repeat(contextPtr, tactic.ptr, maxVal)));
|
|
}
|
|
|
|
function TryFor(t: Tactic<Name> | string, ms: number): Tactic<Name> {
|
|
const tactic = _toTactic(t);
|
|
_assertContext(tactic);
|
|
return new TacticImpl(check(Z3.tactic_try_for(contextPtr, tactic.ptr, ms)));
|
|
}
|
|
|
|
function When(p: Probe<Name>, t: Tactic<Name> | string): Tactic<Name> {
|
|
const tactic = _toTactic(t);
|
|
_assertContext(p, tactic);
|
|
return new TacticImpl(check(Z3.tactic_when(contextPtr, p.ptr, tactic.ptr)));
|
|
}
|
|
|
|
function Skip(): Tactic<Name> {
|
|
return new TacticImpl(check(Z3.tactic_skip(contextPtr)));
|
|
}
|
|
|
|
function Fail(): Tactic<Name> {
|
|
return new TacticImpl(check(Z3.tactic_fail(contextPtr)));
|
|
}
|
|
|
|
function FailIf(p: Probe<Name>): Tactic<Name> {
|
|
_assertContext(p);
|
|
return new TacticImpl(check(Z3.tactic_fail_if(contextPtr, p.ptr)));
|
|
}
|
|
|
|
function ParOr(...tactics: (Tactic<Name> | string)[]): Tactic<Name> {
|
|
assert(tactics.length > 0, 'ParOr requires at least one tactic');
|
|
const tacticImpls = tactics.map(t => _toTactic(t));
|
|
_assertContext(...tacticImpls);
|
|
const tacticPtrs = tacticImpls.map(t => t.ptr);
|
|
return new TacticImpl(check(Z3.tactic_par_or(contextPtr, tacticPtrs)));
|
|
}
|
|
|
|
function ParAndThen(t1: Tactic<Name> | string, t2: Tactic<Name> | string): Tactic<Name> {
|
|
const tactic1 = _toTactic(t1);
|
|
const tactic2 = _toTactic(t2);
|
|
_assertContext(tactic1, tactic2);
|
|
return new TacticImpl(check(Z3.tactic_par_and_then(contextPtr, tactic1.ptr, tactic2.ptr)));
|
|
}
|
|
|
|
function With(t: Tactic<Name> | string, params: Record<string, any>): Tactic<Name> {
|
|
const tactic = _toTactic(t);
|
|
_assertContext(tactic);
|
|
// Convert params to Z3_params
|
|
const z3params = check(Z3.mk_params(contextPtr));
|
|
Z3.params_inc_ref(contextPtr, z3params);
|
|
try {
|
|
for (const [key, value] of Object.entries(params)) {
|
|
const sym = _toSymbol(key);
|
|
if (typeof value === 'boolean') {
|
|
Z3.params_set_bool(contextPtr, z3params, sym, value);
|
|
} else if (typeof value === 'number') {
|
|
if (Number.isInteger(value)) {
|
|
Z3.params_set_uint(contextPtr, z3params, sym, value);
|
|
} else {
|
|
Z3.params_set_double(contextPtr, z3params, sym, value);
|
|
}
|
|
} else if (typeof value === 'string') {
|
|
Z3.params_set_symbol(contextPtr, z3params, sym, _toSymbol(value));
|
|
} else {
|
|
throw new Error(`Unsupported parameter type for ${key}`);
|
|
}
|
|
}
|
|
const result = new TacticImpl(check(Z3.tactic_using_params(contextPtr, tactic.ptr, z3params)));
|
|
return result;
|
|
} finally {
|
|
Z3.params_dec_ref(contextPtr, z3params);
|
|
}
|
|
}
|
|
|
|
function LT(a: Arith<Name>, b: CoercibleToArith<Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_lt(contextPtr, a.ast, a.sort.cast(b).ast)));
|
|
}
|
|
|
|
function GT(a: Arith<Name>, b: CoercibleToArith<Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_gt(contextPtr, a.ast, a.sort.cast(b).ast)));
|
|
}
|
|
|
|
function LE(a: Arith<Name>, b: CoercibleToArith<Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_le(contextPtr, a.ast, a.sort.cast(b).ast)));
|
|
}
|
|
|
|
function GE(a: Arith<Name>, b: CoercibleToArith<Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_ge(contextPtr, a.ast, a.sort.cast(b).ast)));
|
|
}
|
|
|
|
function ULT<Bits extends number>(a: BitVec<Bits, Name>, b: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvult(contextPtr, a.ast, a.sort.cast(b).ast)));
|
|
}
|
|
|
|
function UGT<Bits extends number>(a: BitVec<Bits, Name>, b: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvugt(contextPtr, a.ast, a.sort.cast(b).ast)));
|
|
}
|
|
|
|
function ULE<Bits extends number>(a: BitVec<Bits, Name>, b: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvule(contextPtr, a.ast, a.sort.cast(b).ast)));
|
|
}
|
|
|
|
function UGE<Bits extends number>(a: BitVec<Bits, Name>, b: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvuge(contextPtr, a.ast, a.sort.cast(b).ast)));
|
|
}
|
|
|
|
function SLT<Bits extends number>(a: BitVec<Bits, Name>, b: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvslt(contextPtr, a.ast, a.sort.cast(b).ast)));
|
|
}
|
|
|
|
function SGT<Bits extends number>(a: BitVec<Bits, Name>, b: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvsgt(contextPtr, a.ast, a.sort.cast(b).ast)));
|
|
}
|
|
|
|
function SLE<Bits extends number>(a: BitVec<Bits, Name>, b: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvsle(contextPtr, a.ast, a.sort.cast(b).ast)));
|
|
}
|
|
|
|
function SGE<Bits extends number>(a: BitVec<Bits, Name>, b: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvsge(contextPtr, a.ast, a.sort.cast(b).ast)));
|
|
}
|
|
|
|
function Extract<Bits extends number>(hi: number, lo: number, val: BitVec<Bits, Name>): BitVec<number, Name> {
|
|
return new BitVecImpl<number>(check(Z3.mk_extract(contextPtr, hi, lo, val.ast)));
|
|
}
|
|
|
|
function Select<DomainSort extends NonEmptySortArray<Name>, RangeSort extends Sort<Name>>(
|
|
array: SMTArray<Name, DomainSort, RangeSort>,
|
|
...indices: CoercibleToArrayIndexType<Name, DomainSort>
|
|
): SortToExprMap<RangeSort, Name> {
|
|
const args = indices.map((arg, i) => array.domain_n(i).cast(arg as any));
|
|
if (args.length === 1) {
|
|
return _toExpr(check(Z3.mk_select(contextPtr, array.ast, args[0].ast))) as SortToExprMap<RangeSort, Name>;
|
|
}
|
|
const _args = args.map(arg => arg.ast);
|
|
return _toExpr(check(Z3.mk_select_n(contextPtr, array.ast, _args))) as SortToExprMap<RangeSort, Name>;
|
|
}
|
|
|
|
function Store<DomainSort extends NonEmptySortArray<Name>, RangeSort extends Sort<Name>>(
|
|
array: SMTArray<Name, DomainSort, RangeSort>,
|
|
...indicesAndValue: [
|
|
...CoercibleToArrayIndexType<Name, DomainSort>,
|
|
CoercibleToMap<SortToExprMap<RangeSort, Name>, Name>,
|
|
]
|
|
): SMTArray<Name, DomainSort, RangeSort> {
|
|
const args = indicesAndValue.map((arg, i) => {
|
|
if (i === indicesAndValue.length - 1) {
|
|
return array.range().cast(arg as any) as SortToExprMap<RangeSort, Name>;
|
|
}
|
|
return array.domain_n(i).cast(arg as any);
|
|
});
|
|
if (args.length <= 1) {
|
|
throw new Error('Array store requires both index and value arguments');
|
|
}
|
|
if (args.length === 2) {
|
|
return _toExpr(check(Z3.mk_store(contextPtr, array.ast, args[0].ast, args[1].ast))) as SMTArray<
|
|
Name,
|
|
DomainSort,
|
|
RangeSort
|
|
>;
|
|
}
|
|
const _idxs = args.slice(0, args.length - 1).map(arg => arg.ast);
|
|
return _toExpr(check(Z3.mk_store_n(contextPtr, array.ast, _idxs, args[args.length - 1].ast))) as SMTArray<
|
|
Name,
|
|
DomainSort,
|
|
RangeSort
|
|
>;
|
|
}
|
|
|
|
/**
|
|
* Create array extensionality index given two arrays with the same sort.
|
|
* The meaning is given by the axiom:
|
|
* (=> (= (select A (array-ext A B)) (select B (array-ext A B))) (= A B))
|
|
* Two arrays are equal if and only if they are equal on the index returned by this function.
|
|
*/
|
|
function Ext<DomainSort extends NonEmptySortArray<Name>, RangeSort extends Sort<Name>>(
|
|
a: SMTArray<Name, DomainSort, RangeSort>,
|
|
b: SMTArray<Name, DomainSort, RangeSort>,
|
|
): SortToExprMap<DomainSort[0], Name> {
|
|
return _toExpr(check(Z3.mk_array_ext(contextPtr, a.ast, b.ast))) as SortToExprMap<DomainSort[0], Name>;
|
|
}
|
|
|
|
function SetUnion<ElemSort extends AnySort<Name>>(...args: SMTSet<Name, ElemSort>[]): SMTSet<Name, ElemSort> {
|
|
return new SetImpl<ElemSort>(
|
|
check(
|
|
Z3.mk_set_union(
|
|
contextPtr,
|
|
args.map(arg => arg.ast),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
function SetIntersect<ElemSort extends AnySort<Name>>(...args: SMTSet<Name, ElemSort>[]): SMTSet<Name, ElemSort> {
|
|
return new SetImpl<ElemSort>(
|
|
check(
|
|
Z3.mk_set_intersect(
|
|
contextPtr,
|
|
args.map(arg => arg.ast),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
function SetDifference<ElemSort extends AnySort<Name>>(
|
|
a: SMTSet<Name, ElemSort>,
|
|
b: SMTSet<Name, ElemSort>,
|
|
): SMTSet<Name, ElemSort> {
|
|
return new SetImpl<ElemSort>(check(Z3.mk_set_difference(contextPtr, a.ast, b.ast)));
|
|
}
|
|
|
|
function SetAdd<ElemSort extends AnySort<Name>>(
|
|
set: SMTSet<Name, ElemSort>,
|
|
elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>,
|
|
): SMTSet<Name, ElemSort> {
|
|
const arg = set.elemSort().cast(elem as any);
|
|
return new SetImpl<ElemSort>(check(Z3.mk_set_add(contextPtr, set.ast, arg.ast)));
|
|
}
|
|
function SetDel<ElemSort extends AnySort<Name>>(
|
|
set: SMTSet<Name, ElemSort>,
|
|
elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>,
|
|
): SMTSet<Name, ElemSort> {
|
|
const arg = set.elemSort().cast(elem as any);
|
|
return new SetImpl<ElemSort>(check(Z3.mk_set_del(contextPtr, set.ast, arg.ast)));
|
|
}
|
|
function SetComplement<ElemSort extends AnySort<Name>>(set: SMTSet<Name, ElemSort>): SMTSet<Name, ElemSort> {
|
|
return new SetImpl<ElemSort>(check(Z3.mk_set_complement(contextPtr, set.ast)));
|
|
}
|
|
|
|
function EmptySet<ElemSort extends AnySort<Name>>(sort: ElemSort): SMTSet<Name, ElemSort> {
|
|
return new SetImpl<ElemSort>(check(Z3.mk_empty_set(contextPtr, sort.ptr)));
|
|
}
|
|
function FullSet<ElemSort extends AnySort<Name>>(sort: ElemSort): SMTSet<Name, ElemSort> {
|
|
return new SetImpl<ElemSort>(check(Z3.mk_full_set(contextPtr, sort.ptr)));
|
|
}
|
|
function isMember<ElemSort extends AnySort<Name>>(
|
|
elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>,
|
|
set: SMTSet<Name, ElemSort>,
|
|
): Bool<Name> {
|
|
const arg = set.elemSort().cast(elem as any);
|
|
return new BoolImpl(check(Z3.mk_set_member(contextPtr, arg.ast, set.ast)));
|
|
}
|
|
function isSubset<ElemSort extends AnySort<Name>>(
|
|
a: SMTSet<Name, ElemSort>,
|
|
b: SMTSet<Name, ElemSort>,
|
|
): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_set_subset(contextPtr, a.ast, b.ast)));
|
|
}
|
|
|
|
//////////////////////
|
|
// Regular Expressions
|
|
//////////////////////
|
|
|
|
function InRe(seq: Seq<Name> | string, re: Re<Name>): Bool<Name> {
|
|
const seqExpr = isSeq(seq) ? seq : String.val(seq);
|
|
return new BoolImpl(check(Z3.mk_seq_in_re(contextPtr, seqExpr.ast, re.ast)));
|
|
}
|
|
|
|
function Union<SeqSortRef extends SeqSort<Name>>(...res: Re<Name, SeqSortRef>[]): Re<Name, SeqSortRef> {
|
|
if (res.length === 0) {
|
|
throw new Error('Union requires at least one argument');
|
|
}
|
|
if (res.length === 1) {
|
|
return res[0];
|
|
}
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_union(contextPtr, res.map(r => r.ast))));
|
|
}
|
|
|
|
function Intersect<SeqSortRef extends SeqSort<Name>>(...res: Re<Name, SeqSortRef>[]): Re<Name, SeqSortRef> {
|
|
if (res.length === 0) {
|
|
throw new Error('Intersect requires at least one argument');
|
|
}
|
|
if (res.length === 1) {
|
|
return res[0];
|
|
}
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_intersect(contextPtr, res.map(r => r.ast))));
|
|
}
|
|
|
|
function ReConcat<SeqSortRef extends SeqSort<Name>>(...res: Re<Name, SeqSortRef>[]): Re<Name, SeqSortRef> {
|
|
if (res.length === 0) {
|
|
throw new Error('ReConcat requires at least one argument');
|
|
}
|
|
if (res.length === 1) {
|
|
return res[0];
|
|
}
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_concat(contextPtr, res.map(r => r.ast))));
|
|
}
|
|
|
|
function Plus<SeqSortRef extends SeqSort<Name>>(re: Re<Name, SeqSortRef>): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_plus(contextPtr, re.ast)));
|
|
}
|
|
|
|
function Star<SeqSortRef extends SeqSort<Name>>(re: Re<Name, SeqSortRef>): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_star(contextPtr, re.ast)));
|
|
}
|
|
|
|
function Option<SeqSortRef extends SeqSort<Name>>(re: Re<Name, SeqSortRef>): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_option(contextPtr, re.ast)));
|
|
}
|
|
|
|
function Complement<SeqSortRef extends SeqSort<Name>>(re: Re<Name, SeqSortRef>): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_complement(contextPtr, re.ast)));
|
|
}
|
|
|
|
function Diff<SeqSortRef extends SeqSort<Name>>(a: Re<Name, SeqSortRef>, b: Re<Name, SeqSortRef>): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_diff(contextPtr, a.ast, b.ast)));
|
|
}
|
|
|
|
function Range<SeqSortRef extends SeqSort<Name>>(lo: Seq<Name, SeqSortRef> | string, hi: Seq<Name, SeqSortRef> | string): Re<Name, SeqSortRef> {
|
|
const loSeq = isSeq(lo) ? lo : String.val(lo);
|
|
const hiSeq = isSeq(hi) ? hi : String.val(hi);
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_range(contextPtr, loSeq.ast, hiSeq.ast)));
|
|
}
|
|
|
|
function Loop<SeqSortRef extends SeqSort<Name>>(re: Re<Name, SeqSortRef>, lo: number, hi: number = 0): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_loop(contextPtr, re.ast, lo, hi)));
|
|
}
|
|
|
|
function Power<SeqSortRef extends SeqSort<Name>>(re: Re<Name, SeqSortRef>, n: number): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_power(contextPtr, re.ast, n)));
|
|
}
|
|
|
|
function AllChar<SeqSortRef extends SeqSort<Name>>(reSort: ReSort<Name, SeqSortRef>): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_allchar(contextPtr, reSort.ptr)));
|
|
}
|
|
|
|
function Empty<SeqSortRef extends SeqSort<Name>>(reSort: ReSort<Name, SeqSortRef>): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_empty(contextPtr, reSort.ptr)));
|
|
}
|
|
|
|
function Full<SeqSortRef extends SeqSort<Name>>(reSort: ReSort<Name, SeqSortRef>): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_full(contextPtr, reSort.ptr)));
|
|
}
|
|
|
|
function mkPartialOrder(sort: Sort<Name>, index: number): FuncDecl<Name> {
|
|
return new FuncDeclImpl(check(Z3.mk_partial_order(contextPtr, sort.ptr, index)));
|
|
}
|
|
|
|
function mkTransitiveClosure(f: FuncDecl<Name>): FuncDecl<Name> {
|
|
return new FuncDeclImpl(check(Z3.mk_transitive_closure(contextPtr, f.ptr)));
|
|
}
|
|
|
|
async function polynomialSubresultants(
|
|
p: Arith<Name>,
|
|
q: Arith<Name>,
|
|
x: Arith<Name>,
|
|
): Promise<AstVector<Name, Arith<Name>>> {
|
|
const result = await Z3.polynomial_subresultants(contextPtr, p.ast, q.ast, x.ast);
|
|
return new AstVectorImpl<ArithImpl>(check(result));
|
|
}
|
|
|
|
class AstImpl<Ptr extends Z3_ast> implements Ast<Name, Ptr> {
|
|
declare readonly __typename: Ast['__typename'];
|
|
readonly ctx: Context<Name>;
|
|
|
|
constructor(readonly ptr: Ptr) {
|
|
this.ctx = ctx;
|
|
const myAst = this.ast;
|
|
|
|
Z3.inc_ref(contextPtr, myAst);
|
|
cleanup.register(this, () => Z3.dec_ref(contextPtr, myAst), this);
|
|
}
|
|
|
|
get ast(): Z3_ast {
|
|
return this.ptr;
|
|
}
|
|
|
|
id() {
|
|
return Z3.get_ast_id(contextPtr, this.ast);
|
|
}
|
|
|
|
eqIdentity(other: Ast<Name>) {
|
|
_assertContext(other);
|
|
return check(Z3.is_eq_ast(contextPtr, this.ast, other.ast));
|
|
}
|
|
|
|
neqIdentity(other: Ast<Name>) {
|
|
_assertContext(other);
|
|
return !this.eqIdentity(other);
|
|
}
|
|
|
|
sexpr() {
|
|
return Z3.ast_to_string(contextPtr, this.ast);
|
|
}
|
|
|
|
hash() {
|
|
return Z3.get_ast_hash(contextPtr, this.ast);
|
|
}
|
|
|
|
toString() {
|
|
return this.sexpr();
|
|
}
|
|
}
|
|
|
|
class SolverImpl implements Solver<Name> {
|
|
declare readonly __typename: Solver['__typename'];
|
|
|
|
readonly ctx: Context<Name>;
|
|
private _ptr: Z3_solver | null;
|
|
get ptr(): Z3_solver {
|
|
_assertPtr(this._ptr);
|
|
return this._ptr;
|
|
}
|
|
|
|
constructor(ptr: Z3_solver | string = Z3.mk_solver(contextPtr)) {
|
|
this.ctx = ctx;
|
|
let myPtr: Z3_solver;
|
|
if (typeof ptr === 'string') {
|
|
myPtr = check(Z3.mk_solver_for_logic(contextPtr, _toSymbol(ptr)));
|
|
} else {
|
|
myPtr = ptr;
|
|
}
|
|
this._ptr = myPtr;
|
|
Z3.solver_inc_ref(contextPtr, myPtr);
|
|
cleanup.register(this, () => Z3.solver_dec_ref(contextPtr, myPtr), this);
|
|
}
|
|
|
|
set(key: string, value: any): void {
|
|
Z3.solver_set_params(contextPtr, this.ptr, _toParams(key, value));
|
|
}
|
|
|
|
push() {
|
|
Z3.solver_push(contextPtr, this.ptr);
|
|
}
|
|
|
|
pop(num: number = 1) {
|
|
Z3.solver_pop(contextPtr, this.ptr, num);
|
|
}
|
|
|
|
numScopes() {
|
|
return Z3.solver_get_num_scopes(contextPtr, this.ptr);
|
|
}
|
|
|
|
reset() {
|
|
Z3.solver_reset(contextPtr, this.ptr);
|
|
}
|
|
|
|
add(...exprs: (Bool<Name> | AstVector<Name, Bool<Name>>)[]) {
|
|
_flattenArgs(exprs).forEach(expr => {
|
|
_assertContext(expr);
|
|
check(Z3.solver_assert(contextPtr, this.ptr, expr.ast));
|
|
});
|
|
}
|
|
|
|
addAndTrack(expr: Bool<Name>, constant: Bool<Name> | string) {
|
|
if (typeof constant === 'string') {
|
|
constant = Bool.const(constant);
|
|
}
|
|
assert(isConst(constant), 'Provided expression that is not a constant to addAndTrack');
|
|
check(Z3.solver_assert_and_track(contextPtr, this.ptr, expr.ast, constant.ast));
|
|
}
|
|
|
|
addSimplifier(simplifier: Simplifier<Name>): void {
|
|
_assertContext(simplifier);
|
|
check(Z3.solver_add_simplifier(contextPtr, this.ptr, simplifier.ptr));
|
|
}
|
|
|
|
assertions(): AstVector<Name, Bool<Name>> {
|
|
return new AstVectorImpl(check(Z3.solver_get_assertions(contextPtr, this.ptr)));
|
|
}
|
|
|
|
async check(...exprs: (Bool<Name> | AstVector<Name, Bool<Name>>)[]): Promise<CheckSatResult> {
|
|
const assumptions = _flattenArgs(exprs).map(expr => {
|
|
_assertContext(expr);
|
|
return expr.ast;
|
|
});
|
|
const result = await asyncMutex.runExclusive(() =>
|
|
check(Z3.solver_check_assumptions(contextPtr, this.ptr, assumptions)),
|
|
);
|
|
switch (result) {
|
|
case Z3_lbool.Z3_L_FALSE:
|
|
return 'unsat';
|
|
case Z3_lbool.Z3_L_TRUE:
|
|
return 'sat';
|
|
case Z3_lbool.Z3_L_UNDEF:
|
|
return 'unknown';
|
|
default:
|
|
assertExhaustive(result);
|
|
}
|
|
}
|
|
|
|
unsatCore(): AstVector<Name, Bool<Name>> {
|
|
return new AstVectorImpl(check(Z3.solver_get_unsat_core(contextPtr, this.ptr)));
|
|
}
|
|
|
|
model() {
|
|
return new ModelImpl(check(Z3.solver_get_model(contextPtr, this.ptr)));
|
|
}
|
|
|
|
statistics(): Statistics<Name> {
|
|
return new StatisticsImpl(check(Z3.solver_get_statistics(contextPtr, this.ptr)));
|
|
}
|
|
|
|
reasonUnknown(): string {
|
|
return check(Z3.solver_get_reason_unknown(contextPtr, this.ptr));
|
|
}
|
|
|
|
toString() {
|
|
return check(Z3.solver_to_string(contextPtr, this.ptr));
|
|
}
|
|
|
|
toSmtlib2(status: string = 'unknown'): string {
|
|
const assertionsVec = this.assertions();
|
|
const numAssertions = assertionsVec.length();
|
|
let formula: Z3_ast;
|
|
let assumptions: Z3_ast[];
|
|
|
|
if (numAssertions > 0) {
|
|
// Use last assertion as formula and rest as assumptions
|
|
assumptions = [];
|
|
for (let i = 0; i < numAssertions - 1; i++) {
|
|
assumptions.push(assertionsVec.get(i).ast);
|
|
}
|
|
formula = assertionsVec.get(numAssertions - 1).ast;
|
|
} else {
|
|
// No assertions, use true
|
|
assumptions = [];
|
|
formula = ctx.Bool.val(true).ast;
|
|
}
|
|
|
|
return check(Z3.benchmark_to_smtlib_string(
|
|
contextPtr,
|
|
'',
|
|
'',
|
|
status,
|
|
'',
|
|
assumptions,
|
|
formula
|
|
));
|
|
}
|
|
|
|
fromString(s: string) {
|
|
Z3.solver_from_string(contextPtr, this.ptr, s);
|
|
throwIfError();
|
|
}
|
|
|
|
units(): AstVector<Name, Bool<Name>> {
|
|
return new AstVectorImpl(check(Z3.solver_get_units(contextPtr, this.ptr)));
|
|
}
|
|
|
|
nonUnits(): AstVector<Name, Bool<Name>> {
|
|
return new AstVectorImpl(check(Z3.solver_get_non_units(contextPtr, this.ptr)));
|
|
}
|
|
|
|
trail(): AstVector<Name, Bool<Name>> {
|
|
return new AstVectorImpl(check(Z3.solver_get_trail(contextPtr, this.ptr)));
|
|
}
|
|
|
|
congruenceRoot(expr: Expr<Name>): Expr<Name> {
|
|
_assertContext(expr);
|
|
return _toExpr(check(Z3.solver_congruence_root(contextPtr, this.ptr, expr.ast)));
|
|
}
|
|
|
|
congruenceNext(expr: Expr<Name>): Expr<Name> {
|
|
_assertContext(expr);
|
|
return _toExpr(check(Z3.solver_congruence_next(contextPtr, this.ptr, expr.ast)));
|
|
}
|
|
|
|
congruenceExplain(a: Expr<Name>, b: Expr<Name>): Expr<Name> {
|
|
_assertContext(a);
|
|
_assertContext(b);
|
|
return _toExpr(check(Z3.solver_congruence_explain(contextPtr, this.ptr, a.ast, b.ast)));
|
|
}
|
|
|
|
fromFile(filename: string) {
|
|
Z3.solver_from_file(contextPtr, this.ptr, filename);
|
|
throwIfError();
|
|
}
|
|
|
|
release() {
|
|
Z3.solver_dec_ref(contextPtr, this.ptr);
|
|
// Mark the ptr as null to prevent double free
|
|
this._ptr = null;
|
|
cleanup.unregister(this);
|
|
}
|
|
}
|
|
|
|
class OptimizeImpl implements Optimize<Name> {
|
|
declare readonly __typename: Optimize['__typename'];
|
|
|
|
readonly ctx: Context<Name>;
|
|
private _ptr: Z3_optimize | null;
|
|
get ptr(): Z3_optimize {
|
|
_assertPtr(this._ptr);
|
|
return this._ptr;
|
|
}
|
|
|
|
constructor(ptr: Z3_optimize = Z3.mk_optimize(contextPtr)) {
|
|
this.ctx = ctx;
|
|
let myPtr: Z3_optimize;
|
|
myPtr = ptr;
|
|
this._ptr = myPtr;
|
|
Z3.optimize_inc_ref(contextPtr, myPtr);
|
|
cleanup.register(this, () => Z3.optimize_dec_ref(contextPtr, myPtr), this);
|
|
}
|
|
|
|
set(key: string, value: any): void {
|
|
Z3.optimize_set_params(contextPtr, this.ptr, _toParams(key, value));
|
|
}
|
|
|
|
push() {
|
|
Z3.optimize_push(contextPtr, this.ptr);
|
|
}
|
|
|
|
pop() {
|
|
Z3.optimize_pop(contextPtr, this.ptr);
|
|
}
|
|
|
|
add(...exprs: (Bool<Name> | AstVector<Name, Bool<Name>>)[]) {
|
|
_flattenArgs(exprs).forEach(expr => {
|
|
_assertContext(expr);
|
|
check(Z3.optimize_assert(contextPtr, this.ptr, expr.ast));
|
|
});
|
|
}
|
|
|
|
addSoft(expr: Bool<Name>, weight: number | bigint | string | CoercibleRational, id: number | string = '') {
|
|
if (isCoercibleRational(weight)) {
|
|
weight = `${weight.numerator}/${weight.denominator}`;
|
|
}
|
|
check(Z3.optimize_assert_soft(contextPtr, this.ptr, expr.ast, weight.toString(), _toSymbol(id)));
|
|
}
|
|
|
|
addAndTrack(expr: Bool<Name>, constant: Bool<Name> | string) {
|
|
if (typeof constant === 'string') {
|
|
constant = Bool.const(constant);
|
|
}
|
|
assert(isConst(constant), 'Provided expression that is not a constant to addAndTrack');
|
|
check(Z3.optimize_assert_and_track(contextPtr, this.ptr, expr.ast, constant.ast));
|
|
}
|
|
|
|
assertions(): AstVector<Name, Bool<Name>> {
|
|
return new AstVectorImpl(check(Z3.optimize_get_assertions(contextPtr, this.ptr)));
|
|
}
|
|
|
|
maximize(expr: Arith<Name> | BitVec<number, Name>) {
|
|
check(Z3.optimize_maximize(contextPtr, this.ptr, expr.ast));
|
|
}
|
|
|
|
minimize(expr: Arith<Name> | BitVec<number, Name>) {
|
|
check(Z3.optimize_minimize(contextPtr, this.ptr, expr.ast));
|
|
}
|
|
|
|
async check(...exprs: (Bool<Name> | AstVector<Name, Bool<Name>>)[]): Promise<CheckSatResult> {
|
|
const assumptions = _flattenArgs(exprs).map(expr => {
|
|
_assertContext(expr);
|
|
return expr.ast;
|
|
});
|
|
const result = await asyncMutex.runExclusive(() => check(Z3.optimize_check(contextPtr, this.ptr, assumptions)));
|
|
switch (result) {
|
|
case Z3_lbool.Z3_L_FALSE:
|
|
return 'unsat';
|
|
case Z3_lbool.Z3_L_TRUE:
|
|
return 'sat';
|
|
case Z3_lbool.Z3_L_UNDEF:
|
|
return 'unknown';
|
|
default:
|
|
assertExhaustive(result);
|
|
}
|
|
}
|
|
|
|
model() {
|
|
return new ModelImpl(check(Z3.optimize_get_model(contextPtr, this.ptr)));
|
|
}
|
|
|
|
statistics(): Statistics<Name> {
|
|
return new StatisticsImpl(check(Z3.optimize_get_statistics(contextPtr, this.ptr)));
|
|
}
|
|
|
|
toString() {
|
|
return check(Z3.optimize_to_string(contextPtr, this.ptr));
|
|
}
|
|
|
|
fromString(s: string) {
|
|
Z3.optimize_from_string(contextPtr, this.ptr, s);
|
|
throwIfError();
|
|
}
|
|
|
|
release() {
|
|
Z3.optimize_dec_ref(contextPtr, this.ptr);
|
|
this._ptr = null;
|
|
cleanup.unregister(this);
|
|
}
|
|
}
|
|
|
|
class FixedpointImpl implements Fixedpoint<Name> {
|
|
declare readonly __typename: Fixedpoint['__typename'];
|
|
|
|
readonly ctx: Context<Name>;
|
|
private _ptr: Z3_fixedpoint | null;
|
|
get ptr(): Z3_fixedpoint {
|
|
_assertPtr(this._ptr);
|
|
return this._ptr;
|
|
}
|
|
|
|
constructor(ptr: Z3_fixedpoint = Z3.mk_fixedpoint(contextPtr)) {
|
|
this.ctx = ctx;
|
|
let myPtr: Z3_fixedpoint;
|
|
myPtr = ptr;
|
|
this._ptr = myPtr;
|
|
Z3.fixedpoint_inc_ref(contextPtr, myPtr);
|
|
cleanup.register(this, () => Z3.fixedpoint_dec_ref(contextPtr, myPtr), this);
|
|
}
|
|
|
|
set(key: string, value: any): void {
|
|
Z3.fixedpoint_set_params(contextPtr, this.ptr, _toParams(key, value));
|
|
}
|
|
|
|
help(): string {
|
|
return check(Z3.fixedpoint_get_help(contextPtr, this.ptr));
|
|
}
|
|
|
|
add(...constraints: Bool<Name>[]) {
|
|
constraints.forEach(constraint => {
|
|
_assertContext(constraint);
|
|
check(Z3.fixedpoint_assert(contextPtr, this.ptr, constraint.ast));
|
|
});
|
|
}
|
|
|
|
registerRelation(pred: FuncDecl<Name>): void {
|
|
_assertContext(pred);
|
|
check(Z3.fixedpoint_register_relation(contextPtr, this.ptr, pred.ptr));
|
|
}
|
|
|
|
addRule(rule: Bool<Name>, name?: string): void {
|
|
_assertContext(rule);
|
|
const symbol = _toSymbol(name ?? '');
|
|
check(Z3.fixedpoint_add_rule(contextPtr, this.ptr, rule.ast, symbol));
|
|
}
|
|
|
|
addFact(pred: FuncDecl<Name>, ...args: number[]): void {
|
|
_assertContext(pred);
|
|
check(Z3.fixedpoint_add_fact(contextPtr, this.ptr, pred.ptr, args));
|
|
}
|
|
|
|
updateRule(rule: Bool<Name>, name: string): void {
|
|
_assertContext(rule);
|
|
const symbol = _toSymbol(name);
|
|
check(Z3.fixedpoint_update_rule(contextPtr, this.ptr, rule.ast, symbol));
|
|
}
|
|
|
|
async query(query: Bool<Name>): Promise<CheckSatResult> {
|
|
_assertContext(query);
|
|
const result = await asyncMutex.runExclusive(() => check(Z3.fixedpoint_query(contextPtr, this.ptr, query.ast)));
|
|
switch (result) {
|
|
case Z3_lbool.Z3_L_FALSE:
|
|
return 'unsat';
|
|
case Z3_lbool.Z3_L_TRUE:
|
|
return 'sat';
|
|
case Z3_lbool.Z3_L_UNDEF:
|
|
return 'unknown';
|
|
default:
|
|
assertExhaustive(result);
|
|
}
|
|
}
|
|
|
|
async queryRelations(...relations: FuncDecl<Name>[]): Promise<CheckSatResult> {
|
|
relations.forEach(rel => _assertContext(rel));
|
|
const decls = relations.map(rel => rel.ptr);
|
|
const result = await asyncMutex.runExclusive(() =>
|
|
check(Z3.fixedpoint_query_relations(contextPtr, this.ptr, decls)),
|
|
);
|
|
switch (result) {
|
|
case Z3_lbool.Z3_L_FALSE:
|
|
return 'unsat';
|
|
case Z3_lbool.Z3_L_TRUE:
|
|
return 'sat';
|
|
case Z3_lbool.Z3_L_UNDEF:
|
|
return 'unknown';
|
|
default:
|
|
assertExhaustive(result);
|
|
}
|
|
}
|
|
|
|
getAnswer(): Expr<Name> | null {
|
|
const ans = check(Z3.fixedpoint_get_answer(contextPtr, this.ptr));
|
|
return ans ? _toExpr(ans) : null;
|
|
}
|
|
|
|
getReasonUnknown(): string {
|
|
return check(Z3.fixedpoint_get_reason_unknown(contextPtr, this.ptr));
|
|
}
|
|
|
|
getNumLevels(pred: FuncDecl<Name>): number {
|
|
_assertContext(pred);
|
|
return check(Z3.fixedpoint_get_num_levels(contextPtr, this.ptr, pred.ptr));
|
|
}
|
|
|
|
getCoverDelta(level: number, pred: FuncDecl<Name>): Expr<Name> | null {
|
|
_assertContext(pred);
|
|
const res = check(Z3.fixedpoint_get_cover_delta(contextPtr, this.ptr, level, pred.ptr));
|
|
return res ? _toExpr(res) : null;
|
|
}
|
|
|
|
addCover(level: number, pred: FuncDecl<Name>, property: Expr<Name>): void {
|
|
_assertContext(pred);
|
|
_assertContext(property);
|
|
check(Z3.fixedpoint_add_cover(contextPtr, this.ptr, level, pred.ptr, property.ast));
|
|
}
|
|
|
|
getRules(): AstVector<Name, Bool<Name>> {
|
|
return new AstVectorImpl(check(Z3.fixedpoint_get_rules(contextPtr, this.ptr)));
|
|
}
|
|
|
|
getAssertions(): AstVector<Name, Bool<Name>> {
|
|
return new AstVectorImpl(check(Z3.fixedpoint_get_assertions(contextPtr, this.ptr)));
|
|
}
|
|
|
|
setPredicateRepresentation(pred: FuncDecl<Name>, kinds: string[]): void {
|
|
_assertContext(pred);
|
|
const symbols = kinds.map(kind => _toSymbol(kind));
|
|
check(Z3.fixedpoint_set_predicate_representation(contextPtr, this.ptr, pred.ptr, symbols));
|
|
}
|
|
|
|
toString(): string {
|
|
return check(Z3.fixedpoint_to_string(contextPtr, this.ptr, []));
|
|
}
|
|
|
|
fromString(s: string): AstVector<Name, Bool<Name>> {
|
|
const av = check(Z3.fixedpoint_from_string(contextPtr, this.ptr, s));
|
|
return new AstVectorImpl(av);
|
|
}
|
|
|
|
fromFile(file: string): AstVector<Name, Bool<Name>> {
|
|
const av = check(Z3.fixedpoint_from_file(contextPtr, this.ptr, file));
|
|
return new AstVectorImpl(av);
|
|
}
|
|
|
|
statistics(): Statistics<Name> {
|
|
return new StatisticsImpl(check(Z3.fixedpoint_get_statistics(contextPtr, this.ptr)));
|
|
}
|
|
|
|
release() {
|
|
Z3.fixedpoint_dec_ref(contextPtr, this.ptr);
|
|
this._ptr = null;
|
|
cleanup.unregister(this);
|
|
}
|
|
}
|
|
|
|
class ModelImpl implements Model<Name> {
|
|
declare readonly __typename: Model['__typename'];
|
|
readonly ctx: Context<Name>;
|
|
private _ptr: Z3_model | null;
|
|
get ptr(): Z3_model {
|
|
_assertPtr(this._ptr);
|
|
return this._ptr;
|
|
}
|
|
|
|
constructor(ptr: Z3_model = Z3.mk_model(contextPtr)) {
|
|
this.ctx = ctx;
|
|
this._ptr = ptr;
|
|
Z3.model_inc_ref(contextPtr, ptr);
|
|
cleanup.register(this, () => Z3.model_dec_ref(contextPtr, ptr), this);
|
|
}
|
|
|
|
length() {
|
|
return Z3.model_get_num_consts(contextPtr, this.ptr) + Z3.model_get_num_funcs(contextPtr, this.ptr);
|
|
}
|
|
|
|
[Symbol.iterator](): Iterator<FuncDecl<Name>> {
|
|
return this.values();
|
|
}
|
|
|
|
*entries(): IterableIterator<[number, FuncDecl<Name>]> {
|
|
const length = this.length();
|
|
for (let i = 0; i < length; i++) {
|
|
yield [i, this.get(i)];
|
|
}
|
|
}
|
|
|
|
*keys(): IterableIterator<number> {
|
|
for (const [key] of this.entries()) {
|
|
yield key;
|
|
}
|
|
}
|
|
|
|
*values(): IterableIterator<FuncDecl<Name>> {
|
|
for (const [, value] of this.entries()) {
|
|
yield value;
|
|
}
|
|
}
|
|
|
|
decls() {
|
|
return [...this.values()];
|
|
}
|
|
|
|
sexpr() {
|
|
return check(Z3.model_to_string(contextPtr, this.ptr));
|
|
}
|
|
|
|
toString() {
|
|
return this.sexpr();
|
|
}
|
|
|
|
eval(expr: Bool<Name>, modelCompletion?: boolean): Bool<Name>;
|
|
eval(expr: Arith<Name>, modelCompletion?: boolean): Arith<Name>;
|
|
eval<Bits extends number = number>(expr: BitVec<Bits, Name>, modelCompletion?: boolean): BitVecNum<Bits, Name>;
|
|
eval(expr: Expr<Name>, modelCompletion: boolean = false) {
|
|
_assertContext(expr);
|
|
const r = check(Z3.model_eval(contextPtr, this.ptr, expr.ast, modelCompletion));
|
|
if (r === null) {
|
|
throw new Z3Error('Failed to evaluate expression in the model');
|
|
}
|
|
return _toExpr(r);
|
|
}
|
|
|
|
get(i: number): FuncDecl<Name>;
|
|
get(from: number, to: number): FuncDecl<Name>[];
|
|
get(declaration: FuncDecl<Name>): FuncInterp<Name> | Expr<Name>;
|
|
get(constant: Expr<Name>): Expr<Name>;
|
|
get(sort: Sort<Name>): AstVector<Name, AnyExpr<Name>>;
|
|
get(
|
|
i: number | FuncDecl<Name> | Expr<Name> | Sort<Name>,
|
|
to?: number,
|
|
): FuncDecl<Name> | FuncInterp<Name> | Expr<Name> | AstVector<Name, AnyAst<Name>> | FuncDecl<Name>[] {
|
|
assert(to === undefined || typeof i === 'number');
|
|
if (typeof i === 'number') {
|
|
const length = this.length();
|
|
|
|
if (i >= length) {
|
|
throw new RangeError(`expected index ${i} to be less than length ${length}`);
|
|
}
|
|
|
|
if (to === undefined) {
|
|
const numConsts = check(Z3.model_get_num_consts(contextPtr, this.ptr));
|
|
if (i < numConsts) {
|
|
return new FuncDeclImpl(check(Z3.model_get_const_decl(contextPtr, this.ptr, i)));
|
|
} else {
|
|
return new FuncDeclImpl(check(Z3.model_get_func_decl(contextPtr, this.ptr, i - numConsts)));
|
|
}
|
|
}
|
|
|
|
if (to < 0) {
|
|
to += length;
|
|
}
|
|
if (to >= length) {
|
|
throw new RangeError(`expected index ${to} to be less than length ${length}`);
|
|
}
|
|
const result = [];
|
|
for (let j = i; j < to; j++) {
|
|
result.push(this.get(j));
|
|
}
|
|
return result;
|
|
} else if (isFuncDecl(i) || (isExpr(i) && isConst(i))) {
|
|
const result = this.getInterp(i);
|
|
assert(result !== null);
|
|
return result;
|
|
} else if (isSort(i)) {
|
|
return this.getUniverse(i);
|
|
}
|
|
assert(false, 'Number, declaration or constant expected');
|
|
}
|
|
|
|
updateValue(decl: FuncDecl<Name> | Expr<Name>, a: Ast<Name> | FuncInterp<Name>): void {
|
|
_assertContext(decl);
|
|
_assertContext(a);
|
|
if (isExpr(decl)) {
|
|
decl = decl.decl();
|
|
}
|
|
if (isFuncDecl(decl) && decl.arity() !== 0 && isFuncInterp(a)) {
|
|
const funcInterp = this.addFuncInterp(decl, a.elseValue() as Expr<Name>);
|
|
for (let i = 0; i < a.numEntries(); i++) {
|
|
const e = a.entry(i);
|
|
const n = e.numArgs();
|
|
const args = global.Array(n).map((_, i) => e.argValue(i));
|
|
funcInterp.addEntry(args, e.value());
|
|
}
|
|
return;
|
|
}
|
|
if (!isFuncDecl(decl) || decl.arity() !== 0) {
|
|
throw new Z3Error('Expecting 0-ary function or constant expression');
|
|
}
|
|
if (!isAst(a)) {
|
|
throw new Z3Error('Only func declarations can be assigned to func interpretations');
|
|
}
|
|
check(Z3.add_const_interp(contextPtr, this.ptr, decl.ptr, a.ast));
|
|
}
|
|
|
|
addFuncInterp<DomainSort extends Sort<Name>[] = Sort<Name>[], RangeSort extends Sort<Name> = Sort<Name>>(
|
|
decl: FuncDecl<Name, DomainSort, RangeSort>,
|
|
defaultValue: CoercibleToMap<SortToExprMap<RangeSort, Name>, Name>,
|
|
): FuncInterp<Name> {
|
|
const fi = check(
|
|
Z3.add_func_interp(contextPtr, this.ptr, decl.ptr, decl.range().cast(defaultValue).ptr as Z3_ast),
|
|
);
|
|
return new FuncInterpImpl(fi);
|
|
}
|
|
|
|
private getInterp(expr: FuncDecl<Name> | Expr<Name>): Expr<Name> | FuncInterp<Name> | null {
|
|
assert(isFuncDecl(expr) || isConst(expr), 'Declaration expected');
|
|
if (isConst(expr)) {
|
|
assert(isExpr(expr));
|
|
expr = expr.decl();
|
|
}
|
|
assert(isFuncDecl(expr));
|
|
if (expr.arity() === 0) {
|
|
const result = check(Z3.model_get_const_interp(contextPtr, this.ptr, expr.ptr));
|
|
if (result === null) {
|
|
return null;
|
|
}
|
|
return _toExpr(result);
|
|
} else {
|
|
const interp = check(Z3.model_get_func_interp(contextPtr, this.ptr, expr.ptr));
|
|
if (interp === null) {
|
|
return null;
|
|
}
|
|
return new FuncInterpImpl(interp);
|
|
}
|
|
}
|
|
|
|
private getUniverse(sort: Sort<Name>): AstVector<Name, AnyAst<Name>> {
|
|
_assertContext(sort);
|
|
return new AstVectorImpl(check(Z3.model_get_sort_universe(contextPtr, this.ptr, sort.ptr)));
|
|
}
|
|
|
|
numSorts(): number {
|
|
return check(Z3.model_get_num_sorts(contextPtr, this.ptr));
|
|
}
|
|
|
|
getSort(i: number): Sort<Name> {
|
|
return _toSort(check(Z3.model_get_sort(contextPtr, this.ptr, i)));
|
|
}
|
|
|
|
getSorts(): Sort<Name>[] {
|
|
const n = this.numSorts();
|
|
const result: Sort<Name>[] = [];
|
|
for (let i = 0; i < n; i++) {
|
|
result.push(this.getSort(i));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
sortUniverse(sort: Sort<Name>): AstVector<Name, AnyExpr<Name>> {
|
|
return this.getUniverse(sort) as AstVector<Name, AnyExpr<Name>>;
|
|
}
|
|
|
|
release() {
|
|
Z3.model_dec_ref(contextPtr, this.ptr);
|
|
this._ptr = null;
|
|
cleanup.unregister(this);
|
|
}
|
|
}
|
|
|
|
class StatisticsImpl implements Statistics<Name> {
|
|
declare readonly __typename: Statistics['__typename'];
|
|
readonly ctx: Context<Name>;
|
|
private _ptr: Z3_stats | null;
|
|
get ptr(): Z3_stats {
|
|
_assertPtr(this._ptr);
|
|
return this._ptr;
|
|
}
|
|
|
|
constructor(ptr: Z3_stats) {
|
|
this.ctx = ctx;
|
|
this._ptr = ptr;
|
|
Z3.stats_inc_ref(contextPtr, ptr);
|
|
cleanup.register(this, () => Z3.stats_dec_ref(contextPtr, ptr), this);
|
|
}
|
|
|
|
size(): number {
|
|
return Z3.stats_size(contextPtr, this.ptr);
|
|
}
|
|
|
|
keys(): string[] {
|
|
const result: string[] = [];
|
|
const sz = this.size();
|
|
for (let i = 0; i < sz; i++) {
|
|
result.push(Z3.stats_get_key(contextPtr, this.ptr, i));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
get(key: string): number {
|
|
const sz = this.size();
|
|
for (let i = 0; i < sz; i++) {
|
|
if (Z3.stats_get_key(contextPtr, this.ptr, i) === key) {
|
|
if (Z3.stats_is_uint(contextPtr, this.ptr, i)) {
|
|
return Z3.stats_get_uint_value(contextPtr, this.ptr, i);
|
|
} else {
|
|
return Z3.stats_get_double_value(contextPtr, this.ptr, i);
|
|
}
|
|
}
|
|
}
|
|
throw new Error(`Statistics key not found: ${key}`);
|
|
}
|
|
|
|
entries(): StatisticsEntry<Name>[] {
|
|
const result: StatisticsEntry<Name>[] = [];
|
|
const sz = this.size();
|
|
for (let i = 0; i < sz; i++) {
|
|
const key = Z3.stats_get_key(contextPtr, this.ptr, i);
|
|
const isUint = Z3.stats_is_uint(contextPtr, this.ptr, i);
|
|
const isDouble = Z3.stats_is_double(contextPtr, this.ptr, i);
|
|
const value = isUint
|
|
? Z3.stats_get_uint_value(contextPtr, this.ptr, i)
|
|
: Z3.stats_get_double_value(contextPtr, this.ptr, i);
|
|
result.push({
|
|
__typename: 'StatisticsEntry' as const,
|
|
key,
|
|
value,
|
|
isUint,
|
|
isDouble,
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
[Symbol.iterator](): Iterator<StatisticsEntry<Name>> {
|
|
return this.entries()[Symbol.iterator]();
|
|
}
|
|
|
|
release() {
|
|
Z3.stats_dec_ref(contextPtr, this.ptr);
|
|
this._ptr = null;
|
|
cleanup.unregister(this);
|
|
}
|
|
}
|
|
|
|
class FuncEntryImpl implements FuncEntry<Name> {
|
|
declare readonly __typename: FuncEntry['__typename'];
|
|
|
|
readonly ctx: Context<Name>;
|
|
|
|
constructor(readonly ptr: Z3_func_entry) {
|
|
this.ctx = ctx;
|
|
Z3.func_entry_inc_ref(contextPtr, ptr);
|
|
cleanup.register(this, () => Z3.func_entry_dec_ref(contextPtr, ptr), this);
|
|
}
|
|
|
|
numArgs() {
|
|
return check(Z3.func_entry_get_num_args(contextPtr, this.ptr));
|
|
}
|
|
|
|
argValue(i: number): Expr<Name> {
|
|
return _toExpr(check(Z3.func_entry_get_arg(contextPtr, this.ptr, i)));
|
|
}
|
|
|
|
value(): Expr<Name> {
|
|
return _toExpr(check(Z3.func_entry_get_value(contextPtr, this.ptr)));
|
|
}
|
|
}
|
|
|
|
class FuncInterpImpl implements FuncInterp<Name> {
|
|
declare readonly __typename: FuncInterp['__typename'];
|
|
readonly ctx: Context<Name>;
|
|
|
|
constructor(readonly ptr: Z3_func_interp) {
|
|
this.ctx = ctx;
|
|
Z3.func_interp_inc_ref(contextPtr, ptr);
|
|
cleanup.register(this, () => Z3.func_interp_dec_ref(contextPtr, ptr), this);
|
|
}
|
|
|
|
elseValue(): Expr<Name> {
|
|
return _toExpr(check(Z3.func_interp_get_else(contextPtr, this.ptr)));
|
|
}
|
|
|
|
numEntries(): number {
|
|
return check(Z3.func_interp_get_num_entries(contextPtr, this.ptr));
|
|
}
|
|
|
|
arity(): number {
|
|
return check(Z3.func_interp_get_arity(contextPtr, this.ptr));
|
|
}
|
|
|
|
entry(i: number): FuncEntry<Name> {
|
|
return new FuncEntryImpl(check(Z3.func_interp_get_entry(contextPtr, this.ptr, i)));
|
|
}
|
|
|
|
addEntry(args: Expr<Name>[], value: Expr<Name>): void {
|
|
const argsVec = new AstVectorImpl();
|
|
for (const arg of args) {
|
|
argsVec.push(arg);
|
|
}
|
|
_assertContext(argsVec);
|
|
_assertContext(value);
|
|
assert(this.arity() === argsVec.length(), "Number of arguments in entry doesn't match function arity");
|
|
check(Z3.func_interp_add_entry(contextPtr, this.ptr, argsVec.ptr, value.ptr as Z3_ast));
|
|
}
|
|
}
|
|
|
|
class SortImpl extends AstImpl<Z3_sort> implements Sort<Name> {
|
|
declare readonly __typename: Sort['__typename'];
|
|
|
|
get ast(): Z3_ast {
|
|
return Z3.sort_to_ast(contextPtr, this.ptr);
|
|
}
|
|
|
|
kind() {
|
|
return Z3.get_sort_kind(contextPtr, this.ptr);
|
|
}
|
|
|
|
subsort(other: Sort<Name>) {
|
|
_assertContext(other);
|
|
return false;
|
|
}
|
|
|
|
cast(expr: Expr<Name>): Expr<Name> {
|
|
_assertContext(expr);
|
|
assert(expr.sort.eqIdentity(expr.sort), 'Sort mismatch');
|
|
return expr;
|
|
}
|
|
|
|
name() {
|
|
return _fromSymbol(Z3.get_sort_name(contextPtr, this.ptr));
|
|
}
|
|
|
|
eqIdentity(other: Sort<Name>) {
|
|
_assertContext(other);
|
|
return check(Z3.is_eq_sort(contextPtr, this.ptr, other.ptr));
|
|
}
|
|
|
|
neqIdentity(other: Sort<Name>) {
|
|
return !this.eqIdentity(other);
|
|
}
|
|
}
|
|
|
|
class FuncDeclImpl<DomainSort extends Sort<Name>[], RangeSort extends Sort<Name>>
|
|
extends AstImpl<Z3_func_decl>
|
|
implements FuncDecl<Name>
|
|
{
|
|
declare readonly __typename: FuncDecl['__typename'];
|
|
|
|
get ast(): Z3_ast {
|
|
return Z3.func_decl_to_ast(contextPtr, this.ptr);
|
|
}
|
|
|
|
name() {
|
|
return _fromSymbol(Z3.get_decl_name(contextPtr, this.ptr));
|
|
}
|
|
|
|
arity() {
|
|
return Z3.get_arity(contextPtr, this.ptr);
|
|
}
|
|
|
|
domain<T extends number>(i: T): DomainSort[T] {
|
|
assert(i < this.arity(), 'Index out of bounds');
|
|
return _toSort(Z3.get_domain(contextPtr, this.ptr, i));
|
|
}
|
|
|
|
range(): RangeSort {
|
|
return _toSort(Z3.get_range(contextPtr, this.ptr)) as RangeSort;
|
|
}
|
|
|
|
kind() {
|
|
return Z3.get_decl_kind(contextPtr, this.ptr);
|
|
}
|
|
|
|
params(): (number | string | Sort<Name> | Expr<Name> | FuncDecl<Name>)[] {
|
|
const n = Z3.get_decl_num_parameters(contextPtr, this.ptr);
|
|
const result = [];
|
|
for (let i = 0; i < n; i++) {
|
|
const kind = check(Z3.get_decl_parameter_kind(contextPtr, this.ptr, i));
|
|
switch (kind) {
|
|
case Z3_parameter_kind.Z3_PARAMETER_INT:
|
|
result.push(check(Z3.get_decl_int_parameter(contextPtr, this.ptr, i)));
|
|
break;
|
|
case Z3_parameter_kind.Z3_PARAMETER_DOUBLE:
|
|
result.push(check(Z3.get_decl_double_parameter(contextPtr, this.ptr, i)));
|
|
break;
|
|
case Z3_parameter_kind.Z3_PARAMETER_RATIONAL:
|
|
result.push(check(Z3.get_decl_rational_parameter(contextPtr, this.ptr, i)));
|
|
break;
|
|
case Z3_parameter_kind.Z3_PARAMETER_SYMBOL:
|
|
result.push(_fromSymbol(check(Z3.get_decl_symbol_parameter(contextPtr, this.ptr, i))));
|
|
break;
|
|
case Z3_parameter_kind.Z3_PARAMETER_SORT:
|
|
result.push(new SortImpl(check(Z3.get_decl_sort_parameter(contextPtr, this.ptr, i))));
|
|
break;
|
|
case Z3_parameter_kind.Z3_PARAMETER_AST:
|
|
result.push(new ExprImpl(check(Z3.get_decl_ast_parameter(contextPtr, this.ptr, i))));
|
|
break;
|
|
case Z3_parameter_kind.Z3_PARAMETER_FUNC_DECL:
|
|
result.push(new FuncDeclImpl(check(Z3.get_decl_func_decl_parameter(contextPtr, this.ptr, i))));
|
|
break;
|
|
case Z3_parameter_kind.Z3_PARAMETER_INTERNAL:
|
|
break;
|
|
case Z3_parameter_kind.Z3_PARAMETER_ZSTRING:
|
|
break;
|
|
default:
|
|
assertExhaustive(kind);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
call(...args: CoercibleToArrayIndexType<Name, DomainSort>): SortToExprMap<RangeSort, Name> {
|
|
assert(args.length === this.arity(), `Incorrect number of arguments to ${this}`);
|
|
return _toExpr(
|
|
check(
|
|
Z3.mk_app(
|
|
contextPtr,
|
|
this.ptr,
|
|
args.map((arg, i) => {
|
|
return this.domain(i).cast(arg as any).ast;
|
|
}),
|
|
),
|
|
),
|
|
) as SortToExprMap<RangeSort, Name>;
|
|
}
|
|
}
|
|
|
|
class ExprImpl<Ptr extends Z3_ast, S extends Sort<Name> = AnySort<Name>>
|
|
extends AstImpl<Ptr>
|
|
implements Expr<Name>
|
|
{
|
|
declare readonly __typename: Expr['__typename'];
|
|
|
|
get sort(): S {
|
|
return _toSort(Z3.get_sort(contextPtr, this.ast)) as S;
|
|
}
|
|
|
|
eq(other: CoercibleToExpr<Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_eq(contextPtr, this.ast, from(other).ast)));
|
|
}
|
|
|
|
neq(other: CoercibleToExpr<Name>): Bool<Name> {
|
|
return new BoolImpl(
|
|
check(
|
|
Z3.mk_distinct(
|
|
contextPtr,
|
|
[this, other].map(expr => from(expr).ast),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
name() {
|
|
return this.decl().name();
|
|
}
|
|
|
|
params() {
|
|
return this.decl().params();
|
|
}
|
|
|
|
decl(): FuncDecl<Name> {
|
|
assert(isApp(this), 'Z3 application expected');
|
|
return new FuncDeclImpl(check(Z3.get_app_decl(contextPtr, check(Z3.to_app(contextPtr, this.ast)))));
|
|
}
|
|
|
|
numArgs(): number {
|
|
assert(isApp(this), 'Z3 applicaiton expected');
|
|
return check(Z3.get_app_num_args(contextPtr, check(Z3.to_app(contextPtr, this.ast))));
|
|
}
|
|
|
|
arg(i: number): ReturnType<typeof _toExpr> {
|
|
assert(isApp(this), 'Z3 applicaiton expected');
|
|
assert(i < this.numArgs(), `Invalid argument index - expected ${i} to be less than ${this.numArgs()}`);
|
|
return _toExpr(check(Z3.get_app_arg(contextPtr, check(Z3.to_app(contextPtr, this.ast)), i)));
|
|
}
|
|
|
|
children(): ReturnType<typeof _toExpr>[] {
|
|
const num_args = this.numArgs();
|
|
if (isApp(this)) {
|
|
const result = [];
|
|
for (let i = 0; i < num_args; i++) {
|
|
result.push(this.arg(i));
|
|
}
|
|
return result;
|
|
}
|
|
return [];
|
|
}
|
|
}
|
|
|
|
class PatternImpl implements Pattern<Name> {
|
|
declare readonly __typename: Pattern['__typename'];
|
|
readonly ctx: Context<Name>;
|
|
|
|
constructor(readonly ptr: Z3_pattern) {
|
|
this.ctx = ctx;
|
|
// TODO: implement rest of Pattern
|
|
}
|
|
}
|
|
|
|
class BoolSortImpl extends SortImpl implements BoolSort<Name> {
|
|
declare readonly __typename: BoolSort['__typename'];
|
|
|
|
cast(other: Bool<Name> | boolean): Bool<Name>;
|
|
cast(other: CoercibleToExpr<Name>): never;
|
|
cast(other: CoercibleToExpr<Name> | Bool<Name>) {
|
|
if (typeof other === 'boolean') {
|
|
other = Bool.val(other);
|
|
}
|
|
assert(isExpr(other), 'true, false or Z3 Boolean expression expected.');
|
|
assert(this.eqIdentity(other.sort), 'Value cannot be converted into a Z3 Boolean value');
|
|
return other;
|
|
}
|
|
|
|
subsort(other: Sort<Name>) {
|
|
_assertContext(other.ctx);
|
|
return other instanceof ArithSortImpl;
|
|
}
|
|
}
|
|
|
|
class BoolImpl extends ExprImpl<Z3_ast, BoolSort<Name>> implements Bool<Name> {
|
|
declare readonly __typename: 'Bool' | 'NonLambdaQuantifier';
|
|
|
|
not(): Bool<Name> {
|
|
return Not(this);
|
|
}
|
|
|
|
and(other: Bool<Name> | boolean): Bool<Name> {
|
|
return And(this, other);
|
|
}
|
|
|
|
or(other: Bool<Name> | boolean): Bool<Name> {
|
|
return Or(this, other);
|
|
}
|
|
|
|
xor(other: Bool<Name> | boolean): Bool<Name> {
|
|
return Xor(this, other);
|
|
}
|
|
|
|
implies(other: Bool<Name> | boolean): Bool<Name> {
|
|
return Implies(this, other);
|
|
}
|
|
|
|
iff(other: Bool<Name> | boolean): Bool<Name> {
|
|
return Iff(this, other);
|
|
}
|
|
}
|
|
|
|
class ProbeImpl implements Probe<Name> {
|
|
declare readonly __typename: Probe['__typename'];
|
|
readonly ctx: Context<Name>;
|
|
|
|
constructor(readonly ptr: Z3_probe) {
|
|
this.ctx = ctx;
|
|
}
|
|
|
|
apply(goal: Goal<Name>): number {
|
|
_assertContext(goal);
|
|
return Z3.probe_apply(contextPtr, this.ptr, goal.ptr);
|
|
}
|
|
}
|
|
|
|
class GoalImpl implements Goal<Name> {
|
|
declare readonly __typename: Goal['__typename'];
|
|
readonly ctx: Context<Name>;
|
|
readonly ptr: Z3_goal;
|
|
|
|
constructor(models: boolean = true, unsat_cores: boolean = false, proofs: boolean = false) {
|
|
this.ctx = ctx;
|
|
const myPtr = check(Z3.mk_goal(contextPtr, models, unsat_cores, proofs));
|
|
this.ptr = myPtr;
|
|
Z3.goal_inc_ref(contextPtr, myPtr);
|
|
cleanup.register(this, () => Z3.goal_dec_ref(contextPtr, myPtr), this);
|
|
}
|
|
|
|
// Factory method for creating from existing Z3_goal pointer
|
|
static fromPtr(goalPtr: Z3_goal): GoalImpl {
|
|
const goal = Object.create(GoalImpl.prototype) as GoalImpl;
|
|
(goal as any).ctx = ctx;
|
|
(goal as any).ptr = goalPtr;
|
|
Z3.goal_inc_ref(contextPtr, goalPtr);
|
|
cleanup.register(goal, () => Z3.goal_dec_ref(contextPtr, goalPtr), goal);
|
|
return goal;
|
|
}
|
|
|
|
add(...constraints: (Bool<Name> | boolean)[]): void {
|
|
for (const constraint of constraints) {
|
|
const boolConstraint = isBool(constraint) ? constraint : Bool.val(constraint);
|
|
_assertContext(boolConstraint);
|
|
Z3.goal_assert(contextPtr, this.ptr, boolConstraint.ast);
|
|
}
|
|
}
|
|
|
|
size(): number {
|
|
return Z3.goal_size(contextPtr, this.ptr);
|
|
}
|
|
|
|
get(i: number): Bool<Name> {
|
|
assert(i >= 0 && i < this.size(), 'Index out of bounds');
|
|
const ast = check(Z3.goal_formula(contextPtr, this.ptr, i));
|
|
return new BoolImpl(ast);
|
|
}
|
|
|
|
depth(): number {
|
|
return Z3.goal_depth(contextPtr, this.ptr);
|
|
}
|
|
|
|
inconsistent(): boolean {
|
|
return Z3.goal_inconsistent(contextPtr, this.ptr);
|
|
}
|
|
|
|
precision(): Z3_goal_prec {
|
|
return Z3.goal_precision(contextPtr, this.ptr);
|
|
}
|
|
|
|
reset(): void {
|
|
Z3.goal_reset(contextPtr, this.ptr);
|
|
}
|
|
|
|
numExprs(): number {
|
|
return Z3.goal_num_exprs(contextPtr, this.ptr);
|
|
}
|
|
|
|
isDecidedSat(): boolean {
|
|
return Z3.goal_is_decided_sat(contextPtr, this.ptr);
|
|
}
|
|
|
|
isDecidedUnsat(): boolean {
|
|
return Z3.goal_is_decided_unsat(contextPtr, this.ptr);
|
|
}
|
|
|
|
convertModel(model: Model<Name>): Model<Name> {
|
|
_assertContext(model);
|
|
const convertedModel = check(Z3.goal_convert_model(contextPtr, this.ptr, model.ptr));
|
|
return new ModelImpl(convertedModel);
|
|
}
|
|
|
|
asExpr(): Bool<Name> {
|
|
const sz = this.size();
|
|
if (sz === 0) {
|
|
return Bool.val(true);
|
|
} else if (sz === 1) {
|
|
return this.get(0);
|
|
} else {
|
|
const constraints: Bool<Name>[] = [];
|
|
for (let i = 0; i < sz; i++) {
|
|
constraints.push(this.get(i));
|
|
}
|
|
return And(...constraints);
|
|
}
|
|
}
|
|
|
|
toString(): string {
|
|
return Z3.goal_to_string(contextPtr, this.ptr);
|
|
}
|
|
|
|
dimacs(includeNames: boolean = true): string {
|
|
return Z3.goal_to_dimacs_string(contextPtr, this.ptr, includeNames);
|
|
}
|
|
}
|
|
|
|
class ApplyResultImpl implements ApplyResult<Name> {
|
|
declare readonly __typename: ApplyResult['__typename'];
|
|
readonly ctx: Context<Name>;
|
|
readonly ptr: Z3_apply_result;
|
|
|
|
constructor(ptr: Z3_apply_result) {
|
|
this.ctx = ctx;
|
|
this.ptr = ptr;
|
|
Z3.apply_result_inc_ref(contextPtr, ptr);
|
|
cleanup.register(this, () => Z3.apply_result_dec_ref(contextPtr, ptr), this);
|
|
}
|
|
|
|
length(): number {
|
|
return Z3.apply_result_get_num_subgoals(contextPtr, this.ptr);
|
|
}
|
|
|
|
getSubgoal(i: number): Goal<Name> {
|
|
assert(i >= 0 && i < this.length(), 'Index out of bounds');
|
|
const goalPtr = check(Z3.apply_result_get_subgoal(contextPtr, this.ptr, i));
|
|
return GoalImpl.fromPtr(goalPtr);
|
|
}
|
|
|
|
toString(): string {
|
|
return Z3.apply_result_to_string(contextPtr, this.ptr);
|
|
}
|
|
|
|
[index: number]: Goal<Name>;
|
|
}
|
|
|
|
// Add indexer support to ApplyResultImpl
|
|
const applyResultHandler = {
|
|
get(target: ApplyResultImpl, prop: string | symbol): any {
|
|
if (typeof prop === 'string') {
|
|
const index = parseInt(prop, 10);
|
|
if (!isNaN(index) && index >= 0 && index < target.length()) {
|
|
return target.getSubgoal(index);
|
|
}
|
|
}
|
|
return (target as any)[prop];
|
|
},
|
|
};
|
|
|
|
class TacticImpl implements Tactic<Name> {
|
|
declare readonly __typename: Tactic['__typename'];
|
|
|
|
readonly ptr: Z3_tactic;
|
|
readonly ctx: Context<Name>;
|
|
|
|
constructor(tactic: string | Z3_tactic) {
|
|
this.ctx = ctx;
|
|
let myPtr: Z3_tactic;
|
|
if (typeof tactic === 'string') {
|
|
myPtr = check(Z3.mk_tactic(contextPtr, tactic));
|
|
} else {
|
|
myPtr = tactic;
|
|
}
|
|
|
|
this.ptr = myPtr;
|
|
|
|
Z3.tactic_inc_ref(contextPtr, myPtr);
|
|
cleanup.register(this, () => Z3.tactic_dec_ref(contextPtr, myPtr), this);
|
|
}
|
|
|
|
async apply(goal: Goal<Name> | Bool<Name>): Promise<ApplyResult<Name>> {
|
|
let goalToUse: Goal<Name>;
|
|
|
|
if (isBool(goal)) {
|
|
// Convert Bool expression to Goal
|
|
goalToUse = new GoalImpl();
|
|
goalToUse.add(goal);
|
|
} else {
|
|
goalToUse = goal;
|
|
}
|
|
|
|
_assertContext(goalToUse);
|
|
const result = await Z3.tactic_apply(contextPtr, this.ptr, goalToUse.ptr);
|
|
const applyResult = new ApplyResultImpl(check(result));
|
|
// Wrap with Proxy to enable indexer access
|
|
return new Proxy(applyResult, applyResultHandler) as ApplyResult<Name>;
|
|
}
|
|
|
|
solver(): Solver<Name> {
|
|
const solverPtr = check(Z3.mk_solver_from_tactic(contextPtr, this.ptr));
|
|
return new SolverImpl(solverPtr);
|
|
}
|
|
|
|
help(): string {
|
|
return Z3.tactic_get_help(contextPtr, this.ptr);
|
|
}
|
|
|
|
paramDescrs(): ParamDescrs<Name> {
|
|
const descrs = check(Z3.tactic_get_param_descrs(contextPtr, this.ptr));
|
|
return new ParamDescrsImpl(descrs);
|
|
}
|
|
|
|
usingParams(params: Params<Name>): Tactic<Name> {
|
|
_assertContext(params);
|
|
const newTactic = check(Z3.tactic_using_params(contextPtr, this.ptr, params.ptr));
|
|
return new TacticImpl(newTactic);
|
|
}
|
|
}
|
|
|
|
class ParamsImpl implements Params<Name> {
|
|
declare readonly __typename: Params['__typename'];
|
|
|
|
readonly ptr: Z3_params;
|
|
readonly ctx: Context<Name>;
|
|
|
|
constructor(params?: Z3_params) {
|
|
this.ctx = ctx;
|
|
if (params) {
|
|
this.ptr = params;
|
|
} else {
|
|
this.ptr = Z3.mk_params(contextPtr);
|
|
}
|
|
Z3.params_inc_ref(contextPtr, this.ptr);
|
|
cleanup.register(this, () => Z3.params_dec_ref(contextPtr, this.ptr), this);
|
|
}
|
|
|
|
set(name: string, value: boolean | number | string): void {
|
|
const sym = _toSymbol(name);
|
|
if (typeof value === 'boolean') {
|
|
Z3.params_set_bool(contextPtr, this.ptr, sym, value);
|
|
} else if (typeof value === 'number') {
|
|
if (Number.isInteger(value)) {
|
|
check(Z3.params_set_uint(contextPtr, this.ptr, sym, value));
|
|
} else {
|
|
check(Z3.params_set_double(contextPtr, this.ptr, sym, value));
|
|
}
|
|
} else if (typeof value === 'string') {
|
|
check(Z3.params_set_symbol(contextPtr, this.ptr, sym, _toSymbol(value)));
|
|
}
|
|
}
|
|
|
|
validate(descrs: ParamDescrs<Name>): void {
|
|
_assertContext(descrs);
|
|
Z3.params_validate(contextPtr, this.ptr, descrs.ptr);
|
|
}
|
|
|
|
toString(): string {
|
|
return Z3.params_to_string(contextPtr, this.ptr);
|
|
}
|
|
}
|
|
|
|
class ParamDescrsImpl implements ParamDescrs<Name> {
|
|
declare readonly __typename: ParamDescrs['__typename'];
|
|
|
|
readonly ptr: Z3_param_descrs;
|
|
readonly ctx: Context<Name>;
|
|
|
|
constructor(paramDescrs: Z3_param_descrs) {
|
|
this.ctx = ctx;
|
|
this.ptr = paramDescrs;
|
|
Z3.param_descrs_inc_ref(contextPtr, this.ptr);
|
|
cleanup.register(this, () => Z3.param_descrs_dec_ref(contextPtr, this.ptr), this);
|
|
}
|
|
|
|
size(): number {
|
|
return Z3.param_descrs_size(contextPtr, this.ptr);
|
|
}
|
|
|
|
getName(i: number): string {
|
|
const sym = Z3.param_descrs_get_name(contextPtr, this.ptr, i);
|
|
const name = _fromSymbol(sym);
|
|
return typeof name === 'string' ? name : `${name}`;
|
|
}
|
|
|
|
getKind(name: string): number {
|
|
return Z3.param_descrs_get_kind(contextPtr, this.ptr, _toSymbol(name));
|
|
}
|
|
|
|
getDocumentation(name: string): string {
|
|
return Z3.param_descrs_get_documentation(contextPtr, this.ptr, _toSymbol(name));
|
|
}
|
|
|
|
toString(): string {
|
|
return Z3.param_descrs_to_string(contextPtr, this.ptr);
|
|
}
|
|
}
|
|
|
|
class SimplifierImpl implements Simplifier<Name> {
|
|
declare readonly __typename: Simplifier['__typename'];
|
|
|
|
readonly ptr: Z3_simplifier;
|
|
readonly ctx: Context<Name>;
|
|
|
|
constructor(simplifier: string | Z3_simplifier) {
|
|
this.ctx = ctx;
|
|
let myPtr: Z3_simplifier;
|
|
if (typeof simplifier === 'string') {
|
|
myPtr = check(Z3.mk_simplifier(contextPtr, simplifier));
|
|
} else {
|
|
myPtr = simplifier;
|
|
}
|
|
|
|
this.ptr = myPtr;
|
|
|
|
Z3.simplifier_inc_ref(contextPtr, myPtr);
|
|
cleanup.register(this, () => Z3.simplifier_dec_ref(contextPtr, myPtr), this);
|
|
}
|
|
|
|
help(): string {
|
|
return Z3.simplifier_get_help(contextPtr, this.ptr);
|
|
}
|
|
|
|
paramDescrs(): ParamDescrs<Name> {
|
|
const descrs = check(Z3.simplifier_get_param_descrs(contextPtr, this.ptr));
|
|
return new ParamDescrsImpl(descrs);
|
|
}
|
|
|
|
usingParams(params: Params<Name>): Simplifier<Name> {
|
|
_assertContext(params);
|
|
const newSimplifier = check(Z3.simplifier_using_params(contextPtr, this.ptr, params.ptr));
|
|
return new SimplifierImpl(newSimplifier);
|
|
}
|
|
|
|
andThen(other: Simplifier<Name>): Simplifier<Name> {
|
|
_assertContext(other);
|
|
const newSimplifier = check(Z3.simplifier_and_then(contextPtr, this.ptr, other.ptr));
|
|
return new SimplifierImpl(newSimplifier);
|
|
}
|
|
}
|
|
|
|
class ArithSortImpl extends SortImpl implements ArithSort<Name> {
|
|
declare readonly __typename: ArithSort['__typename'];
|
|
|
|
cast(other: bigint | number | string): IntNum<Name> | RatNum<Name>;
|
|
cast(other: CoercibleRational | RatNum<Name>): RatNum<Name>;
|
|
cast(other: IntNum<Name>): IntNum<Name>;
|
|
cast(other: Bool<Name> | Arith<Name>): Arith<Name>;
|
|
cast(other: CoercibleToExpr<Name>): never;
|
|
cast(other: CoercibleToExpr<Name> | string): Arith<Name> | RatNum<Name> | IntNum<Name> {
|
|
const sortTypeStr = isIntSort(this) ? 'IntSort' : 'RealSort';
|
|
if (isExpr(other)) {
|
|
const otherS = other.sort;
|
|
if (isArith(other)) {
|
|
if (this.eqIdentity(otherS)) {
|
|
return other;
|
|
} else if (isIntSort(otherS) && isRealSort(this)) {
|
|
return ToReal(other);
|
|
}
|
|
assert(false, "Can't cast Real to IntSort without loss");
|
|
} else if (isBool(other)) {
|
|
if (isIntSort(this)) {
|
|
return If(other, 1, 0);
|
|
} else {
|
|
return ToReal(If(other, 1, 0));
|
|
}
|
|
}
|
|
assert(false, `Can't cast expression to ${sortTypeStr}`);
|
|
} else {
|
|
if (typeof other !== 'boolean') {
|
|
if (isIntSort(this)) {
|
|
assert(!isCoercibleRational(other), "Can't cast fraction to IntSort");
|
|
return Int.val(other);
|
|
}
|
|
return Real.val(other);
|
|
}
|
|
assert(false, `Can't cast primitive to ${sortTypeStr}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function Sum(arg0: Arith<Name>, ...args: CoercibleToArith<Name>[]): Arith<Name>;
|
|
function Sum<Bits extends number>(
|
|
arg0: BitVec<Bits, Name>,
|
|
...args: CoercibleToBitVec<Bits, Name>[]
|
|
): BitVec<Bits, Name>;
|
|
function Sum<T extends Expr<Name>>(arg0: T, ...args: CoercibleToMap<T, Name>[]): T {
|
|
if (arg0 instanceof BitVecImpl) {
|
|
// Assert only 2
|
|
if (args.length !== 1) {
|
|
throw new Error('BitVec add only supports 2 arguments');
|
|
}
|
|
return new BitVecImpl<number>(
|
|
check(Z3.mk_bvadd(contextPtr, arg0.ast, arg0.sort.cast(args[0]).ast)),
|
|
) as unknown as T;
|
|
} else {
|
|
assert(arg0 instanceof ArithImpl);
|
|
return new ArithImpl(
|
|
check(Z3.mk_add(contextPtr, [arg0.ast].concat(args.map(arg => arg0.sort.cast(arg).ast)))),
|
|
) as unknown as T;
|
|
}
|
|
}
|
|
|
|
function Sub(arg0: Arith<Name>, ...args: CoercibleToArith<Name>[]): Arith<Name>;
|
|
function Sub<Bits extends number>(
|
|
arg0: BitVec<Bits, Name>,
|
|
...args: CoercibleToBitVec<Bits, Name>[]
|
|
): BitVec<Bits, Name>;
|
|
function Sub<T extends Expr<Name>>(arg0: T, ...args: CoercibleToMap<T, Name>[]): T {
|
|
if (arg0 instanceof BitVecImpl) {
|
|
// Assert only 2
|
|
if (args.length !== 1) {
|
|
throw new Error('BitVec sub only supports 2 arguments');
|
|
}
|
|
return new BitVecImpl<number>(
|
|
check(Z3.mk_bvsub(contextPtr, arg0.ast, arg0.sort.cast(args[0]).ast)),
|
|
) as unknown as T;
|
|
} else {
|
|
assert(arg0 instanceof ArithImpl);
|
|
return new ArithImpl(
|
|
check(Z3.mk_sub(contextPtr, [arg0.ast].concat(args.map(arg => arg0.sort.cast(arg).ast)))),
|
|
) as unknown as T;
|
|
}
|
|
}
|
|
|
|
function Product(arg0: Arith<Name>, ...args: CoercibleToArith<Name>[]): Arith<Name>;
|
|
function Product<Bits extends number>(
|
|
arg0: BitVec<Bits, Name>,
|
|
...args: CoercibleToBitVec<Bits, Name>[]
|
|
): BitVec<Bits, Name>;
|
|
function Product<T extends Expr<Name>>(arg0: T, ...args: CoercibleToMap<T, Name>[]): T {
|
|
if (arg0 instanceof BitVecImpl) {
|
|
// Assert only 2
|
|
if (args.length !== 1) {
|
|
throw new Error('BitVec mul only supports 2 arguments');
|
|
}
|
|
return new BitVecImpl<number>(
|
|
check(Z3.mk_bvmul(contextPtr, arg0.ast, arg0.sort.cast(args[0]).ast)),
|
|
) as unknown as T;
|
|
} else {
|
|
assert(arg0 instanceof ArithImpl);
|
|
return new ArithImpl(
|
|
check(Z3.mk_mul(contextPtr, [arg0.ast].concat(args.map(arg => arg0.sort.cast(arg).ast)))),
|
|
) as unknown as T;
|
|
}
|
|
}
|
|
|
|
function Div(arg0: Arith<Name>, arg1: CoercibleToArith<Name>): Arith<Name>;
|
|
function Div<Bits extends number>(
|
|
arg0: BitVec<Bits, Name>,
|
|
arg1: CoercibleToBitVec<Bits, Name>,
|
|
): BitVec<Bits, Name>;
|
|
function Div<T extends Expr<Name>>(arg0: T, arg1: CoercibleToMap<T, Name>): T {
|
|
if (arg0 instanceof BitVecImpl) {
|
|
return new BitVecImpl<number>(
|
|
check(Z3.mk_bvsdiv(contextPtr, arg0.ast, arg0.sort.cast(arg1).ast)),
|
|
) as unknown as T;
|
|
} else {
|
|
assert(arg0 instanceof ArithImpl);
|
|
return new ArithImpl(check(Z3.mk_div(contextPtr, arg0.ast, arg0.sort.cast(arg1).ast))) as unknown as T;
|
|
}
|
|
}
|
|
|
|
function BUDiv<Bits extends number>(
|
|
arg0: BitVec<Bits, Name>,
|
|
arg1: CoercibleToBitVec<Bits, Name>,
|
|
): BitVec<Bits, Name> {
|
|
return new BitVecImpl<number>(
|
|
check(Z3.mk_bvudiv(contextPtr, arg0.ast, arg0.sort.cast(arg1).ast)),
|
|
) as unknown as BitVec<Bits, Name>;
|
|
}
|
|
|
|
function Neg(a: Arith<Name>): Arith<Name>;
|
|
function Neg<Bits extends number>(a: BitVec<Bits, Name>): BitVec<Bits, Name>;
|
|
function Neg<T extends Expr<Name>>(a: T): T {
|
|
if (a instanceof BitVecImpl) {
|
|
return new BitVecImpl<number>(check(Z3.mk_bvneg(contextPtr, a.ast))) as unknown as T;
|
|
} else {
|
|
assert(a instanceof ArithImpl);
|
|
return new ArithImpl(check(Z3.mk_unary_minus(contextPtr, a.ast))) as unknown as T;
|
|
}
|
|
}
|
|
|
|
function Mod(a: Arith<Name>, b: CoercibleToArith<Name>): Arith<Name>;
|
|
function Mod<Bits extends number>(a: BitVec<Bits, Name>, b: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name>;
|
|
function Mod<T extends Expr<Name>>(a: T, b: CoercibleToMap<T, Name>): T {
|
|
if (a instanceof BitVecImpl) {
|
|
return new BitVecImpl<number>(check(Z3.mk_bvsrem(contextPtr, a.ast, a.sort.cast(b).ast))) as unknown as T;
|
|
} else {
|
|
assert(a instanceof ArithImpl);
|
|
return new ArithImpl(check(Z3.mk_mod(contextPtr, a.ast, a.sort.cast(b).ast))) as unknown as T;
|
|
}
|
|
}
|
|
|
|
class ArithImpl extends ExprImpl<Z3_ast, ArithSort<Name>> implements Arith<Name> {
|
|
declare readonly __typename: Arith['__typename'];
|
|
|
|
add(other: CoercibleToArith<Name>) {
|
|
return Sum(this, other);
|
|
}
|
|
|
|
mul(other: CoercibleToArith<Name>) {
|
|
return Product(this, other);
|
|
}
|
|
|
|
sub(other: CoercibleToArith<Name>) {
|
|
return Sub(this, other);
|
|
}
|
|
|
|
pow(exponent: CoercibleToArith<Name>) {
|
|
return new ArithImpl(check(Z3.mk_power(contextPtr, this.ast, this.sort.cast(exponent).ast)));
|
|
}
|
|
|
|
div(other: CoercibleToArith<Name>) {
|
|
return Div(this, other);
|
|
}
|
|
|
|
mod(other: CoercibleToArith<Name>) {
|
|
return Mod(this, other);
|
|
}
|
|
|
|
neg() {
|
|
return Neg(this);
|
|
}
|
|
|
|
le(other: CoercibleToArith<Name>) {
|
|
return LE(this, other);
|
|
}
|
|
|
|
lt(other: CoercibleToArith<Name>) {
|
|
return LT(this, other);
|
|
}
|
|
|
|
gt(other: CoercibleToArith<Name>) {
|
|
return GT(this, other);
|
|
}
|
|
|
|
ge(other: CoercibleToArith<Name>) {
|
|
return GE(this, other);
|
|
}
|
|
}
|
|
|
|
class IntNumImpl extends ArithImpl implements IntNum<Name> {
|
|
declare readonly __typename: IntNum['__typename'];
|
|
|
|
value() {
|
|
return BigInt(this.asString());
|
|
}
|
|
|
|
asString() {
|
|
return Z3.get_numeral_string(contextPtr, this.ast);
|
|
}
|
|
|
|
asBinary() {
|
|
return Z3.get_numeral_binary_string(contextPtr, this.ast);
|
|
}
|
|
}
|
|
|
|
class RatNumImpl extends ArithImpl implements RatNum<Name> {
|
|
declare readonly __typename: RatNum['__typename'];
|
|
|
|
value() {
|
|
return { numerator: this.numerator().value(), denominator: this.denominator().value() };
|
|
}
|
|
|
|
numerator() {
|
|
return new IntNumImpl(Z3.get_numerator(contextPtr, this.ast));
|
|
}
|
|
|
|
denominator() {
|
|
return new IntNumImpl(Z3.get_denominator(contextPtr, this.ast));
|
|
}
|
|
|
|
asNumber() {
|
|
const { numerator, denominator } = this.value();
|
|
const div = numerator / denominator;
|
|
return Number(div) + Number(numerator - div * denominator) / Number(denominator);
|
|
}
|
|
|
|
asDecimal(prec: number = Number.parseInt(getParam('precision') ?? FALLBACK_PRECISION.toString())) {
|
|
return Z3.get_numeral_decimal_string(contextPtr, this.ast, prec);
|
|
}
|
|
|
|
asString() {
|
|
return Z3.get_numeral_string(contextPtr, this.ast);
|
|
}
|
|
}
|
|
|
|
class RCFNumImpl implements RCFNum<Name> {
|
|
declare readonly __typename: RCFNum['__typename'];
|
|
readonly ctx: Context<Name>;
|
|
readonly ptr: Z3_rcf_num;
|
|
|
|
constructor(value: string | number);
|
|
constructor(ptr: Z3_rcf_num);
|
|
constructor(valueOrPtr: string | number | Z3_rcf_num) {
|
|
this.ctx = ctx;
|
|
let myPtr: Z3_rcf_num;
|
|
if (typeof valueOrPtr === 'string') {
|
|
myPtr = check(Z3.rcf_mk_rational(contextPtr, valueOrPtr));
|
|
} else if (typeof valueOrPtr === 'number') {
|
|
myPtr = check(Z3.rcf_mk_small_int(contextPtr, valueOrPtr));
|
|
} else {
|
|
myPtr = valueOrPtr;
|
|
}
|
|
this.ptr = myPtr;
|
|
cleanup.register(this, () => Z3.rcf_del(contextPtr, myPtr), this);
|
|
}
|
|
|
|
add(other: RCFNum<Name>): RCFNum<Name> {
|
|
_assertContext(other);
|
|
return new RCFNumImpl(check(Z3.rcf_add(contextPtr, this.ptr, (other as RCFNumImpl).ptr)));
|
|
}
|
|
|
|
sub(other: RCFNum<Name>): RCFNum<Name> {
|
|
_assertContext(other);
|
|
return new RCFNumImpl(check(Z3.rcf_sub(contextPtr, this.ptr, (other as RCFNumImpl).ptr)));
|
|
}
|
|
|
|
mul(other: RCFNum<Name>): RCFNum<Name> {
|
|
_assertContext(other);
|
|
return new RCFNumImpl(check(Z3.rcf_mul(contextPtr, this.ptr, (other as RCFNumImpl).ptr)));
|
|
}
|
|
|
|
div(other: RCFNum<Name>): RCFNum<Name> {
|
|
_assertContext(other);
|
|
return new RCFNumImpl(check(Z3.rcf_div(contextPtr, this.ptr, (other as RCFNumImpl).ptr)));
|
|
}
|
|
|
|
neg(): RCFNum<Name> {
|
|
return new RCFNumImpl(check(Z3.rcf_neg(contextPtr, this.ptr)));
|
|
}
|
|
|
|
inv(): RCFNum<Name> {
|
|
return new RCFNumImpl(check(Z3.rcf_inv(contextPtr, this.ptr)));
|
|
}
|
|
|
|
power(k: number): RCFNum<Name> {
|
|
return new RCFNumImpl(check(Z3.rcf_power(contextPtr, this.ptr, k)));
|
|
}
|
|
|
|
lt(other: RCFNum<Name>): boolean {
|
|
_assertContext(other);
|
|
return check(Z3.rcf_lt(contextPtr, this.ptr, (other as RCFNumImpl).ptr));
|
|
}
|
|
|
|
gt(other: RCFNum<Name>): boolean {
|
|
_assertContext(other);
|
|
return check(Z3.rcf_gt(contextPtr, this.ptr, (other as RCFNumImpl).ptr));
|
|
}
|
|
|
|
le(other: RCFNum<Name>): boolean {
|
|
_assertContext(other);
|
|
return check(Z3.rcf_le(contextPtr, this.ptr, (other as RCFNumImpl).ptr));
|
|
}
|
|
|
|
ge(other: RCFNum<Name>): boolean {
|
|
_assertContext(other);
|
|
return check(Z3.rcf_ge(contextPtr, this.ptr, (other as RCFNumImpl).ptr));
|
|
}
|
|
|
|
eq(other: RCFNum<Name>): boolean {
|
|
_assertContext(other);
|
|
return check(Z3.rcf_eq(contextPtr, this.ptr, (other as RCFNumImpl).ptr));
|
|
}
|
|
|
|
neq(other: RCFNum<Name>): boolean {
|
|
_assertContext(other);
|
|
return check(Z3.rcf_neq(contextPtr, this.ptr, (other as RCFNumImpl).ptr));
|
|
}
|
|
|
|
isRational(): boolean {
|
|
return check(Z3.rcf_is_rational(contextPtr, this.ptr));
|
|
}
|
|
|
|
isAlgebraic(): boolean {
|
|
return check(Z3.rcf_is_algebraic(contextPtr, this.ptr));
|
|
}
|
|
|
|
isInfinitesimal(): boolean {
|
|
return check(Z3.rcf_is_infinitesimal(contextPtr, this.ptr));
|
|
}
|
|
|
|
isTranscendental(): boolean {
|
|
return check(Z3.rcf_is_transcendental(contextPtr, this.ptr));
|
|
}
|
|
|
|
toString(compact: boolean = false): string {
|
|
return check(Z3.rcf_num_to_string(contextPtr, this.ptr, compact, false));
|
|
}
|
|
|
|
toDecimal(precision: number): string {
|
|
return check(Z3.rcf_num_to_decimal_string(contextPtr, this.ptr, precision));
|
|
}
|
|
}
|
|
|
|
class BitVecSortImpl<Bits extends number> extends SortImpl implements BitVecSort<Bits, Name> {
|
|
declare readonly __typename: BitVecSort['__typename'];
|
|
|
|
size() {
|
|
return Z3.get_bv_sort_size(contextPtr, this.ptr) as Bits;
|
|
}
|
|
|
|
subsort(other: Sort<Name>): boolean {
|
|
return isBitVecSort(other) && this.size() < other.size();
|
|
}
|
|
|
|
cast(other: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name>;
|
|
cast(other: CoercibleToExpr<Name>): Expr<Name>;
|
|
cast(other: CoercibleToExpr<Name>): Expr<Name> {
|
|
if (isExpr(other)) {
|
|
_assertContext(other);
|
|
return other;
|
|
}
|
|
assert(!isCoercibleRational(other), "Can't convert rational to BitVec");
|
|
return BitVec.val(other, this.size());
|
|
}
|
|
}
|
|
|
|
class BitVecImpl<Bits extends number> extends ExprImpl<Z3_ast, BitVecSortImpl<Bits>> implements BitVec<Bits, Name> {
|
|
declare readonly __typename: BitVec['__typename'];
|
|
|
|
size() {
|
|
return this.sort.size();
|
|
}
|
|
|
|
add(other: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return Sum(this, other);
|
|
}
|
|
|
|
mul(other: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return Product(this, other);
|
|
}
|
|
|
|
sub(other: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return Sub(this, other);
|
|
}
|
|
|
|
sdiv(other: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return Div(this, other);
|
|
}
|
|
|
|
udiv(other: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return BUDiv(this, other);
|
|
}
|
|
|
|
smod(other: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return Mod(this, other);
|
|
}
|
|
|
|
urem(other: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_bvurem(contextPtr, this.ast, this.sort.cast(other).ast)));
|
|
}
|
|
|
|
srem(other: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_bvsrem(contextPtr, this.ast, this.sort.cast(other).ast)));
|
|
}
|
|
|
|
neg(): BitVec<Bits, Name> {
|
|
return Neg(this);
|
|
}
|
|
|
|
or(other: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_bvor(contextPtr, this.ast, this.sort.cast(other).ast)));
|
|
}
|
|
|
|
and(other: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_bvand(contextPtr, this.ast, this.sort.cast(other).ast)));
|
|
}
|
|
|
|
nand(other: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_bvnand(contextPtr, this.ast, this.sort.cast(other).ast)));
|
|
}
|
|
|
|
xor(other: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_bvxor(contextPtr, this.ast, this.sort.cast(other).ast)));
|
|
}
|
|
|
|
xnor(other: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_bvxnor(contextPtr, this.ast, this.sort.cast(other).ast)));
|
|
}
|
|
|
|
shr(count: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_bvashr(contextPtr, this.ast, this.sort.cast(count).ast)));
|
|
}
|
|
|
|
lshr(count: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_bvlshr(contextPtr, this.ast, this.sort.cast(count).ast)));
|
|
}
|
|
|
|
shl(count: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_bvshl(contextPtr, this.ast, this.sort.cast(count).ast)));
|
|
}
|
|
|
|
rotateRight(count: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_ext_rotate_right(contextPtr, this.ast, this.sort.cast(count).ast)));
|
|
}
|
|
|
|
rotateLeft(count: CoercibleToBitVec<Bits, Name>): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_ext_rotate_left(contextPtr, this.ast, this.sort.cast(count).ast)));
|
|
}
|
|
|
|
not(): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_bvnot(contextPtr, this.ast)));
|
|
}
|
|
|
|
extract(high: number, low: number): BitVec<number, Name> {
|
|
return Extract(high, low, this);
|
|
}
|
|
|
|
signExt(count: number): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_sign_ext(contextPtr, count, this.ast)));
|
|
}
|
|
|
|
zeroExt(count: number): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_zero_ext(contextPtr, count, this.ast)));
|
|
}
|
|
|
|
repeat(count: number): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_repeat(contextPtr, count, this.ast)));
|
|
}
|
|
|
|
sle(other: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return SLE(this, other);
|
|
}
|
|
|
|
ule(other: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return ULE(this, other);
|
|
}
|
|
|
|
slt(other: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return SLT(this, other);
|
|
}
|
|
|
|
ult(other: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return ULT(this, other);
|
|
}
|
|
|
|
sge(other: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return SGE(this, other);
|
|
}
|
|
|
|
uge(other: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return UGE(this, other);
|
|
}
|
|
|
|
sgt(other: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return SGT(this, other);
|
|
}
|
|
|
|
ugt(other: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return UGT(this, other);
|
|
}
|
|
|
|
redAnd(): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_bvredand(contextPtr, this.ast)));
|
|
}
|
|
|
|
redOr(): BitVec<Bits, Name> {
|
|
return new BitVecImpl<Bits>(check(Z3.mk_bvredor(contextPtr, this.ast)));
|
|
}
|
|
|
|
addNoOverflow(other: CoercibleToBitVec<Bits, Name>, isSigned: boolean): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvadd_no_overflow(contextPtr, this.ast, this.sort.cast(other).ast, isSigned)));
|
|
}
|
|
|
|
addNoUnderflow(other: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvadd_no_underflow(contextPtr, this.ast, this.sort.cast(other).ast)));
|
|
}
|
|
|
|
subNoOverflow(other: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvsub_no_overflow(contextPtr, this.ast, this.sort.cast(other).ast)));
|
|
}
|
|
|
|
subNoUnderflow(other: CoercibleToBitVec<Bits, Name>, isSigned: boolean): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvsub_no_underflow(contextPtr, this.ast, this.sort.cast(other).ast, isSigned)));
|
|
}
|
|
|
|
sdivNoOverflow(other: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvsdiv_no_overflow(contextPtr, this.ast, this.sort.cast(other).ast)));
|
|
}
|
|
|
|
mulNoOverflow(other: CoercibleToBitVec<Bits, Name>, isSigned: boolean): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvmul_no_overflow(contextPtr, this.ast, this.sort.cast(other).ast, isSigned)));
|
|
}
|
|
|
|
mulNoUnderflow(other: CoercibleToBitVec<Bits, Name>): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvmul_no_underflow(contextPtr, this.ast, this.sort.cast(other).ast)));
|
|
}
|
|
|
|
negNoOverflow(): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_bvneg_no_overflow(contextPtr, this.ast)));
|
|
}
|
|
}
|
|
|
|
class BitVecNumImpl<Bits extends number> extends BitVecImpl<Bits> implements BitVecNum<Bits, Name> {
|
|
declare readonly __typename: BitVecNum['__typename'];
|
|
|
|
value() {
|
|
return BigInt(this.asString());
|
|
}
|
|
|
|
asSignedValue() {
|
|
let val = this.value();
|
|
const size = BigInt(this.size());
|
|
if (val >= 2n ** (size - 1n)) {
|
|
val = val - 2n ** size;
|
|
}
|
|
if (val < (-2n) ** (size - 1n)) {
|
|
val = val + 2n ** size;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
asString() {
|
|
return Z3.get_numeral_string(contextPtr, this.ast);
|
|
}
|
|
|
|
asBinaryString() {
|
|
return Z3.get_numeral_binary_string(contextPtr, this.ast);
|
|
}
|
|
}
|
|
|
|
class FPRMSortImpl extends SortImpl implements FPRMSort<Name> {
|
|
declare readonly __typename: FPRMSort['__typename'];
|
|
|
|
cast(other: FPRM<Name>): FPRM<Name>;
|
|
cast(other: CoercibleToExpr<Name>): never;
|
|
cast(other: any): any {
|
|
if (isFPRM(other)) {
|
|
_assertContext(other);
|
|
return other;
|
|
}
|
|
throw new Error("Can't cast to FPRMSort");
|
|
}
|
|
}
|
|
|
|
class FPRMImpl extends ExprImpl<Z3_ast, FPRMSortImpl> implements FPRM<Name> {
|
|
declare readonly __typename: FPRM['__typename'];
|
|
}
|
|
|
|
class FPSortImpl extends SortImpl implements FPSort<Name> {
|
|
declare readonly __typename: FPSort['__typename'];
|
|
|
|
ebits() {
|
|
return Z3.fpa_get_ebits(contextPtr, this.ptr);
|
|
}
|
|
|
|
sbits() {
|
|
return Z3.fpa_get_sbits(contextPtr, this.ptr);
|
|
}
|
|
|
|
cast(other: CoercibleToFP<Name>): FP<Name>;
|
|
cast(other: CoercibleToExpr<Name>): Expr<Name>;
|
|
cast(other: CoercibleToExpr<Name>): Expr<Name> {
|
|
if (isExpr(other)) {
|
|
_assertContext(other);
|
|
return other;
|
|
}
|
|
if (typeof other === 'number') {
|
|
return Float.val(other, this);
|
|
}
|
|
throw new Error("Can't cast to FPSort");
|
|
}
|
|
}
|
|
|
|
class FPImpl extends ExprImpl<Z3_ast, FPSortImpl> implements FP<Name> {
|
|
declare readonly __typename: FP['__typename'];
|
|
|
|
add(rm: FPRM<Name>, other: CoercibleToFP<Name>): FP<Name> {
|
|
const otherFP = isFP(other) ? other : Float.val(other, this.sort);
|
|
return new FPImpl(check(Z3.mk_fpa_add(contextPtr, rm.ast, this.ast, otherFP.ast)));
|
|
}
|
|
|
|
sub(rm: FPRM<Name>, other: CoercibleToFP<Name>): FP<Name> {
|
|
const otherFP = isFP(other) ? other : Float.val(other, this.sort);
|
|
return new FPImpl(check(Z3.mk_fpa_sub(contextPtr, rm.ast, this.ast, otherFP.ast)));
|
|
}
|
|
|
|
mul(rm: FPRM<Name>, other: CoercibleToFP<Name>): FP<Name> {
|
|
const otherFP = isFP(other) ? other : Float.val(other, this.sort);
|
|
return new FPImpl(check(Z3.mk_fpa_mul(contextPtr, rm.ast, this.ast, otherFP.ast)));
|
|
}
|
|
|
|
div(rm: FPRM<Name>, other: CoercibleToFP<Name>): FP<Name> {
|
|
const otherFP = isFP(other) ? other : Float.val(other, this.sort);
|
|
return new FPImpl(check(Z3.mk_fpa_div(contextPtr, rm.ast, this.ast, otherFP.ast)));
|
|
}
|
|
|
|
neg(): FP<Name> {
|
|
return new FPImpl(check(Z3.mk_fpa_neg(contextPtr, this.ast)));
|
|
}
|
|
|
|
abs(): FP<Name> {
|
|
return new FPImpl(check(Z3.mk_fpa_abs(contextPtr, this.ast)));
|
|
}
|
|
|
|
sqrt(rm: FPRM<Name>): FP<Name> {
|
|
return new FPImpl(check(Z3.mk_fpa_sqrt(contextPtr, rm.ast, this.ast)));
|
|
}
|
|
|
|
rem(other: CoercibleToFP<Name>): FP<Name> {
|
|
const otherFP = isFP(other) ? other : Float.val(other, this.sort);
|
|
return new FPImpl(check(Z3.mk_fpa_rem(contextPtr, this.ast, otherFP.ast)));
|
|
}
|
|
|
|
fma(rm: FPRM<Name>, y: CoercibleToFP<Name>, z: CoercibleToFP<Name>): FP<Name> {
|
|
const yFP = isFP(y) ? y : Float.val(y, this.sort);
|
|
const zFP = isFP(z) ? z : Float.val(z, this.sort);
|
|
return new FPImpl(check(Z3.mk_fpa_fma(contextPtr, rm.ast, this.ast, yFP.ast, zFP.ast)));
|
|
}
|
|
|
|
lt(other: CoercibleToFP<Name>): Bool<Name> {
|
|
const otherFP = isFP(other) ? other : Float.val(other, this.sort);
|
|
return new BoolImpl(check(Z3.mk_fpa_lt(contextPtr, this.ast, otherFP.ast)));
|
|
}
|
|
|
|
gt(other: CoercibleToFP<Name>): Bool<Name> {
|
|
const otherFP = isFP(other) ? other : Float.val(other, this.sort);
|
|
return new BoolImpl(check(Z3.mk_fpa_gt(contextPtr, this.ast, otherFP.ast)));
|
|
}
|
|
|
|
le(other: CoercibleToFP<Name>): Bool<Name> {
|
|
const otherFP = isFP(other) ? other : Float.val(other, this.sort);
|
|
return new BoolImpl(check(Z3.mk_fpa_leq(contextPtr, this.ast, otherFP.ast)));
|
|
}
|
|
|
|
ge(other: CoercibleToFP<Name>): Bool<Name> {
|
|
const otherFP = isFP(other) ? other : Float.val(other, this.sort);
|
|
return new BoolImpl(check(Z3.mk_fpa_geq(contextPtr, this.ast, otherFP.ast)));
|
|
}
|
|
|
|
isNaN(): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_fpa_is_nan(contextPtr, this.ast)));
|
|
}
|
|
|
|
isInf(): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_fpa_is_infinite(contextPtr, this.ast)));
|
|
}
|
|
|
|
isZero(): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_fpa_is_zero(contextPtr, this.ast)));
|
|
}
|
|
|
|
isNormal(): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_fpa_is_normal(contextPtr, this.ast)));
|
|
}
|
|
|
|
isSubnormal(): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_fpa_is_subnormal(contextPtr, this.ast)));
|
|
}
|
|
|
|
isNegative(): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_fpa_is_negative(contextPtr, this.ast)));
|
|
}
|
|
|
|
isPositive(): Bool<Name> {
|
|
return new BoolImpl(check(Z3.mk_fpa_is_positive(contextPtr, this.ast)));
|
|
}
|
|
}
|
|
|
|
class FPNumImpl extends FPImpl implements FPNum<Name> {
|
|
declare readonly __typename: FPNum['__typename'];
|
|
|
|
value(): number {
|
|
// Get the floating-point numeral as a JavaScript number
|
|
// Note: This may lose precision for values outside JavaScript number range
|
|
return Z3.get_numeral_double(contextPtr, this.ast);
|
|
}
|
|
}
|
|
|
|
class SeqSortImpl<ElemSort extends Sort<Name> = Sort<Name>> extends SortImpl implements SeqSort<Name, ElemSort> {
|
|
declare readonly __typename: SeqSort['__typename'];
|
|
|
|
isString(): boolean {
|
|
return Z3.is_string_sort(contextPtr, this.ptr);
|
|
}
|
|
|
|
basis(): Sort<Name> {
|
|
return _toSort(check(Z3.get_seq_sort_basis(contextPtr, this.ptr)));
|
|
}
|
|
|
|
cast(other: Seq<Name>): Seq<Name>;
|
|
cast(other: string): Seq<Name>;
|
|
cast(other: CoercibleToExpr<Name>): Expr<Name>;
|
|
cast(other: any): any {
|
|
if (isSeq(other)) {
|
|
_assertContext(other);
|
|
return other;
|
|
}
|
|
if (typeof other === 'string') {
|
|
return String.val(other);
|
|
}
|
|
throw new Error("Can't cast to SeqSort");
|
|
}
|
|
}
|
|
|
|
class SeqImpl<ElemSort extends Sort<Name> = Sort<Name>>
|
|
extends ExprImpl<Z3_ast, SeqSortImpl<ElemSort>>
|
|
implements Seq<Name, ElemSort>
|
|
{
|
|
declare readonly __typename: Seq['__typename'];
|
|
|
|
isString(): boolean {
|
|
return Z3.is_string_sort(contextPtr, Z3.get_sort(contextPtr, this.ast));
|
|
}
|
|
|
|
asString(): string {
|
|
if (!Z3.is_string(contextPtr, this.ast)) {
|
|
throw new Error('Not a string value');
|
|
}
|
|
return Z3.get_string(contextPtr, this.ast);
|
|
}
|
|
|
|
concat(other: Seq<Name, ElemSort> | string): Seq<Name, ElemSort> {
|
|
const otherSeq = isSeq(other) ? other : String.val(other);
|
|
return new SeqImpl<ElemSort>(check(Z3.mk_seq_concat(contextPtr, [this.ast, otherSeq.ast])));
|
|
}
|
|
|
|
length(): Arith<Name> {
|
|
return new ArithImpl(check(Z3.mk_seq_length(contextPtr, this.ast)));
|
|
}
|
|
|
|
at(index: Arith<Name> | number | bigint): Seq<Name, ElemSort> {
|
|
const indexExpr = isArith(index) ? index : Int.val(index);
|
|
return new SeqImpl<ElemSort>(check(Z3.mk_seq_at(contextPtr, this.ast, indexExpr.ast)));
|
|
}
|
|
|
|
nth(index: Arith<Name> | number | bigint): Expr<Name> {
|
|
const indexExpr = isArith(index) ? index : Int.val(index);
|
|
return _toExpr(check(Z3.mk_seq_nth(contextPtr, this.ast, indexExpr.ast)));
|
|
}
|
|
|
|
extract(offset: Arith<Name> | number | bigint, length: Arith<Name> | number | bigint): Seq<Name, ElemSort> {
|
|
const offsetExpr = isArith(offset) ? offset : Int.val(offset);
|
|
const lengthExpr = isArith(length) ? length : Int.val(length);
|
|
return new SeqImpl<ElemSort>(check(Z3.mk_seq_extract(contextPtr, this.ast, offsetExpr.ast, lengthExpr.ast)));
|
|
}
|
|
|
|
indexOf(substr: Seq<Name, ElemSort> | string, offset?: Arith<Name> | number | bigint): Arith<Name> {
|
|
const substrSeq = isSeq(substr) ? substr : String.val(substr);
|
|
const offsetExpr = offset !== undefined ? (isArith(offset) ? offset : Int.val(offset)) : Int.val(0);
|
|
return new ArithImpl(check(Z3.mk_seq_index(contextPtr, this.ast, substrSeq.ast, offsetExpr.ast)));
|
|
}
|
|
|
|
lastIndexOf(substr: Seq<Name, ElemSort> | string): Arith<Name> {
|
|
const substrSeq = isSeq(substr) ? substr : String.val(substr);
|
|
return new ArithImpl(check(Z3.mk_seq_last_index(contextPtr, this.ast, substrSeq.ast)));
|
|
}
|
|
|
|
contains(substr: Seq<Name, ElemSort> | string): Bool<Name> {
|
|
const substrSeq = isSeq(substr) ? substr : String.val(substr);
|
|
return new BoolImpl(check(Z3.mk_seq_contains(contextPtr, this.ast, substrSeq.ast)));
|
|
}
|
|
|
|
prefixOf(s: Seq<Name, ElemSort> | string): Bool<Name> {
|
|
const sSeq = isSeq(s) ? s : String.val(s);
|
|
return new BoolImpl(check(Z3.mk_seq_prefix(contextPtr, this.ast, sSeq.ast)));
|
|
}
|
|
|
|
suffixOf(s: Seq<Name, ElemSort> | string): Bool<Name> {
|
|
const sSeq = isSeq(s) ? s : String.val(s);
|
|
return new BoolImpl(check(Z3.mk_seq_suffix(contextPtr, this.ast, sSeq.ast)));
|
|
}
|
|
|
|
replace(src: Seq<Name, ElemSort> | string, dst: Seq<Name, ElemSort> | string): Seq<Name, ElemSort> {
|
|
const srcSeq = isSeq(src) ? src : String.val(src);
|
|
const dstSeq = isSeq(dst) ? dst : String.val(dst);
|
|
return new SeqImpl<ElemSort>(check(Z3.mk_seq_replace(contextPtr, this.ast, srcSeq.ast, dstSeq.ast)));
|
|
}
|
|
|
|
replaceAll(src: Seq<Name, ElemSort> | string, dst: Seq<Name, ElemSort> | string): Seq<Name, ElemSort> {
|
|
const srcSeq = isSeq(src) ? src : String.val(src);
|
|
const dstSeq = isSeq(dst) ? dst : String.val(dst);
|
|
return new SeqImpl<ElemSort>(check(Z3.mk_seq_replace_all(contextPtr, this.ast, srcSeq.ast, dstSeq.ast)));
|
|
}
|
|
}
|
|
|
|
class ReSortImpl<SeqSortRef extends SeqSort<Name> = SeqSort<Name>> extends SortImpl implements ReSort<Name, SeqSortRef> {
|
|
declare readonly __typename: ReSort['__typename'];
|
|
|
|
basis(): SeqSortRef {
|
|
return _toSort(check(Z3.get_re_sort_basis(contextPtr, this.ptr))) as SeqSortRef;
|
|
}
|
|
|
|
cast(other: Re<Name, SeqSortRef>): Re<Name, SeqSortRef>;
|
|
cast(other: CoercibleToExpr<Name>): Expr<Name>;
|
|
cast(other: any): any {
|
|
if (isRe(other)) {
|
|
_assertContext(other);
|
|
return other;
|
|
}
|
|
throw new Error("Can't cast to ReSort");
|
|
}
|
|
}
|
|
|
|
class ReImpl<SeqSortRef extends SeqSort<Name> = SeqSort<Name>>
|
|
extends ExprImpl<Z3_ast, ReSortImpl<SeqSortRef>>
|
|
implements Re<Name, SeqSortRef>
|
|
{
|
|
declare readonly __typename: Re['__typename'];
|
|
|
|
plus(): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_plus(contextPtr, this.ast)));
|
|
}
|
|
|
|
star(): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_star(contextPtr, this.ast)));
|
|
}
|
|
|
|
option(): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_option(contextPtr, this.ast)));
|
|
}
|
|
|
|
complement(): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_complement(contextPtr, this.ast)));
|
|
}
|
|
|
|
union(other: Re<Name, SeqSortRef>): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_union(contextPtr, [this.ast, other.ast])));
|
|
}
|
|
|
|
intersect(other: Re<Name, SeqSortRef>): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_intersect(contextPtr, [this.ast, other.ast])));
|
|
}
|
|
|
|
diff(other: Re<Name, SeqSortRef>): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_diff(contextPtr, this.ast, other.ast)));
|
|
}
|
|
|
|
concat(other: Re<Name, SeqSortRef>): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_concat(contextPtr, [this.ast, other.ast])));
|
|
}
|
|
|
|
loop(lo: number, hi: number = 0): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_loop(contextPtr, this.ast, lo, hi)));
|
|
}
|
|
|
|
power(n: number): Re<Name, SeqSortRef> {
|
|
return new ReImpl<SeqSortRef>(check(Z3.mk_re_power(contextPtr, this.ast, n)));
|
|
}
|
|
}
|
|
|
|
class ArraySortImpl<DomainSort extends NonEmptySortArray<Name>, RangeSort extends Sort<Name>>
|
|
extends SortImpl
|
|
implements SMTArraySort<Name, DomainSort, RangeSort>
|
|
{
|
|
declare readonly __typename: SMTArraySort['__typename'];
|
|
|
|
domain(): DomainSort[0] {
|
|
return _toSort(check(Z3.get_array_sort_domain(contextPtr, this.ptr)));
|
|
}
|
|
|
|
domain_n<T extends number>(i: T): DomainSort[T] {
|
|
return _toSort(check(Z3.get_array_sort_domain_n(contextPtr, this.ptr, i)));
|
|
}
|
|
|
|
range(): RangeSort {
|
|
return _toSort(check(Z3.get_array_sort_range(contextPtr, this.ptr))) as RangeSort;
|
|
}
|
|
}
|
|
|
|
class ArrayImpl<DomainSort extends NonEmptySortArray<Name>, RangeSort extends Sort<Name>>
|
|
extends ExprImpl<Z3_ast, ArraySortImpl<DomainSort, RangeSort>>
|
|
implements SMTArray<Name, DomainSort, RangeSort>
|
|
{
|
|
declare readonly __typename: 'Array' | 'Lambda';
|
|
|
|
domain(): DomainSort[0] {
|
|
return this.sort.domain();
|
|
}
|
|
|
|
domain_n<T extends number>(i: T): DomainSort[T] {
|
|
return this.sort.domain_n(i);
|
|
}
|
|
|
|
range(): RangeSort {
|
|
return this.sort.range();
|
|
}
|
|
|
|
select(...indices: CoercibleToArrayIndexType<Name, DomainSort>): SortToExprMap<RangeSort, Name> {
|
|
return Select<DomainSort, RangeSort>(this, ...indices) as SortToExprMap<RangeSort, Name>;
|
|
}
|
|
|
|
store(
|
|
...indicesAndValue: [
|
|
...CoercibleToArrayIndexType<Name, DomainSort>,
|
|
CoercibleToMap<SortToExprMap<RangeSort, Name>, Name>,
|
|
]
|
|
): SMTArray<Name, DomainSort, RangeSort> {
|
|
return Store(this, ...indicesAndValue);
|
|
}
|
|
|
|
/**
|
|
* Access the array default value.
|
|
* Produces the default range value, for arrays that can be represented as
|
|
* finite maps with a default range value.
|
|
*/
|
|
default(): SortToExprMap<RangeSort, Name> {
|
|
return _toExpr(check(Z3.mk_array_default(contextPtr, this.ast))) as SortToExprMap<RangeSort, Name>;
|
|
}
|
|
}
|
|
|
|
class SetImpl<ElemSort extends Sort<Name>>
|
|
extends ExprImpl<Z3_ast, ArraySortImpl<[ElemSort], BoolSort<Name>>>
|
|
implements SMTSet<Name, ElemSort>
|
|
{
|
|
declare readonly __typename: 'Array';
|
|
|
|
elemSort(): ElemSort {
|
|
return this.sort.domain();
|
|
}
|
|
union(...args: SMTSet<Name, ElemSort>[]): SMTSet<Name, ElemSort> {
|
|
return SetUnion(this, ...args);
|
|
}
|
|
intersect(...args: SMTSet<Name, ElemSort>[]): SMTSet<Name, ElemSort> {
|
|
return SetIntersect(this, ...args);
|
|
}
|
|
diff(b: SMTSet<Name, ElemSort>): SMTSet<Name, ElemSort> {
|
|
return SetDifference(this, b);
|
|
}
|
|
add(elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>): SMTSet<Name, ElemSort> {
|
|
return SetAdd(this, elem);
|
|
}
|
|
del(elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>): SMTSet<Name, ElemSort> {
|
|
return SetDel(this, elem);
|
|
}
|
|
complement(): SMTSet<Name, ElemSort> {
|
|
return SetComplement(this);
|
|
}
|
|
contains(elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>): Bool<Name> {
|
|
return isMember(elem, this);
|
|
}
|
|
subsetOf(b: SMTSet<Name, ElemSort>): Bool<Name> {
|
|
return isSubset(this, b);
|
|
}
|
|
}
|
|
|
|
////////////////////////////
|
|
// Datatypes
|
|
////////////////////////////
|
|
|
|
class DatatypeImpl implements Datatype<Name> {
|
|
readonly ctx: Context<Name>;
|
|
readonly name: string;
|
|
public constructors: Array<[string, Array<[string, Sort<Name> | Datatype<Name>]>]> = [];
|
|
|
|
constructor(ctx: Context<Name>, name: string) {
|
|
this.ctx = ctx;
|
|
this.name = name;
|
|
}
|
|
|
|
declare(name: string, ...fields: Array<[string, Sort<Name> | Datatype<Name>]>): this {
|
|
this.constructors.push([name, fields]);
|
|
return this;
|
|
}
|
|
|
|
create(): DatatypeSort<Name> {
|
|
const datatypes = createDatatypes(this);
|
|
return datatypes[0];
|
|
}
|
|
}
|
|
|
|
class DatatypeSortImpl extends SortImpl implements DatatypeSort<Name> {
|
|
declare readonly __typename: DatatypeSort['__typename'];
|
|
|
|
numConstructors(): number {
|
|
return Z3.get_datatype_sort_num_constructors(contextPtr, this.ptr);
|
|
}
|
|
|
|
constructorDecl(idx: number): FuncDecl<Name> {
|
|
const ptr = Z3.get_datatype_sort_constructor(contextPtr, this.ptr, idx);
|
|
return new FuncDeclImpl(ptr);
|
|
}
|
|
|
|
recognizer(idx: number): FuncDecl<Name> {
|
|
const ptr = Z3.get_datatype_sort_recognizer(contextPtr, this.ptr, idx);
|
|
return new FuncDeclImpl(ptr);
|
|
}
|
|
|
|
accessor(constructorIdx: number, accessorIdx: number): FuncDecl<Name> {
|
|
const ptr = Z3.get_datatype_sort_constructor_accessor(contextPtr, this.ptr, constructorIdx, accessorIdx);
|
|
return new FuncDeclImpl(ptr);
|
|
}
|
|
|
|
cast(other: CoercibleToExpr<Name>): DatatypeExpr<Name>;
|
|
cast(other: DatatypeExpr<Name>): DatatypeExpr<Name>;
|
|
cast(other: CoercibleToExpr<Name> | DatatypeExpr<Name>): DatatypeExpr<Name> {
|
|
if (isExpr(other)) {
|
|
assert(this.eqIdentity(other.sort), 'Value cannot be converted to this datatype');
|
|
return other as DatatypeExpr<Name>;
|
|
}
|
|
throw new Error('Cannot coerce value to datatype expression');
|
|
}
|
|
|
|
subsort(other: Sort<Name>) {
|
|
_assertContext(other.ctx);
|
|
return this.eqIdentity(other);
|
|
}
|
|
}
|
|
|
|
class DatatypeExprImpl extends ExprImpl<Z3_ast, DatatypeSortImpl> implements DatatypeExpr<Name> {
|
|
declare readonly __typename: DatatypeExpr['__typename'];
|
|
}
|
|
|
|
function createDatatypes(...datatypes: DatatypeImpl[]): DatatypeSortImpl[] {
|
|
if (datatypes.length === 0) {
|
|
throw new Error('At least one datatype must be provided');
|
|
}
|
|
|
|
// All datatypes must be from the same context
|
|
const dtCtx = datatypes[0].ctx;
|
|
for (const dt of datatypes) {
|
|
if (dt.ctx !== dtCtx) {
|
|
throw new Error('All datatypes must be from the same context');
|
|
}
|
|
}
|
|
|
|
const sortNames = datatypes.map(dt => dt.name);
|
|
const constructorLists: Z3_constructor_list[] = [];
|
|
const scopedConstructors: Z3_constructor[] = [];
|
|
|
|
try {
|
|
// Create constructor lists for each datatype
|
|
for (const dt of datatypes) {
|
|
const constructors: Z3_constructor[] = [];
|
|
|
|
for (const [constructorName, fields] of dt.constructors) {
|
|
const fieldNames: string[] = [];
|
|
const fieldSorts: Z3_sort[] = [];
|
|
const fieldRefs: number[] = [];
|
|
|
|
for (const [fieldName, fieldSort] of fields) {
|
|
fieldNames.push(fieldName);
|
|
|
|
if (fieldSort instanceof DatatypeImpl) {
|
|
// Reference to another datatype being defined
|
|
const refIndex = datatypes.indexOf(fieldSort);
|
|
if (refIndex === -1) {
|
|
throw new Error(`Referenced datatype "${fieldSort.name}" not found in datatypes being created`);
|
|
}
|
|
// For recursive references, we pass null and the ref index
|
|
fieldSorts.push(null as any); // null will be handled by the Z3 API
|
|
fieldRefs.push(refIndex);
|
|
} else {
|
|
// Regular sort
|
|
fieldSorts.push((fieldSort as Sort<Name>).ptr);
|
|
fieldRefs.push(0);
|
|
}
|
|
}
|
|
|
|
const constructor = Z3.mk_constructor(
|
|
contextPtr,
|
|
Z3.mk_string_symbol(contextPtr, constructorName),
|
|
Z3.mk_string_symbol(contextPtr, `is_${constructorName}`),
|
|
fieldNames.map(name => Z3.mk_string_symbol(contextPtr, name)),
|
|
fieldSorts,
|
|
fieldRefs,
|
|
);
|
|
constructors.push(constructor);
|
|
scopedConstructors.push(constructor);
|
|
}
|
|
|
|
const constructorList = Z3.mk_constructor_list(contextPtr, constructors);
|
|
constructorLists.push(constructorList);
|
|
}
|
|
|
|
// Create the datatypes
|
|
const sortSymbols = sortNames.map(name => Z3.mk_string_symbol(contextPtr, name));
|
|
const resultSorts = Z3.mk_datatypes(contextPtr, sortSymbols, constructorLists);
|
|
|
|
// Create DatatypeSortImpl instances
|
|
const results: DatatypeSortImpl[] = [];
|
|
for (let i = 0; i < resultSorts.length; i++) {
|
|
const sortImpl = new DatatypeSortImpl(resultSorts[i]);
|
|
|
|
// Attach constructor, recognizer, and accessor functions dynamically
|
|
const numConstructors = sortImpl.numConstructors();
|
|
for (let j = 0; j < numConstructors; j++) {
|
|
const constructor = sortImpl.constructorDecl(j);
|
|
const recognizer = sortImpl.recognizer(j);
|
|
const constructorName = constructor.name().toString();
|
|
|
|
// Attach constructor function
|
|
if (constructor.arity() === 0) {
|
|
// Nullary constructor (constant)
|
|
(sortImpl as any)[constructorName] = constructor.call();
|
|
} else {
|
|
(sortImpl as any)[constructorName] = constructor;
|
|
}
|
|
|
|
// Attach recognizer function
|
|
(sortImpl as any)[`is_${constructorName}`] = recognizer;
|
|
|
|
// Attach accessor functions
|
|
for (let k = 0; k < constructor.arity(); k++) {
|
|
const accessor = sortImpl.accessor(j, k);
|
|
const accessorName = accessor.name().toString();
|
|
(sortImpl as any)[accessorName] = accessor;
|
|
}
|
|
}
|
|
|
|
results.push(sortImpl);
|
|
}
|
|
|
|
return results;
|
|
} finally {
|
|
// Clean up resources
|
|
for (const constructor of scopedConstructors) {
|
|
Z3.del_constructor(contextPtr, constructor);
|
|
}
|
|
for (const constructorList of constructorLists) {
|
|
Z3.del_constructor_list(contextPtr, constructorList);
|
|
}
|
|
}
|
|
}
|
|
|
|
class QuantifierImpl<
|
|
QVarSorts extends NonEmptySortArray<Name>,
|
|
QSort extends BoolSort<Name> | SMTArraySort<Name, QVarSorts>,
|
|
>
|
|
extends ExprImpl<Z3_ast, QSort>
|
|
implements Quantifier<Name, QVarSorts, QSort>
|
|
{
|
|
declare readonly __typename: Quantifier['__typename'];
|
|
|
|
is_forall(): boolean {
|
|
return Z3.is_quantifier_forall(contextPtr, this.ast);
|
|
}
|
|
|
|
is_exists(): boolean {
|
|
return Z3.is_quantifier_exists(contextPtr, this.ast);
|
|
}
|
|
|
|
is_lambda(): boolean {
|
|
return Z3.is_lambda(contextPtr, this.ast);
|
|
}
|
|
|
|
weight(): number {
|
|
return Z3.get_quantifier_weight(contextPtr, this.ast);
|
|
}
|
|
|
|
num_patterns(): number {
|
|
return Z3.get_quantifier_num_patterns(contextPtr, this.ast);
|
|
}
|
|
|
|
pattern(i: number): Pattern<Name> {
|
|
return new PatternImpl(check(Z3.get_quantifier_pattern_ast(contextPtr, this.ast, i)));
|
|
}
|
|
|
|
num_no_patterns(): number {
|
|
return Z3.get_quantifier_num_no_patterns(contextPtr, this.ast);
|
|
}
|
|
|
|
no_pattern(i: number): Expr<Name> {
|
|
return _toExpr(check(Z3.get_quantifier_no_pattern_ast(contextPtr, this.ast, i)));
|
|
}
|
|
|
|
body(): BodyT<Name, QVarSorts, QSort> {
|
|
return _toExpr(check(Z3.get_quantifier_body(contextPtr, this.ast))) as any;
|
|
}
|
|
|
|
num_vars(): number {
|
|
return Z3.get_quantifier_num_bound(contextPtr, this.ast);
|
|
}
|
|
|
|
var_name(i: number): string | number {
|
|
return _fromSymbol(Z3.get_quantifier_bound_name(contextPtr, this.ast, i));
|
|
}
|
|
|
|
var_sort<T extends number>(i: T): QVarSorts[T] {
|
|
return _toSort(check(Z3.get_quantifier_bound_sort(contextPtr, this.ast, i)));
|
|
}
|
|
|
|
children(): [BodyT<Name, QVarSorts, QSort>] {
|
|
return [this.body()];
|
|
}
|
|
}
|
|
|
|
class NonLambdaQuantifierImpl<QVarSorts extends NonEmptySortArray<Name>>
|
|
extends QuantifierImpl<QVarSorts, BoolSort<Name>>
|
|
implements Quantifier<Name, QVarSorts, BoolSort<Name>>, Bool<Name>
|
|
{
|
|
declare readonly __typename: 'NonLambdaQuantifier';
|
|
|
|
not(): Bool<Name> {
|
|
return Not(this);
|
|
}
|
|
|
|
and(other: Bool<Name> | boolean): Bool<Name> {
|
|
return And(this, other);
|
|
}
|
|
|
|
or(other: Bool<Name> | boolean): Bool<Name> {
|
|
return Or(this, other);
|
|
}
|
|
|
|
xor(other: Bool<Name> | boolean): Bool<Name> {
|
|
return Xor(this, other);
|
|
}
|
|
|
|
implies(other: Bool<Name> | boolean): Bool<Name> {
|
|
return Implies(this, other);
|
|
}
|
|
|
|
iff(other: Bool<Name> | boolean): Bool<Name> {
|
|
return Iff(this, other);
|
|
}
|
|
}
|
|
|
|
// isBool will return false which is unlike the python API (but like the C API)
|
|
class LambdaImpl<DomainSort extends NonEmptySortArray<Name>, RangeSort extends Sort<Name>>
|
|
extends QuantifierImpl<DomainSort, SMTArraySort<Name, DomainSort, RangeSort>>
|
|
implements
|
|
Quantifier<Name, DomainSort, SMTArraySort<Name, DomainSort, RangeSort>>,
|
|
SMTArray<Name, DomainSort, RangeSort>
|
|
{
|
|
declare readonly __typename: 'Lambda';
|
|
|
|
domain(): DomainSort[0] {
|
|
return this.sort.domain();
|
|
}
|
|
|
|
domain_n<T extends number>(i: T): DomainSort[T] {
|
|
return this.sort.domain_n(i);
|
|
}
|
|
|
|
range(): RangeSort {
|
|
return this.sort.range();
|
|
}
|
|
|
|
select(...indices: CoercibleToArrayIndexType<Name, DomainSort>): SortToExprMap<RangeSort, Name> {
|
|
return Select<DomainSort, RangeSort>(this, ...indices);
|
|
}
|
|
|
|
store(
|
|
...indicesAndValue: [
|
|
...CoercibleToArrayIndexType<Name, DomainSort>,
|
|
CoercibleToMap<SortToExprMap<RangeSort, Name>, Name>,
|
|
]
|
|
): SMTArray<Name, DomainSort, RangeSort> {
|
|
return Store(this, ...indicesAndValue);
|
|
}
|
|
|
|
/**
|
|
* Access the array default value.
|
|
* Produces the default range value, for arrays that can be represented as
|
|
* finite maps with a default range value.
|
|
*/
|
|
default(): SortToExprMap<RangeSort, Name> {
|
|
return _toExpr(check(Z3.mk_array_default(contextPtr, this.ast))) as SortToExprMap<RangeSort, Name>;
|
|
}
|
|
}
|
|
|
|
class AstVectorImpl<Item extends AnyAst<Name>> {
|
|
declare readonly __typename: AstVector['__typename'];
|
|
readonly ctx: Context<Name>;
|
|
|
|
constructor(readonly ptr: Z3_ast_vector = Z3.mk_ast_vector(contextPtr)) {
|
|
this.ctx = ctx;
|
|
Z3.ast_vector_inc_ref(contextPtr, ptr);
|
|
cleanup.register(this, () => Z3.ast_vector_dec_ref(contextPtr, ptr), this);
|
|
}
|
|
|
|
length(): number {
|
|
return Z3.ast_vector_size(contextPtr, this.ptr);
|
|
}
|
|
|
|
[Symbol.iterator](): IterableIterator<Item> {
|
|
return this.values();
|
|
}
|
|
|
|
*entries(): IterableIterator<[number, Item]> {
|
|
const length = this.length();
|
|
for (let i = 0; i < length; i++) {
|
|
yield [i, this.get(i)];
|
|
}
|
|
}
|
|
|
|
*keys(): IterableIterator<number> {
|
|
for (let [key] of this.entries()) {
|
|
yield key;
|
|
}
|
|
}
|
|
|
|
*values(): IterableIterator<Item> {
|
|
for (let [, value] of this.entries()) {
|
|
yield value;
|
|
}
|
|
}
|
|
|
|
get(i: number): Item;
|
|
get(from: number, to: number): Item[];
|
|
get(from: number, to?: number): Item | Item[] {
|
|
const length = this.length();
|
|
if (from < 0) {
|
|
from += length;
|
|
}
|
|
if (from >= length) {
|
|
throw new RangeError(`expected from index ${from} to be less than length ${length}`);
|
|
}
|
|
|
|
if (to === undefined) {
|
|
return _toAst(check(Z3.ast_vector_get(contextPtr, this.ptr, from))) as Item;
|
|
}
|
|
|
|
if (to < 0) {
|
|
to += length;
|
|
}
|
|
if (to >= length) {
|
|
throw new RangeError(`expected to index ${to} to be less than length ${length}`);
|
|
}
|
|
|
|
const result: Item[] = [];
|
|
for (let i = from; i < to; i++) {
|
|
result.push(_toAst(check(Z3.ast_vector_get(contextPtr, this.ptr, i))) as Item);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
set(i: number, v: Item): void {
|
|
_assertContext(v);
|
|
if (i >= this.length()) {
|
|
throw new RangeError(`expected index ${i} to be less than length ${this.length()}`);
|
|
}
|
|
check(Z3.ast_vector_set(contextPtr, this.ptr, i, v.ast));
|
|
}
|
|
|
|
push(v: Item): void {
|
|
_assertContext(v);
|
|
check(Z3.ast_vector_push(contextPtr, this.ptr, v.ast));
|
|
}
|
|
|
|
resize(size: number): void {
|
|
check(Z3.ast_vector_resize(contextPtr, this.ptr, size));
|
|
}
|
|
|
|
has(v: Item): boolean {
|
|
_assertContext(v);
|
|
for (const item of this.values()) {
|
|
if (item.eqIdentity(v)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
sexpr(): string {
|
|
return check(Z3.ast_vector_to_string(contextPtr, this.ptr));
|
|
}
|
|
}
|
|
|
|
class AstMapImpl<Key extends AnyAst<Name>, Value extends AnyAst<Name>> implements AstMap<Name, Key, Value> {
|
|
declare readonly __typename: AstMap['__typename'];
|
|
readonly ctx: Context<Name>;
|
|
|
|
constructor(readonly ptr: Z3_ast_map = Z3.mk_ast_map(contextPtr)) {
|
|
this.ctx = ctx;
|
|
Z3.ast_map_inc_ref(contextPtr, ptr);
|
|
cleanup.register(this, () => Z3.ast_map_dec_ref(contextPtr, ptr), this);
|
|
}
|
|
|
|
[Symbol.iterator](): Iterator<[Key, Value]> {
|
|
return this.entries();
|
|
}
|
|
|
|
get size(): number {
|
|
return Z3.ast_map_size(contextPtr, this.ptr);
|
|
}
|
|
|
|
*entries(): IterableIterator<[Key, Value]> {
|
|
for (const key of this.keys()) {
|
|
yield [key, this.get(key)];
|
|
}
|
|
}
|
|
|
|
keys(): AstVector<Name, Key> {
|
|
return new AstVectorImpl(Z3.ast_map_keys(contextPtr, this.ptr));
|
|
}
|
|
|
|
*values(): IterableIterator<Value> {
|
|
for (const [_, value] of this.entries()) {
|
|
yield value;
|
|
}
|
|
}
|
|
|
|
get(key: Key): Value {
|
|
return _toAst(check(Z3.ast_map_find(contextPtr, this.ptr, key.ast))) as Value;
|
|
}
|
|
|
|
set(key: Key, value: Value): void {
|
|
check(Z3.ast_map_insert(contextPtr, this.ptr, key.ast, value.ast));
|
|
}
|
|
|
|
delete(key: Key): void {
|
|
check(Z3.ast_map_erase(contextPtr, this.ptr, key.ast));
|
|
}
|
|
|
|
clear(): void {
|
|
check(Z3.ast_map_reset(contextPtr, this.ptr));
|
|
}
|
|
|
|
has(key: Key): boolean {
|
|
return check(Z3.ast_map_contains(contextPtr, this.ptr, key.ast));
|
|
}
|
|
|
|
sexpr(): string {
|
|
return check(Z3.ast_map_to_string(contextPtr, this.ptr));
|
|
}
|
|
}
|
|
|
|
function substitute(t: Expr<Name>, ...substitutions: [Expr<Name>, Expr<Name>][]): Expr<Name> {
|
|
_assertContext(t);
|
|
const from: Z3_ast[] = [];
|
|
const to: Z3_ast[] = [];
|
|
for (const [f, t] of substitutions) {
|
|
_assertContext(f);
|
|
_assertContext(t);
|
|
from.push(f.ast);
|
|
to.push(t.ast);
|
|
}
|
|
return _toExpr(check(Z3.substitute(contextPtr, t.ast, from, to)));
|
|
}
|
|
|
|
function substituteVars(t: Expr<Name>, ...to: Expr<Name>[]): Expr<Name> {
|
|
_assertContext(t);
|
|
const toAsts: Z3_ast[] = [];
|
|
for (const expr of to) {
|
|
_assertContext(expr);
|
|
toAsts.push(expr.ast);
|
|
}
|
|
return _toExpr(check(Z3.substitute_vars(contextPtr, t.ast, toAsts)));
|
|
}
|
|
|
|
function substituteFuns(t: Expr<Name>, ...substitutions: [FuncDecl<Name>, Expr<Name>][]): Expr<Name> {
|
|
_assertContext(t);
|
|
const from: Z3_func_decl[] = [];
|
|
const to: Z3_ast[] = [];
|
|
for (const [f, body] of substitutions) {
|
|
_assertContext(f);
|
|
_assertContext(body);
|
|
from.push(f.ptr);
|
|
to.push(body.ast);
|
|
}
|
|
return _toExpr(check(Z3.substitute_funs(contextPtr, t.ast, from, to)));
|
|
}
|
|
|
|
function ast_from_string(s: string): Ast<Name> {
|
|
const sort_names: Z3_symbol[] = [];
|
|
const sorts: Z3_sort[] = [];
|
|
const decl_names: Z3_symbol[] = [];
|
|
const decls: Z3_func_decl[] = [];
|
|
const v = new AstVectorImpl(check(Z3.parse_smtlib2_string(contextPtr, s, sort_names, sorts, decl_names, decls)));
|
|
if (v.length() !== 1) {
|
|
throw new Error('Expected exactly one AST. Instead got ' + v.length() + ': ' + v.sexpr());
|
|
}
|
|
return v.get(0);
|
|
}
|
|
|
|
const ctx: Context<Name> = {
|
|
ptr: contextPtr,
|
|
name,
|
|
|
|
/////////////
|
|
// Classes //
|
|
/////////////
|
|
Solver: SolverImpl,
|
|
Optimize: OptimizeImpl,
|
|
Fixedpoint: FixedpointImpl,
|
|
Model: ModelImpl,
|
|
Tactic: TacticImpl,
|
|
Goal: GoalImpl,
|
|
Params: ParamsImpl,
|
|
Simplifier: SimplifierImpl,
|
|
AstVector: AstVectorImpl as AstVectorCtor<Name>,
|
|
AstMap: AstMapImpl as AstMapCtor<Name>,
|
|
|
|
///////////////
|
|
// Functions //
|
|
///////////////
|
|
interrupt,
|
|
setPrintMode,
|
|
isModel,
|
|
isAst,
|
|
isSort,
|
|
isFuncDecl,
|
|
isFuncInterp,
|
|
isApp,
|
|
isConst,
|
|
isExpr,
|
|
isVar,
|
|
isAppOf,
|
|
isBool,
|
|
isTrue,
|
|
isFalse,
|
|
isAnd,
|
|
isOr,
|
|
isImplies,
|
|
isNot,
|
|
isEq,
|
|
isDistinct,
|
|
isQuantifier,
|
|
isArith,
|
|
isArithSort,
|
|
isInt,
|
|
isIntVal,
|
|
isIntSort,
|
|
isReal,
|
|
isRealVal,
|
|
isRealSort,
|
|
isRCFNum,
|
|
isBitVecSort,
|
|
isBitVec,
|
|
isBitVecVal, // TODO fix ordering
|
|
isFPRMSort,
|
|
isFPRM,
|
|
isFPSort,
|
|
isFP,
|
|
isFPVal,
|
|
isSeqSort,
|
|
isSeq,
|
|
isStringSort,
|
|
isString,
|
|
isArraySort,
|
|
isArray,
|
|
isConstArray,
|
|
isProbe,
|
|
isTactic,
|
|
isGoal,
|
|
isAstVector,
|
|
eqIdentity,
|
|
getVarIndex,
|
|
from,
|
|
solve,
|
|
|
|
/////////////
|
|
// Objects //
|
|
/////////////
|
|
Sort,
|
|
Function,
|
|
RecFunc,
|
|
Bool,
|
|
Int,
|
|
Real,
|
|
RCFNum,
|
|
BitVec,
|
|
Float,
|
|
FloatRM,
|
|
String,
|
|
Seq,
|
|
Re,
|
|
Array,
|
|
Set,
|
|
Datatype,
|
|
|
|
////////////////
|
|
// Operations //
|
|
////////////////
|
|
If,
|
|
Distinct,
|
|
Const,
|
|
Consts,
|
|
FreshConst,
|
|
Var,
|
|
Implies,
|
|
Iff,
|
|
Eq,
|
|
Xor,
|
|
Not,
|
|
And,
|
|
Or,
|
|
PbEq,
|
|
PbGe,
|
|
PbLe,
|
|
AtMost,
|
|
AtLeast,
|
|
ForAll,
|
|
Exists,
|
|
Lambda,
|
|
ToReal,
|
|
ToInt,
|
|
IsInt,
|
|
Sqrt,
|
|
Cbrt,
|
|
BV2Int,
|
|
Int2BV,
|
|
Concat,
|
|
Cond,
|
|
AndThen,
|
|
OrElse,
|
|
Repeat,
|
|
TryFor,
|
|
When,
|
|
Skip,
|
|
Fail,
|
|
FailIf,
|
|
ParOr,
|
|
ParAndThen,
|
|
With,
|
|
LT,
|
|
GT,
|
|
LE,
|
|
GE,
|
|
ULT,
|
|
UGT,
|
|
ULE,
|
|
UGE,
|
|
SLT,
|
|
SGT,
|
|
SLE,
|
|
SGE,
|
|
Sum,
|
|
Sub,
|
|
Product,
|
|
Div,
|
|
BUDiv,
|
|
Neg,
|
|
Mod,
|
|
Select,
|
|
Store,
|
|
Ext,
|
|
Extract,
|
|
|
|
substitute,
|
|
substituteVars,
|
|
substituteFuns,
|
|
simplify,
|
|
|
|
/////////////
|
|
// Loading //
|
|
/////////////
|
|
ast_from_string,
|
|
|
|
SetUnion,
|
|
SetIntersect,
|
|
SetDifference,
|
|
SetAdd,
|
|
SetDel,
|
|
SetComplement,
|
|
EmptySet,
|
|
FullSet,
|
|
isMember,
|
|
isSubset,
|
|
|
|
InRe,
|
|
Union,
|
|
Intersect,
|
|
ReConcat,
|
|
Plus,
|
|
Star,
|
|
Option,
|
|
Complement,
|
|
Diff,
|
|
Range,
|
|
Loop,
|
|
Power,
|
|
AllChar,
|
|
Empty,
|
|
Full,
|
|
|
|
mkPartialOrder,
|
|
mkTransitiveClosure,
|
|
polynomialSubresultants,
|
|
};
|
|
cleanup.register(ctx, () => Z3.del_context(contextPtr));
|
|
return ctx;
|
|
}
|
|
|
|
return {
|
|
enableTrace,
|
|
disableTrace,
|
|
getVersion,
|
|
getVersionString,
|
|
getFullVersion,
|
|
openLog,
|
|
appendLog,
|
|
getParam,
|
|
setParam,
|
|
resetParams,
|
|
|
|
Context: createContext as ContextCtor,
|
|
};
|
|
}
|