3
0
Fork 0
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:
Olaf Tomalka 2022-06-14 18:55:58 +02:00 committed by GitHub
parent 3d00d1d56b
commit 7fdcbbaee9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 15973 additions and 643 deletions

16
src/api/js/src/browser.ts Normal file
View 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 };
}

View 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);
});
});
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,2 @@
export * from './high-level';
export * from './types';

File diff suppressed because it is too large Load diff

View 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();
});
});

View 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
View 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();
});
}

View file

@ -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) {

View 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']>>;

View file

@ -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
View 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 };
}