"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
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) {
    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
        to[j] = from[i];
    return to;
};
var __values = (this && this.__values) || function(o) {
    var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
    if (m) return m.call(o);
    if (o && typeof o.length === "number") return {
        next: function () {
            if (o && i >= o.length) o = void 0;
            return { value: o && o[i++], done: !o };
        }
    };
    throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.expandMacros = exports.MacroError = void 0;
var michelson_types_1 = require("./michelson-types");
var MacroError = /** @class */ (function (_super) {
    __extends(MacroError, _super);
    function MacroError(prim, message) {
        var _this = _super.call(this, message) || this;
        _this.prim = prim;
        Object.setPrototypeOf(_this, MacroError.prototype);
        return _this;
    }
    return MacroError;
}(Error));
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) {
    var i = 0;
    var ai = 0;
    var ann = [null, null];
    // Left expression
    var lexpr;
    if (i === expr.length) {
        throw new MacroError(p, "unexpected end: " + p.prim);
    }
    var c = expr[i++];
    switch (c) {
        case "P":
            var _a = parsePairUnpairExpr(p, expr.slice(i), annotations.slice(ai), agg), r = _a.r, n = _a.n, an = _a.an;
            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
    var rexpr;
    if (i === expr.length) {
        throw new MacroError(p, "unexpected end: " + p.prim);
    }
    c = expr[i++];
    switch (c) {
        case "P":
            var _b = parsePairUnpairExpr(p, expr.slice(i), annotations.slice(ai), agg), r = _b.r, n = _b.n, an = _b.an;
            rexpr = r.map(function (_a) {
                var _b = __read(_a, 2), v = _b[0], a = _b[1];
                return [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) {
    var 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: __spreadArray(["%@", "%@"], __read(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: __spreadArray(["%@", "%@"], __read(vann)) },
                ] : term.d;
        default:
            throw new MacroError(p, p.prim + ": unexpected character: " + c);
    }
}
function trimLast(a, v) {
    var l = a.length;
    while (l > 0 && a[l - 1] === v) {
        l--;
    }
    return a.slice(0, l);
}
function filterAnnotations(a) {
    var e_1, _a;
    var fields = [];
    var rest = [];
    if (a !== undefined) {
        try {
            for (var a_1 = __values(a), a_1_1 = a_1.next(); !a_1_1.done; a_1_1 = a_1.next()) {
                var v = a_1_1.value;
                (v.length !== 0 && v[0] === "%" ? fields : rest).push(v);
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (a_1_1 && !a_1_1.done && (_a = a_1.return)) _a.call(a_1);
            }
            finally { if (e_1) throw e_1.error; }
        }
    }
    return { fields: fields, rest: rest };
}
function mkPrim(_a) {
    var prim = _a.prim, annots = _a.annots, args = _a.args;
    return __assign(__assign({ prim: prim }, (annots && { annots: annots })), (args && { args: args }));
}
var pairRe = /^P[PAI]{3,}R$/;
var unpairRe = /^UNP[PAI]{2,}R$/;
var cadrRe = /^C[AD]{2,}R$/;
var setCadrRe = /^SET_C[AD]+R$/;
var mapCadrRe = /^MAP_C[AD]+R$/;
var diipRe = /^DI{2,}P$/;
var duupRe = /^DU+P$/;
function expandMacros(ex, opt) {
    var proto = (opt === null || opt === void 0 ? void 0 : opt.protocol) || michelson_types_1.DefaultProtocol;
    function mayRename(annots) {
        return annots !== undefined ? [{ prim: "RENAME", annots: 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])) {
                    var 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)) {
            var _a = filterAnnotations(ex.annots), fields = _a.fields, rest_1 = _a.rest;
            var r_1 = parsePairUnpairExpr(ex, ex.prim.slice(1), fields, function (l, r, top) { return __spreadArray(__spreadArray(__spreadArray([], __read((l || []))), __read((r || []))), [top]); }).r;
            return r_1.map(function (_a, i) {
                var _b = __read(_a, 2), v = _b[0], a = _b[1];
                var ann = __spreadArray(__spreadArray([], __read(trimLast(a, null).map(function (v) { return v === null ? "%" : v; }))), __read(((v === 0 && i === r_1.length - 1) ? rest_1 : [])));
                var 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 (proto === michelson_types_1.Protocol.PtEdo2Zk || proto === michelson_types_1.Protocol.PsFLorena || proto === michelson_types_1.Protocol.PtGRANADs) {
            if (ex.prim === "UNPAIR") {
                return ex;
            }
            if (assertArgs(ex, 0)) {
                // 008_edo: annotations are deprecated
                var r = parsePairUnpairExpr(ex, ex.prim.slice(3), [], function (l, r, top) { return __spreadArray(__spreadArray([top], __read((r || []))), __read((l || []))); }).r;
                return r.map(function (_a) {
                    var _b = __read(_a, 1), v = _b[0];
                    var leaf = mkPrim({
                        prim: "UNPAIR",
                    });
                    return v === 0 ? leaf : {
                        prim: "DIP",
                        args: v === 1 ? [[leaf]] : [{ int: String(v) }, [leaf]],
                    };
                });
            }
        }
        else if (assertArgs(ex, 0)) {
            var r = parsePairUnpairExpr(ex, ex.prim.slice(3), ex.annots || [], function (l, r, top) { return __spreadArray(__spreadArray([top], __read((r || []))), __read((l || []))); }).r;
            return r.map(function (_a) {
                var _b = __read(_a, 2), v = _b[0], a = _b[1];
                var 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]],
                };
            });
        }
    }
    // C[AD]+R macro
    if (cadrRe.test(ex.prim)) {
        if (assertArgs(ex, 0)) {
            var ch_1 = __spreadArray([], __read(ex.prim.slice(1, ex.prim.length - 1)));
            return ch_1.map(function (c, i) {
                var ann = i === ch_1.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)) {
            var _b = filterAnnotations(ex.annots), fields = _b.fields, rest = _b.rest;
            if (fields.length > 1) {
                throw new MacroError(ex, "unexpected annotation on macro " + ex.prim + ": " + fields);
            }
            var 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)) {
            var fields = filterAnnotations(ex.annots).fields;
            if (fields.length > 1) {
                throw new MacroError(ex, "unexpected annotation on macro " + ex.prim + ": " + fields);
            }
            var 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)) {
            var 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)) {
        var n = 0;
        while (ex.prim[1 + n] === "U") {
            n++;
        }
        if (proto === michelson_types_1.Protocol.PtEdo2Zk || proto === michelson_types_1.Protocol.PsFLorena || proto === michelson_types_1.Protocol.PtGRANADs) {
            if (n === 1) {
                return ex;
            }
            if (assertArgs(ex, 0)) {
                return mkPrim({ prim: "DUP", args: [{ int: String(n) }], annots: ex.annots });
            }
        }
        else {
            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) }],
                    },
                ];
            }
        }
    }
    return ex;
}
exports.expandMacros = expandMacros;
//# sourceMappingURL=macros.js.map