export const description = 'Utilities for generating code snippets for uniformity tests';
import { assert, unreachable } from '../../../../common/util/util.js';
export type Verdict =
| 'sensitive'
| 'forbid'
| 'permit';
export function compileShouldSucceed({
requires_uniformity,
condition_is_uniform,
verdict,
}: {
requires_uniformity: boolean;
condition_is_uniform: boolean;
verdict: Verdict;
}): boolean {
switch (verdict) {
case 'sensitive':
return !requires_uniformity || condition_is_uniform;
case 'forbid':
return !requires_uniformity;
case 'permit':
return true;
}
}
export type Snippet = {
name: string;
code: string;
verdict: Verdict;
};
type LoopKind = 'loop' | 'for' | 'for-unif' | 'for-nonunif' | 'while-unif' | 'while-nonunif';
export function specToCode(spec: string): string {
let matches = spec.match('^(loop|for-unif|for-nonunif|for|while-unif|while-nonunif)-(.*)');
assert(matches !== null, `invalid spec string: ${spec}`);
let prefix = ' ';
const parts = [];
const end_parts = [prefix, '}\n'];
const kind = matches[1] as LoopKind;
let rest = matches[2];
parts.push(prefix);
switch (kind) {
case 'loop':
parts.push('loop {');
break;
case 'for':
parts.push('for (;;) {');
break;
case 'for-unif':
parts.push(`for (;<uniform_cond>;) {`);
break;
case 'for-nonunif':
parts.push(`for (;<nonuniform_cond>;) {`);
break;
case 'while-unif':
parts.push(`while (<uniform_cond>) {`);
break;
case 'while-nonunif':
parts.push(`while (<nonuniform_cond>) {`);
break;
}
parts.push('\n');
let in_continuing = false;
prefix = ' ';
while (rest.length > 0) {
const current_len = rest.length;
matches = rest.match(
'^(op|continuing|end|unif-break|always-break|cond-break|unif-break|always-return|cond-return|always-continue|cond-continue)(-|$)(.*)'
);
assert(matches !== null, `invalid spec string: ${spec}`);
const elem = matches[1];
rest = matches[3];
assert(rest.length < current_len, `pattern is not shrinking: '${rest}', from ${spec}`);
switch (elem) {
case 'op':
parts.push(prefix, '<op>\n');
break;
case 'end':
if (in_continuing) {
prefix = ' ';
}
prefix = ' ';
parts.push(...end_parts);
end_parts.length = 0;
in_continuing = false;
break;
case 'continuing':
parts.push(prefix, 'continuing {\n');
end_parts.unshift(prefix, '}\n');
in_continuing = true;
prefix = ' ';
break;
case 'unif-break':
assert(!in_continuing);
parts.push(prefix, `if <uniform_cond> {break;}\n`);
break;
case 'always-break':
assert(!in_continuing);
parts.push(prefix, 'break;\n');
break;
case 'cond-break':
if (in_continuing) {
parts.push(prefix, `break if <cond>;\n`);
} else {
parts.push(prefix, `if <cond> {break;}\n`);
}
break;
case 'always-return':
assert(!in_continuing);
parts.push(prefix, 'return;\n');
break;
case 'cond-return':
assert(!in_continuing);
parts.push(prefix, `if <cond> {return;}\n`);
break;
case 'always-continue':
assert(!in_continuing);
parts.push(prefix, 'continue;\n');
break;
case 'cond-continue':
assert(!in_continuing);
parts.push(prefix, `if <cond> {continue;}\n`);
break;
default:
unreachable(`invalid loop case spec ${spec}`);
}
}
parts.push(...end_parts);
return parts.join('');
}
export function LoopCase(spec: string, verdict: Verdict): Snippet {
return { name: spec, verdict, code: specToCode(spec) };
}