diff --git a/Cargo.lock b/Cargo.lock index a07d12dc..9b6d3e0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,7 @@ dependencies = [ [[package]] name = "calcit_runner" -version = "0.3.0-a10" +version = "0.3.0-a11" dependencies = [ "cirru_edn", "cirru_parser", diff --git a/Cargo.toml b/Cargo.toml index 9cf04b58..09371364 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "calcit_runner" -version = "0.3.0-a10" +version = "0.3.0-a11" authors = ["jiyinyiyong "] edition = "2018" license = "MIT" diff --git a/package.json b/package.json index a78e29b3..8911dc0b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@calcit/procs", - "version": "0.3.0-a10", + "version": "0.3.0-a11", "main": "./lib/calcit.procs.js", "devDependencies": { "@types/node": "^14.14.41", diff --git a/src/builtins/effects.rs b/src/builtins/effects.rs index 7b9d5e00..c673ef74 100644 --- a/src/builtins/effects.rs +++ b/src/builtins/effects.rs @@ -111,7 +111,7 @@ pub fn get_env(xs: &CalcitItems) -> Result { Some(Calcit::Str(s)) => match env::var(s) { Ok(v) => Ok(Calcit::Str(v)), Err(e) => { - println!("get-env {}", e); + println!("(get-env {}): {}", s, e); Ok(Calcit::Nil) } }, diff --git a/src/call_stack.rs b/src/call_stack.rs index 67e1c52c..6b42694b 100644 --- a/src/call_stack.rs +++ b/src/call_stack.rs @@ -20,7 +20,8 @@ pub enum StackKind { Fn, Proc, Macro, - Syntax, // rarely used + Syntax, // rarely used + Codegen, // track preprocessing } // TODO impl fmt @@ -112,5 +113,6 @@ fn name_kind(k: &StackKind) -> String { StackKind::Proc => String::from("proc"), StackKind::Macro => String::from("macro"), StackKind::Syntax => String::from("syntax"), + StackKind::Codegen => String::from("codegen"), } } diff --git a/src/cirru/calcit-core.cirru b/src/cirru/calcit-core.cirru index 74f4ce09..f2a4d368 100644 --- a/src/cirru/calcit-core.cirru +++ b/src/cirru/calcit-core.cirru @@ -980,6 +980,7 @@ |do $ quote defmacro do (& body) ; echo "|body:" (format-to-lisp body) + assert "|empty do is not okay" $ not $ empty? body quasiquote &let nil ~@ body diff --git a/src/codegen/emit_js.rs b/src/codegen/emit_js.rs index bb83e7f8..6d603d2b 100644 --- a/src/codegen/emit_js.rs +++ b/src/codegen/emit_js.rs @@ -2,12 +2,15 @@ mod internal_states; mod snippets; use std::cell::RefCell; +use std::cmp::Ordering; use std::collections::{BTreeMap, HashMap, HashSet}; use std::fs; use std::path::Path; use crate::builtins::meta::{js_gensym, reset_js_gensym_index}; use crate::builtins::{is_proc_name, is_syntax_name}; +use crate::call_stack; +use crate::call_stack::StackKind; use crate::primes; use crate::primes::{Calcit, CalcitItems, SymbolResolved::*}; use crate::program; @@ -266,12 +269,14 @@ fn gen_call_code( match s.as_str() { "if" => match (body.get(0), body.get(1)) { (Some(condition), Some(true_branch)) => { + call_stack::push_call_stack(ns, "if", StackKind::Codegen, &Some(xs.to_owned()), &im::vector![]); let false_code = match body.get(2) { Some(fal) => to_js_code(fal, ns, local_defs, file_imports)?, None => String::from("null"), }; let cond_code = to_js_code(condition, ns, local_defs, file_imports)?; let true_code = to_js_code(true_branch, ns, local_defs, file_imports)?; + call_stack::pop_call_stack(); Ok(format!("( {} ? {} : {} )", cond_code, true_code, false_code)) } (_, _) => Err(format!("if expected 2~3 nodes, got: {:?}", body)), @@ -289,7 +294,9 @@ fn gen_call_code( (Some(Calcit::Symbol(sym, ..)), Some(v)) => { // let _name = escape_var(sym); // TODO let ref_path = wrap_js_str(&format!("{}/{}", ns, sym.clone())); + call_stack::push_call_stack(ns, sym, StackKind::Codegen, &Some(xs.to_owned()), &im::vector![]); let value_code = &to_js_code(v, ns, local_defs, file_imports)?; + call_stack::pop_call_stack(); Ok(format!( "\n({}peekDefatom({}) ?? {}defatom({}, {}))\n", &var_prefix, &ref_path, &var_prefix, &ref_path, value_code @@ -302,7 +309,10 @@ fn gen_call_code( "defn" => match (body.get(0), body.get(1)) { (Some(Calcit::Symbol(sym, ..)), Some(Calcit::List(ys))) => { let func_body = body.skip(2); - gen_js_func(sym, &ys, &func_body, ns, false, local_defs, file_imports) + call_stack::push_call_stack(ns, sym, StackKind::Codegen, &Some(xs.to_owned()), &im::vector![]); + let ret = gen_js_func(sym, &ys, &func_body, ns, false, local_defs, file_imports); + call_stack::pop_call_stack(); + ret } (_, _) => Err(format!("defn expected name arguments, got: {:?}", body)), }, @@ -330,10 +340,13 @@ fn gen_call_code( } "try" => match (body.get(0), body.get(1)) { (Some(expr), Some(handler)) => { + call_stack::push_call_stack(ns, "try", StackKind::Codegen, &Some(xs.to_owned()), &im::vector![]); let code = to_js_code(expr, ns, local_defs, file_imports)?; let err_var = js_gensym("errMsg"); let handler = to_js_code(handler, ns, local_defs, file_imports)?; + call_stack::pop_call_stack(); + Ok(snippets::tmpl_fn_wrapper(snippets::tmpl_try(err_var, code, handler))) } (_, _) => Err(format!("try expected 2 nodes, got {:?}", body)), @@ -540,13 +553,12 @@ fn gen_let_code( // defined new local variable let mut scoped_defs = local_defs.clone(); let mut defs_code = String::from(""); - let mut variable_existed = false; let mut body_part = String::from(""); // break unless nested &let is found loop { if let_def_body.len() <= 1 { - return Err(format!("unexpected empty content in let, {:?}", xs)); + return Err(format!("&let expected body, but got empty, {}", xs.lisp_str())); } let pair = let_def_body[0].clone(); let content = let_def_body.skip(1); @@ -569,23 +581,16 @@ fn gen_let_code( } Calcit::List(xs) if xs.len() == 2 => { let def_name = xs[0].clone(); - let expr_code = xs[1].clone(); + let def_code = xs[1].clone(); match def_name { Calcit::Symbol(sym, ..) => { // TODO `let` inside expressions makes syntax error let left = escape_var(&sym); - let right = to_js_code(&expr_code, &ns, &scoped_defs, file_imports)?; - + let right = to_js_code(&def_code, &ns, &scoped_defs, file_imports)?; defs_code.push_str(&format!("let {} = {};\n", left, right)); if scoped_defs.contains(&sym) { - variable_existed = true; - } else { - scoped_defs.insert(sym.clone()); - } - - if variable_existed { for (idx, x) in content.iter().enumerate() { if idx == content.len() - 1 { body_part.push_str("return "); @@ -604,14 +609,19 @@ fn gen_let_code( return Ok(make_let_with_wrapper(&left, &right, &body_part)); } } else { + // track variable + scoped_defs.insert(sym.clone()); + if content.len() == 1 { - let child = content[0].clone(); - match child { - Calcit::List(ys) if ys.len() == 2 => match (&ys[0], &ys[1]) { - (Calcit::Symbol(sym, ..), Calcit::List(zs)) if sym == "&let" && zs.len() == 2 => { - let_def_body = ys.skip(1); - continue; - } + match &content[0] { + Calcit::List(ys) if ys.len() > 2 => match (&ys[0], &ys[1]) { + (Calcit::Syntax(sym, _ns), Calcit::List(zs)) if sym == "&let" && zs.len() == 2 => match &zs[0] { + Calcit::Symbol(s2, ..) if !scoped_defs.contains(s2) => { + let_def_body = ys.skip(1); + continue; + } + _ => (), + }, _ => (), }, _ => (), @@ -632,7 +642,7 @@ fn gen_let_code( break; } } - _ => return Err(format!("Expected symbol behind let, got: {}", &pair)), + _ => return Err(format!("Expected symbol in &let binding, got: {}", &pair)), } } Calcit::List(_xs) => return Err(format!("expected pair of length 2, got: {}", &pair)), @@ -838,8 +848,6 @@ fn contains_symbol(xs: &Calcit, y: &str) -> bool { } fn sort_by_deps(deps: &HashMap) -> Vec { - let mut result: Vec = vec![]; - let mut deps_graph: HashMap> = HashMap::new(); let mut def_names: Vec = vec![]; for (k, v) in deps { @@ -856,26 +864,40 @@ fn sort_by_deps(deps: &HashMap) -> Vec { } deps_graph.insert(k.to_string(), deps_info); } - // echo depsGraph - def_names.sort(); - for x in def_names { - let mut inserted = false; + // println!("\ndefs graph {:?}", deps_graph); + def_names.sort(); // alphabet order first + + let mut result: Vec = vec![]; + 'outer: for x in def_names { for (idx, y) in result.iter().enumerate() { - if deps_graph.contains_key(y) && deps_graph[y].contains(&x) { + if depends_on(y, &x, &deps_graph, 3) { result.insert(idx, x.clone()); - inserted = true; - break; + continue 'outer; } } - if inserted { - continue; - } result.push(x.clone()); } + // println!("\ndef names {:?}", def_names); result } +// could be slow, need real topology sorting +fn depends_on(x: &str, y: &str, deps: &HashMap>, decay: usize) -> bool { + if decay == 0 { + false + } else { + for item in &deps[x] { + if item == y || depends_on(&item, y, &deps, decay - 1) { + return true; + } else { + // nothing + } + } + false + } +} + fn write_file_if_changed(filename: &Path, content: &str) -> bool { if filename.exists() && fs::read_to_string(filename).unwrap() == content { return false; @@ -972,17 +994,27 @@ pub fn emit_js(entry_ns: &str, emit_path: &str) -> Result<(), String> { escape_var(&def) )); } - Calcit::Fn(_name, _def_ns, _, _, args, code) => { + Calcit::Fn(name, def_ns, _, _, args, code) => { + call_stack::push_call_stack(def_ns, name, StackKind::Codegen, &Some(f.to_owned()), &im::vector![]); defs_code.push_str(&gen_js_func(&def, args, code, &ns, true, &def_names, &file_imports)?); + call_stack::pop_call_stack(); } Calcit::Thunk(code) => { // TODO need topological sorting for accuracy // values are called directly, put them after fns + call_stack::push_call_stack( + &ns, + &def, + StackKind::Codegen, + &Some((**code).to_owned()), + &im::vector![], + ); vals_code.push_str(&format!( "\nexport var {} = {};\n", escape_var(&def), to_js_code(code, &ns, &def_names, &file_imports)? )); + call_stack::pop_call_stack() } Calcit::Macro(..) => { // macro should be handled during compilation, psuedo code @@ -1017,7 +1049,11 @@ pub fn emit_js(entry_ns: &str, emit_path: &str) -> Result<(), String> { )); } ImportedTarget::FromNs(target_ns) => { - let import_target = to_js_import_name(&target_ns, false); // TODO js_mode + let import_target = if is_cirru_string(&target_ns) { + wrap_js_str(&target_ns[1..]) + } else { + to_js_import_name(&target_ns, false) // TODO js_mode + }; import_code.push_str(&format!( "\nimport {{ {} }} from {};\n", escape_var(&def), diff --git a/src/data/cirru.rs b/src/data/cirru.rs index 512abe90..b5fccd4e 100644 --- a/src/data/cirru.rs +++ b/src/data/cirru.rs @@ -17,7 +17,7 @@ pub fn code_to_calcit(xs: &Cirru, ns: &str) -> Result { _ => match s.chars().next().unwrap() { ':' => Ok(Calcit::Keyword(String::from(&s[1..]))), '"' | '|' => Ok(Calcit::Str(String::from(&s[1..]))), - '0' if s.starts_with("0x") => match u8::from_str_radix(&s[2..], 16) { + '0' if s.starts_with("0x") => match u32::from_str_radix(&s[2..], 16) { Ok(n) => Ok(Calcit::Number(n as f64)), Err(e) => Err(format!("failed to parse hex: {} => {:?}", s, e)), }, @@ -139,6 +139,8 @@ pub fn calcit_to_cirru(x: &Calcit) -> Cirru { } Cirru::List(ys) } + Calcit::Proc(s) => Cirru::Leaf(s.to_owned()), + Calcit::Syntax(s, _ns) => Cirru::Leaf(s.to_owned()), a => Cirru::List(vec![Cirru::Leaf(String::from("TODO")), Cirru::Leaf(a.to_string())]), } } diff --git a/src/data/edn.rs b/src/data/edn.rs index 0f407286..557b173b 100644 --- a/src/data/edn.rs +++ b/src/data/edn.rs @@ -148,7 +148,8 @@ pub fn calcit_to_edn(x: &Calcit) -> Edn { Edn::Record(name.clone(), fields.clone(), ys) } Calcit::Fn(name, ..) => Edn::Str(format!("&fn {}", name)), - Calcit::Proc(name) => Edn::Str(format!("&proc {}", name)), + Calcit::Proc(name) => Edn::Symbol(name.to_owned()), + Calcit::Syntax(name, _ns) => Edn::Symbol(name.to_owned()), a => Edn::Str(format!("TODO {}", a)), // TODO more types to handle } } diff --git a/src/main.rs b/src/main.rs index adb4db67..0b63c494 100644 --- a/src/main.rs +++ b/src/main.rs @@ -258,7 +258,15 @@ fn run_codegen( return Err(failure); } } - emit_js(&init_ns, &emit_path)?; // TODO entry ns + // TODO entry ns + match emit_js(&init_ns, &emit_path) { + Ok(_) => (), + Err(failure) => { + println!("\nfailed codegen, {}", failure); + call_stack::display_stack(&failure); + return Err(failure); + } + } let duration = Instant::now().duration_since(started_time); println!("took {}ms", duration.as_micros() as f64 / 1000.0); Ok(())