@harmoniclabs/plu-ts-onchain
Version:
An embedded DSL for Cardano smart contracts creation coupled with a library for Cardano transactions, all in Typescript
519 lines (515 loc) • 20.6 kB
JavaScript
;
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMinVarDbn = exports.getNormalizedLettedArgs = exports.getLettedTerms = exports.default_getLettedTermsOptions = exports.getSortedLettedSet = exports.IRLetted = exports.expandedJsonLettedSetEntry = exports.jsonLettedSetEntry = void 0;
var iterTree_1 = require("../toUPLC/_internal/iterTree.js");
var IRVar_1 = require("./IRVar.js");
var positiveIntAsBytes_1 = require("../utils/positiveIntAsBytes.js");
var obj_utils_1 = require("@harmoniclabs/obj-utils");
var BasePlutsError_1 = require("../../utils/BasePlutsError.js");
var concatUint8Arr_1 = require("../utils/concatUint8Arr.js");
var isIRTerm_1 = require("../utils/isIRTerm.js");
var IRApp_1 = require("./IRApp.js");
var IRDelayed_1 = require("./IRDelayed.js");
var IRForced_1 = require("./IRForced.js");
var IRFunc_1 = require("./IRFunc.js");
var IRHoisted_1 = require("./IRHoisted.js");
var utils_1 = require("../utils/index.js");
var isIRParentTerm_1 = require("../utils/isIRParentTerm.js");
var _modifyChildFromTo_1 = require("../toUPLC/_internal/_modifyChildFromTo.js");
var groupByScope_1 = require("../toUPLC/subRoutines/handleLetted/groupByScope.js");
var IRConstr_1 = require("./IRConstr.js");
var IRCase_1 = require("./IRCase.js");
var IRHash_1 = require("../IRHash.js");
var equalIRTerm_1 = require("../utils/equalIRTerm.js");
var IRNodeKind_1 = require("../IRNodeKind.js");
var IRRecursive_1 = require("./IRRecursive.js");
var IRSelfCall_1 = require("./IRSelfCall.js");
function jsonLettedSetEntry(entry) {
return {
letted: (0, IRHash_1.irHashToHex)(entry.letted.hash),
nReferences: entry.nReferences
};
}
exports.jsonLettedSetEntry = jsonLettedSetEntry;
function expandedJsonLettedSetEntry(entry) {
return {
letted: (0, IRHash_1.irHashToHex)(entry.letted.hash),
letted_value: (0, utils_1.prettyIR)(entry.letted.value).text.split("\n"),
nReferences: entry.nReferences
};
}
exports.expandedJsonLettedSetEntry = expandedJsonLettedSetEntry;
var defaultLettedMeta = (0, obj_utils_1.freezeAll)({
forceHoist: false,
__src__: undefined,
isClosed: false
});
var n_hash_access = 0;
var IRLetted = /** @class */ (function () {
function IRLetted(DeBruijn, toLet, metadata, _unsafeHash) {
if (metadata === void 0) { metadata = {}; }
if (_unsafeHash === void 0) { _unsafeHash = undefined; }
DeBruijn = typeof DeBruijn === "bigint" ? Number(DeBruijn) : DeBruijn;
if (!(Number.isSafeInteger(DeBruijn) && DeBruijn >= 0))
throw new BasePlutsError_1.BasePlutsError("invalid index for an `IRLetted` instance");
while (toLet instanceof IRLetted ||
toLet instanceof IRHoisted_1.IRHoisted) {
toLet = toLet instanceof IRLetted ? toLet.value : toLet.hoisted;
}
if (!(0, isIRTerm_1.isIRTerm)(toLet))
throw new BasePlutsError_1.BasePlutsError("letted value was not an IRTerm");
this._dbn = DeBruijn;
this._value = toLet;
this._value.parent = this;
// we need the has before setting dependecies
this._hash = (0, IRHash_1.isIRHash)(_unsafeHash) ? _unsafeHash : undefined;
this._deps = undefined;
this._parent = undefined;
this.meta = __assign(__assign({}, defaultLettedMeta), metadata);
}
Object.defineProperty(IRLetted.prototype, "hash", {
get: function () {
if (!(0, IRHash_1.isIRHash)(this._hash)) {
/*
NOTE TO SELF:
NEVER
NEVER
NEVER
NEVER GET THE HASH BASED ON THE METADATA
NEVER
*/
var normalized = getNormalizedLettedArgs(this.dbn, this._value);
if (normalized === undefined) {
// `_value` doesn't includes any `IRVar`
// aka. there is nothing to normalize
// just use the value regardless of the
// `IRLetted` dbn instantiation
this._hash = (0, IRHash_1.hashIrData)((0, concatUint8Arr_1.concatUint8Arr)(IRLetted.tag, this._value.hash));
}
else {
this._hash = (0, IRHash_1.hashIrData)((0, concatUint8Arr_1.concatUint8Arr)(IRLetted.tag, (0, positiveIntAsBytes_1.positiveIntAsBytes)(normalized[0]), normalized[1].hash));
/*
const [ normalized_dbn, normalized_value] = normalized;
if( toHex( hash ) === "ee84fb036ed2726e01b0415109246927" )
{
const original_value = _value.clone();
const minDbn = getMinVarDbn( original_value );
const minUnb = _getMinUnboundDbn( original_value );
console.log(
"_ee84fb036ed2726e01b0415109246927_",
"\noriginal value:", prettyIRJsonStr( original_value, 2, { hoisted: false } ),
"\nmin dbn:", minDbn,
"\nmin unbound:", minUnb,
"\nnormalized dbn:", normalized_dbn,
);
}
//*/
}
}
return this._hash;
},
enumerable: false,
configurable: true
});
IRLetted.prototype.markHashAsInvalid = function () {
var _a;
this._hash = undefined;
this._deps = undefined;
(_a = this.parent) === null || _a === void 0 ? void 0 : _a.markHashAsInvalid();
};
IRLetted.prototype.isHashPresent = function () { return (0, IRHash_1.isIRHash)(this._hash); };
Object.defineProperty(IRLetted.prototype, "value", {
get: function () { return this._value; },
set: function (newVal) {
if (!(0, isIRTerm_1.isIRTerm)(newVal))
throw new BasePlutsError_1.BasePlutsError("letted term was not IRTerm");
if (!(0, equalIRTerm_1.shallowEqualIRTermHash)(this._value, newVal))
this.markHashAsInvalid();
// remove deps even if the value is the same
// newValue might be a clone of the current value
// and so have different (new) objects
this._deps = undefined;
// keep the parent reference in the old child, useful for compilation
// _value.parent = undefined;
this._value = newVal;
this._value.parent = this;
},
enumerable: false,
configurable: true
});
Object.defineProperty(IRLetted.prototype, "dbn", {
/**
* we need to keep track of the debruijn at which the `IRLetted` is instantiated
*
* this is because stuff like `new IRLetted( new IRVar(0) )` has different meaning
* at different DeBruijn levels
*
* knowing the DeBruijn we can differentiate them
*/
get: function () { return this._dbn; },
set: function (newDbn) {
if (!(Number.isSafeInteger(newDbn) && newDbn >= 0))
throw new BasePlutsError_1.BasePlutsError("invalid index for an `IRLetted` instance");
if (newDbn === this._dbn) {
// everything ok
// avoid calling `markHashAsInvalid`
return;
}
this.markHashAsInvalid();
this._dbn = newDbn;
},
enumerable: false,
configurable: true
});
IRLetted.prototype.isClosedAtDbn = function (dbn) {
if (!Number.isSafeInteger(dbn))
throw new Error("unexpected unsafe dbn integer");
var minUnbound = (0, groupByScope_1._getMinUnboundDbn)(this._value);
if (minUnbound === undefined)
return true;
return minUnbound < dbn;
};
Object.defineProperty(IRLetted.prototype, "dependencies", {
get: function () {
if (this._deps === undefined)
this._deps = getSortedLettedSet(getLettedTerms(this._value));
return this._deps;
},
enumerable: false,
configurable: true
});
Object.defineProperty(IRLetted.prototype, "parent", {
get: function () { return this._parent; },
set: function (newParent) {
if (!( // assert
// new parent value is different than current
this._parent !== newParent && (
// and the new parent value is valid
newParent === undefined ||
(0, isIRParentTerm_1.isIRParentTerm)(newParent))))
return;
this._parent = newParent;
},
enumerable: false,
configurable: true
});
Object.defineProperty(IRLetted, "kind", {
get: function () { return IRNodeKind_1.IRNodeKind.Letted; },
enumerable: false,
configurable: true
});
Object.defineProperty(IRLetted.prototype, "kind", {
get: function () { return IRLetted.kind; },
enumerable: false,
configurable: true
});
Object.defineProperty(IRLetted, "tag", {
get: function () { return new Uint8Array([IRLetted.kind]); },
enumerable: false,
configurable: true
});
IRLetted.prototype.clone = function () {
return new IRLetted(this.dbn, this.value.clone(), __assign({}, this.meta), this.isHashPresent() ? this.hash : undefined);
};
IRLetted.prototype.toJSON = function () { return this.toJson(); };
IRLetted.prototype.toJson = function () {
return {
type: "IRLetted",
hash: (0, IRHash_1.irHashToHex)(this.hash),
value: this.value.toJson()
};
};
return IRLetted;
}());
exports.IRLetted = IRLetted;
/**
* basically an insertion sort;
*
* @param {LettedSetEntry[]} lettedTerms
* @returns {LettedSetEntry[]} a **new** array with ```IRLetted```s with no dependencies first, followed by the dependents
*/
function getSortedLettedSet(lettedTerms) {
var set = [];
var hashesSet = [];
/**
* **O((n * m) * d)**
*
* where
*
* n = length of set
* m = number of terms passed
* d = number of unique dependencies among the passed terms
*/
function addToSet(_terms) {
var _loop_1 = function (i) {
var thisLettedEntry = _terms[i];
var thisHash = thisLettedEntry.letted.hash;
var idxInSet = hashesSet.findIndex(function (hash) { return (0, IRHash_1.equalIrHash)(hash, thisHash); });
// if( !hashesSet.includes( hash ) )
// "includes" uses standard equality (===)
if (idxInSet < 0) // not present
{
// add dependencies first
// dependencies don't have references to the current letted
// (of course, wouldn't be much of a dependecy otherwhise)
//
// don't add dependecies of dependecies since `dependecies` proerty
// already calls `getSortedLettedSet( getLettedTerms( _value ) )`
// so repeating it here would count deps twice (exponentially for deps of deps)
addToSet(getLettedTerms(thisLettedEntry.letted.value));
hashesSet.push(thisHash);
set.push({
letted: thisLettedEntry.letted,
nReferences: thisLettedEntry.nReferences
});
}
else {
var entry = set[idxInSet];
entry.nReferences += thisLettedEntry.nReferences;
entry.letted.meta.forceHoist =
entry.letted.meta.forceHoist ||
thisLettedEntry.letted.meta.forceHoist;
}
};
for (var i = 0; i < _terms.length; i++) {
_loop_1(i);
}
}
addToSet(lettedTerms);
return set;
}
exports.getSortedLettedSet = getSortedLettedSet;
exports.default_getLettedTermsOptions = {
all: false,
includeHoisted: false
};
/**
*
* @param {IRTerm} irTerm term to search in
* @returns direct letted terms (no possible dependencies)
*/
function getLettedTerms(irTerm, options) {
var _a = __assign(__assign({}, exports.default_getLettedTermsOptions), options), all = _a.all, includeHoisted = _a.includeHoisted;
var lettedTerms = [];
var stack = [irTerm];
while (stack.length > 0) {
var t = stack.pop();
if (t instanceof IRLetted) {
lettedTerms.push({ letted: t, nReferences: 1 });
if (all) {
stack.push(t.value);
}
continue;
}
if (t instanceof IRApp_1.IRApp) {
stack.push(t.fn, t.arg);
continue;
}
if (t instanceof IRConstr_1.IRConstr) {
stack.push.apply(stack, __spreadArray([], __read(Array.from(t.fields)), false));
continue;
}
if (t instanceof IRCase_1.IRCase) {
stack.push.apply(stack, __spreadArray([t.constrTerm], __read(Array.from(t.continuations)), false));
continue;
}
if (t instanceof IRFunc_1.IRFunc) {
stack.push(t.body);
continue;
}
if (t instanceof IRRecursive_1.IRRecursive) {
stack.push(t.body);
continue;
}
if (t instanceof IRForced_1.IRForced) {
stack.push(t.forced);
continue;
}
if (t instanceof IRDelayed_1.IRDelayed) {
stack.push(t.delayed);
continue;
}
if (includeHoisted) {
if (t instanceof IRHoisted_1.IRHoisted) {
stack.push(t.hoisted);
continue;
}
}
}
return lettedTerms;
}
exports.getLettedTerms = getLettedTerms;
function getNormalizedLettedArgs(lettedDbn, value) {
var _a;
var normalized_value = value.clone();
var minDbn = getMinVarDbn(normalized_value);
if (minDbn === undefined)
return undefined;
if (minDbn !== 0)
(0, iterTree_1.iterTree)(normalized_value, function (node, relativeDbn) {
if (node instanceof IRVar_1.IRVar) {
node.dbn -= minDbn;
}
else if (node instanceof IRSelfCall_1.IRSelfCall) {
node.dbn -= minDbn;
}
//*
else if (node instanceof IRLetted) {
var max = getMaxVarDbn(node.value);
if (
// no vars
typeof max !== "number" ||
// defined outside of letted
max >= relativeDbn) {
node.dbn -= minDbn;
}
else // if depends on vars in this letted
{
// TODO: fix double checking already inlined values
//
// this is currently a workaround
//
// the real problem is that sometimes (when?)
// `iterTree` goes back checking some value that was already modified
// in the case of `IRApp` as parent, throwing an error
// because we don't know which value we should modify
if (node.parent instanceof IRApp_1.IRApp) {
var parent_1 = node.parent;
var currentChild = node;
if ( // parent is actually pointing to child
currentChild === parent_1.arg ||
currentChild === parent_1.fn ||
(0, IRHash_1.equalIrHash)(parent_1.arg.hash, currentChild.hash) ||
(0, IRHash_1.equalIrHash)(parent_1.fn.hash, currentChild.hash)) {
// inline
(0, _modifyChildFromTo_1._modifyChildFromTo)(node.parent, node, node.value);
return true; // modified parent
}
else
return false; // modified parent
}
else {
if (node.parent) // only modify if not root
{
// inline
(0, _modifyChildFromTo_1._modifyChildFromTo)(node.parent, node, node.value);
return true; // modified parent
}
else
return false;
}
}
}
//*/
},
// shouldSkipNode ?
// hoisted terms are not really here
// will be substituted to variables when the time comes
// however now are here and we need to skip them
//
// !!! WARNING !! removing this causes strange uplc compilation BUGS
function (node, dbn) { return node instanceof IRHoisted_1.IRHoisted; });
// !!! IMPORTANT !!! _getMinUnboundDbn( value ) MUST be called on the original value; NOT the **modified** `normalized_value`
return [lettedDbn - ((_a = (0, groupByScope_1._getMinUnboundDbn)(value)) !== null && _a !== void 0 ? _a : minDbn), normalized_value];
}
exports.getNormalizedLettedArgs = getNormalizedLettedArgs;
/**
*
* @param term the ir term to iter to search for vars
* @returns {number | undefined}
*
* @example
* ```ts
* let minDbn = getMinVarDbn( new IRVar(0) ); // 0
* minDbn = getMinVarDbn( IRConst.unit ); // undefined
* minDbn = getMinVarDbn( new IRFunc( 1, new IRVar( 0 ) ) ); // 0
* ```
*/
function getMinVarDbn(term) {
var min = undefined;
var foundAny = false;
(0, iterTree_1.iterTree)(term, function (node) {
if (node instanceof IRVar_1.IRVar ||
node instanceof IRSelfCall_1.IRSelfCall) {
if (foundAny) {
min = Math.min(min, node.dbn);
}
else {
foundAny = true;
min = node.dbn;
}
}
},
// shouldSkipNode ?
// hoisted terms are not really here
// will be substituted to variables when the time comes
// however now are here and we need to skip them
function (node) { return node instanceof IRHoisted_1.IRHoisted; } // skip if hoisted
);
return min;
}
exports.getMinVarDbn = getMinVarDbn;
/**
*
* @param term the ir term to iter to search for vars
* @returns {number | undefined}
*/
function getMaxVarDbn(term) {
var max = undefined;
var foundAny = false;
(0, iterTree_1.iterTree)(term, function (node) {
if (node instanceof IRVar_1.IRVar ||
node instanceof IRSelfCall_1.IRSelfCall) {
if (foundAny) {
max = Math.max(max, node.dbn);
}
else {
foundAny = true;
max = node.dbn;
}
}
},
// shouldSkipNode ?
// hoisted terms are not really here
// will be substituted to variables when the time comes
// however now are here and we need to skip them
function (node) { return node instanceof IRHoisted_1.IRHoisted; } // skip if hoisted
);
return max;
}