use num_bigint::ToBigInt;
use std::borrow::Borrow;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::PathBuf;
use std::rc::Rc;
use clvm_rs::allocator::Allocator;
use crate::classic::clvm::__type_compatibility__::{bi_one, bi_zero};
use crate::classic::clvm_tools::stages::stage_0::TRunProgram;
use crate::classic::clvm_tools::stages::stage_2::optimize::optimize_sexp;
use crate::compiler::clvm::{convert_from_clvm_rs, convert_to_clvm_rs, sha256tree};
use crate::compiler::codegen::codegen;
use crate::compiler::comptypes::{
CompileErr, CompileForm, CompilerOpts, DefunData, HelperForm, PrimaryCodegen,
};
use crate::compiler::evaluate::{build_reflex_captures, Evaluator};
use crate::compiler::frontend::frontend;
use crate::compiler::prims;
use crate::compiler::runtypes::RunFailure;
use crate::compiler::sexp::{parse_sexp, SExp};
use crate::compiler::srcloc::Srcloc;
use crate::util::Number;
lazy_static! {
pub static ref KNOWN_DIALECTS: HashMap<String, String> = {
let mut known_dialects: HashMap<String, String> = HashMap::new();
known_dialects.insert(
"*standard-cl-21*".to_string(),
indoc! {"(
(defconstant *chialisp-version* 21)
)"}
.to_string(),
);
known_dialects.insert(
"*standard-cl-22*".to_string(),
indoc! {"(
(defconstant *chialisp-version* 22)
)"}
.to_string(),
);
known_dialects
};
pub static ref STANDARD_MACROS: String = {
indoc! {"(
(defmacro if (A B C) (qq (a (i (unquote A) (com (unquote B)) (com (unquote C))) @)))
(defmacro list ARGS
(defun compile-list
(args)
(if args
(qq (c (unquote (f args))
(unquote (compile-list (r args)))))
()))
(compile-list ARGS)
)
(defun-inline / (A B) (f (divmod A B)))
)
"}
.to_string()
};
}
#[derive(Clone, Debug)]
pub struct DefaultCompilerOpts {
pub include_dirs: Vec<String>,
pub filename: String,
pub compiler: Option<PrimaryCodegen>,
pub in_defun: bool,
pub stdenv: bool,
pub optimize: bool,
pub frontend_opt: bool,
pub frontend_check_live: bool,
pub start_env: Option<Rc<SExp>>,
pub prim_map: Rc<HashMap<Vec<u8>, Rc<SExp>>>,
known_dialects: Rc<HashMap<String, String>>,
}
pub fn create_prim_map() -> Rc<HashMap<Vec<u8>, Rc<SExp>>> {
let mut prim_map: HashMap<Vec<u8>, Rc<SExp>> = HashMap::new();
for p in prims::prims() {
prim_map.insert(p.0.clone(), Rc::new(p.1.clone()));
}
Rc::new(prim_map)
}
fn fe_opt(
allocator: &mut Allocator,
runner: Rc<dyn TRunProgram>,
opts: Rc<dyn CompilerOpts>,
compileform: CompileForm,
) -> Result<CompileForm, CompileErr> {
let mut compiler_helpers = compileform.helpers.clone();
let mut used_names = HashSet::new();
if !opts.in_defun() {
for c in compileform.helpers.iter() {
used_names.insert(c.name().clone());
}
for helper in (opts
.compiler()
.map(|c| c.orig_help)
.unwrap_or_else(Vec::new))
.iter()
{
if !used_names.contains(helper.name()) {
compiler_helpers.push(helper.clone());
}
}
}
let evaluator = Evaluator::new(opts.clone(), runner.clone(), compiler_helpers.clone());
let mut optimized_helpers: Vec<HelperForm> = Vec::new();
for h in compiler_helpers.iter() {
match h {
HelperForm::Defun(inline, defun) => {
let mut env = HashMap::new();
build_reflex_captures(&mut env, defun.args.clone());
let body_rc = evaluator.shrink_bodyform(
allocator,
defun.args.clone(),
&env,
defun.body.clone(),
true,
)?;
let new_helper = HelperForm::Defun(
*inline,
DefunData {
loc: defun.loc.clone(),
nl: defun.nl.clone(),
kw: defun.kw.clone(),
name: defun.name.clone(),
args: defun.args.clone(),
body: body_rc.clone(),
},
);
optimized_helpers.push(new_helper);
}
obj => {
optimized_helpers.push(obj.clone());
}
}
}
let new_evaluator = Evaluator::new(opts.clone(), runner.clone(), optimized_helpers.clone());
let shrunk = new_evaluator.shrink_bodyform(
allocator,
Rc::new(SExp::Nil(compileform.args.loc())),
&HashMap::new(),
compileform.exp.clone(),
true,
)?;
Ok(CompileForm {
loc: compileform.loc.clone(),
include_forms: compileform.include_forms.clone(),
args: compileform.args,
helpers: optimized_helpers.clone(),
exp: shrunk,
})
}
pub fn compile_pre_forms(
allocator: &mut Allocator,
runner: Rc<dyn TRunProgram>,
opts: Rc<dyn CompilerOpts>,
pre_forms: &[Rc<SExp>],
symbol_table: &mut HashMap<String, String>,
) -> Result<SExp, CompileErr> {
let g = frontend(opts.clone(), pre_forms)?;
let compileform = if opts.frontend_opt() {
fe_opt(allocator, runner.clone(), opts.clone(), g)?
} else {
CompileForm {
loc: g.loc.clone(),
include_forms: g.include_forms.clone(),
args: g.args.clone(),
helpers: g.helpers.clone(), exp: g.exp,
}
};
codegen(allocator, runner, opts.clone(), &compileform, symbol_table)
}
pub fn compile_file(
allocator: &mut Allocator,
runner: Rc<dyn TRunProgram>,
opts: Rc<dyn CompilerOpts>,
content: &str,
symbol_table: &mut HashMap<String, String>,
) -> Result<SExp, CompileErr> {
let pre_forms = parse_sexp(Srcloc::start(&opts.filename()), content.bytes())
.map_err(|e| CompileErr(e.0, e.1))?;
compile_pre_forms(allocator, runner, opts, &pre_forms, symbol_table)
}
pub fn run_optimizer(
allocator: &mut Allocator,
runner: Rc<dyn TRunProgram>,
r: Rc<SExp>,
) -> Result<Rc<SExp>, CompileErr> {
let to_clvm_rs = convert_to_clvm_rs(allocator, r.clone())
.map(|x| (r.loc(), x))
.map_err(|e| match e {
RunFailure::RunErr(l, e) => CompileErr(l, e),
RunFailure::RunExn(s, e) => CompileErr(s, format!("exception {}\n", e)),
})?;
let optimized = optimize_sexp(allocator, to_clvm_rs.1, runner)
.map_err(|e| CompileErr(to_clvm_rs.0.clone(), e.1))
.map(|x| (to_clvm_rs.0, x))?;
convert_from_clvm_rs(allocator, optimized.0, optimized.1).map_err(|e| match e {
RunFailure::RunErr(l, e) => CompileErr(l, e),
RunFailure::RunExn(s, e) => CompileErr(s, format!("exception {}\n", e)),
})
}
impl CompilerOpts for DefaultCompilerOpts {
fn filename(&self) -> String {
self.filename.clone()
}
fn compiler(&self) -> Option<PrimaryCodegen> {
self.compiler.clone()
}
fn in_defun(&self) -> bool {
self.in_defun
}
fn stdenv(&self) -> bool {
self.stdenv
}
fn optimize(&self) -> bool {
self.optimize
}
fn frontend_opt(&self) -> bool {
self.frontend_opt
}
fn frontend_check_live(&self) -> bool {
self.frontend_check_live
}
fn start_env(&self) -> Option<Rc<SExp>> {
self.start_env.clone()
}
fn prim_map(&self) -> Rc<HashMap<Vec<u8>, Rc<SExp>>> {
self.prim_map.clone()
}
fn set_search_paths(&self, dirs: &[String]) -> Rc<dyn CompilerOpts> {
let mut copy = self.clone();
copy.include_dirs = dirs.to_owned();
Rc::new(copy)
}
fn set_in_defun(&self, new_in_defun: bool) -> Rc<dyn CompilerOpts> {
let mut copy = self.clone();
copy.in_defun = new_in_defun;
Rc::new(copy)
}
fn set_stdenv(&self, new_stdenv: bool) -> Rc<dyn CompilerOpts> {
let mut copy = self.clone();
copy.stdenv = new_stdenv;
Rc::new(copy)
}
fn set_optimize(&self, optimize: bool) -> Rc<dyn CompilerOpts> {
let mut copy = self.clone();
copy.optimize = optimize;
Rc::new(copy)
}
fn set_frontend_opt(&self, optimize: bool) -> Rc<dyn CompilerOpts> {
let mut copy = self.clone();
copy.frontend_opt = optimize;
Rc::new(copy)
}
fn set_frontend_check_live(&self, check: bool) -> Rc<dyn CompilerOpts> {
let mut copy = self.clone();
copy.frontend_check_live = check;
Rc::new(copy)
}
fn set_compiler(&self, new_compiler: PrimaryCodegen) -> Rc<dyn CompilerOpts> {
let mut copy = self.clone();
copy.compiler = Some(new_compiler);
Rc::new(copy)
}
fn set_start_env(&self, start_env: Option<Rc<SExp>>) -> Rc<dyn CompilerOpts> {
let mut copy = self.clone();
copy.start_env = start_env;
Rc::new(copy)
}
fn read_new_file(
&self,
inc_from: String,
filename: String,
) -> Result<(String, String), CompileErr> {
if filename == "*macros*" {
return Ok((filename, STANDARD_MACROS.clone()));
} else if let Some(content) = self.known_dialects.get(&filename) {
return Ok((filename, content.to_string()));
}
for dir in self.include_dirs.iter() {
let mut p = PathBuf::from(dir);
p.push(filename.clone());
match fs::read_to_string(p) {
Err(_e) => {
continue;
}
Ok(content) => {
return Ok((filename, content));
}
}
}
Err(CompileErr(
Srcloc::start(&inc_from),
format!("could not find {} to include", filename),
))
}
fn compile_program(
&self,
allocator: &mut Allocator,
runner: Rc<dyn TRunProgram>,
sexp: Rc<SExp>,
symbol_table: &mut HashMap<String, String>,
) -> Result<SExp, CompileErr> {
let me = Rc::new(self.clone());
compile_pre_forms(allocator, runner, me, &[sexp], symbol_table)
}
}
impl DefaultCompilerOpts {
pub fn new(filename: &str) -> DefaultCompilerOpts {
DefaultCompilerOpts {
include_dirs: vec![".".to_string()],
filename: filename.to_string(),
compiler: None,
in_defun: false,
stdenv: true,
optimize: false,
frontend_opt: false,
frontend_check_live: true,
start_env: None,
prim_map: create_prim_map(),
known_dialects: Rc::new(KNOWN_DIALECTS.clone()),
}
}
}
fn path_to_function_inner(
program: Rc<SExp>,
hash: &[u8],
path_mask: Number,
current_path: Number,
) -> Option<Number> {
let nextpath = path_mask.clone() * 2_i32.to_bigint().unwrap();
match program.borrow() {
SExp::Cons(_, a, b) => {
path_to_function_inner(a.clone(), hash, nextpath.clone(), current_path.clone())
.map(Some)
.unwrap_or_else(|| {
path_to_function_inner(
b.clone(),
hash,
nextpath.clone(),
current_path.clone() + path_mask.clone(),
)
.map(Some)
.unwrap_or_else(|| {
let current_hash = sha256tree(program.clone());
if current_hash == hash {
Some(current_path + path_mask)
} else {
None
}
})
})
}
_ => {
let current_hash = sha256tree(program.clone());
if current_hash == hash {
Some(current_path + path_mask)
} else {
None
}
}
}
}
pub fn path_to_function(program: Rc<SExp>, hash: &[u8]) -> Option<Number> {
path_to_function_inner(program, hash, bi_one(), bi_zero())
}
fn op2(op: u32, code: Rc<SExp>, env: Rc<SExp>) -> Rc<SExp> {
Rc::new(SExp::Cons(
code.loc(),
Rc::new(SExp::Integer(env.loc(), op.to_bigint().unwrap())),
Rc::new(SExp::Cons(
code.loc(),
code.clone(),
Rc::new(SExp::Cons(
env.loc(),
env.clone(),
Rc::new(SExp::Nil(code.loc())),
)),
)),
))
}
fn quoted(env: Rc<SExp>) -> Rc<SExp> {
Rc::new(SExp::Cons(
env.loc(),
Rc::new(SExp::Integer(env.loc(), bi_one())),
env.clone(),
))
}
fn apply(code: Rc<SExp>, env: Rc<SExp>) -> Rc<SExp> {
op2(2, code, env)
}
fn cons(f: Rc<SExp>, r: Rc<SExp>) -> Rc<SExp> {
op2(4, f, r)
}
pub fn rewrite_in_program(path: Number, env: Rc<SExp>) -> Rc<SExp> {
apply(
apply(
quoted(Rc::new(SExp::Integer(env.loc(), path / 2))),
env.clone(),
),
cons(env.clone(), Rc::new(SExp::Integer(env.loc(), bi_one()))),
)
}
pub fn is_operator(op: u32, atom: &SExp) -> bool {
match atom.to_bigint() {
Some(n) => n == op.to_bigint().unwrap(),
None => false,
}
}
pub fn is_whole_env(atom: &SExp) -> bool {
is_operator(1, atom)
}
pub fn is_apply(atom: &SExp) -> bool {
is_operator(2, atom)
}
pub fn is_cons(atom: &SExp) -> bool {
is_operator(4, atom)
}
pub fn extract_program_and_env(program: Rc<SExp>) -> Option<(Rc<SExp>, Rc<SExp>)> {
match program.proper_list() {
Some(lst) => {
if lst.len() != 3 {
return None;
}
match (is_apply(&lst[0]), lst[1].borrow(), lst[2].proper_list()) {
(true, real_program, Some(cexp)) => {
if cexp.len() != 3 || !is_cons(&cexp[0]) || !is_whole_env(&cexp[2]) {
None
} else {
Some((Rc::new(real_program.clone()), Rc::new(cexp[1].clone())))
}
}
_ => None,
}
}
_ => None,
}
}
pub fn is_at_capture(head: Rc<SExp>, rest: Rc<SExp>) -> Option<(Vec<u8>, Rc<SExp>)> {
rest.proper_list().and_then(|l| {
if l.len() != 2 {
return None;
}
if let (SExp::Atom(_, a), SExp::Atom(_, cap)) = (head.borrow(), l[0].borrow()) {
if a == &vec![b'@'] {
return Some((cap.clone(), Rc::new(l[1].clone())));
}
}
None
})
}