clvm_tools
Version:
Javascript implementation of clvm_tools
263 lines (262 loc) • 11.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.compile_mod = exports.compile_functions = exports.build_macro_lookup_program = exports.symbol_table_for_tree = exports.compile_mod_stage_1 = exports.parse_mod_sexp = exports.defun_inline_to_macro = exports.unquote_args = exports.parse_include = exports.build_used_constants_names = exports.flatten = exports.build_tree_program = exports.build_tree = exports.MAIN_NAME = exports.CONS_ATOM = exports.QUOTE_ATOM = void 0;
const clvm_1 = require("clvm");
const binutils = require("../../clvm_tools/binutils");
const debug_1 = require("../../clvm_tools/debug");
const NodePath_1 = require("../../clvm_tools/NodePath");
const helpers_1 = require("./helpers");
const optimize_1 = require("./optimize");
exports.QUOTE_ATOM = clvm_1.KEYWORD_TO_ATOM["q"];
exports.CONS_ATOM = clvm_1.KEYWORD_TO_ATOM["c"];
exports.MAIN_NAME = "";
function build_tree(items) {
// This function takes a Python list of items and turns it into a binary tree
// of the items, suitable for casting to an s-expression.
const size = items.length;
if (size === 0) {
return [];
}
else if (size === 1) {
return clvm_1.h(items[0]); // items[0] is expected to be a hex string representing constant name atom
}
const half_size = size >> 1;
const left = build_tree(items.slice(0, half_size));
const right = build_tree(items.slice(half_size));
return clvm_1.t(left, right);
}
exports.build_tree = build_tree;
function build_tree_program(items) {
// This function takes a Python list of items and turns it into a program that
// a binary tree of the items, suitable for casting to an s-expression.
const size = items.length;
if (size === 0) {
return [helpers_1.quote([])];
}
else if (size === 1) {
return items[0];
}
const half_size = size >> 1;
const left = build_tree_program(items.slice(0, half_size));
const right = build_tree_program(items.slice(half_size));
return [clvm_1.h(exports.CONS_ATOM), left, right];
}
exports.build_tree_program = build_tree_program;
function flatten(sexp) {
// Return a (python) list of every atom.
if (sexp.listp()) {
let r = [];
r = r.concat(flatten(sexp.first()));
r = r.concat(flatten(sexp.rest()));
return r;
}
return [sexp.atom];
}
exports.flatten = flatten;
/**
* @return Used constants name array in `hex string` format.
*/
function build_used_constants_names(functions, constants, macros) {
/*
Do a naïve pruning of unused symbols. It may be too big, but it shouldn't
be too small. Return a list of all atoms used that are also the names of
functions or constants, starting with the MAIN_NAME function.
*/
const macro_as_dict = macros.reduce((acc, _) => {
acc[_.rest().first().atom.hex()] = _;
return acc;
}, {});
const possible_symbols = new Set(Object.keys(functions));
Object.keys(constants).forEach(c => possible_symbols.add(c));
let new_names = new Set([exports.MAIN_NAME]);
const used_names = new Set(new_names);
while (new_names.size) {
const prior_new_names = new Set(new_names);
new_names = new Set();
for (const _ of prior_new_names) {
for (const k of [functions, macro_as_dict]) {
if (_ in k) {
flatten(k[_]).forEach(atom => new_names.add(atom.hex()));
}
}
}
// new_names.difference_update(used_names)
used_names.forEach(n => new_names.delete(n));
// used_names.update(new_names)
new_names.forEach(n => used_names.add(n));
}
// used_names.intersection_update(possible_symbols)
const used_name_list = [];
used_names.forEach(n => {
if (possible_symbols.has(n) && n !== exports.MAIN_NAME) {
used_name_list.push(n);
}
});
used_name_list.sort();
return used_name_list;
}
exports.build_used_constants_names = build_used_constants_names;
function parse_include(name, namespace, functions, constants, macros, run_program) {
const prog = binutils.assemble("(_read (_full_path_for_name 1))");
const assembled_sexp = run_program(prog, name)[1];
for (const sexp of assembled_sexp.as_iter()) {
parse_mod_sexp(sexp, namespace, functions, constants, macros, run_program);
}
}
exports.parse_include = parse_include;
function unquote_args(code, args) {
if (code.listp()) {
const c1 = code.first();
const c2 = code.rest();
return unquote_args(c1, args).cons(unquote_args(c2, args));
}
if (clvm_1.isAtom(code) && args.some(arg => arg.equal_to(code.atom))) {
return clvm_1.SExp.to([clvm_1.b("unquote"), code]);
}
return code;
}
exports.unquote_args = unquote_args;
function defun_inline_to_macro(declaration_sexp) {
const d2 = declaration_sexp.rest();
const d3 = d2.rest();
const r = [clvm_1.b("defmacro"), d2.first(), d3.first()];
const code = d3.rest().first();
const args = flatten(d3.first()).filter(_ => !_.equal_to(clvm_1.Bytes.NULL));
const unquoted_code = unquote_args(code, args);
const r2 = [...r, [clvm_1.b("qq"), unquoted_code]];
return clvm_1.SExp.to(r2);
}
exports.defun_inline_to_macro = defun_inline_to_macro;
function parse_mod_sexp(declaration_sexp, namespace, functions, constants, macros, run_program) {
const op = declaration_sexp.first().atom;
const name = declaration_sexp.rest().first();
if (op.equal_to(clvm_1.b("include"))) {
parse_include(name, namespace, functions, constants, macros, run_program);
return;
}
const name_atom = name.atom;
if (namespace.has(name_atom.hex())) {
const errMsg = `symbol "${name_atom.decode()}" redefined`;
// printError(`SyntaxError: ${errMsg}`);
throw new SyntaxError(errMsg);
}
namespace.add(name_atom.hex());
if (op.equal_to(clvm_1.b("defmacro"))) {
macros.push(declaration_sexp);
}
else if (op.equal_to(clvm_1.b("defun"))) {
functions[name_atom.hex()] = declaration_sexp.rest().rest();
}
else if (op.equal_to(clvm_1.b("defun-inline"))) {
macros.push(defun_inline_to_macro(declaration_sexp));
}
else if (op.equal_to(clvm_1.b("defconstant"))) {
constants[name_atom.hex()] = clvm_1.SExp.to(helpers_1.quote(declaration_sexp.rest().rest().first()));
}
else {
const errMsg = "expected defun, defun-inline, defmacro, or defconstant";
// printError(`SyntaxError: ${errMsg}`);
throw new SyntaxError(errMsg);
}
}
exports.parse_mod_sexp = parse_mod_sexp;
function compile_mod_stage_1(args, run_program) {
// stage 1: collect up names of globals (functions, constants, macros)
const functions = {};
const constants = {};
const macros = [];
const main_local_arguments = args.first();
const namespace = new Set();
// eslint-disable-next-line no-constant-condition
while (true) {
args = args.rest();
if (args.rest().nullp()) {
break;
}
parse_mod_sexp(args.first(), namespace, functions, constants, macros, run_program);
}
const uncompiled_main = args.first();
functions[exports.MAIN_NAME] = clvm_1.SExp.to([main_local_arguments, uncompiled_main]);
return [functions, constants, macros];
}
exports.compile_mod_stage_1 = compile_mod_stage_1;
function symbol_table_for_tree(tree, root_node) {
if (tree.nullp()) {
return [];
}
else if (!tree.listp()) {
return [[tree, root_node.as_path()]];
}
const left = symbol_table_for_tree(tree.first(), root_node.add(NodePath_1.LEFT));
const right = symbol_table_for_tree(tree.rest(), root_node.add(NodePath_1.RIGHT));
return left.concat(right);
}
exports.symbol_table_for_tree = symbol_table_for_tree;
function build_macro_lookup_program(macro_lookup, macros, run_program) {
let macro_lookup_program = clvm_1.SExp.to(helpers_1.quote(macro_lookup));
for (const macro of macros) {
macro_lookup_program = helpers_1.evaluate(clvm_1.SExp.to([clvm_1.b("opt"), [clvm_1.b("com"), helpers_1.quote([clvm_1.h(exports.CONS_ATOM), macro, macro_lookup_program]), macro_lookup_program]]), NodePath_1.TOP.as_path());
macro_lookup_program = optimize_1.optimize_sexp(macro_lookup_program, run_program);
}
return macro_lookup_program;
}
exports.build_macro_lookup_program = build_macro_lookup_program;
function compile_functions(functions, macro_lookup_program, constants_symbol_table, args_root_node) {
const compiled_functions = {};
for (const [name, lambda_expression] of Object.entries(functions)) {
const local_symbol_table = symbol_table_for_tree(lambda_expression.first(), args_root_node);
const all_symbols = local_symbol_table.concat(constants_symbol_table);
compiled_functions[name] = clvm_1.SExp.to([clvm_1.b("opt"), [clvm_1.b("com"),
helpers_1.quote(lambda_expression.rest().first()),
macro_lookup_program,
helpers_1.quote(all_symbols)]]);
}
return compiled_functions;
}
exports.compile_functions = compile_functions;
function compile_mod(args, macro_lookup, symbol_table, run_program) {
// Deal with the "mod" keyword.
const [functions, constants, macros] = compile_mod_stage_1(args, run_program);
// move macros into the macro lookup
const macro_lookup_program = build_macro_lookup_program(macro_lookup, macros, run_program);
// get a list of all symbols that are possibly used
const all_constants_names = build_used_constants_names(functions, constants, macros);
const has_constants_tree = all_constants_names.length > 0;
// build defuns table, with function names as keys
const constants_tree = clvm_1.SExp.to(build_tree(all_constants_names));
const constants_root_node = NodePath_1.LEFT;
let args_root_node;
if (has_constants_tree) {
args_root_node = NodePath_1.RIGHT;
}
else {
args_root_node = NodePath_1.TOP;
}
const constants_symbol_table = symbol_table_for_tree(constants_tree, constants_root_node);
const compiled_functions = compile_functions(functions, macro_lookup_program, constants_symbol_table, args_root_node);
const main_path_src = binutils.disassemble(compiled_functions[exports.MAIN_NAME]);
let main_code;
if (has_constants_tree) {
const all_constants_lookup = {};
Object.entries(compiled_functions).forEach(([k, v]) => {
if (all_constants_names.includes(k)) {
all_constants_lookup[k] = v;
}
});
Object.entries(constants).forEach(([k, v]) => {
all_constants_lookup[k] = v;
});
const all_constants_list = all_constants_names.map(_ => all_constants_lookup[_]);
const all_constants_tree_program = clvm_1.SExp.to(build_tree_program(all_constants_list));
const all_constants_tree_src = binutils.disassemble(all_constants_tree_program);
const arg_tree_src = `(c ${all_constants_tree_src} 1)`;
main_code = `(opt (q . (a ${main_path_src} ${arg_tree_src})))`;
debug_1.build_symbol_dump(all_constants_lookup, run_program, "main.sym");
}
else {
const arg_tree_src = "1";
main_code = `(opt (q . (a ${main_path_src} ${arg_tree_src})))`;
}
return binutils.assemble(main_code);
}
exports.compile_mod = compile_mod;