UNPKG

scilla-data-parser

Version:

Scilla data types can be very verbose, making it hard for developers to use the state directly. The parser will help developers can make references and manipulation to state more easily.

643 lines (584 loc) 16.7 kB
import { BN } from 'bn.js'; interface ICustomTypeRule { argtypes: [any]; } var customTypeMap = {}; interface INodeType { type: string; childTypes: INodeType[]; } function isObject(a: any): boolean { return !!a && a.constructor === Object; } // Check a object is in scilla data format function isScillaData(node: any): boolean { if (!isObject(node)) return false; if ('vname' in node && 'type' in node && 'value' in node) { return true; } return false; } //With the type field in scilla object, parse it to a tree. function parseTypeTree(strType: string): INodeType { var typeStack = []; var typeCtx = { type: '', childTypes: [] }; typeStack.push(typeCtx); var subType = ''; var i = 0; while (i < strType.length) { switch (strType[i]) { case '(': typeStack.push({ type: '', childTypes: [] }); if (typeCtx != null) { if (typeCtx.type == '') { typeCtx.type = subType; } typeCtx.childTypes.push(typeStack[typeStack.length - 1]); } subType = ''; typeCtx = typeStack[typeStack.length - 1]; break; case ')': if (typeStack.length == 0) throw new TypeError('JSON Parse: Too many closing brackets'); typeStack.pop(); if (subType.length > 0) { typeCtx.type = subType; } subType = ''; if (typeStack.length > 0) { typeCtx = typeStack[typeStack.length - 1]; } break; case ' ': case ' ': case '\n': break; default: subType += strType[i]; break; } ++i; } //In case root node has no child the node type is the input string. if (typeStack[0].childTypes.length == 0) { typeStack[0].type = strType; } return typeStack[0]; } //Convert INodeType to string type. function getTypeString(typeCtx: INodeType): string { var ret = typeCtx.type; if (typeCtx.childTypes.length == 0) { return ret; } ret += ' '; for (let i = 0; i < typeCtx.childTypes.length; i++) { ret += '('; ret += getTypeString(typeCtx.childTypes[i]); ret += ')'; if (i < typeCtx.childTypes.length - 1) { ret += ' '; } } return ret; } function toSimpleData(node: any): any { var vname = node.vname; var type = node.type; var value = node.value; var ret = {}; var typeTree = parseTypeTree(type); switch (typeTree.type) { case 'Bool': ret[vname] = value['constructor'].toLowerCase() == 'true'; break; case 'String': case 'ByStr20': ret[vname] = value; break; case 'Option': if (typeTree.childTypes.length == 1) { if (value['constructor'] == 'Some') { var op = toSimpleData({ vname: 'op', type: getTypeString(typeTree.childTypes[0]), value: value.arguments[0], }); ret[vname] = op.op; } else { ret[vname] = ''; } } else { throw new TypeError('Invalid Option node'); } break; case 'Map': { ret[vname] = {}; var childs = value; if (Array.isArray(childs) && typeTree.childTypes.length == 2) { for (let c of childs) { var key = toSimpleData({ vname: 'key', type: getTypeString(typeTree.childTypes[0]), value: c.key, }); var val = toSimpleData({ vname: 'val', type: getTypeString(typeTree.childTypes[1]), value: c.val, }); ret[vname][key.key] = val.val; } } else { throw new TypeError('Invalid Map node'); } break; } case 'List': ret[vname] = []; var childs = value; if (Array.isArray(childs) && typeTree.childTypes.length == 1) { for (let c of childs) { var l = toSimpleData({ vname: 'l', type: getTypeString(typeTree.childTypes[0]), value: c, }); ret[vname].push(l.l); } } else { throw new TypeError('Invalid List node'); } break; case 'Pair': { ret[vname] = {}; var childs = value.arguments; if (Array.isArray(childs) && childs.length == 2 && typeTree.childTypes.length == 2) { var x = toSimpleData({ vname: 'x', type: getTypeString(typeTree.childTypes[0]), value: childs[0], }); var y = toSimpleData({ vname: 'y', type: getTypeString(typeTree.childTypes[1]), value: childs[1], }); ret[vname].x = x.x; ret[vname].y = y.y; } else { throw new TypeError('Invalid Pair node'); } break; } case 'Uint32': case 'Int32': ret[vname] = parseInt(value); break; case 'Uint64': case 'Uint128': case 'Uint256': case 'Int64': case 'Int128': case 'Uint256': case 'BNum': ret[vname] = new BN(value); break; default: if (customTypeMap[typeTree.type] != null) { ret[vname] = {}; var rule: ICustomTypeRule = customTypeMap[typeTree.type]; var childs = value.arguments; if (Array.isArray(childs) && childs.length == rule.argtypes.length) { for (let k = 0; k < rule.argtypes.length; k++) { var rname = rule.argtypes[k]['vname']; var rtype = rule.argtypes[k]['type']; ret[vname][rname] = toSimpleData({ vname: rname, type: rtype, value: childs[k], })[rname]; } } else { throw new TypeError('Invalid Custom node'); } } else { throw new TypeError('Unhandle type ' + typeTree.type); } break; } return ret; } function toStraightData(node: any): any { var vname = node.vname; var type = node.type; var value = node.value; var ret = { vname: node.vname, type: node.type, value: null, }; var typeTree = parseTypeTree(type); switch (typeTree.type) { case 'Bool': ret.value = value['constructor'].toLowerCase() == 'true'; break; case 'String': case 'ByStr20': ret.value = value; break; case 'Option': if (typeTree.childTypes.length == 1) { if (value['constructor'] == 'Some') { var op = toStraightData({ vname: 'op', type: getTypeString(typeTree.childTypes[0]), value: value.arguments[0], }); ret.value = op.value; } else { ret.value = ''; } } else { throw new TypeError('Invalid Option node'); } break; case 'Map': { ret.value = {}; var childs = value; if (Array.isArray(childs) && typeTree.childTypes.length == 2) { for (let c of childs) { var key = toStraightData({ vname: 'key', type: getTypeString(typeTree.childTypes[0]), value: c.key, }); var val = toStraightData({ vname: 'val', type: getTypeString(typeTree.childTypes[1]), value: c.val, }); ret.value[key.value] = val.value; } } else { throw new TypeError('Invalid Map node'); } break; } case 'List': ret.value = []; var childs = value; if (Array.isArray(childs) && typeTree.childTypes.length == 1) { for (let c of childs) { var l = toStraightData({ vname: 'l', type: getTypeString(typeTree.childTypes[0]), value: c, }); ret.value.push(l.value); } } else { throw new TypeError('Invalid List node'); } break; case 'Pair': { ret.value = {}; var childs = value.arguments; if (Array.isArray(childs) && childs.length == 2 && typeTree.childTypes.length == 2) { var x = toStraightData({ vname: 'x', type: getTypeString(typeTree.childTypes[0]), value: childs[0], }); var y = toStraightData({ vname: 'y', type: getTypeString(typeTree.childTypes[1]), value: childs[1], }); ret.value.x = x.value; ret.value.y = y.value; } else { throw new TypeError('Invalid Pair node'); } break; } case 'Uint32': case 'Int32': ret.value = parseInt(value); break; case 'Uint64': case 'Uint128': case 'Uint256': case 'Int64': case 'Int128': case 'Uint256': case 'BNum': ret.value = new BN(value); break; default: if (customTypeMap[typeTree.type] != null) { ret.value = {}; var rule: ICustomTypeRule = customTypeMap[typeTree.type]; var childs = value.arguments; if (Array.isArray(childs) && childs.length == rule.argtypes.length) { for (let k = 0; k < rule.argtypes.length; k++) { var rname = rule.argtypes[k]['vname']; var rtype = rule.argtypes[k]['type']; ret.value[rname] = toStraightData({ vname: rname, type: rtype, value: childs[k], }).value; } } else { throw new TypeError('Invalid Custom node'); } } else { throw new TypeError('Unhandle type ' + typeTree.type); } break; } return ret; } //New node with same type. function getEmptyData(node: any): any { if (isObject(node)) { return {}; } else if (Array.isArray(node)) { return []; } return ''; } //With each node is in scilla format, convert it to simple format. //The bStraight flag mean keep the sciila format in root node. It still have enough info to convert back to scilla format. function convertToSimpleJson(input: any, bStraight: boolean = false): any { var stackIns = []; var stackParents = []; var stackOuts = []; stackIns.push(input); stackParents.push(-1); stackOuts.push(getEmptyData(input)); var k = 0; while (k < stackIns.length) { var curIn = stackIns[k]; if (isScillaData(curIn)) { stackOuts[k] = bStraight ? toStraightData(curIn) : toSimpleData(curIn); } else if (isObject(curIn)) { for (let p of Object.keys(curIn)) { var c = curIn[p]; stackIns.push(c); stackOuts[k][p] = stackOuts.length; //index to out node. var emptyData = getEmptyData(c); stackOuts.push(emptyData); stackParents.push(k); } } else if (Array.isArray(curIn)) { for (let p of curIn) { stackIns.push(p); var emptyData = getEmptyData(p); stackOuts.push(emptyData); stackParents.push(k); } } else { stackOuts[k] = JSON.parse(JSON.stringify(curIn)); } k++; } k = stackIns.length - 1; while (k >= 0) { var curIn = stackIns[k]; if (isScillaData(curIn)) { } else if (isObject(curIn)) { for (let p of Object.keys(curIn)) { stackOuts[k][p] = stackOuts[stackOuts[k][p]]; } } else if (Array.isArray(curIn)) { var mappable = {}; for (let i = 0; i < stackIns.length; i++) { if (stackParents[i] == k) { stackOuts[k].push(stackOuts[i]); if (mappable != null) { var size = 0, vname; for (vname in stackOuts[i]) { mappable[vname] = stackOuts[i][vname]; size++; } if (size != 1) { mappable = null; } } } } if (mappable != null) { stackOuts[k] = mappable; } } else { } k--; } return stackOuts[0]; } function isFloat(n) { return n === +n && n !== (n | 0); } function isInteger(n) { return n === +n && n === (n | 0); } function toScillaBool(value: any): string { var ret = false; if (typeof value === 'boolean') { ret = value; } else if (typeof value === 'string') { ret = value.toLowerCase() == 'true'; } else if (isInteger(value)) { ret = parseInt(value) != 0; } else { ret = false; } return ret ? 'True' : 'False'; } function convertToScillaData(node: any): any { var vname = node.vname; var type = node.type; var value = node.value; var ret = { vname: vname, type: type, value: null, }; var typeTree = parseTypeTree(type); switch (typeTree.type) { case 'Bool': ret.value = { constructor: toScillaBool(value), argtypes: [], arguments: [] }; break; case 'String': case 'ByStr20': ret.value = value; break; case 'Option': ret.value = {}; if (typeTree.childTypes.length == 1) { if (value != null && value != '') { var op = convertToScillaData({ vname: 'op', type: getTypeString(typeTree.childTypes[0]), value: value, }); ret.value['constructor'] = 'Some'; ret.value['argtypes'] = [op.type]; ret.value['arguments'] = [op.value]; } else { ret.value['constructor'] = 'None'; ret.value['argtypes'] = [getTypeString(typeTree.childTypes[0])]; ret.value['arguments'] = []; } } else { throw new TypeError('Invalid Option node'); } break; case 'Map': { ret.value = []; var childs = value; if (isObject(childs) && typeTree.childTypes.length == 2) { for (let c of Object.keys(childs)) { var key = convertToScillaData({ vname: 'key', type: getTypeString(typeTree.childTypes[0]), value: c, }); var val = convertToScillaData({ vname: 'val', type: getTypeString(typeTree.childTypes[1]), value: childs[c], }); ret.value.push({ key: key.value, val: val.value, }); } } else { throw new TypeError('Invalid Map node'); } break; } case 'List': { ret.value = []; var childs = value; if (Array.isArray(childs) && typeTree.childTypes.length == 1) { for (let c of childs) { var l = convertToScillaData({ vname: 'l', type: getTypeString(typeTree.childTypes[0]), value: c, }); ret.value.push(l.value); } } else { throw new TypeError('Invalid List node'); } break; } case 'Pair': { ret.value = {}; var childs = value; if (isObject(childs) && typeTree.childTypes.length == 2) { var x = convertToScillaData({ vname: 'x', type: getTypeString(typeTree.childTypes[0]), value: childs.x, }); var y = convertToScillaData({ vname: 'y', type: getTypeString(typeTree.childTypes[1]), value: childs.y, }); ret.value['constructor'] = 'Pair'; ret.value['argtypes'] = [x.type, y.type]; ret.value['arguments'] = [x.value, y.value]; } else { throw new TypeError('Invalid Pair node'); } break; } case 'Uint32': case 'Int32': ret.value = value.toString(); break; case 'Uint64': case 'Uint128': case 'Uint256': case 'Int64': case 'Int128': case 'Uint256': case 'BNum': ret.value = value.toString(); break; default: throw new TypeError('Unhandle type ' + typeTree.type); break; } return ret; } function convertToScillaDataList(input: any): any { var ret = []; if (Array.isArray(input)) { for (let p of input) { if (isScillaData(p)) { ret.push(convertToScillaData(p)); } else { throw new TypeError('Invalid scilla node'); } } } return ret; } function fetchCustomData(name, rule): any { customTypeMap[name] = rule; } export const ScillaDataParser = { convertToSimpleJson, convertToScillaData, convertToScillaDataList, fetchCustomData, };