@mavrykdynamics/taquito-michel-codec
Version:
Michelson parser/validator/formatter
549 lines (548 loc) • 18.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.expandMacros = exports.MacroError = void 0;
const taquito_core_1 = require("@mavrykdynamics/taquito-core");
const michelson_types_1 = require("./michelson-types");
/**
* @category Error
* @description Error that indicates macros failed to be expanded
*/
class MacroError extends taquito_core_1.TaquitoError {
constructor(prim, message) {
super();
this.prim = prim;
this.message = message;
this.name = 'MacroError';
}
}
exports.MacroError = MacroError;
function assertArgs(ex, n) {
var _a, _b;
if ((n === 0 && ex.args === undefined) || ((_a = ex.args) === null || _a === void 0 ? void 0 : _a.length) === n) {
return true;
}
throw new MacroError(ex, `macro ${ex.prim} expects ${n} arguments, was given ${(_b = ex.args) === null || _b === void 0 ? void 0 : _b.length}`);
}
function assertNoAnnots(ex) {
if (ex.annots === undefined) {
return true;
}
throw new MacroError(ex, `unexpected annotation on macro ${ex.prim}: ${ex.annots}`);
}
function assertIntArg(ex, arg) {
if ('int' in arg) {
return true;
}
throw new MacroError(ex, `macro ${ex.prim} expects int argument`);
}
function parsePairUnpairExpr(p, expr, annotations, agg) {
let i = 0;
let ai = 0;
const ann = [null, null];
// Left expression
let lexpr;
if (i === expr.length) {
throw new MacroError(p, `unexpected end: ${p.prim}`);
}
let c = expr[i++];
switch (c) {
case 'P': {
const { r, n, an } = parsePairUnpairExpr(p, expr.slice(i), annotations.slice(ai), agg);
lexpr = r;
i += n;
ai += an;
break;
}
case 'A':
if (ai !== annotations.length) {
ann[0] = annotations[ai++];
}
break;
default:
throw new MacroError(p, `${p.prim}: unexpected character: ${c}`);
}
// Right expression
let rexpr;
if (i === expr.length) {
throw new MacroError(p, `unexpected end: ${p.prim}`);
}
c = expr[i++];
switch (c) {
case 'P': {
const { r, n, an } = parsePairUnpairExpr(p, expr.slice(i), annotations.slice(ai), agg);
rexpr = r.map(([v, a]) => [v + 1, a]);
i += n;
ai += an;
break;
}
case 'I':
if (ai !== annotations.length) {
ann[1] = annotations[ai++];
}
break;
default:
throw new MacroError(p, `${p.prim}: unexpected character: ${c}`);
}
return { r: agg(lexpr, rexpr, [0, ann]), n: i, an: ai };
}
function parseSetMapCadr(p, expr, vann, term) {
const c = expr[0];
switch (c) {
case 'A':
return expr.length > 1
? [
{ prim: 'DUP' },
{
prim: 'DIP',
args: [
[{ prim: 'CAR', annots: ['@%%'] }, parseSetMapCadr(p, expr.slice(1), [], term)],
],
},
{ prim: 'CDR', annots: ['@%%'] },
{ prim: 'SWAP' },
{ prim: 'PAIR', annots: ['%@', '%@', ...vann] },
]
: term.a;
case 'D':
return expr.length > 1
? [
{ prim: 'DUP' },
{
prim: 'DIP',
args: [
[{ prim: 'CDR', annots: ['@%%'] }, parseSetMapCadr(p, expr.slice(1), [], term)],
],
},
{ prim: 'CAR', annots: ['@%%'] },
{ prim: 'PAIR', annots: ['%@', '%@', ...vann] },
]
: term.d;
default:
throw new MacroError(p, `${p.prim}: unexpected character: ${c}`);
}
}
function trimLast(a, v) {
let l = a.length;
while (l > 0 && a[l - 1] === v) {
l--;
}
return a.slice(0, l);
}
function filterAnnotations(a) {
const fields = [];
const rest = [];
if (a !== undefined) {
for (const v of a) {
(v.length !== 0 && v[0] === '%' ? fields : rest).push(v);
}
}
return { fields, rest };
}
function mkPrim({ prim, annots, args }) {
return Object.assign(Object.assign({ prim }, (annots && { annots })), (args && { args }));
}
const pairRe = /^P[PAI]{3,}R$/;
const unpairRe = /^UNP[PAI]{2,}R$/;
const cadrRe = /^C[AD]{2,}R$/;
const setCadrRe = /^SET_C[AD]+R$/;
const mapCadrRe = /^MAP_C[AD]+R$/;
const diipRe = /^DI{2,}P$/;
const duupRe = /^DU+P$/;
function expandMacros(ex, opt) {
const proto = (opt === null || opt === void 0 ? void 0 : opt.protocol) || michelson_types_1.DefaultProtocol;
function mayRename(annots) {
return annots !== undefined ? [{ prim: 'RENAME', annots }] : [];
}
switch (ex.prim) {
// Compare
case 'CMPEQ':
case 'CMPNEQ':
case 'CMPLT':
case 'CMPGT':
case 'CMPLE':
case 'CMPGE':
if (assertArgs(ex, 0)) {
return [{ prim: 'COMPARE' }, mkPrim({ prim: ex.prim.slice(3), annots: ex.annots })];
}
break;
case 'IFEQ':
case 'IFNEQ':
case 'IFLT':
case 'IFGT':
case 'IFLE':
case 'IFGE':
if (assertArgs(ex, 2)) {
return [
{ prim: ex.prim.slice(2) },
mkPrim({ prim: 'IF', annots: ex.annots, args: ex.args }),
];
}
break;
case 'IFCMPEQ':
case 'IFCMPNEQ':
case 'IFCMPLT':
case 'IFCMPGT':
case 'IFCMPLE':
case 'IFCMPGE':
if (assertArgs(ex, 2)) {
return [
{ prim: 'COMPARE' },
{ prim: ex.prim.slice(5) },
mkPrim({ prim: 'IF', annots: ex.annots, args: ex.args }),
];
}
break;
// Fail
case 'FAIL':
if (assertArgs(ex, 0) && assertNoAnnots(ex)) {
return [{ prim: 'UNIT' }, { prim: 'FAILWITH' }];
}
break;
// Assertion macros
case 'ASSERT':
if (assertArgs(ex, 0) && assertNoAnnots(ex)) {
return [
{
prim: 'IF',
args: [[], [[{ prim: 'UNIT' }, { prim: 'FAILWITH' }]]],
},
];
}
break;
case 'ASSERT_EQ':
case 'ASSERT_NEQ':
case 'ASSERT_LT':
case 'ASSERT_GT':
case 'ASSERT_LE':
case 'ASSERT_GE':
if (assertArgs(ex, 0) && assertNoAnnots(ex)) {
return [
{ prim: ex.prim.slice(7) },
{
prim: 'IF',
args: [[], [[{ prim: 'UNIT' }, { prim: 'FAILWITH' }]]],
},
];
}
break;
case 'ASSERT_CMPEQ':
case 'ASSERT_CMPNEQ':
case 'ASSERT_CMPLT':
case 'ASSERT_CMPGT':
case 'ASSERT_CMPLE':
case 'ASSERT_CMPGE':
if (assertArgs(ex, 0) && assertNoAnnots(ex)) {
return [
[{ prim: 'COMPARE' }, { prim: ex.prim.slice(10) }],
{
prim: 'IF',
args: [[], [[{ prim: 'UNIT' }, { prim: 'FAILWITH' }]]],
},
];
}
break;
case 'ASSERT_NONE':
if (assertArgs(ex, 0) && assertNoAnnots(ex)) {
return [
{
prim: 'IF_NONE',
args: [[], [[{ prim: 'UNIT' }, { prim: 'FAILWITH' }]]],
},
];
}
break;
case 'ASSERT_SOME':
if (assertArgs(ex, 0)) {
return [
{
prim: 'IF_NONE',
args: [[[{ prim: 'UNIT' }, { prim: 'FAILWITH' }]], mayRename(ex.annots)],
},
];
}
break;
case 'ASSERT_LEFT':
if (assertArgs(ex, 0)) {
return [
{
prim: 'IF_LEFT',
args: [mayRename(ex.annots), [[{ prim: 'UNIT' }, { prim: 'FAILWITH' }]]],
},
];
}
break;
case 'ASSERT_RIGHT':
if (assertArgs(ex, 0)) {
return [
{
prim: 'IF_LEFT',
args: [[[{ prim: 'UNIT' }, { prim: 'FAILWITH' }]], mayRename(ex.annots)],
},
];
}
break;
// Syntactic conveniences
case 'IF_SOME':
if (assertArgs(ex, 2)) {
return [mkPrim({ prim: 'IF_NONE', annots: ex.annots, args: [ex.args[1], ex.args[0]] })];
}
break;
case 'IF_RIGHT':
if (assertArgs(ex, 2)) {
return [mkPrim({ prim: 'IF_LEFT', annots: ex.annots, args: [ex.args[1], ex.args[0]] })];
}
break;
// CAR/CDR n
case 'CAR':
case 'CDR':
if (ex.args !== undefined) {
if (assertArgs(ex, 1) && assertIntArg(ex, ex.args[0])) {
const n = parseInt(ex.args[0].int, 10);
return mkPrim({
prim: 'GET',
args: [{ int: ex.prim === 'CAR' ? String(n * 2 + 1) : String(n * 2) }],
annots: ex.annots,
});
}
}
else {
return ex;
}
}
// More syntactic conveniences
// PAPPAIIR macro
if (pairRe.test(ex.prim)) {
if (assertArgs(ex, 0)) {
const { fields, rest } = filterAnnotations(ex.annots);
const { r } = parsePairUnpairExpr(ex, ex.prim.slice(1), fields, (l, r, top) => [
...(l || []),
...(r || []),
top,
]);
return r.map(([v, a], i) => {
const ann = [
...trimLast(a, null).map((v) => (v === null ? '%' : v)),
...(v === 0 && i === r.length - 1 ? rest : []),
];
const leaf = mkPrim({ prim: 'PAIR', annots: ann.length !== 0 ? ann : undefined });
return v === 0
? leaf
: {
prim: 'DIP',
args: v === 1 ? [[leaf]] : [{ int: String(v) }, [leaf]],
};
});
}
}
// UNPAPPAIIR macro
if (unpairRe.test(ex.prim)) {
if ((0, michelson_types_1.ProtoInferiorTo)(proto, michelson_types_1.Protocol.PtAtLas) && assertArgs(ex, 0)) {
const { r } = parsePairUnpairExpr(ex, ex.prim.slice(3), ex.annots || [], (l, r, top) => [
top,
...(r || []),
...(l || []),
]);
return r.map(([v, a]) => {
const leaf = [
{ prim: 'DUP' },
mkPrim({ prim: 'CAR', annots: a[0] !== null ? [a[0]] : undefined }),
{
prim: 'DIP',
args: [[mkPrim({ prim: 'CDR', annots: a[1] !== null ? [a[1]] : undefined })]],
},
];
return v === 0
? leaf
: {
prim: 'DIP',
args: v === 1 ? [[leaf]] : [{ int: String(v) }, [leaf]],
};
});
}
else {
if (ex.prim === 'UNPAIR') {
return ex;
}
if (assertArgs(ex, 0)) {
// 008_edo: annotations are deprecated
const { r } = parsePairUnpairExpr(ex, ex.prim.slice(3), [], (l, r, top) => [
top,
...(r || []),
...(l || []),
]);
return r.map(([v]) => {
const leaf = mkPrim({
prim: 'UNPAIR',
});
return v === 0
? leaf
: {
prim: 'DIP',
args: v === 1 ? [[leaf]] : [{ int: String(v) }, [leaf]],
};
});
}
}
}
// C[AD]+R macro
if (cadrRe.test(ex.prim)) {
if (assertArgs(ex, 0)) {
const ch = [...ex.prim.slice(1, ex.prim.length - 1)];
return ch.map((c, i) => {
const ann = i === ch.length - 1 ? ex.annots : undefined;
switch (c) {
case 'A':
return mkPrim({ prim: 'CAR', annots: ann });
case 'D':
return mkPrim({ prim: 'CDR', annots: ann });
default:
throw new MacroError(ex, `unexpected character: ${c}`);
}
});
}
}
// SET_C[AD]+R macro
if (setCadrRe.test(ex.prim)) {
if (assertArgs(ex, 0)) {
const { fields, rest } = filterAnnotations(ex.annots);
if (fields.length > 1) {
throw new MacroError(ex, `unexpected annotation on macro ${ex.prim}: ${fields}`);
}
const term = fields.length !== 0
? {
a: [
{ prim: 'DUP' },
{ prim: 'CAR', annots: fields },
{ prim: 'DROP' },
{ prim: 'CDR', annots: ['@%%'] },
{ prim: 'SWAP' },
{ prim: 'PAIR', annots: [fields[0], '%@'] },
],
d: [
{ prim: 'DUP' },
{ prim: 'CDR', annots: fields },
{ prim: 'DROP' },
{ prim: 'CAR', annots: ['@%%'] },
{ prim: 'PAIR', annots: ['%@', fields[0]] },
],
}
: {
a: [
{ prim: 'CDR', annots: ['@%%'] },
{ prim: 'SWAP' },
{ prim: 'PAIR', annots: ['%', '%@'] },
],
d: [
{ prim: 'CAR', annots: ['@%%'] },
{ prim: 'PAIR', annots: ['%@', '%'] },
],
};
return parseSetMapCadr(ex, ex.prim.slice(5, ex.prim.length - 1), rest, term);
}
}
// MAP_C[AD]+R macro
if (mapCadrRe.test(ex.prim)) {
if (assertArgs(ex, 1)) {
const { fields } = filterAnnotations(ex.annots);
if (fields.length > 1) {
throw new MacroError(ex, `unexpected annotation on macro ${ex.prim}: ${fields}`);
}
const term = {
a: [
{ prim: 'DUP' },
{ prim: 'CDR', annots: ['@%%'] },
{
prim: 'DIP',
args: [
[
mkPrim({
prim: 'CAR',
annots: fields.length !== 0 ? ['@' + fields[0].slice(1)] : undefined,
}),
ex.args[0],
],
],
},
{ prim: 'SWAP' },
{ prim: 'PAIR', annots: [fields.length !== 0 ? fields[0] : '%', '%@'] },
],
d: [
{ prim: 'DUP' },
mkPrim({
prim: 'CDR',
annots: fields.length !== 0 ? ['@' + fields[0].slice(1)] : undefined,
}),
ex.args[0],
{ prim: 'SWAP' },
{ prim: 'CAR', annots: ['@%%'] },
{ prim: 'PAIR', annots: ['%@', fields.length !== 0 ? fields[0] : '%'] },
],
};
return parseSetMapCadr(ex, ex.prim.slice(5, ex.prim.length - 1), [], term);
}
}
// Expand deprecated DI...IP to [DIP n]
if (diipRe.test(ex.prim)) {
if (assertArgs(ex, 1)) {
let n = 0;
while (ex.prim[1 + n] === 'I') {
n++;
}
return mkPrim({ prim: 'DIP', args: [{ int: String(n) }, ex.args[0]] });
}
}
// Expand DU...UP and DUP n
if (duupRe.test(ex.prim)) {
let n = 0;
while (ex.prim[1 + n] === 'U') {
n++;
}
if ((0, michelson_types_1.ProtoInferiorTo)(proto, michelson_types_1.Protocol.PtAtLas)) {
if (n === 1) {
if (ex.args === undefined) {
return ex; // skip
}
if (assertArgs(ex, 1) && assertIntArg(ex, ex.args[0])) {
n = parseInt(ex.args[0].int, 10);
}
}
else {
assertArgs(ex, 0);
}
if (n === 1) {
return [mkPrim({ prim: 'DUP', annots: ex.annots })];
}
else if (n === 2) {
return [
{
prim: 'DIP',
args: [[mkPrim({ prim: 'DUP', annots: ex.annots })]],
},
{ prim: 'SWAP' },
];
}
else {
return [
{
prim: 'DIP',
args: [{ int: String(n - 1) }, [mkPrim({ prim: 'DUP', annots: ex.annots })]],
},
{
prim: 'DIG',
args: [{ int: String(n) }],
},
];
}
}
else {
if (n === 1) {
return ex;
}
if (assertArgs(ex, 0)) {
return mkPrim({ prim: 'DUP', args: [{ int: String(n) }], annots: ex.annots });
}
}
}
return ex;
}
exports.expandMacros = expandMacros;