@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
230 lines • 8.48 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AbstractDomain = exports.DEFAULT_INFERENCE_LIMIT = void 0;
const assert_1 = require("../../util/assert");
const lattice_1 = require("./lattice");
/**
* The default limit of inferred constraints in {@link AbstractDomain|AbstractDomains}.
*/
exports.DEFAULT_INFERENCE_LIMIT = 50;
/**
* An abstract domain as complete lattice with a widening and narrowing operator.
* All operations of value abstract domains should not modify the domain in-place but return new values using {@link create}.
* @template Value - Type of an abstract element of the abstract domain representing possible elements (excludes `Top` and `Bot`)
* @template Top - Type of the Top element of the abstract domain representing all possible elements
* @template Bot - Type of the Bottom element of the abstract domain representing no possible elements
* @template Lift - Type of the current abstract value in the abstract domain (defaults to `Value` or `Top` or `Bot`)
*/
class AbstractDomain {
_value;
constructor(value) {
this._value = value;
}
get value() {
return this._value;
}
equals(other) {
if (this.value === other.value || (this.isTop() && other.isTop()) || (this.isBottom() && other.isBottom())) {
return true;
}
else if (!this.isValue() || !other.isValue()) {
return false;
}
return this.equalsValue(other);
}
leq(other) {
if (this.isBottom() || other.isTop() || this.equals(other)) {
return true;
}
else if (!this.isValue() || !other.isValue()) {
return false;
}
return this.leqValue(other);
}
join(other) {
other = other instanceof AbstractDomain ? other : this.create(other);
if (this.isTop() || other.isTop()) {
return this.top();
}
else if (this.isBottom()) {
return this.create(other.value);
}
else if (other.isBottom()) {
return this.create(this.value);
}
else if (!this.isValue() || !other.isValue()) {
return this.bottom();
}
return this.joinValue(other);
}
/**
* Joins the current abstract value with multiple other abstract values.
*/
joinAll(values) {
let result = this.create(this.value);
for (const other of values) {
result = result.join(other);
}
return result;
}
meet(other) {
other = other instanceof AbstractDomain ? other : this.create(other);
if (this.isTop()) {
return this.create(other.value);
}
else if (other.isTop()) {
return this.create(this.value);
}
else if (this.isBottom() || other.isBottom()) {
return this.bottom();
}
else if (!this.isValue() || !other.isValue()) {
return this.top();
}
return this.meetValue(other);
}
/**
* Meets the current abstract value with multiple other abstract values.
*/
meetAll(values) {
let result = this.create(this.value);
for (const other of values) {
result = result.meet(other);
}
return result;
}
/**
* Widens the current abstract value with another abstract value as a sound over-approximation of the join (least upper bound) for fixpoint iteration acceleration.
*/
widen(other) {
if (this.isTop() || other.isTop()) {
return this.top();
}
else if (this.isBottom()) {
return this.create(other.value);
}
else if (other.isBottom()) {
return this.create(this.value);
}
else if (!this.isValue() || !other.isValue()) {
return this.bottom();
}
return this.widenValue?.(other) ?? (other.leq(this) ? this.create(this.value) : this.top());
}
/**
* Narrows the current abstract value with another abstract value as a sound over-approximation of the meet (greatest lower bound) to refine the value after widening.
*/
narrow(other) {
if (this.isTop()) {
return this.create(other.value);
}
else if (other.isTop()) {
return this.create(this.value);
}
else if (this.isBottom() || other.isBottom()) {
return this.bottom();
}
else if (!this.isValue() || !other.isValue()) {
return this.top();
}
return this.narrowValue?.(other) ?? (this.isTop() ? this.create(other.value) : this.create(this.value));
}
transform(transform, bottomDefault, topDefault) {
if (bottomDefault !== undefined && this.isBottom()) {
return this.create(bottomDefault);
}
else if (topDefault !== undefined && this.isTop()) {
return this.create(topDefault);
}
return this.create(transform(this.value));
}
merge(other, merge, bottomDefault, topDefault) {
if (bottomDefault !== undefined) {
if (this.isBottom()) {
return this.create(bottomDefault(other.value));
}
else if (other.isBottom()) {
return this.create(bottomDefault(this.value));
}
}
if (topDefault !== undefined) {
if (this.isTop()) {
return this.create(topDefault(other.value));
}
else if (other.isTop()) {
return this.create(topDefault(this.value));
}
}
return this.create(merge(this.value, other.value));
}
toJSON() {
if (this.value === lattice_1.Top) {
return lattice_1.Top.description;
}
else if (this.value === lattice_1.Bottom) {
return lattice_1.Bottom.description;
}
return this.jsonify();
}
toString() {
if (this.value === lattice_1.Top) {
return lattice_1.TopSymbol;
}
else if (this.value === lattice_1.Bottom) {
return lattice_1.BottomSymbol;
}
return this.stringify();
}
isNotTop() {
return !this.isTop();
}
isNotBottom() {
return !this.isBottom();
}
isNotValue() {
return !this.isValue();
}
/**
* Joins an array of abstract values by joining the first abstract value with the other values in the array.
* The provided array of abstract values must not be empty or a default value must be provided!
*/
static joinAll(values, defaultValue) {
(0, assert_1.guard)(values.length > 0 || defaultValue !== undefined, 'Abstract values to join cannot be empty');
return values[0]?.joinAll(values.slice(1)) ?? defaultValue;
}
/**
* Meets an array of abstract values by meeting the first abstract value with the other values in the array.
* The provided array of abstract values must not be empty or a default value must be provided!
*/
static meetAll(values, defaultValue) {
(0, assert_1.guard)(values.length > 0 || defaultValue !== undefined, 'Abstract values to meet cannot be empty');
return values[0]?.meetAll(values.slice(1)) ?? defaultValue;
}
/**
* Converts an element of an abstract domain into a string.
*/
static toString(value) {
if (value instanceof Map) {
return `{${value.entries().map(([key, value]) => `${AbstractDomain.toString(key)} -> ${AbstractDomain.toString(value)}`).toArray().join(', ')}}`;
}
else if (value instanceof Set) {
return `{${value.values().map(AbstractDomain.toString).toArray().join(', ')}}`;
}
else if (typeof value === 'object' && value !== null && value.toString !== Object.prototype.toString) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
return value.toString();
}
else if (Array.isArray(value)) {
return `[${value.map(AbstractDomain.toString).join(', ')}]`;
}
else if (value === lattice_1.Top) {
return lattice_1.TopSymbol;
}
else if (value === lattice_1.Bottom) {
return lattice_1.BottomSymbol;
}
return JSON.stringify(value);
}
}
exports.AbstractDomain = AbstractDomain;
//# sourceMappingURL=abstract-domain.js.map