UNPKG

clvm

Version:

Javascript implementation of chia lisp

470 lines (469 loc) 14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.divmod = exports.modulo = exports.division = exports.Stream = exports.isBytes = exports.isIterable = exports.isList = exports.isTuple = exports.t = exports.Tuple = exports.repr = exports.str = exports.list = exports.h = exports.b = exports.Bytes = exports.PyBytes_Repr = exports.to_hexstr = void 0; const Hex_1 = require("jscrypto/Hex"); const Utf8_1 = require("jscrypto/Utf8"); const Word32Array_1 = require("jscrypto/Word32Array"); const SHA256_1 = require("jscrypto/SHA256"); const __python_types__1 = require("./__python_types__"); function to_hexstr(r) { return (new Word32Array_1.Word32Array(r)).toString(); } exports.to_hexstr = to_hexstr; /** * Get python's bytes.__repr__ style string. * @see https://github.com/python/cpython/blob/main/Objects/bytesobject.c#L1337 * @param {Uint8Array} r - byteArray to stringify */ function PyBytes_Repr(r) { let squotes = 0; let dquotes = 0; for (let i = 0; i < r.length; i++) { const b = r[i]; const c = String.fromCodePoint(b); switch (c) { case "'": squotes++; break; case "\"": dquotes++; break; } } let quote = "'"; if (squotes && !dquotes) { quote = "\""; } let s = "b" + quote; for (let i = 0; i < r.length; i++) { const b = r[i]; const c = String.fromCodePoint(b); if (c === quote || c === "\\") { s += "\\" + c; } else if (c === "\t") { s += "\\t"; } else if (c === "\n") { s += "\\n"; } else if (c === "\r") { s += "\\r"; } else if (c < " " || b >= 0x7f) { s += "\\x"; s += b.toString(16).padStart(2, "0"); } else { s += c; } } s += quote; return s; } exports.PyBytes_Repr = PyBytes_Repr; /** * Unlike python, there is no immutable byte type in javascript. */ class Bytes { constructor(value) { if (value instanceof Uint8Array) { this._b = value; } else if (isBytes(value)) { this._b = value.raw(); } else if (!value || value === __python_types__1.None) { this._b = new Uint8Array(); } else { throw new Error(`Invalid value: ${JSON.stringify(value)}`); } } static from(value, type) { if (value === __python_types__1.None || value === null) { return new Bytes(value); } else if (value instanceof Uint8Array) { return new Bytes(value.slice()); } else if (isBytes(value)) { return new Bytes(value.data()); } else if (Array.isArray(value) && value.every(v => typeof v === "number")) { if (value.some(v => (v < 0 || v > 255))) { throw new Error("Bytes must be in range [0, 256)"); } return new Bytes(Uint8Array.from(value)); } else if (typeof value === "string") { if (!value) { return new Bytes(); } if (type === "hex") { value = value.replace(/^0x/, ""); return new Bytes(Hex_1.Hex.parse(value).toUint8Array()); } else /* if(type === "utf8") */ { return new Bytes(Utf8_1.Utf8.parse(value).toUint8Array()); } } else if (type === "G1Element") { if (typeof value.serialize !== "function") { throw new Error("Invalid G1Element"); } const uint8array = value.serialize(); return new Bytes(uint8array); } throw new Error(`Invalid value: ${JSON.stringify(value)}`); } static SHA256(value) { let w; if (typeof value === "string") { w = SHA256_1.SHA256.hash(value); } else if (value instanceof Uint8Array) { w = new Word32Array_1.Word32Array(value); w = SHA256_1.SHA256.hash(w); } else if (isBytes(value)) { w = value.as_word(); w = SHA256_1.SHA256.hash(w); } else { throw new Error("Invalid argument"); } return new Bytes(w.toUint8Array()); } get length() { return this._b.length; } at(i) { return this._b[i] | 0; } concat(b) { const thisBin = this._b; const thatBin = b.raw(); const concatBin = new Uint8Array(thisBin.length + thatBin.length); concatBin.set(thisBin, 0); concatBin.set(thatBin, thisBin.length); return new Bytes(concatBin); } repeat(n) { const ret = new Uint8Array(this.length * n); for (let i = 0; i < n; i++) { ret.set(this._b, i * this.length); } return new Bytes(ret); } slice(start, length) { const len = typeof length === "number" ? length : (this.length - start); const ui8_clone = this._b.slice(start, start + len); return new Bytes(ui8_clone); } subarray(start, length) { const len = typeof length === "number" ? length : (this.length - start); const ui8_raw = this._b.subarray(start, start + len); return new Bytes(ui8_raw); } as_word() { return new Word32Array_1.Word32Array(this._b); } data() { return new Uint8Array(this._b); } raw() { return this._b; } clone() { return new Bytes(this._b.slice()); } toString() { return PyBytes_Repr(this._b); } hex() { return to_hexstr(this._b); } decode() { return Utf8_1.Utf8.stringify(this.as_word()); } startswith(b) { return this.hex().startsWith(b.hex()); } endswith(b) { return this.hex().endsWith(b.hex()); } equal_to(b) { if (b === __python_types__1.None) { return false; } else if (typeof b.length === "number" && isBytes(b)) { return this.compare(b) === 0; } else if (typeof b.equal_to === "function") { return b.equal_to(this); } return false; } /** * Returns: * +1 if argument is smaller * 0 if this and argument is the same * -1 if argument is larger * @param other */ compare(other) { if (this.length === 0 && other.length === 0) { return 0; } else if (this.length * other.length === 0) { // Either length of this or other is zero. return this.length > 0 ? 1 : -1; } const self_raw_byte = this._b; const self_byteLength = self_raw_byte.byteLength; const dv_self = new DataView(self_raw_byte.buffer, self_raw_byte.byteOffset, self_byteLength); const other_raw_byte = other.raw(); const other_byteLength = other_raw_byte.byteLength; const dv_other = new DataView(other_raw_byte.buffer, other_raw_byte.byteOffset, other_byteLength); // const minByteLength = Math.min(self_byteLength, other_byteLength); const minByteLength = Math.min(self_byteLength, other_byteLength) - 4; const ui32MaxCount = (Math.max(self_byteLength, other_byteLength) / 4) | 0; let offset = 0; for (offset = 0; offset < ui32MaxCount; offset++) { const k = offset * 4; // if(k + 4 > minByteLength){ // k > minByteLength - 4 ==(optimize)==> minByteLength = minByteLength - 4 if (k > minByteLength) { break; } const ui32_self = dv_self.getUint32(k); const ui32_other = dv_other.getUint32(k); if (ui32_self !== ui32_other) { return ui32_self > ui32_other ? 1 : -1; } } offset = offset * 4; const ui8MaxCount = Math.max(self_byteLength, other_byteLength); for (let i = offset; i < ui8MaxCount; i++) { const k = i + 1; if (k > self_byteLength) { return -1; } else if (k > other_byteLength) { return 1; } const ui8_self = dv_self.getUint8(i); const ui8_other = dv_other.getUint8(i); if (ui8_self !== ui8_other) { return ui8_self > ui8_other ? 1 : -1; } } return 0; } toJSON() { return this.hex(); } } exports.Bytes = Bytes; Bytes.NULL = new Bytes(); function b(utf8Str, type = "utf8") { return Bytes.from(utf8Str, type); } exports.b = b; function h(hexStr) { return Bytes.from(hexStr, "hex"); } exports.h = h; function list(iterable) { const arr = []; for (const item of iterable) { arr.push(item); } return arr; } exports.list = list; function str(x) { if (typeof x.toString === "function") { return x.toString(); } return `${x}`; } exports.str = str; function repr(x) { if (typeof x.__repr__ === "function") { return x.__repr__(); } return str(x); } exports.repr = repr; class Tuple extends Array { constructor(...items) { super(...items); Object.freeze(this); return this; } toString() { return `(${this[0]}, ${this[1]})`; } } exports.Tuple = Tuple; function t(v1, v2) { return new Tuple(v1, v2); } exports.t = t; function isTuple(v) { return v instanceof Array && Object.isFrozen(v) && v.length === 2; } exports.isTuple = isTuple; /** * Check whether an argument is a list and not a tuple */ function isList(v) { return Array.isArray(v) && !isTuple(v); } exports.isList = isList; function isIterable(v) { if (Array.isArray(v)) { // Including Tuple. return true; } else if (typeof v === "string") { return false; } else if (typeof v[Symbol.iterator] === "function") { return true; } return false; } exports.isIterable = isIterable; function isBytes(v) { return v && typeof v.length === "number" && typeof v.at === "function" && typeof v.raw === "function" && typeof v.data === "function" && typeof v.hex === "function" && typeof v.decode === "function" && typeof v.equal_to === "function" && typeof v.compare === "function"; } exports.isBytes = isBytes; class Stream { constructor(b) { this._bufAllocMultiplier = 4; this._seek = 0; if (b) { if (b.length > Stream.INITIAL_BUFFER_SIZE) { this._buffer = new Uint8Array(b.length * 2); } else { this._buffer = new Uint8Array(Stream.INITIAL_BUFFER_SIZE); } this._buffer.set(b.raw()); this._length = b.length; } else { this._buffer = new Uint8Array(Stream.INITIAL_BUFFER_SIZE); this._length = 0; } } get seek() { return this._seek; } set seek(value) { if (value < 0) { this._seek = this.length - 1; } else if (value > this.length - 1) { this._seek = this.length; } else { this._seek = value; } } get length() { return this._length; } reAllocate(size) { let s = typeof size === "number" ? size : this._buffer.length * this._bufAllocMultiplier; /** * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length */ if (s > 4294967295) { // 4294967295 = 2**32 - 1 s = 4294967295; } const buf = new Uint8Array(s); buf.set(this._buffer); this._buffer = buf; } write(b) { const newLength = Math.max(this.length, b.length + this._seek); if (newLength > this._buffer.length) { this.reAllocate(newLength * this._bufAllocMultiplier); } const offset = this.seek; this._buffer.set(b.raw(), offset); this._length = newLength; this.seek += b.length; // Don't move this line prior to `this._length = newLength`! return b.length; } read(size) { if (this.seek > this.length - 1) { return new Bytes(); // Return empty byte } if (this.seek + size <= this.length) { const u8 = this._buffer.subarray(this.seek, this.seek + size); this.seek += size; return new Bytes(u8); } const u8 = this._buffer.subarray(this.seek, this.length); this.seek += size; return new Bytes(u8); } getValue() { return new Bytes(this.asUint8Array()); } asUint8Array() { return this._buffer.subarray(0, this.length); } } exports.Stream = Stream; Stream.INITIAL_BUFFER_SIZE = 64 * 1024; /** * Python's style division. * In javascript, `-8 / 5 === -1` while `-8 / 5 == -2` in Python */ function division(a, b) { if (a === BigInt(0)) { return a; } else if (b === BigInt(0)) { throw new Error("Division by zero!"); } else if (a > BigInt(0) && b > BigInt(0) && a < b) { return BigInt(0); } else if (a < BigInt(0) && b < BigInt(0) && a > b) { return BigInt(0); } const div = a / b; if (a === div * b) { return div; } else if (div > BigInt(0)) { return div; } return div - BigInt(1); } exports.division = division; /** * Python's style modulo. * In javascript, `-8 % 5 === -3` while `-8 % 5 == 2` in Python */ function modulo(a, b) { const div = division(a, b); return a - b * div; } exports.modulo = modulo; function divmod(a, b) { const div = division(a, b); return t(div, a - b * div); } exports.divmod = divmod;