UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

115 lines 5.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LogicalOps = exports.ComparisonOps = void 0; exports.resolveAsComparison = resolveAsComparison; exports.resolveAsLogical = resolveAsLogical; exports.resolveAsGroup = resolveAsGroup; const r_function_call_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call"); 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 logical_constants_1 = require("../values/logical/logical-constants"); const r_value_2 = require("../values/r-value"); const general_1 = require("../values/general"); const resolve_helper_1 = require("../../environments/resolve-helper"); const node_value_1 = require("./node-value"); /** the scalar an operand folds to, with a logical counting as its `0`/`1` just like R would coerce it */ function operand(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; } return typeof value === 'object' && value !== null && 'str' in value ? value.str : undefined; } /** as a condition, R takes any non-zero number for `TRUE`; `NA`, strings and everything else do not fold */ function asLogical(node, args) { const value = operand(node, args); return typeof value === 'number' && !Number.isNaN(value) ? value !== 0 : undefined; } /** the operands of a binary operator and its entry in `ops`, with R presenting the `%.%` ones as an infix call */ function binary(node, ops) { let name, lhs, rhs; if (node.type === type_1.RType.BinaryOp) { [name, lhs, rhs] = [node.operator, node.lhs, node.rhs]; } else if (node.type === type_1.RType.FunctionCall && node.named && node.arguments.length === 2) { const [l, r] = node.arguments; name = identifier_1.Identifier.getName(node.functionName.content); /* a named argument may swap the operands, as in `` `-`(e2 = 1, e1 = 5) ``, so we only take positional ones */ [lhs, rhs] = [l === r_function_call_1.EmptyArgument || l.name ? undefined : l.value, r === r_function_call_1.EmptyArgument || r.name ? undefined : r.value]; } else { return undefined; } const op = ops[name]; return op === undefined || lhs === undefined || rhs === undefined ? undefined : { op, lhs, rhs }; } function logicalValue(value) { return value ? logical_constants_1.ValueLogicalTrue : logical_constants_1.ValueLogicalFalse; } /** `strings` marks the operators that also fold for two strings; the ordering ones do not, as R compares them by locale collation */ exports.ComparisonOps = { '==': { apply: (a, b) => a === b, strings: true }, '!=': { apply: (a, b) => a !== b, strings: true }, '<': { apply: (a, b) => a < b, strings: false }, '>': { apply: (a, b) => a > b, strings: false }, '<=': { apply: (a, b) => a <= b, strings: false }, '>=': { apply: (a, b) => a >= b, strings: false } }; /** * Resolves a comparison operator on two scalars of the same kind to a {@link Value} logical, with a logical counting as * its `0`/`1`. Operands of different kinds stay Top, as R would coerce them to a common type first. */ function resolveAsComparison(args) { const bin = binary(args.node, exports.ComparisonOps); if (bin === undefined) { return r_value_2.Top; } const lhs = operand(bin.lhs, args); const rhs = operand(bin.rhs, args); if (lhs === undefined || rhs === undefined || typeof lhs !== typeof rhs || (typeof lhs === 'string' && !bin.op.strings)) { return r_value_2.Top; } return logicalValue(bin.op.apply(lhs, rhs)); } /** `decidedBy` is the lhs that fixes the result of the short-circuiting `&&`/`||` on its own; `&`/`|` vectorize, so they need both sides */ exports.LogicalOps = { '&&': { apply: (a, b) => a && b, decidedBy: false }, '||': { apply: (a, b) => a || b, decidedBy: true }, '&': { apply: (a, b) => a && b, decidedBy: undefined }, '|': { apply: (a, b) => a || b, decidedBy: undefined } }; /** * Resolves the unary `!` and the binary {@link LogicalOps} on scalar logicals to a {@link Value} logical. * `NA` and any operand that does not fold keep the result Top, unless the lhs alone already decides it. */ function resolveAsLogical(args) { const node = args.node; if (node.type === type_1.RType.UnaryOp) { const value = node.operator === '!' ? asLogical(node.operand, args) : undefined; return value === undefined ? r_value_2.Top : logicalValue(!value); } const bin = binary(node, exports.LogicalOps); const lhs = bin === undefined ? undefined : asLogical(bin.lhs, args); if (bin === undefined || lhs === undefined) { return r_value_2.Top; } else if (lhs === bin.op.decidedBy) { return logicalValue(lhs); } const rhs = asLogical(bin.rhs, args); return rhs === undefined ? r_value_2.Top : logicalValue(bin.op.apply(lhs, rhs)); } /** Resolves `(x)` to the {@link Value} of `x`, so that a parenthesized sub-expression folds like the expression itself. */ function resolveAsGroup(args) { const node = args.node; if (node.type !== type_1.RType.ExpressionList || node.children.length !== 1) { return r_value_2.Top; } /* unwrap the set again, as the caller of a handler wraps the returned value in one */ return node_value_1.NodeValue.sole((0, general_1.valueSetGuard)(resolve_helper_1.Resolve.toValue(node.children[0], args))) ?? r_value_2.Top; } //# sourceMappingURL=resolve-operators.js.map