@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
115 lines • 5.8 kB
JavaScript
;
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