@mavrykdynamics/taquito-michel-codec
Version:
Michelson parser/validator/formatter
404 lines (403 loc) • 15 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parser = exports.JSONParseError = exports.MichelineParseError = void 0;
const scan_1 = require("./scan");
const micheline_1 = require("./micheline");
const macros_1 = require("./macros");
const global_constants_1 = require("./global-constants");
const taquito_core_1 = require("@mavrykdynamics/taquito-core");
/**
* @category Error
* @description Error that indicates a failure when parsing Micheline expressions
*/
class MichelineParseError extends taquito_core_1.TaquitoError {
/**
* @param token A token caused the error
* @param message An error message
*/
constructor(token, message) {
super();
this.token = token;
this.message = message;
this.name = 'MichelineParseError';
}
}
exports.MichelineParseError = MichelineParseError;
/**
* @category Error
* @description Error indicates a failure when parsing Micheline JSON
*/
class JSONParseError extends taquito_core_1.TaquitoError {
/**
* @param node A node caused the error
* @param message An error message
*/
constructor(node, message) {
super();
this.node = node;
this.message = message;
this.name = 'JSONParseError';
}
}
exports.JSONParseError = JSONParseError;
const errEOF = new MichelineParseError(null, 'Unexpected EOF');
function isAnnotation(tok) {
return tok.t === scan_1.Literal.Ident && (tok.v[0] === '@' || tok.v[0] === '%' || tok.v[0] === ':');
}
const intRe = new RegExp('^-?[0-9]+$');
const bytesRe = new RegExp('^([0-9a-fA-F]{2})*$');
/**
* Converts and validates Michelson expressions between JSON-based Michelson and Micheline
*
* Pretty Print a Michelson Smart Contract:
* ```
* const contract = await Mavryk.contract.at("KT1Vsw3kh9638gqWoHTjvHCoHLPKvCbMVbCg");
* const p = new Parser();
*
* const michelsonCode = p.parseJSON(contract.script.code);
* const storage = p.parseJSON(contract.script.storage);
*
* console.log("Pretty print Michelson smart contract:");
* console.log(emitMicheline(michelsonCode, {indent:" ", newline: "\n",}));
*
* console.log("Pretty print Storage:");
* console.log(emitMicheline(storage, {indent:" ", newline: "\n",}));
* ```
*
* Encode a Michelson expression for initial storage of a smart contract
* ```
* const src = `(Pair (Pair { Elt 1
* (Pair (Pair "mv1JQ19UKK5w264P8SDJmwjHsrXZASegkXrH" "mv18Cw7psUrAAPBpXYd9CtCpHg9EgjHP9KTe")
* 0x0501000000026869) }
* 10000000)
* (Pair 2 333))`;
*
* const p = new Parser();
*
* const exp = p.parseMichelineExpression(src);
* console.log(JSON.stringify(exp));
* ```
*/
class Parser {
constructor(opt) {
this.opt = opt;
}
expand(ex) {
var _a, _b, _c;
if (((_a = this.opt) === null || _a === void 0 ? void 0 : _a.expandGlobalConstant) !== undefined && ex.prim === 'constant') {
const ret = (0, global_constants_1.expandGlobalConstants)(ex, this.opt.expandGlobalConstant);
if (ret !== ex) {
ret[micheline_1.sourceReference] = Object.assign(Object.assign({}, (ex[micheline_1.sourceReference] || { first: 0, last: 0 })), { globalConstant: ex });
}
return ret;
}
if (((_b = this.opt) === null || _b === void 0 ? void 0 : _b.expandMacros) !== undefined ? (_c = this.opt) === null || _c === void 0 ? void 0 : _c.expandMacros : true) {
const ret = (0, macros_1.expandMacros)(ex, this.opt);
if (ret !== ex) {
ret[micheline_1.sourceReference] = Object.assign(Object.assign({}, (ex[micheline_1.sourceReference] || { first: 0, last: 0 })), { macro: ex });
}
return ret;
}
else {
return ex;
}
}
parseListExpr(scanner, start) {
var _a;
const ref = {
first: start.first,
last: start.last,
};
const expectBracket = start.t === '(';
let tok;
if (expectBracket) {
tok = scanner.next();
if (tok.done) {
throw errEOF;
}
ref.last = tok.value.last;
}
else {
tok = { value: start };
}
if (tok.value.t !== scan_1.Literal.Ident) {
throw new MichelineParseError(tok.value, `not an identifier: ${tok.value.v}`);
}
const ret = {
prim: tok.value.v,
[micheline_1.sourceReference]: ref,
};
for (;;) {
const tok = scanner.next();
if (tok.done) {
if (expectBracket) {
throw errEOF;
}
break;
}
else if (tok.value.t === ')') {
if (!expectBracket) {
throw new MichelineParseError(tok.value, 'unexpected closing bracket');
}
ref.last = tok.value.last;
break;
}
else if (isAnnotation(tok.value)) {
ret.annots = ret.annots || [];
ret.annots.push(tok.value.v);
ref.last = tok.value.last;
}
else {
ret.args = ret.args || [];
const arg = this.parseExpr(scanner, tok.value);
ref.last = ((_a = arg[micheline_1.sourceReference]) === null || _a === void 0 ? void 0 : _a.last) || ref.last;
ret.args.push(arg);
}
}
return this.expand(ret);
}
parseArgs(scanner, start) {
var _a;
// Identifier with arguments
const ref = {
first: start.first,
last: start.last,
};
const p = {
prim: start.v,
[micheline_1.sourceReference]: ref,
};
for (;;) {
const t = scanner.next();
if (t.done || t.value.t === '}' || t.value.t === ';') {
return [p, t];
}
if (isAnnotation(t.value)) {
ref.last = t.value.last;
p.annots = p.annots || [];
p.annots.push(t.value.v);
}
else {
const arg = this.parseExpr(scanner, t.value);
ref.last = ((_a = arg[micheline_1.sourceReference]) === null || _a === void 0 ? void 0 : _a.last) || ref.last;
p.args = p.args || [];
p.args.push(arg);
}
}
}
parseSequenceExpr(scanner, start) {
var _a, _b;
const ref = {
first: start.first,
last: start.last,
};
const seq = [];
seq[micheline_1.sourceReference] = ref;
const expectBracket = start.t === '{';
let tok = start.t === '{' ? null : { value: start };
for (;;) {
if (tok === null) {
tok = scanner.next();
if (!tok.done) {
ref.last = tok.value.last;
}
}
if (tok.done) {
if (expectBracket) {
throw errEOF;
}
else {
return seq;
}
}
if (tok.value.t === '}') {
if (!expectBracket) {
throw new MichelineParseError(tok.value, 'unexpected closing bracket');
}
else {
return seq;
}
}
else if (tok.value.t === scan_1.Literal.Ident) {
// Identifier with arguments
const [itm, n] = this.parseArgs(scanner, tok.value);
ref.last = ((_a = itm[micheline_1.sourceReference]) === null || _a === void 0 ? void 0 : _a.last) || ref.last;
seq.push(this.expand(itm));
tok = n;
}
else {
// Other
const ex = this.parseExpr(scanner, tok.value);
ref.last = ((_b = ex[micheline_1.sourceReference]) === null || _b === void 0 ? void 0 : _b.last) || ref.last;
seq.push(ex);
tok = null;
}
if (tok === null) {
tok = scanner.next();
if (!tok.done) {
ref.last = tok.value.last;
}
}
if (!tok.done && tok.value.t === ';') {
tok = null;
}
}
}
parseExpr(scanner, tok) {
switch (tok.t) {
case scan_1.Literal.Ident:
return this.expand({
prim: tok.v,
[micheline_1.sourceReference]: { first: tok.first, last: tok.last },
});
case scan_1.Literal.Number:
return { int: tok.v, [micheline_1.sourceReference]: { first: tok.first, last: tok.last } };
case scan_1.Literal.String:
return {
string: JSON.parse(tok.v),
[micheline_1.sourceReference]: { first: tok.first, last: tok.last },
};
case scan_1.Literal.Bytes:
return { bytes: tok.v.slice(2), [micheline_1.sourceReference]: { first: tok.first, last: tok.last } };
case '{':
return this.parseSequenceExpr(scanner, tok);
default:
return this.parseListExpr(scanner, tok);
}
}
/**
* Parses a Micheline sequence expression, such as smart contract source. Enclosing curly brackets may be omitted.
* @param src A Micheline sequence `{parameter ...; storage int; code { DUP ; ...};}` or `parameter ...; storage int; code { DUP ; ...};`
*/
parseSequence(src) {
if (typeof src !== 'string') {
throw new TypeError(`string type was expected, got ${typeof src} instead`);
}
const scanner = (0, scan_1.scan)(src);
const tok = scanner.next();
if (tok.done) {
return null;
}
return this.parseSequenceExpr(scanner, tok.value);
}
/**
* Parse a Micheline sequence expression. Enclosing curly brackets may be omitted.
* @param src A Michelson list expression such as `(Pair {Elt "0" 0} 0)` or `Pair {Elt "0" 0} 0`
* @returns An AST node or null for empty document.
*/
parseList(src) {
if (typeof src !== 'string') {
throw new TypeError(`string type was expected, got ${typeof src} instead`);
}
const scanner = (0, scan_1.scan)(src);
const tok = scanner.next();
if (tok.done) {
return null;
}
return this.parseListExpr(scanner, tok.value);
}
/**
* Parse any Michelson expression
* @param src A Michelson expression such as `(Pair {Elt "0" 0} 0)` or `{parameter ...; storage int; code { DUP ; ...};}`
* @returns An AST node or null for empty document.
*/
parseMichelineExpression(src) {
if (typeof src !== 'string') {
throw new TypeError(`string type was expected, got ${typeof src} instead`);
}
const scanner = (0, scan_1.scan)(src);
const tok = scanner.next();
if (tok.done) {
return null;
}
return this.parseExpr(scanner, tok.value);
}
/**
* Parse a Micheline sequence expression, such as smart contract source. Enclosing curly brackets may be omitted.
* An alias for `parseSequence`
* @param src A Micheline sequence `{parameter ...; storage int; code { DUP ; ...};}` or `parameter ...; storage int; code { DUP ; ...};`
*/
parseScript(src) {
return this.parseSequence(src);
}
/**
* Parse a Micheline sequence expression. Enclosing curly brackets may be omitted.
* An alias for `parseList`
* @param src A Michelson list expression such as `(Pair {Elt "0" 0} 0)` or `Pair {Elt "0" 0} 0`
* @returns An AST node or null for empty document.
*/
parseData(src) {
return this.parseList(src);
}
/**
* Takes a JSON-encoded Michelson, validates it, strips away unneeded properties and optionally expands macros (See {@link ParserOptions}).
* @param src An object containing JSON-encoded Michelson, usually returned by `JSON.parse()`
*/
parseJSON(src) {
if (typeof src !== 'object') {
throw new TypeError(`object type was expected, got ${typeof src} instead`);
}
if (Array.isArray(src)) {
const ret = [];
for (const n of src) {
if (n === null || typeof n !== 'object') {
throw new JSONParseError(n, `unexpected sequence element: ${n}`);
}
ret.push(this.parseJSON(n));
}
return ret;
}
else if ('prim' in src) {
const p = src;
if (typeof p.prim === 'string' &&
(p.annots === undefined || Array.isArray(p.annots)) &&
(p.args === undefined || Array.isArray(p.args))) {
const ret = {
prim: p.prim,
};
if (p.annots !== undefined) {
for (const a of p.annots) {
if (typeof a !== 'string') {
throw new JSONParseError(a, `string expected: ${a}`);
}
}
ret.annots = p.annots;
}
if (p.args !== undefined) {
ret.args = [];
for (const a of p.args) {
if (a === null || typeof a !== 'object') {
throw new JSONParseError(a, `unexpected argument: ${a}`);
}
ret.args.push(this.parseJSON(a));
}
}
return this.expand(ret);
}
throw new JSONParseError(src, `malformed prim expression: ${src}`);
}
else if ('string' in src) {
if (typeof src.string === 'string') {
return { string: src.string };
}
throw new JSONParseError(src, `malformed string literal: ${src}`);
}
else if ('int' in src) {
if (typeof src.int === 'string' && intRe.test(src.int)) {
return { int: src.int };
}
throw new JSONParseError(src, `malformed int literal: ${src}`);
}
else if ('bytes' in src) {
if (typeof src.bytes === 'string' &&
bytesRe.test(src.bytes)) {
return { bytes: src.bytes };
}
throw new JSONParseError(src, `malformed bytes literal: ${src}`);
}
else {
throw new JSONParseError(src, `unexpected object: ${src}`);
}
}
}
exports.Parser = Parser;