function grid(w, h) {
const arr = new Array(w);
for(let i = 0; i < w; i++) {
arr[i] = new Array(h);
}
return arr;
}
function canCoerce(param: string, arg: string) {
return param === arg;
}
function checkParameters(
params: string[],
args: string[]
) {
const compatibility: boolean[][] = grid(args.length, params.length);
let allCompatible: boolean = params.length === args.length; // Make sure our happy path is fast
for(let i = 0; i < Math.min(params.length, args.length); i++) {
let paramCompatible = canCoerce(params[i], args[i]);
allCompatible = allCompatible && paramCompatible;
compatibility[i][i] = paramCompatible;
}
if(allCompatible) {
console.log("Function call allowed!");
return;
}
// We had some kind of incompatibility. Either some params didn't match, or the lengths didn't match
// To provide good heuristics, probe to see if adjacent arguments can line up
// Start by filling in a compatibility grid
for(let i = 0; i < args.length; i++) {
for(let j = 0; j < params.length; j++) {
if(i === j) continue; // We've already computed these
compatibility[i][j] = canCoerce(args[i], params[j]);
}
}
function directly_invalid(i) {
if(i >= args.length || i >= params.length) {
return false;
}
// Check if the input is completely unsatisfied
for(let j = 0; j < args.length; j++) {
if(compatibility[j][i]) {
return false;
}
}
// Check if the parameter is useless
for(let j = 0; j < params.length; j++) {
if(compatibility[i][j]) {
return false;
}
}
return true;
}
function argument_extra(i) {
if(i >= compatibility.length) {
return false;
}
if(compatibility[i].length == 0) {
return true;
}
if(!compatibility[i].some(x => x)) {
return true;
}
return false;
}
function param_missing(i) {
if(i >= params.length) {
return false;
}
if(!compatibility.some(r => r[i])) {
return true;
}
return false;
}
function permuted() {
// Find the rows that might be part of a permutation
let possible_rows = [];
for(let i = 0; i < args.length; i++) {
if(compatibility[i].filter(j => j).length === 1) {
possible_rows.push(i);
}
}
let possible_cols = possible_rows.filter(i => {
let j = compatibility[i].indexOf(true);
let mr = possible_rows.filter(r => compatibility[r][j]);
return mr.length === 1;
});
let permuted_cols = possible_cols.filter(c => !compatibility[c][c]);
return permuted_cols;
}
function swapped(i) {
if(i >= args.length || i >= params.length) {
return false;
}
if(compatibility[i][i]) {
return false;
}
for(let j = 0; j < Math.min(args.length, params.length); j++) {
if(i == j) continue;
if(compatibility[j][j]) {
continue;
}
if(compatibility[i][j] && compatibility[j][i]) {
return j;
}
}
return false;
}
function removeArg(i) {
args.splice(i, 1);
compatibility.splice(i, 1);
}
function removeParam(i) {
params.splice(i, 1);
compatibility.forEach(r => r.splice(i, 1));
}
let limit = 10;
while((params.length || args.length) && limit > 0) {
let issueFound = false;
for(let i = 0; i < Math.max(args.length, params.length); i++) {
if (directly_invalid(i)) {
console.log(`Expected ${params[i]}, got ${args[i]}`);
removeArg(i);
removeParam(i);
issueFound = true;
break;
}
if (argument_extra(i)) {
console.log(`${args[i]} is extra!`);
removeArg(i);
issueFound = true;
break;
}
if (param_missing(i)) {
console.log(`${params[i]} is missing!`);
removeParam(i);
issueFound = true;
break;
}
}
let permutable = permuted();
if(permutable.length > 1) {
if(permutable.length == 2) {
console.log(`${args[permutable[0]]} and ${args[permutable[1]]} are swapped!`);
} else {
console.log(`The following arguments are in the wrong order: ${permutable.map(p => args[p]).join(', ')}`);
}
permutable.sort().reverse().forEach(p => {
removeArg(p);
removeParam(p);
});
issueFound = true;
continue;
}
if(!issueFound) {
// Now that there are no obvious issues, eliminate one correctly mapped parameter
for(let i = 0; i < Math.min(args.length, params.length); i++) {
if(compatibility[i][i]) {
removeArg(i);
removeParam(i);
break;
}
}
}
limit--;
}
if(limit <= 0) {
console.log("Diverged!!");
}
}
type TestCase = [string, string[], string[]]
const testCases: TestCase[] = [
/**/
['Valid', [], []],
['Valid', ['A'], ['A']],
['Valid', ['A','B'], ['A','B']],
['Valid', ['A', 'A'], ['A', 'A']],
['Invalid', ['A'], ['B']],
['Invalid', ['A', 'B'], ['A', 'C']],
['Invalid', ['A', 'B', 'C'], ['A', 'D', 'C']],
['Invalid', ['A', 'A', 'B'], ['A', 'A', 'C']],
['Invalid', ['A', 'A'], ['B', 'B']],
['Extra', [], ['A']],
['Extra', ['A'], ['A', 'B']],
['Extra', ['A'], ['A', 'B', 'C']],
['Extra', ['A', 'C'], ['A', 'B', 'C']],
['Extra', ['A', 'C'], ['B', 'A', 'C']],
/**/
['Extra', ['A'], ['A', 'A']],
/**/
['Extra', ['A', 'A'], ['A','A','A']],
['Extra', ['A', 'B'], ['A', 'A', 'B']],
['Extra', ['A', 'B'], ['A', 'B', 'B']],
['Extra', ['A', 'B'], ['A', 'A', 'B', 'B']],
['Extra and Invalid', ['A', 'B'], ['A', 'C', 'D']],
['Extra and Invalid', ['A', 'B', 'C'], ['A', 'C', 'F', 'C']],
['Missing', ['A'], []],
['Missing', ['A', 'B'], ['A']],
['Missing', ['A', 'B'], ['B']],
['Missing', ['A', 'A'], ['A']],
['Missing', ['A', 'A', 'A'], ['A']],
['Missing', ['A', 'B', 'C'], ['A', 'C']],
['Missing', ['A', 'B', 'C'], ['A', 'C']],
['Missing', ['A', 'B', 'B', 'C'], ['A', 'C']],
['Missing', ['A', 'B', 'A', 'B', 'C'], ['A', 'C']],
['Missing and Invalid', ['A', 'B', 'C'], ['A', 'D']],
['Missing and Extra', ['A', 'B', 'C'], ['A', 'C', 'F']],
['Swapped', ['A', 'B'], ['B', 'A']],
['Swapped', ['A', 'B', 'C'], ['B', 'A', 'C']],
['Swapped', ['A', 'B', 'C'], ['A', 'C', 'B']],
['Swapped', ['A', 'B', 'C'], ['C', 'B', 'A']],
['Permuted', ['A', 'B', 'C', 'D', 'E'], ['D', 'E', 'C', 'A', 'B']],
['Permuted', ['A', 'B', 'C'], ['B', 'C', 'A']],
['Swapped and Invalid', ['A', 'B', 'C'], ['C', 'F', 'A']],
['Swapped and Missing', ['A', 'B', 'C'], ['C', 'A']],/**/
['Missing, Extra, Invalid, Swapped', ["A", "B", "C", "D", "E", "F"], ["A", "C", "X", "G", "F", "E"]],
];
for(const c of testCases) {
console.log(`fn MyFunc(${c[1].join(', ')}) { ... }`);
console.log(` MyFunc(${c[2].join(', ')});`);
checkParameters(c[1], c[2]);
console.log('----------------------');
}