UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

163 lines 8.18 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NumericFns = void 0; exports.resolveAsNumeric = resolveAsNumeric; const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type"); const identifier_1 = require("../../environments/identifier"); const r_value_1 = require("../../../util/r-value"); const r_value_2 = require("../values/r-value"); const interval_constants_1 = require("../values/intervals/interval-constants"); const vector_constants_1 = require("../values/vectors/vector-constants"); const match_arguments_1 = require("./match-arguments"); const resolve_helper_1 = require("../../environments/resolve-helper"); /** R breaks a tie to the even neighbor, unlike `Math.round`, which always goes up */ function roundHalfEven(x, digits = 0) { const scale = 10 ** digits; const scaled = x * scale; const below = Math.floor(scaled); const rounded = scaled - below !== 0.5 ? Math.round(scaled) : below % 2 === 0 ? below : below + 1; return rounded / scale; } /** R rounds `%%` and `%/%` towards `-Inf`, unlike the JS `%` */ function mod(a, b) { return a - Math.floor(a / b) * b; } /** * Every numeric built-in the value solver folds, operators included: `-` is an entry like `sqrt` is, and both * are reached through {@link resolveAsNumeric}. Teaching flowR one more is a line here plus the matching * `evalHandler` in the built-in configuration -- a test checks that the two agree. * * An operator gets its operands under the names R gives them (`e1`, `e2`), and the ones that also exist as a * unary form declare `e2` as optional. Anything the fold hands back that is not a finite number stays `Top`, * so `sqrt(-1)` or `1/0` need no special case here. */ exports.NumericFns = { /* the arithmetic operators; `+` and `-` fold their unary form as well, which is why `e2` may be missing */ '+': { params: ['e1', 'e2'], fold: (a, b) => b === undefined ? a : a + b }, '-': { params: ['e1', 'e2'], fold: (a, b) => b === undefined ? -a : a - b }, '*': { params: ['e1', 'e2'], fold: (a, b) => a * b }, '/': { params: ['e1', 'e2'], fold: (a, b) => a / b }, '^': { params: ['e1', 'e2'], fold: (a, b) => a ** b }, '**': { params: ['e1', 'e2'], fold: (a, b) => a ** b }, '%%': { params: ['e1', 'e2'], fold: mod }, '%/%': { params: ['e1', 'e2'], fold: (a, b) => Math.floor(a / b) }, /* rounding, each under the parameter names R documents */ abs: { params: ['x'], fold: Math.abs }, sqrt: { params: ['x'], fold: Math.sqrt }, floor: { params: ['x'], fold: Math.floor }, ceiling: { params: ['x'], fold: Math.ceil }, trunc: { params: ['x'], fold: Math.trunc }, sign: { params: ['x'], fold: Math.sign }, round: { params: ['x', 'digits'], fold: roundHalfEven }, signif: { params: ['x', 'digits'], fold: (x, digits = 6) => digits >= 1 && digits <= 21 ? Number(x.toPrecision(digits)) : undefined }, /* exponentials and logarithms; `log` takes its base as a second argument, the rest are fixed */ exp: { params: ['x'], fold: Math.exp }, expm1: { params: ['x'], fold: Math.expm1 }, log: { params: ['x', 'base'], fold: (x, base) => base === undefined ? Math.log(x) : Math.log(x) / Math.log(base) }, log2: { params: ['x'], fold: Math.log2 }, log10: { params: ['x'], fold: Math.log10 }, log1p: { params: ['x'], fold: Math.log1p }, /* trigonometry and its hyperbolic counterparts */ sin: { params: ['x'], fold: Math.sin }, cos: { params: ['x'], fold: Math.cos }, tan: { params: ['x'], fold: Math.tan }, asin: { params: ['x'], fold: Math.asin }, acos: { params: ['x'], fold: Math.acos }, atan: { params: ['x'], fold: Math.atan }, atan2: { params: ['y', 'x'], fold: Math.atan2 }, sinh: { params: ['x'], fold: Math.sinh }, cosh: { params: ['x'], fold: Math.cosh }, tanh: { params: ['x'], fold: Math.tanh }, asinh: { params: ['x'], fold: Math.asinh }, acosh: { params: ['x'], fold: Math.acosh }, atanh: { params: ['x'], fold: Math.atanh }, /* bit twiddling, which R defines on 32-bit integers just as JS does */ bitwAnd: { params: ['a', 'b'], fold: (a, b) => a & b }, bitwOr: { params: ['a', 'b'], fold: (a, b) => a | b }, bitwXor: { params: ['a', 'b'], fold: (a, b) => a ^ b }, bitwNot: { params: ['a'], fold: (a) => ~a }, bitwShiftL: { params: ['a', 'n'], fold: (a, n) => a << n }, bitwShiftR: { params: ['a', 'n'], fold: (a, n) => a >>> n } }; /** the number an operand folds to, with a logical counting as its `0`/`1` just like R would coerce it */ function numeric(node, args) { const value = (0, r_value_1.unliftRValue)(resolve_helper_1.Resolve.toValue(node, args)); if (typeof value === 'boolean') { return Number(value); } else if ((0, r_value_1.isRNumberValue)(value)) { return value.complexNumber ? undefined : value.num; } /* a vector folds elementwise, so it only counts when every element is a plain number */ if (!Array.isArray(value) || value.length === 0) { return undefined; } const nums = value.map(e => (0, r_value_1.isRNumberValue)(e) && !e.complexNumber ? e.num : undefined); return nums.every(n => n !== undefined) ? nums : undefined; } /** the name a node calls, whether it is written as an operator or as a plain call of the quoted operator */ function calledName(node) { switch (node.type) { case type_1.RType.UnaryOp: case type_1.RType.BinaryOp: return node.operator; case type_1.RType.FunctionCall: return node.named ? identifier_1.Identifier.getName(node.functionName.content) : undefined; default: return undefined; } } /** apply `fold` to the operands, mapping over the one vector among them (R recycles, we only fold equal lengths) */ function apply(fold, operands) { const lengths = operands.filter(o => Array.isArray(o)).map(o => o.length); if (lengths.length === 0) { return fold(...operands); } else if (lengths.some(l => l !== lengths[0])) { return undefined; // R would recycle the shorter one, which is too easy to get wrong to guess at } const out = []; for (let i = 0; i < lengths[0]; i++) { const value = fold(...operands.map(o => Array.isArray(o) ? o[i] : o)); if (value === undefined || !Number.isFinite(value)) { return undefined; } out.push(value); } return out; } /** * Resolves any call of a {@link NumericFns} entry to a {@link Value}: the operators in prefix or infix form, * the unary `+`/`-`, and the named functions with their arguments in any order R accepts. An operand may be a * number, a logical (counting as its `0`/`1`), or a vector of numbers, which folds elementwise. Anything that * does not resolve, a result that is not finite, and a vector length mismatch all stay `Top`. */ function resolveAsNumeric(args) { const name = calledName(args.node); const fn = name === undefined ? undefined : exports.NumericFns[name]; if (fn === undefined) { return r_value_2.Top; } const nodes = (0, match_arguments_1.matchCallArguments)(args.node, fn.params); if (nodes === undefined || nodes[0] === undefined) { return r_value_2.Top; } const operands = []; for (const node of nodes) { if (node === undefined) { break; // the parameters R gives a default are the trailing ones, so the fold sees a shorter prefix } const value = numeric(node, args); if (value === undefined) { return r_value_2.Top; } operands.push(value); } const folded = apply(fn.fold, operands); if (typeof folded === 'number') { return Number.isFinite(folded) ? (0, interval_constants_1.intervalFrom)(folded, folded) : r_value_2.Top; } /* a number is an exact interval everywhere else in the solver, so the elements are lifted the same way */ return folded === undefined ? r_value_2.Top : (0, vector_constants_1.vectorFrom)(folded.map(n => (0, interval_constants_1.intervalFrom)(n, n))); } //# sourceMappingURL=resolve-numbers.js.map