api = {
const CONSTANTS = {"PI": "real", "E": "real", "TRUE": "bool", "FALSE": "bool"}
const FUNCTIONS = {}
"+ - * / pow copysign fdim fmin fmax fmod hypot remainder".split(" ").forEach(function(op) {
FUNCTIONS[op] = [["real", "real"], "real"];
});
("fabs sqrt exp log sin cos tan asin acos atan sinh cosh tanh asinh acosh atanh " +
"cbrt ceil erf erfc exp2 expm1 floor lgamma log10 log1p log2 logb rint " +
"round tgamma trunc").split(" ").forEach(function(op) {
FUNCTIONS[op] = [["real"], "real"];
});
FUNCTIONS["fma"] = [["real", "real", "real"], "real"];
"< > == != <= >=".split(" ").forEach(function(op) {
FUNCTIONS[op] = [["real", "real"], "bool"];
});
"and or".split(" ").forEach(function(op) {
FUNCTIONS[op] = [["bool", "bool"], "bool"];
});
const SECRETFUNCTIONS = {"^": "pow", "**": "pow", "abs": "fabs", "min": "fmin", "max": "fmax", "mod": "fmod"}
function tree_errors(tree, expected) {
var messages = [];
var names = [];
var rtype = bottom_up(tree, function(node, path, parent) {
switch(node.type) {
case "ConstantNode":
if (["number", "boolean"].indexOf((node.valueType || typeof node.value)) === -1) {
messages.push("Constants that are " + (node.valueType || typeof node.value) + "s not supported.");
}
return ({"number": "real", "boolean": "bool"})[(node.valueType || typeof node.value)] || "real";
case "FunctionNode":
const name = SECRETFUNCTIONS[node.name] || node.name;
if (!FUNCTIONS[name]) {
messages.push("Function <code>" + name + "</code> unsupported.");
} else if (FUNCTIONS[name][0].length !== node.args.length) {
messages.push("Function <code>" + name + "</code> expects " +
FUNCTIONS[name][0].length + " arguments");
} else if (""+extract(node.args) !== ""+FUNCTIONS[name][0]) {
messages.push("Function <code>" + name + "</code>" +
" expects arguments of type " +
FUNCTIONS[name][0].join(", ") +
", got " + extract(node.args).join(", "));
}
return (FUNCTIONS[name] || [[], "real"])[1];
case "OperatorNode":
node.op = SECRETFUNCTIONS[node.op] || node.op;
if (!FUNCTIONS[node.op]) {
messages.push("Operator <code>" + node.op + "</code> unsupported.");
} else if (FUNCTIONS[node.op][0].length !== node.args.length &&
!(node.op === "-" && node.args.length === 1)) {
messages.push("Operator <code>" + node.op + "</code> expects " +
FUNCTIONS[node.op][0].length + " arguments");
} else if (""+extract(node.args) !== ""+FUNCTIONS[node.op][0] &&
!(node.op === "-" && ""+extract(node.args) === "real") &&
!(is_comparison(node.op) )) {
messages.push("Operator <code>" + node.op + "</code>" +
" expects arguments of type " +
FUNCTIONS[node.op][0].join(", ") +
", got " + extract(node.args).join(", "));
}
return (FUNCTIONS[node.op] || [[], "real"])[1];
case "SymbolNode":
if (!CONSTANTS[node.name]) {
names.push(node.name);
return "real";
} else {
return CONSTANTS[node.name];
}
case "ConditionalNode":
if (node.condition.res !== "bool") {
messages.push("Conditional has type " + node.condition.res + " instead of bool");
}
if (node.trueExpr.res !== node.falseExpr.res) {
messages.push("Conditional branches have different types " + node.trueExpr.res + " and " + node.falseExpr.res);
}
return node.trueExpr.res;
default:
messages.push("Unsupported syntax; found unexpected <code>" + node.type + "</code>.")
return "real";
}
}).res;
if (rtype !== expected) {
messages.push("Expected an expression of type " + expected + ", got " + rtype);
}
return messages;
}
function bottom_up(tree, cb) {
if (tree.args) {
tree.args = tree.args.map(function(node) {return bottom_up(node, cb)});
} else if (tree.condition) {
tree.condition = bottom_up(tree.condition, cb);
tree.trueExpr = bottom_up(tree.trueExpr, cb);
tree.falseExpr = bottom_up(tree.falseExpr, cb);
}
tree.res = cb(tree);
return tree;
}
function dump_fpcore(formula, ranges) {
var tree = math.parse(formula);
var names = [];
var body = dump_tree(tree, names);
var precondition = ranges ? get_precondition_from_input_ranges(ranges) : null;
var dnames = [];
for (var i = 0; i < names.length; i++) {
if (dnames.indexOf(names[i]) === -1) dnames.push(names[i]);
}
var name = formula.replace("\\", "\\\\").replace("\"", "\\\"");
var fpcore = "(FPCore (" + dnames.join(" ") + ")\n :name \"" + name + "\"";
if (precondition) fpcore += "\n :pre " + precondition;
return fpcore + "\n " + body + ")";
}
function is_comparison(name) {
return ["==", "!=", "<", ">", "<=", ">="].indexOf(name) !== -1;
}
function flatten_comparisons(node) {
var terms = [];
(function collect_terms(node) {
if (node.type == "OperatorNode" && is_comparison(node.op)) {
collect_terms(node.args[0]);
collect_terms(node.args[1]);
} else {
terms.push(node.res);
}
})(node);
var conjuncts = [];
var iters = 0;
(function do_flatten(node) {
if (node.type == "OperatorNode" && is_comparison(node.op)) {
do_flatten(node.args[0]);
var i = iters++;
var prev = conjuncts[conjuncts.length - 1];
if (prev && prev[0] == node.op && prev[2] == terms[i]) {
prev.push(terms[i + 1]);
} else {
conjuncts.push([node.op, terms[i], terms[i+1]]);
}
do_flatten(node.args[1]);
}
})(node);
var comparisons = [];
for (var i = 0; i < conjuncts.length; i++) {
comparisons.push("(" + conjuncts[i].join(" ") + ")");
}
if (comparisons.length == 0) {
return "TRUE";
} else if (comparisons.length == 1) {
return comparisons[0];
} else {
return "(and " + comparisons.join(" ") + ")";
}
}
function extract(args) {return args.map(function(n) {return n.res});}
function dump_tree(tree, names) {
return bottom_up(tree, function(node) {
switch(node.type) {
case "ConstantNode":
return "" + node.value;
case "FunctionNode":
const name = SECRETFUNCTIONS[node.name] || node.name;
return "(" + name + " " + extract(node.args).join(" ") + ")";
case "OperatorNode":
node.op = SECRETFUNCTIONS[node.op] || node.op;
if (is_comparison(node.op)) {
return flatten_comparisons(node);
} else {
return "(" + node.op + " " + extract(node.args).join(" ") + ")";
}
case "SymbolNode":
if (!CONSTANTS[node.name])
names.push(node.name);
return node.name;
case "ConditionalNode":
return "(if " + node.condition.res +
" " + node.trueExpr.res +
" " + node.falseExpr.res + ")";
default:
throw SyntaxError("Invalid tree!");
}
}).res;
}
function get_varnames_mathjs(mathjs_text) {
const names = []
dump_tree(math.parse(mathjs_text), names)
var dnames = [];
for (var i = 0; i < names.length; i++) {
if (dnames.indexOf(names[i]) === -1) dnames.push(names[i]);
}
return dnames
}
function get_precondition_from_input_ranges(ranges) {
const exprs = ranges.map(([name, [start, end]]) => `(<= ${start} ${name} ${end})`).join(' ')
return `(and ${exprs})`
}
function get_input_range_errors([low, high] = [undefined, undefined], empty_if_missing=false) {
if ((low === undefined || low === '') || (high === undefined || high === '')) return empty_if_missing ? [] : ['input missing']
const A = []
if (!(low === undefined || low === '') && isNaN(Number(low))) {
A.push(`The start of the range (${low}) is not a number.`)
} else if (!Number.isFinite(Number(low))) {
A.push(`The start of the range (${low}) is outside the floating point range.`)
}
if (!(high === undefined || high === '') && isNaN(Number(high))) {
A.push(`The end of the range (${high}) is not a number.`)
} else if (!Number.isFinite(Number(high))) {
A.push(`The end of the range (${high}) is outside the floating point range.`)
}
if (Number(low) > Number(high)) A.push(`The start of the range is higher than the end.`)
return A
}
function FPCoreBody(mathJSExpr) {
return dump_tree(math.parse(mathJSExpr), [])
}
return {
rangeErrors: get_input_range_errors,
FPCorePrecondition: get_precondition_from_input_ranges,
getVarnamesMathJS: get_varnames_mathjs,
parseErrors: mathJSExpr => {
function mathJSErrors (mathJSExpr) {
try {math.parse(mathJSExpr)} catch (e) {return [e.message]}
return []
}
const mjserrors = mathJSErrors(mathJSExpr)
return mjserrors.length > 0 ? mjserrors : tree_errors(math.parse(mathJSExpr), 'real')
},
FPCoreBody,
makeFPCore: ({ specMathJS, ranges, targetFPCoreBody, name=specMathJS }) => {
const vars = get_varnames_mathjs(specMathJS)
const target = targetFPCoreBody ? `:herbie-target ${targetFPCoreBody}\n ` : ''
return `(FPCore (${vars.join(' ')})\n :name "${name}"\n :pre ${get_precondition_from_input_ranges(ranges)}\n ${target}${FPCoreBody(specMathJS)})`
}
}
}