UNPKG

clvm

Version:

Javascript implementation of chia lisp

311 lines (300 loc) 9.64 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OPERATOR_LOOKUP = exports.OperatorDict = exports.APPLY_ATOM = exports.QUOTE_ATOM = exports.default_unknown_op = exports.args_len = exports.OP_REWRITE = exports.KEYWORD_TO_ATOM = exports.KEYWORD_FROM_ATOM = void 0; const casts_1 = require("./casts"); const SExp_1 = require("./SExp"); const __type_compatibility__1 = require("./__type_compatibility__"); const EvalError_1 = require("./EvalError"); const costs_1 = require("./costs"); const op_utils_1 = require("./op_utils"); const core_ops = require("./core_ops"); const more_ops = require("./more_ops"); /* export const KEYWORDS = [ // core opcodes 0x01-x08 ". q a i c f r l x ", // opcodes on atoms as strings 0x09-0x0f "= >s sha256 substr strlen concat . ", // opcodes on atoms as ints 0x10-0x17 "+ - * / divmod > ash lsh ", // opcodes on atoms as vectors of bools 0x18-0x1c "logand logior logxor lognot . ", // opcodes for bls 1381 0x1d-0x1f "point_add pubkey_for_exp . ", // bool opcodes 0x20-0x23 "not any all . ", // misc 0x24 "softfork ", ].join("").trim().split(/\s/); export const KEYWORD_FROM_ATOM = Object .entries(KEYWORDS) .reduce<Record<string, string>>((acc, v) => { acc[int_to_bytes(+v[0]).toString()] = v[1]; return acc; }, {}); export const KEYWORD_TO_ATOM = Object .entries(KEYWORD_FROM_ATOM) .reduce<Record<string, string>>((acc, v) => { acc[v[1]] = v[0]; return acc; }, {}); */ exports.KEYWORD_FROM_ATOM = { "00": ".", // core opcodes 0x01-x08 "01": "q", "02": "a", "03": "i", "04": "c", "05": "f", "06": "r", "07": "l", "08": "x", // opcodes on atoms as strings 0x09-0x0f "09": "=", "0a": ">s", "0b": "sha256", "0c": "substr", "0d": "strlen", "0e": "concat", "0f": ".", // opcodes on atoms as ints 0x10-0x17 "10": "+", "11": "-", "12": "*", "13": "/", "14": "divmod", "15": ">", "16": "ash", "17": "lsh", // opcodes on atoms as vectors of bools 0x18-0x1c "18": "logand", "19": "logior", "1a": "logxor", "1b": "lognot", "1c": ".", // opcodes for bls 1381 0x1d-0x1f "1d": "point_add", "1e": "pubkey_for_exp", "1f": ".", // bool opcodes 0x20-0x23 "20": "not", "21": "any", "22": "all", "23": ".", // misc 0x24 "24": "softfork", }; exports.KEYWORD_TO_ATOM = { // ".": "00", // core opcodes 0x01-x08 "q": "01", "a": "02", "i": "03", "c": "04", "f": "05", "r": "06", "l": "07", "x": "08", // opcodes on atoms as strings 0x09-0x0f "=": "09", ">s": "0a", "sha256": "0b", "substr": "0c", "strlen": "0d", "concat": "0e", // ".": "0f", // opcodes on atoms as ints 0x10-0x17 "+": "10", "-": "11", "*": "12", "/": "13", "divmod": "14", ">": "15", "ash": "16", "lsh": "17", // opcodes on atoms as vectors of bools 0x18-0x1c "logand": "18", "logior": "19", "logxor": "1a", "lognot": "1b", // ".": "1c", // opcodes for bls 1381 0x1d-0x1f "point_add": "1d", "pubkey_for_exp": "1e", // ".": "1f", // bool opcodes 0x20-0x23 "not": "20", "any": "21", "all": "22", ".": "23", // misc 0x24 "softfork": "24", }; exports.OP_REWRITE = { "+": "add", "-": "subtract", "*": "multiply", "/": "div", "i": "if", "c": "cons", "f": "first", "r": "rest", "l": "listp", "x": "raise", "=": "eq", ">": "gr", ">s": "gr_bytes", }; function* args_len(op_name, args) { for (const arg of args.as_iter()) { if (arg.pair) { throw new EvalError_1.EvalError(`${op_name} requires int args`, arg); } yield arg.atom.length; } } exports.args_len = args_len; /* unknown ops are reserved if they start with 0xffff otherwise, unknown ops are no-ops, but they have costs. The cost is computed like this: byte index (reverse): | 4 | 3 | 2 | 1 | 0 | +---+---+---+---+------------+ | multiplier |XX | XXXXXX | +---+---+---+---+---+--------+ ^ ^ ^ | | + 6 bits ignored when computing cost cost_multiplier | + 2 bits cost_function 1 is always added to the multiplier before using it to multiply the cost, this is since cost may not be 0. cost_function is 2 bits and defines how cost is computed based on arguments: 0: constant, cost is 1 * (multiplier + 1) 1: computed like operator add, multiplied by (multiplier + 1) 2: computed like operator mul, multiplied by (multiplier + 1) 3: computed like operator concat, multiplied by (multiplier + 1) this means that unknown ops where cost_function is 1, 2, or 3, may still be fatal errors if the arguments passed are not atoms. */ function default_unknown_op(op, args) { // any opcode starting with ffff is reserved (i.e. fatal error) // opcodes are not allowed to be empty if (op.length === 0 || op.subarray(0, 2).equal_to(__type_compatibility__1.Bytes.from("0xffff", "hex"))) { throw new EvalError_1.EvalError("reserved operator", SExp_1.SExp.to(op)); } /* all other unknown opcodes are no-ops the cost of the no-ops is determined by the opcode number, except the 6 least significant bits. */ const cost_function = (op.at(op.length - 1) & 0b11000000) >> 6; // the multiplier cannot be 0. it starts at 1 if (op.length > 5) { throw new EvalError_1.EvalError("invalid operator", SExp_1.SExp.to(op)); } // The bytes here is 4bytes or smaller. So `int_from_bytes` is enough. (No bigint_from_bytes required) const cost_multiplier = (0, casts_1.int_from_bytes)(op.subarray(0, op.length - 1), { signed: false }) + 1; /* 0 = constant 1 = like op_add/op_sub 2 = like op_multiply 3 = like op_concat */ let cost; if (cost_function === 0) { cost = 1; } else if (cost_function === 1) { cost = costs_1.ARITH_BASE_COST; let arg_size = 0; for (const length of args_len("unknown op", args)) { arg_size += length; cost += costs_1.ARITH_COST_PER_ARG; } cost += arg_size * costs_1.ARITH_COST_PER_BYTE; } else if (cost_function === 2) { // like op_multiply cost = costs_1.MUL_BASE_COST; const operands = args_len("unknown op", args); const res = operands.next(); if (!res.done) { let vs = res.value; for (const rs of operands) { cost += costs_1.MUL_COST_PER_OP; cost += (rs + vs) * costs_1.MUL_LINEAR_COST_PER_BYTE; cost += ((rs * vs) / costs_1.MUL_SQUARE_COST_PER_BYTE_DIVIDER) | 0; vs += rs; } } } else if (cost_function === 3) { // like concat cost = costs_1.CONCAT_BASE_COST; let length = 0; for (const arg of args.as_iter()) { if (arg.pair) { throw new EvalError_1.EvalError("unknown op on list", arg); } cost += costs_1.CONCAT_COST_PER_ARG; length += arg.atom.length; } cost += length * costs_1.CONCAT_COST_PER_BYTE; } else { throw new Error(`Invalid cost_function: ${cost_function}`); } cost *= cost_multiplier; if (cost >= 2 ** 32) { throw new EvalError_1.EvalError("invalid operator", SExp_1.SExp.to(op)); } return (0, __type_compatibility__1.t)(cost, SExp_1.SExp.null()); } exports.default_unknown_op = default_unknown_op; exports.QUOTE_ATOM = __type_compatibility__1.Bytes.from(exports.KEYWORD_TO_ATOM["q"], "hex"); exports.APPLY_ATOM = __type_compatibility__1.Bytes.from(exports.KEYWORD_TO_ATOM["a"], "hex"); function merge(obj1, obj2) { Object.keys(obj2).forEach(key => { obj1[key] = obj2[key]; }); } function OperatorDict(atom_op_function_map, option = {}) { const dict = Object.assign(Object.assign({}, atom_op_function_map), { quote_atom: option.quote_atom || atom_op_function_map.quote_atom, apply_atom: option.apply_atom || atom_op_function_map.apply_atom, unknown_op_handler: option.unknown_op_handler || default_unknown_op }); if (!dict.quote_atom) { throw new Error("object has not attribute 'quote_atom'"); } else if (!dict.apply_atom) { throw new Error("object has not attribute 'apply_atom'"); } const OperatorDict = function (op, args) { if (typeof op === "string") { op = __type_compatibility__1.Bytes.from(op, "hex"); } else if (typeof op === "number") { op = __type_compatibility__1.Bytes.from([op]); } else if (!(0, __type_compatibility__1.isBytes)(op)) { throw new Error(`Invalid op: ${JSON.stringify(op)}`); } merge(dict, OperatorDict); const f = dict[op.hex()]; if (typeof f !== "function") { return dict.unknown_op_handler(op, args); } else { return f(args); } }; merge(OperatorDict, dict); return OperatorDict; } exports.OperatorDict = OperatorDict; const _OPERATOR_LOOKUP = OperatorDict((0, op_utils_1.operators_for_module)(exports.KEYWORD_TO_ATOM, core_ops, exports.OP_REWRITE), { quote_atom: exports.QUOTE_ATOM, apply_atom: exports.APPLY_ATOM, }); merge(_OPERATOR_LOOKUP, (0, op_utils_1.operators_for_module)(exports.KEYWORD_TO_ATOM, more_ops, exports.OP_REWRITE)); exports.OPERATOR_LOOKUP = _OPERATOR_LOOKUP;