UNPKG

clvm_tools

Version:

Javascript implementation of clvm_tools

304 lines (300 loc) 11.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.read_ir = exports.token_stream = exports.tokenize_sexp = exports.tokenize_atom = exports.tokenize_symbol = exports.tokenize_quotes = exports.tokenize_hex = exports.tokenize_int = exports.next_cons_token = exports.consume_until_whitespace = exports.consume_whitespace = void 0; const clvm_1 = require("clvm"); const Type_1 = require("./Type"); const utils_1 = require("./utils"); function consume_whitespace(s, offset) { // This also deals with comments // eslint-disable-next-line no-constant-condition while (true) { while (offset < s.length && /\s+/.test(s[offset])) { offset += 1; } if (offset >= s.length || s[offset] !== ";") { break; } while (offset < s.length && !/[\n\r]/.test(s[offset])) { offset += 1; } } return offset; } exports.consume_whitespace = consume_whitespace; function consume_until_whitespace(s, offset) { const start = offset; while (offset < s.length && !/\s+/.test(s[offset]) && s[offset] !== ")") { offset += 1; } return clvm_1.t(s.substring(start, offset), offset); } exports.consume_until_whitespace = consume_until_whitespace; function next_cons_token(stream) { let token = ""; let offset = -1; // Fix generator spec incompatibility between python and javascript. // Javascript iterator cannot be re-used while python can. /* for(const s of stream){ found = true; ([token, offset] = s); break; } */ const next = stream.next(); if (next.done) { const errMsg = "missing )"; // printError(`SyntaxError: ${errMsg}`); throw new SyntaxError(errMsg); } ([token, offset] = next.value); return clvm_1.t(token, offset); } exports.next_cons_token = next_cons_token; // `tokenize_cons` was incorporated into `tokenize_cons` to reduce stack size by eliminating deep function nest. /* export function tokenize_cons(token: string, offset: number, stream: Generator<Token>): SExp { if(token === ")"){ return ir_new(Type.NULL.i, 0, offset); } const initial_offset = offset; const first_sexp = tokenize_sexp(token, offset, stream); ([token, offset] = next_cons_token(stream)); let rest_sexp; if(token === "."){ const dot_offset = offset; // grab the last item ([token, offset] = next_cons_token(stream)); rest_sexp = tokenize_sexp(token, offset, stream); ([token, offset] = next_cons_token(stream)); if(token !== ")"){ const errMsg = `illegal dot expression at ${dot_offset}`; // printError(`SyntaxError: ${errMsg}`); throw new SyntaxError(errMsg); } } else{ rest_sexp = tokenize_cons(token, offset, stream); } return ir_cons(first_sexp, rest_sexp, initial_offset); } */ function tokenize_int(token, offset) { try { // Don't recognize hex string to int if (token.slice(0, 2).toUpperCase() === "0X" || !/^[+-]?[0-9]+$/.test(token)) { return clvm_1.None; } return utils_1.ir_new(Type_1.Type.INT.i, BigInt(token), offset); } catch (e) { // Skip } return clvm_1.None; } exports.tokenize_int = tokenize_int; function tokenize_hex(token, offset) { if (token.slice(0, 2).toUpperCase() === "0X") { try { token = token.substring(2); if (token.length % 2 === 1) { token = `0${token}`; } return utils_1.ir_new(Type_1.Type.HEX.i, clvm_1.Bytes.from(token, "hex"), offset); } catch (e) { const errMsg = `invalid hex at ${offset}: 0x${token}`; // printError(`SyntaxError: ${errMsg}`); throw new SyntaxError(errMsg); } } return clvm_1.None; } exports.tokenize_hex = tokenize_hex; function tokenize_quotes(token, offset) { if (token.length < 2) { return clvm_1.None; } const c = token[0]; if (!/['"]/.test(c)) { return clvm_1.None; } if (token[token.length - 1] !== c) { const errMsg = `unterminated string starting at ${offset}: ${token}`; // printError(`SyntaxError: ${errMsg}`); throw new SyntaxError(errMsg); } const q_type = c === "'" ? Type_1.Type.SINGLE_QUOTE : Type_1.Type.DOUBLE_QUOTE; return clvm_1.t(clvm_1.t(q_type.i, offset), clvm_1.b(token.substring(1, token.length - 1))); } exports.tokenize_quotes = tokenize_quotes; function tokenize_symbol(token, offset) { return clvm_1.t(clvm_1.t(Type_1.Type.SYMBOL.i, offset), clvm_1.b(token)); } exports.tokenize_symbol = tokenize_symbol; function tokenize_atom(token, offset) { for (const f of [ tokenize_int, tokenize_hex, tokenize_quotes, tokenize_symbol, ]) { const r = f(token, offset); if (r !== clvm_1.None) { return r; } } return clvm_1.None; } exports.tokenize_atom = tokenize_atom; // In order to reduce stack memory consumed, I made `tokenize_sexp` fully flatten from the previous recursive function callstack. function tokenize_sexp(token, offset, stream) { if (token === "(") { ([token, offset] = next_cons_token(stream)); const input_stack = []; const return_value_stack = []; const callee_address_stack = []; const env_stack = []; let last_return_value; input_stack.push([token, offset]); while (input_stack.length || callee_address_stack.length) { let [local_token, local_offset] = [token, offset]; while (callee_address_stack.length && return_value_stack.length) { const callee_address = callee_address_stack.pop(); const return_value = return_value_stack.pop(); if (callee_address === 1) { const env = env_stack.pop(); const rest_sexp = return_value; const [first_sexp, initial_offset] = env; last_return_value = utils_1.ir_cons(first_sexp, rest_sexp, initial_offset); return_value_stack.push(last_return_value); } else if (callee_address === 2) { const env = env_stack.pop(); const [initial_offset] = env; local_offset = initial_offset; const first_sexp = return_value; ([local_token, local_offset] = next_cons_token(stream)); let rest_sexp; if (local_token === ".") { const dot_offset = local_offset; // grab the last item ([local_token, local_offset] = next_cons_token(stream)); rest_sexp = tokenize_sexp(local_token, local_offset, stream); ([local_token, local_offset] = next_cons_token(stream)); if (local_token !== ")") { const errMsg = `illegal dot expression at ${dot_offset}`; // printError(`SyntaxError: ${errMsg}`); throw new SyntaxError(errMsg); } last_return_value = utils_1.ir_cons(first_sexp, rest_sexp, initial_offset); return_value_stack.push(last_return_value); } else { callee_address_stack.push(1); env_stack.push([first_sexp, initial_offset]); input_stack.push([local_token, local_offset]); } } } if (!input_stack.length) { continue; } ([local_token, local_offset] = input_stack.pop()); if (local_token === ")") { last_return_value = utils_1.ir_new(Type_1.Type.NULL.i, 0, local_offset); return_value_stack.push(last_return_value); continue; } const initial_offset = local_offset; if (local_token === "(") { ([local_token, local_offset] = next_cons_token(stream)); callee_address_stack.push(2); env_stack.push([initial_offset]); input_stack.push([local_token, local_offset]); continue; } const first_sexp = tokenize_atom(local_token, local_offset); ([local_token, local_offset] = next_cons_token(stream)); let rest_sexp; if (local_token === ".") { const dot_offset = local_offset; // grab the last item ([local_token, local_offset] = next_cons_token(stream)); rest_sexp = tokenize_sexp(local_token, local_offset, stream); ([local_token, local_offset] = next_cons_token(stream)); if (local_token !== ")") { const errMsg = `illegal dot expression at ${dot_offset}`; // printError(`SyntaxError: ${errMsg}`); throw new SyntaxError(errMsg); } last_return_value = utils_1.ir_cons(first_sexp, rest_sexp, initial_offset); return_value_stack.push(last_return_value); } else { callee_address_stack.push(1); env_stack.push([first_sexp, initial_offset]); input_stack.push([local_token, local_offset]); } } return last_return_value; } return tokenize_atom(token, offset); } exports.tokenize_sexp = tokenize_sexp; function* token_stream(s) { let offset = 0; while (offset < s.length) { offset = consume_whitespace(s, offset); if (offset >= s.length) { break; } const c = s[offset]; if (/[(.)]/.test(c)) { yield clvm_1.t(c, offset); offset += 1; continue; } if (/["']/.test(c)) { const start = offset; const initial_c = s[start]; offset += 1; while (offset < s.length && s[offset] !== initial_c) { offset += 1; } if (offset < s.length) { yield clvm_1.t(s.substring(start, offset + 1), start); offset += 1; continue; } else { const errMsg = `unterminated string starting at ${start}: ${s.substring(start)}`; // printError(`SyntaxError: ${errMsg}`); throw new SyntaxError(errMsg); } } const [token, end_offset] = consume_until_whitespace(s, offset); yield clvm_1.t(token, offset); offset = end_offset; } } exports.token_stream = token_stream; function read_ir(s, to_sexp = clvm_1.to_sexp_f) { const stream = token_stream(s); // Fix generator spec incompatibility between python and javascript. // Javascript iterator cannot be re-used while python can. /* for(const [token, offset] of stream){ return to_sexp(tokenize_sexp(token, offset, stream)); } */ const next = stream.next(); if (next.done) { const errMsg = "unexpected end of stream"; // printError(`SyntaxError: ${errMsg}`); throw new SyntaxError(errMsg); } const [token, offset] = next.value; return to_sexp(tokenize_sexp(token, offset, stream)); } exports.read_ir = read_ir;