mirror of
https://github.com/Z3Prover/z3
synced 2026-02-24 01:01:19 +00:00
Add high level bindings for js (#6048)
* [Draft] Added unfinished code for high level bindings for js * * Rewrote structure of js api files * Added more high level apis * Minor fixes * Fixed wasm github action * Fix JS test * Removed ContextOptions type * * Added Ints to JS Api * Added tests to JS Api * Added run-time checks for contexts * Removed default contexts * Merged Context and createContext so that the api behaves the sames as in other constructors * Added a test for Solver * Added Reals * Added classes for IntVals and RealVals * Added abillity to specify logic for solver * Try to make CI tests not fail * Changed APIs after a round of review * Fix test * Added BitVectors * Made sort into getter * Added initial JS docs * Added more coercible types * Removed done TODOs
This commit is contained in:
parent
3d00d1d56b
commit
7fdcbbaee9
37 changed files with 15973 additions and 643 deletions
16
src/api/js/src/browser.ts
Normal file
16
src/api/js/src/browser.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { createApi, Z3HighLevel } from './high-level';
|
||||
import { init as initWrapper, Z3LowLevel } from './low-level';
|
||||
export * from './high-level/types';
|
||||
export { Z3Core, Z3LowLevel } from './low-level';
|
||||
export * from './low-level/types.__GENERATED__';
|
||||
|
||||
export async function init(): Promise<Z3LowLevel & Z3HighLevel> {
|
||||
const initZ3 = (global as any).initZ3;
|
||||
if (initZ3 === undefined) {
|
||||
throw new Error('initZ3 was not imported correctly. Please consult documentation on how to load Z3 in browser');
|
||||
}
|
||||
|
||||
const lowLevel = await initWrapper(initZ3);
|
||||
const highLevel = createApi(lowLevel.Z3);
|
||||
return { ...lowLevel, ...highLevel };
|
||||
}
|
||||
450
src/api/js/src/high-level/high-level.test.ts
Normal file
450
src/api/js/src/high-level/high-level.test.ts
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
import assert from 'assert';
|
||||
import asyncToArray from 'iter-tools/methods/async-to-array';
|
||||
import { init, killThreads } from '../jest';
|
||||
import { Arith, Bool, Model, sat, unsat, Z3AssertionError, Z3HighLevel } from './types';
|
||||
|
||||
/**
|
||||
* Generate all possible solutions from given assumptions.
|
||||
*
|
||||
* **NOTE**: The set of solutions might be infinite.
|
||||
* Always ensure to limit amount generated, either by knowing that the
|
||||
* solution space is constrainted, or by taking only a specified
|
||||
* amount of solutions
|
||||
* ```typescript
|
||||
* import { sliceAsync } from 'iter-tools';
|
||||
* // ...
|
||||
* for await (const model of sliceAsync(10, solver.solutions())) {
|
||||
* console.log(model.sexpr());
|
||||
* }
|
||||
* ```
|
||||
* @see http://theory.stanford.edu/~nikolaj/programmingz3.html#sec-blocking-evaluations
|
||||
* @returns Models with solutions. Nothing if no constants provided
|
||||
*/
|
||||
// TODO(ritave): Use faster solution https://stackoverflow.com/a/70656700
|
||||
// TODO(ritave): Move to high-level.ts
|
||||
async function* allSolutions<Name extends string>(...assertions: Bool<Name>[]): AsyncIterable<Model<Name>> {
|
||||
if (assertions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { Or } = assertions[0].ctx;
|
||||
const solver = new assertions[0].ctx.Solver();
|
||||
solver.add(...assertions);
|
||||
|
||||
while ((await solver.check()) === sat) {
|
||||
const model = solver.model();
|
||||
const decls = model.decls();
|
||||
if (decls.length === 0) {
|
||||
return;
|
||||
}
|
||||
yield model;
|
||||
|
||||
solver.add(
|
||||
Or(
|
||||
...decls
|
||||
// TODO(ritave): Assert on arity > 0
|
||||
.filter(decl => decl.arity() === 0)
|
||||
.map(decl => {
|
||||
const term = decl.call();
|
||||
// TODO(ritave): Assert not an array / uinterpeted sort
|
||||
const value = model.eval(term, true);
|
||||
return term.neq(value);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function prove(conjecture: Bool): Promise<void> {
|
||||
const solver = new conjecture.ctx.Solver();
|
||||
const { Not } = solver.ctx;
|
||||
solver.add(Not(conjecture));
|
||||
expect(await solver.check()).toStrictEqual(unsat);
|
||||
}
|
||||
|
||||
async function solve(conjecture: Bool): Promise<Model> {
|
||||
const solver = new conjecture.ctx.Solver();
|
||||
solver.add(conjecture);
|
||||
expect(await solver.check()).toStrictEqual(sat);
|
||||
return solver.model();
|
||||
}
|
||||
|
||||
describe('high-level', () => {
|
||||
let api: { em: any } & Z3HighLevel;
|
||||
|
||||
beforeAll(async () => {
|
||||
api = await init();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await killThreads(api.em);
|
||||
});
|
||||
|
||||
it('can set params', () => {
|
||||
const { setParam, getParam, resetParams } = api;
|
||||
|
||||
expect(getParam('pp.decimal')).toStrictEqual('false');
|
||||
setParam('pp.decimal', 'true');
|
||||
expect(getParam('pp.decimal')).toStrictEqual('true');
|
||||
setParam({ 'pp.decimal': 'false', timeout: 4 });
|
||||
expect(getParam('pp.decimal')).toStrictEqual('false');
|
||||
expect(getParam('timeout')).toStrictEqual('4');
|
||||
|
||||
resetParams();
|
||||
expect(getParam('pp.decimal')).toStrictEqual('false');
|
||||
expect(getParam('timeout')).toStrictEqual('4294967295');
|
||||
});
|
||||
|
||||
it('proves x = y implies g(x) = g(y)', async () => {
|
||||
const { Solver, Int, Function, Implies, Not } = new api.Context('main');
|
||||
const solver = new Solver();
|
||||
|
||||
const sort = Int.sort();
|
||||
const x = Int.const('x');
|
||||
const y = Int.const('y');
|
||||
const g = Function.declare('g', sort, sort);
|
||||
|
||||
const conjecture = Implies(x.eq(y), g.call(x).eq(g.call(y)));
|
||||
solver.add(Not(conjecture));
|
||||
expect(await solver.check()).toStrictEqual(unsat);
|
||||
});
|
||||
|
||||
it('disproves x = y implies g(g(x)) = g(y)', async () => {
|
||||
const { Solver, Int, Function, Implies, Not } = new api.Context('main');
|
||||
const solver = new Solver();
|
||||
|
||||
const sort = Int.sort();
|
||||
const x = Int.const('x');
|
||||
const y = Int.const('y');
|
||||
const g = Function.declare('g', sort, sort);
|
||||
const conjecture = Implies(x.eq(y), g.call(g.call(x)).eq(g.call(y)));
|
||||
solver.add(Not(conjecture));
|
||||
expect(await solver.check()).toStrictEqual(sat);
|
||||
});
|
||||
|
||||
it('checks that Context matches', () => {
|
||||
const c1 = new api.Context('context');
|
||||
const c2 = new api.Context('context');
|
||||
const c3 = new api.Context('foo');
|
||||
const c4 = new api.Context('bar');
|
||||
|
||||
// Contexts with the same name don't do type checking during compile time.
|
||||
// We need to check for different context dynamically
|
||||
expect(() => c1.Or(c2.Int.val(5).eq(2))).toThrowError(Z3AssertionError);
|
||||
|
||||
// On the other hand, this won't compile due to automatic generics
|
||||
// @ts-expect-error
|
||||
expect(() => c3.Or(c4.Int.val(5).eq(2))).toThrowError(Z3AssertionError);
|
||||
|
||||
const allUniqueContexes = new Set([c1, c2, c3, c4]).size === 4;
|
||||
expect(allUniqueContexes).toStrictEqual(true);
|
||||
|
||||
expect(() => c1.Or(c1.Int.val(5).eq(2))).not.toThrowError();
|
||||
});
|
||||
|
||||
describe('booleans', () => {
|
||||
it("proves De Morgan's Law", async () => {
|
||||
const { Bool, Not, And, Eq, Or } = new api.Context('main');
|
||||
const [x, y] = [Bool.const('x'), Bool.const('y')];
|
||||
|
||||
const conjecture = Eq(Not(And(x, y)), Or(Not(x), Not(y)));
|
||||
|
||||
await prove(conjecture);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ints', () => {
|
||||
it('finds a model', async () => {
|
||||
const { Solver, Int, isIntVal } = new api.Context('main');
|
||||
const solver = new Solver();
|
||||
const x = Int.const('x');
|
||||
const y = Int.const('y');
|
||||
|
||||
solver.add(x.ge(1)); // x >= 1
|
||||
solver.add(y.lt(x.add(3))); // y < x + 3
|
||||
|
||||
expect(await solver.check()).toStrictEqual(sat);
|
||||
|
||||
const model = solver.model();
|
||||
expect(model.length).toStrictEqual(2);
|
||||
|
||||
for (const decl of model) {
|
||||
expect(decl.arity()).toStrictEqual(0);
|
||||
}
|
||||
const xValueExpr = model.get(x);
|
||||
const yValueExpr = model.get(y);
|
||||
assert(isIntVal(xValueExpr));
|
||||
assert(isIntVal(yValueExpr));
|
||||
const xValue = xValueExpr.value;
|
||||
const yValue = yValueExpr.value;
|
||||
assert(typeof xValue === 'bigint');
|
||||
assert(typeof yValue === 'bigint');
|
||||
expect(xValue).toBeGreaterThanOrEqual(1n);
|
||||
expect(yValue).toBeLessThanOrEqual(xValue + 3n);
|
||||
});
|
||||
|
||||
// TODO(ritave): After changes made since last commit (a332187c746c23450860deb210d94e6e042dd424),
|
||||
// this test takes twice as long (from 5s to 10s). Figure out why
|
||||
it('solves sudoku', async () => {
|
||||
function toSudoku(data: string): (number | null)[][] {
|
||||
const cells: (number | null)[][] = Array.from({ length: 9 }, () => Array.from({ length: 9 }, () => null));
|
||||
|
||||
const lines = data.trim().split('\n');
|
||||
for (let row = 0; row < 9; row++) {
|
||||
const line = lines[row].trim();
|
||||
for (let col = 0; col < 9; col++) {
|
||||
const char = line[col];
|
||||
if (char !== '.') {
|
||||
cells[row][col] = Number.parseInt(char);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
const INSTANCE = toSudoku(`
|
||||
....94.3.
|
||||
...51...7
|
||||
.89....4.
|
||||
......2.8
|
||||
.6.2.1.5.
|
||||
1.2......
|
||||
.7....52.
|
||||
9...65...
|
||||
.4.97....
|
||||
`);
|
||||
|
||||
const EXPECTED = toSudoku(`
|
||||
715894632
|
||||
234516897
|
||||
689723145
|
||||
493657218
|
||||
867231954
|
||||
152489763
|
||||
376148529
|
||||
928365471
|
||||
541972386
|
||||
`);
|
||||
|
||||
const { Solver, Int, Distinct, isIntVal } = new api.Context('main');
|
||||
|
||||
const cells: Arith[][] = [];
|
||||
// 9x9 matrix of integer variables
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const row = [];
|
||||
for (let j = 0; j < 9; j++) {
|
||||
row.push(Int.const(`x_${i}_${j}`));
|
||||
}
|
||||
cells.push(row);
|
||||
}
|
||||
|
||||
const solver = new Solver();
|
||||
|
||||
// each cell contains a value 1<=x<=9
|
||||
for (let i = 0; i < 9; i++) {
|
||||
for (let j = 0; j < 9; j++) {
|
||||
solver.add(cells[i][j].ge(1), cells[i][j].le(9));
|
||||
}
|
||||
}
|
||||
|
||||
// each row contains a digit only once
|
||||
for (let i = 0; i < 9; i++) {
|
||||
solver.add(Distinct(...cells[i]));
|
||||
}
|
||||
|
||||
// each column contains a digit only once
|
||||
for (let j = 0; j < 9; j++) {
|
||||
const column = [];
|
||||
for (let i = 0; i < 9; i++) {
|
||||
column.push(cells[i][j]);
|
||||
}
|
||||
solver.add(Distinct(...column));
|
||||
}
|
||||
|
||||
// each 3x3 contains a digit at most once
|
||||
for (let iSquare = 0; iSquare < 3; iSquare++) {
|
||||
for (let jSquare = 0; jSquare < 3; jSquare++) {
|
||||
const square = [];
|
||||
|
||||
for (let i = iSquare * 3; i < iSquare * 3 + 3; i++) {
|
||||
for (let j = jSquare * 3; j < jSquare * 3 + 3; j++) {
|
||||
square.push(cells[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
solver.add(Distinct(...square));
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
for (let j = 0; j < 9; j++) {
|
||||
const digit = INSTANCE[i][j];
|
||||
if (digit !== null) {
|
||||
solver.add(cells[i][j].eq(digit));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(await solver.check()).toStrictEqual(sat);
|
||||
|
||||
const model = solver.model();
|
||||
const result = [];
|
||||
for (let i = 0; i < 9; i++) {
|
||||
let row = [];
|
||||
for (let j = 0; j < 9; j++) {
|
||||
const cell = model.eval(cells[i][j]);
|
||||
assert(isIntVal(cell));
|
||||
const value = cell.value;
|
||||
assert(typeof value === 'bigint');
|
||||
expect(value).toBeGreaterThanOrEqual(0n);
|
||||
expect(value).toBeLessThanOrEqual(9n);
|
||||
// JSON.stringify doesn't handle bigints
|
||||
row.push(Number(value));
|
||||
}
|
||||
result.push(row);
|
||||
}
|
||||
expect(JSON.stringify(result)).toStrictEqual(JSON.stringify(EXPECTED));
|
||||
}, 120_000);
|
||||
});
|
||||
|
||||
describe('reals', () => {
|
||||
it('can work with numerals', async () => {
|
||||
const { Real, And } = new api.Context('main');
|
||||
const n1 = Real.val('1/2');
|
||||
const n2 = Real.val('0.5');
|
||||
const n3 = Real.val(0.5);
|
||||
await prove(And(n1.eq(n2), n1.eq(n3)));
|
||||
|
||||
const n4 = Real.val('-1/3');
|
||||
const n5 = Real.val('-0.3333333333333333333333333333333333');
|
||||
await prove(n4.neq(n5));
|
||||
});
|
||||
|
||||
it('can do non-linear arithmetic', async () => {
|
||||
api.setParam('pp.decimal', true);
|
||||
api.setParam('pp.decimal_precision', 20);
|
||||
const { Real, Solver, isReal, isRealVal } = new api.Context('main');
|
||||
const x = Real.const('x');
|
||||
const y = Real.const('y');
|
||||
const z = Real.const('z');
|
||||
|
||||
const solver = new Solver();
|
||||
solver.add(x.mul(x).add(y.mul(y)).eq(1)); // x^2 + y^2 == 1
|
||||
solver.add(x.mul(x).mul(x).add(z.mul(z).mul(z)).lt('1/2')); // x^3 + z^3 < 1/2
|
||||
|
||||
expect(await solver.check()).toStrictEqual(sat);
|
||||
const model = solver.model();
|
||||
|
||||
expect(isRealVal(model.get(x))).toStrictEqual(true);
|
||||
// solution of y is a polynomial
|
||||
// https://stackoverflow.com/a/69740906
|
||||
expect(isReal(model.get(y))).toStrictEqual(true);
|
||||
expect(isRealVal(model.get(z))).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bitvectors', () => {
|
||||
it('can do simple proofs', async () => {
|
||||
const { BitVec, Concat, Implies, isBitVecVal } = new api.Context('main');
|
||||
|
||||
const x = BitVec.const('x', 32);
|
||||
|
||||
const sSol = (await solve(x.sub(10).sle(0).eq(x.sle(10)))).get(x); // signed: (x - 10 <= 0) == (x <= 10)
|
||||
const uSol = (await solve(x.sub(10).ule(0).eq(x.ule(10)))).get(x); // unsigned: (x - 10 <= 0) == (x <= 10)
|
||||
|
||||
assert(isBitVecVal(sSol) && isBitVecVal(uSol));
|
||||
let v = sSol.asSignedValue();
|
||||
expect(v - 10n <= 0n === v <= 10n).toStrictEqual(true);
|
||||
v = uSol.value;
|
||||
expect(v - 10n <= 0n === v <= 10n).toStrictEqual(true);
|
||||
|
||||
const y = BitVec.const('y', 32);
|
||||
|
||||
await prove(Implies(Concat(x, y).eq(Concat(y, x)), x.eq(y)));
|
||||
});
|
||||
|
||||
it('finds x and y such that: x ^ y - 103 == x * y', async () => {
|
||||
const { BitVec, isBitVecVal } = new api.Context('main');
|
||||
|
||||
const x = BitVec.const('x', 32);
|
||||
const y = BitVec.const('y', 32);
|
||||
|
||||
const model = await solve(x.xor(y).sub(103).eq(x.mul(y)));
|
||||
const xSol = model.get(x);
|
||||
const ySol = model.get(y);
|
||||
assert(isBitVecVal(xSol) && isBitVecVal(ySol));
|
||||
const xv = xSol.asSignedValue();
|
||||
const yv = ySol.asSignedValue();
|
||||
|
||||
// this solutions wraps around so we need to check using modulo
|
||||
expect((xv ^ yv) - 103n === (xv * yv) % 2n ** 32n).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Solver', () => {
|
||||
it('can use push and pop', async () => {
|
||||
const { Solver, Int } = new api.Context('main');
|
||||
const solver = new Solver();
|
||||
const x = Int.const('x');
|
||||
|
||||
solver.add(x.gt(0));
|
||||
|
||||
expect(await solver.check()).toStrictEqual(sat);
|
||||
|
||||
solver.push();
|
||||
solver.add(x.lt(0));
|
||||
|
||||
expect(solver.numScopes()).toStrictEqual(1);
|
||||
expect(await solver.check()).toStrictEqual(unsat);
|
||||
|
||||
solver.pop();
|
||||
|
||||
expect(solver.numScopes()).toStrictEqual(0);
|
||||
expect(await solver.check()).toStrictEqual(sat);
|
||||
});
|
||||
|
||||
it('can find multiple solutions', async () => {
|
||||
const { Int, isIntVal } = new api.Context('main');
|
||||
|
||||
const x = Int.const('x');
|
||||
|
||||
const solutions = await asyncToArray(allSolutions(x.ge(1), x.le(5)));
|
||||
expect(solutions.length).toStrictEqual(5);
|
||||
const results = solutions
|
||||
.map(solution => {
|
||||
const expr = solution.eval(x);
|
||||
assert(isIntVal(expr));
|
||||
return expr.value;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
assert(a !== null && b !== null && typeof a === 'bigint' && typeof b === 'bigint');
|
||||
if (a < b) {
|
||||
return -1;
|
||||
} else if (a == b) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
expect(results).toStrictEqual([1n, 2n, 3n, 4n, 5n]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AstVector', () => {
|
||||
it('can use basic methods', async () => {
|
||||
const { Solver, AstVector, Int } = new api.Context('main');
|
||||
const solver = new Solver();
|
||||
|
||||
const vector = new AstVector<Arith>();
|
||||
for (let i = 0; i < 5; i++) {
|
||||
vector.push(Int.const(`int__${i}`));
|
||||
}
|
||||
|
||||
const length = vector.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
solver.add(vector.get(i).gt(1));
|
||||
}
|
||||
|
||||
expect(await solver.check()).toStrictEqual(sat);
|
||||
});
|
||||
});
|
||||
});
|
||||
1888
src/api/js/src/high-level/high-level.ts
Normal file
1888
src/api/js/src/high-level/high-level.ts
Normal file
File diff suppressed because it is too large
Load diff
2
src/api/js/src/high-level/index.ts
Normal file
2
src/api/js/src/high-level/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from './high-level';
|
||||
export * from './types';
|
||||
1132
src/api/js/src/high-level/types.ts
Normal file
1132
src/api/js/src/high-level/types.ts
Normal file
File diff suppressed because it is too large
Load diff
90
src/api/js/src/high-level/utils.test.ts
Normal file
90
src/api/js/src/high-level/utils.test.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import { Z3AssertionError } from './types';
|
||||
import { allSatisfy, assert, assertExhaustive, autoBind } from './utils';
|
||||
|
||||
describe('allSatisfy', () => {
|
||||
it('returns null on empty array', () => {
|
||||
expect(allSatisfy([], () => true)).toBeNull();
|
||||
});
|
||||
|
||||
it('returns true if all satisfy', () => {
|
||||
expect(allSatisfy([2, 4, 6, 8], arg => arg % 2 === 0)).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it('returns false if any element fails', () => {
|
||||
expect(allSatisfy([2, 4, 1, 8], arg => arg % 2 === 0)).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assertExhaustive', () => {
|
||||
enum MyEnum {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
it('stops compilation', () => {
|
||||
const result: MyEnum = MyEnum.A as any;
|
||||
switch (result) {
|
||||
case MyEnum.A:
|
||||
break;
|
||||
default:
|
||||
// @ts-expect-error
|
||||
assertExhaustive(result);
|
||||
}
|
||||
});
|
||||
|
||||
it('allows compilation', () => {
|
||||
const result: MyEnum = MyEnum.A as any;
|
||||
switch (result) {
|
||||
case MyEnum.A:
|
||||
break;
|
||||
case MyEnum.B:
|
||||
break;
|
||||
default:
|
||||
assertExhaustive(result);
|
||||
}
|
||||
});
|
||||
|
||||
it('throws', () => {
|
||||
const result: MyEnum = undefined as any;
|
||||
switch (result) {
|
||||
case MyEnum.A:
|
||||
throw new Error();
|
||||
case MyEnum.B:
|
||||
throw new Error();
|
||||
default:
|
||||
expect(() => assertExhaustive(result)).toThrowError();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('autoBind', () => {
|
||||
class Binded {
|
||||
readonly name = 'Richard';
|
||||
constructor(shouldBind: boolean) {
|
||||
if (shouldBind === true) {
|
||||
autoBind(this);
|
||||
}
|
||||
}
|
||||
|
||||
test(): string {
|
||||
return `Hello ${this.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
it('binds this', () => {
|
||||
const { test: withoutBind } = new Binded(false);
|
||||
const { test: withBind } = new Binded(true);
|
||||
expect(() => withoutBind()).toThrowError(TypeError);
|
||||
expect(withBind()).toStrictEqual('Hello Richard');
|
||||
});
|
||||
});
|
||||
|
||||
describe('assert', () => {
|
||||
it('throws on failure', () => {
|
||||
expect(() => assert(false)).toThrowError(Z3AssertionError);
|
||||
expect(() => assert(false, 'foobar')).toThrowError(new Z3AssertionError('foobar'));
|
||||
});
|
||||
|
||||
it('does nothing on sucess', () => {
|
||||
expect(() => assert(true, 'hello')).not.toThrow();
|
||||
});
|
||||
});
|
||||
85
src/api/js/src/high-level/utils.ts
Normal file
85
src/api/js/src/high-level/utils.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { Z3AssertionError } from './types';
|
||||
|
||||
function getAllProperties(obj: Record<string, any>) {
|
||||
const properties = new Set<[any, string | symbol]>();
|
||||
do {
|
||||
for (const key of Reflect.ownKeys(obj)) {
|
||||
properties.add([obj, key]);
|
||||
}
|
||||
} while ((obj = Reflect.getPrototypeOf(obj)!) && obj !== Object.prototype);
|
||||
return properties;
|
||||
}
|
||||
|
||||
// https://github.com/sindresorhus/auto-bind
|
||||
// We modify it to use CommonJS instead of ESM
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
export function autoBind<Self extends Record<string | symbol, any>>(self: Self): Self {
|
||||
for (const [obj, key] of getAllProperties(self.constructor.prototype)) {
|
||||
if (key === 'constructor') {
|
||||
continue;
|
||||
}
|
||||
const descriptor = Reflect.getOwnPropertyDescriptor(obj, key);
|
||||
if (descriptor && typeof descriptor.value === 'function') {
|
||||
(self[key] as any) = self[key].bind(self);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use to ensure that switches are checked to be exhaustive at compile time
|
||||
*
|
||||
* @example Basic usage
|
||||
* ```typescript
|
||||
* enum Something {
|
||||
* left,
|
||||
* right,
|
||||
* };
|
||||
* const something = getSomething();
|
||||
* switch (something) {
|
||||
* case Something.left:
|
||||
* ...
|
||||
* case Something.right:
|
||||
* ...
|
||||
* default:
|
||||
* assertExhaustive(something);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param x - The param on which the switch operates
|
||||
*/
|
||||
export function assertExhaustive(x: never): never {
|
||||
throw new Error('Unexpected code execution detected, should be caught at compile time');
|
||||
}
|
||||
|
||||
export function assert(condition: boolean, reason?: string): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Z3AssertionError(reason ?? 'Assertion failed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the all elements of a `collection` satisfy the `premise`.
|
||||
* If any of the items fail the `premise`, returns false;
|
||||
* @returns null if the `collection` is empty, boolean otherwise
|
||||
*/
|
||||
export function allSatisfy<T>(collection: Iterable<T>, premise: (arg: T) => boolean): boolean | null {
|
||||
let hasItems = false;
|
||||
for (const arg of collection) {
|
||||
hasItems = true;
|
||||
if (!premise(arg)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return hasItems === true ? true : null;
|
||||
}
|
||||
62
src/api/js/src/jest.ts
Normal file
62
src/api/js/src/jest.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// This file is not included in the build
|
||||
|
||||
// @ts-ignore no-implicit-any
|
||||
import { createApi, Z3HighLevel } from './high-level';
|
||||
import { init as initWrapper, Z3LowLevel } from './low-level';
|
||||
import initModule = require('../build/z3-built');
|
||||
|
||||
export * from './high-level/types';
|
||||
export { Z3Core, Z3LowLevel } from './low-level';
|
||||
export * from './low-level/types.__GENERATED__';
|
||||
|
||||
export async function init(): Promise<Z3HighLevel & Z3LowLevel> {
|
||||
const lowLevel = await initWrapper(initModule);
|
||||
const highLevel = createApi(lowLevel.Z3);
|
||||
return { ...lowLevel, ...highLevel };
|
||||
}
|
||||
|
||||
function delay(ms: number): Promise<void> & { cancel(): void };
|
||||
function delay(ms: number, result: Error): Promise<never> & { cancel(): void };
|
||||
function delay<T>(ms: number, result: T): Promise<T> & { cancel(): void };
|
||||
function delay<T>(ms: number, result?: T | Error): Promise<T | void> & { cancel(): void } {
|
||||
let handle: any;
|
||||
const promise = new Promise<void | T>(
|
||||
(resolve, reject) =>
|
||||
(handle = setTimeout(() => {
|
||||
if (result instanceof Error) {
|
||||
reject(result);
|
||||
} else if (result !== undefined) {
|
||||
resolve(result);
|
||||
}
|
||||
resolve();
|
||||
}, ms)),
|
||||
);
|
||||
return { ...promise, cancel: () => clearTimeout(handle) };
|
||||
}
|
||||
|
||||
function waitWhile(premise: () => boolean, pollMs: number = 100): Promise<void> & { cancel(): void } {
|
||||
let handle: any;
|
||||
const promise = new Promise<void>(resolve => {
|
||||
handle = setInterval(() => {
|
||||
if (premise()) {
|
||||
clearTimeout(handle);
|
||||
resolve();
|
||||
}
|
||||
}, pollMs);
|
||||
});
|
||||
return { ...promise, cancel: () => clearInterval(handle) };
|
||||
}
|
||||
|
||||
export function killThreads(em: any): Promise<void> {
|
||||
em.PThread.terminateAllThreads();
|
||||
|
||||
// Create a polling lock to wait for threads to return
|
||||
// TODO(ritave): Threads should be killed automatically, or there should be a better way to wait for them
|
||||
const lockPromise = waitWhile(() => !em.PThread.unusedWorkers.length && !em.PThread.runningWorkers.length);
|
||||
const delayPromise = delay(5000, new Error('Waiting for threads to be killed timed out'));
|
||||
|
||||
return Promise.race([lockPromise, delayPromise]).finally(() => {
|
||||
lockPromise.cancel();
|
||||
delayPromise.cancel();
|
||||
});
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
// this wrapper works with async-fns to provide promise-based off-thread versions of some functions
|
||||
// It's prepended directly by emscripten to the resulting z3-built.js
|
||||
|
||||
let capability = null;
|
||||
function resolve_async(val) {
|
||||
4
src/api/js/src/low-level/index.ts
Normal file
4
src/api/js/src/low-level/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export * from './types.__GENERATED__';
|
||||
export * from './wrapper.__GENERATED__';
|
||||
export type Z3Core = Awaited<ReturnType<typeof import('./wrapper.__GENERATED__')['init']>>['Z3'];
|
||||
export type Z3LowLevel = Awaited<ReturnType<typeof import('./wrapper.__GENERATED__')['init']>>;
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
// @ts-ignore no-implicit-any
|
||||
import initModule = require('./z3-built.js');
|
||||
|
||||
// @ts-ignore no-implicit-any
|
||||
import { init as initWrapper } from './wrapper';
|
||||
|
||||
export * from './wrapper';
|
||||
export function init() {
|
||||
return initWrapper(initModule);
|
||||
}
|
||||
38
src/api/js/src/node.ts
Normal file
38
src/api/js/src/node.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// @ts-ignore no-implicit-any
|
||||
import initModule = require('./z3-built');
|
||||
|
||||
import { createApi, Z3HighLevel } from './high-level';
|
||||
import { init as initWrapper, Z3LowLevel } from './low-level';
|
||||
export * from './high-level/types';
|
||||
export { Z3Core, Z3LowLevel } from './low-level';
|
||||
export * from './low-level/types.__GENERATED__';
|
||||
|
||||
/**
|
||||
* The main entry point to the Z3 API
|
||||
*
|
||||
* ```typescript
|
||||
* import { init, sat } from 'z3-solver';
|
||||
*
|
||||
* const { Context } = await init();
|
||||
* const { Solver, Int } = new Context('main');
|
||||
*
|
||||
* const x = Int.const('x');
|
||||
* const y = Int.const('y');
|
||||
*
|
||||
* const solver = new Solver();
|
||||
* solver.add(x.add(2).le(y.sub(10))); // x + 2 <= y - 10
|
||||
*
|
||||
* if (await solver.check() !== sat) {
|
||||
* throw new Error("couldn't find a solution")
|
||||
* }
|
||||
* const model = solver.model();
|
||||
*
|
||||
* console.log(`x=${model.get(x)}, y=${model.get(y)}`);
|
||||
* // x=0, y=12
|
||||
* ```
|
||||
* @category Global */
|
||||
export async function init(): Promise<Z3HighLevel & Z3LowLevel> {
|
||||
const lowLevel = await initWrapper(initModule);
|
||||
const highLevel = createApi(lowLevel.Z3);
|
||||
return { ...lowLevel, ...highLevel };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue