clvm_tools
Version:
Javascript implementation of clvm_tools
304 lines (300 loc) • 11.6 kB
JavaScript
;
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;