z3-z3-4.13.0.src.api.js.scripts.parse-api.ts Maven / Gradle / Ivy
The newest version!
import assert from 'assert';
import fs from 'fs';
import path from 'path';
const files = [
'z3_api.h',
'z3_algebraic.h',
'z3_ast_containers.h',
'z3_fixedpoint.h',
'z3_fpa.h',
'z3_optimization.h',
'z3_polynomial.h',
'z3_rcf.h',
'z3_spacer.h',
];
const aliases = {
__proto__: null,
Z3_string: 'string',
bool: 'boolean',
signed: 'int',
} as unknown as Record;
const primitiveTypes = {
__proto__: null,
Z3_char_ptr: 'string',
unsigned: 'number',
int: 'number',
uint64_t: 'bigint',
int64_t: 'bigint',
double: 'number',
float: 'number',
} as unknown as Record;
const optTypes = {
__proto__: null,
Z3_sort_opt: 'Z3_sort',
Z3_ast_opt: 'Z3_ast',
Z3_func_interp_opt: 'Z3_func_interp',
} as unknown as Record;
// parse type declarations
const types = {
__proto__: null,
// these are function types I can't be bothered to parse
// NSB: They can be extracted automatically from z3_api.h thanks to the use
// of a macro.
Z3_error_handler: 'Z3_error_handler',
Z3_push_eh: 'Z3_push_eh',
Z3_pop_eh: 'Z3_pop_eh',
Z3_fresh_eh: 'Z3_fresh_eh',
Z3_fixed_eh: 'Z3_fixed_eh',
Z3_eq_eh: 'Z3_eq_eh',
Z3_final_eh: 'Z3_final_eh',
Z3_created_eh: 'Z3_created_eh',
Z3_decide_eh: 'Z3_decide_eh',
Z3_on_clause_eh: 'Z3_on_clause_eh',
} as unknown as Record;
export type ApiParam = { kind: string; sizeIndex?: number; type: string };
export type Api = { params: ApiParam[]; ret: string; extra: boolean };
const defApis: Record = Object.create(null);
export type FuncParam = {
type: string;
cType: string;
name: string;
isConst: boolean;
isPtr: boolean;
isArray: boolean;
nullable: boolean;
kind?: string;
sizeIndex?: number;
};
export type Func = { ret: string; cRet: string; name: string; params: FuncParam[]; nullableRet: boolean };
const functions: Func[] = [];
let enums: Record> = Object.create(null);
for (let file of files) {
let contents = fs.readFileSync(path.join(__dirname, '..', '..', file), 'utf8');
// we _could_ use an actual C++ parser, which accounted for macros and everything
// but that's super painful
// and the files are regular enough that we can get away without it
// we could also do this by modifying the `update_api.py` script
// which we should probably do eventually
// but this is easier while this remains not upstreamed
// we need to parse the `def_API` stuff so we know which things are out parameters
// unfortunately we also need to parse the actual declarations so we know the parameter names also
let pytypes = Object.create(null);
let typeMatches = contents.matchAll(
/def_Type\(\s*'(?[A-Za-z0-9_]+)',\s*'(?[A-Za-z0-9_]+)',\s*'(?[A-Za-z0-9_]+)'\)/g,
);
for (let { groups } of typeMatches) {
assert(groups !== undefined);
pytypes[groups.name] = groups.cname;
}
// we don't have to pre-populate the types map with closure types
// use the Z3_DECLARE_CLOSURE to identify closure types
// for (let match of contents.matchAll(/Z3_DECLARE_CLOSURE\((?[A-Za-z0-9_]+),/g)) {
// types[match.groups.type] = match.groups.type
// }
// we filter first to ensure our regex isn't too strict
let apiLines = contents.split('\n').filter(l => /def_API|extra_API/.test(l));
for (let line of apiLines) {
let match = line.match(
/^\s*(?def_API|extra_API) *\(\s*'(?[A-Za-z0-9_]+)'\s*,\s*(?[A-Za-z0-9_]+)\s*,\s*\((?((_in|_out|_in_array|_out_array|_fnptr|_inout_array)\([^)]+\)\s*,?\s*)*)\)\s*\)\s*$/,
);
if (match === null) {
throw new Error(`failed to match def_API call ${JSON.stringify(line)}`);
}
assert(match.groups !== undefined);
let { name, ret, def } = match.groups;
let params = match.groups.params.trim();
let text = params;
let parsedParams = [];
while (true) {
text = eatWs(text);
({ text, match } = eat(text, /^_(?in|out|in_array|out_array|inout_array|fnptr)\(/));
if (match == null) {
break;
}
assert(match.groups !== undefined);
let kind = match.groups.kind;
if (kind === 'inout_array') kind = 'in_array'; // https://github.com/Z3Prover/z3/discussions/5761
if (kind === 'in' || kind === 'out' || kind == 'fnptr') {
({ text, match } = expect(text, /^[A-Za-z0-9_]+/));
parsedParams.push({ kind, type: match[0] });
} else {
({ text, match } = expect(text, /^(\d+),/));
let sizeIndex = Number(match[1]);
text = eatWs(text);
({ text, match } = expect(text, /^[A-Za-z0-9_]+/));
parsedParams.push({ kind, sizeIndex, type: match[0] });
}
({ text, match } = expect(text, /^\)/));
text = eatWs(text);
({ text, match } = eat(text, /^,/));
}
if (text !== '') {
throw new Error(`extra text in parameter list ${JSON.stringify(text)}`);
}
if (name in defApis) {
throw new Error(`multiple defApi calls for ${name}`);
}
defApis[name] = { params: parsedParams, ret, extra: def === 'extra_API' };
}
for (let match of contents.matchAll(/DEFINE_TYPE\((?[A-Za-z0-9_]+)\)/g)) {
assert(match.groups !== undefined);
types[match.groups.type] = match.groups.type;
}
// parse enum declarations
for (let idx = 0; idx < contents.length; ) {
let nextIdx = contents.indexOf('typedef enum', idx);
if (nextIdx === -1) {
break;
}
let lineStart = contents.lastIndexOf('\n', nextIdx);
let lineEnd = contents.indexOf(';', nextIdx);
if (lineStart === -1 || lineEnd === -1) {
throw new Error(`could not parse enum at index ${nextIdx}`);
}
idx = lineEnd;
let slice = contents.substring(lineStart, lineEnd);
let { match, text } = eat(slice, /^\s*typedef enum\s*\{/);
if (match === null) {
throw new Error(`could not parse enum ${JSON.stringify(slice)}`);
}
let vals: Record = Object.create(null);
let next = 0;
while (true) {
let blank = true;
while (blank) {
({ match, text } = eat(text, /^\s*(\/\/[^\n]*\n)?/));
assert(match !== null);
blank = match[0].length > 0;
}
({ match, text } = eat(text, /^[A-Za-z0-9_]+/));
if (match === null) {
throw new Error(`could not parse enum value in ${slice}`);
}
let name = match[0];
text = eatWs(text);
({ match, text } = eat(text, /^= *(?[^\n,\s]+)/));
if (match !== null) {
assert(match.groups !== undefined);
let parsedVal = Number(match.groups.val);
if (Object.is(parsedVal, NaN)) {
throw new Error('unknown value ' + match.groups.val);
}
vals[name] = parsedVal;
next = parsedVal;
} else {
vals[name] = next;
}
text = eatWs(text);
({ match, text } = eat(text, /^,?\s*}/));
if (match !== null) {
break;
}
({ match, text } = expect(text, /^,/));
++next;
}
text = eatWs(text);
({ match, text } = expect(text, /^[A-Za-z0-9_]+/));
if (match[0] in enums) {
throw new Error(`duplicate enum definition ${match[0]}`);
}
enums[match[0]] = vals;
text = eatWs(text);
if (text !== '') {
throw new Error('expected end of definition, got ' + text);
}
}
// parse function declarations
for (let idx = 0; idx < contents.length; ) {
let nextIdx = contents.indexOf(' Z3_API ', idx);
if (nextIdx === -1) {
break;
}
let lineStart = contents.lastIndexOf('\n', nextIdx);
let lineEnd = contents.indexOf(';', nextIdx);
if (lineStart === -1 || lineEnd === -1) {
throw new Error(`could not parse definition at index ${nextIdx}`);
}
idx = lineEnd;
let slice = contents.substring(lineStart, lineEnd);
let match = slice.match(/^\s*(?[A-Za-z0-9_]+) +Z3_API +(?[A-Za-z0-9_]+)\s*\((?[^)]*)\)/);
if (match == null) {
throw new Error(`failed to match c definition: ${JSON.stringify(slice)}`);
}
assert(match.groups !== undefined);
let { ret, name, params } = match.groups;
let parsedParams = [];
if (params.trim() !== 'void') {
for (let param of params.split(',')) {
let paramType: string, paramName: string, isConst: boolean, isPtr: boolean, isArray: boolean;
let { match, text } = eat(param, /^\s*/);
({ match, text } = eat(text, /^[A-Za-z0-9_]+/));
if (match === null) {
throw new Error(`failed to parse param type in ${JSON.stringify(slice)} for param ${JSON.stringify(param)}`);
}
paramType = match[0];
text = eatWs(text);
({ match, text } = eat(text, /^const(?![A-Za-z0-9_])/));
isConst = match !== null;
({ match, text } = eat(text, /^\s*\*/));
isPtr = match !== null;
text = eatWs(text);
if (text === '') {
paramName = 'UNNAMED';
isArray = false;
} else {
({ match, text } = eat(text, /^[A-Za-z0-9_]+/));
if (match === null) {
throw new Error(
`failed to parse param name in ${JSON.stringify(slice)} for param ${JSON.stringify(param)}`,
);
}
paramName = match[0];
text = eatWs(text);
({ match, text } = eat(text, /^\[\]/));
isArray = match !== null;
text = eatWs(text);
if (text !== '') {
throw new Error(`excess text in param in ${JSON.stringify(slice)} for param ${JSON.stringify(param)}`);
}
}
if (paramType === 'Z3_string_ptr' && !isPtr) {
paramType = 'Z3_string';
isPtr = true;
}
let nullable = false;
if (paramType in optTypes) {
nullable = true;
paramType = optTypes[paramType];
}
let cType = paramType;
paramType = aliases[paramType] ?? paramType;
parsedParams.push({ type: paramType, cType, name: paramName, isConst, isPtr, isArray, nullable });
}
}
let nullableRet = false;
if (ret in optTypes) {
nullableRet = true;
ret = optTypes[ret];
}
let cRet = ret;
ret = aliases[ret] ?? ret;
if (name in defApis) {
functions.push({ ret, cRet, name, params: parsedParams, nullableRet });
}
// only a few things are missing `def_API`; we'll skip those
}
}
function isKnownType(t: string) {
return t in enums || t in types || t in primitiveTypes || ['string', 'boolean', 'void'].includes(t);
}
for (let fn of functions) {
if (!isKnownType(fn.ret)) {
throw new Error(`unknown type ${fn.ret}`);
}
let defParams = defApis[fn.name].params;
if (fn.params.length !== defParams.length) {
throw new Error(`parameter length mismatch for ${fn.name}`);
}
let idx = 0;
for (let param of fn.params) {
if (!isKnownType(param.type)) {
throw new Error(`unknown type ${param.type}`);
}
param.kind = defParams[idx].kind;
if (param.kind === 'in_array' || param.kind === 'out_array') {
if (defParams[idx].sizeIndex == null) {
throw new Error(`function ${fn.name} parameter ${idx} is marked as ${param.kind} but has no index`);
}
param.sizeIndex = defParams[idx].sizeIndex;
if (!param.isArray && param.isPtr) {
// not clear why some things are written as `int * x` and others `int x[]`
// but we can just cast
param.isArray = true;
param.isPtr = false;
}
if (!param.isArray) {
throw new Error(`function ${fn.name} parameter ${idx} is marked as ${param.kind} but not typed as array`);
}
}
++idx;
}
}
function eat(str: string, regex: string | RegExp) {
const match: RegExpMatchArray | null = str.match(regex);
if (match === null) {
return { match, text: str };
}
return { match, text: str.substring(match[0].length) };
}
function eatWs(text: string) {
return eat(text, /^\s*/).text;
}
function expect(str: string, regex: string | RegExp) {
let { text, match } = eat(str, regex);
if (match === null) {
throw new Error(`expected ${regex}, got ${JSON.stringify(text)}`);
}
return { text, match };
}
export { primitiveTypes, types, enums, functions };