Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

[package]
name = "calcit"
version = "0.9.18"
version = "0.9.20"
authors = ["jiyinyiyong <[email protected]>"]
edition = "2024"
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@calcit/procs",
"version": "0.9.18",
"version": "0.9.20",
"main": "./lib/calcit.procs.mjs",
"devDependencies": {
"@types/node": "^24.1.0",
Expand Down
153 changes: 147 additions & 6 deletions src/bin/cr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use calcit::{
ProgramEntries, builtins, call_stack, cli_args, codegen, codegen::COMPILE_ERRORS_FILE, codegen::emit_js::gen_stack, program, runner,
snapshot, util,
};
use cirru_parser::Cirru;

fn main() -> Result<(), String> {
builtins::effects::init_effects_states();
Expand Down Expand Up @@ -162,10 +163,13 @@ fn main() -> Result<(), String> {
eval_once = true;
}
run_codegen(&entries, &cli_args.emit_path, true)
} else if let Some(CalcitCommand::CheckExamples(check_options)) = &cli_args.subcommand {
eval_once = true;
run_check_examples(&check_options.ns, &snapshot)
} else {
let started_time = Instant::now();

let v = calcit::run_program(entries.init_ns.to_owned(), entries.init_def.to_owned(), &[]).map_err(|e| {
let v = calcit::run_program_with_docs(entries.init_ns.to_owned(), entries.init_def.to_owned(), &[]).map_err(|e| {
LocatedWarning::print_list(&e.warnings);
e.msg
})?;
Expand Down Expand Up @@ -269,6 +273,9 @@ fn recall_program(content: &str, entries: &ProgramEntries, settings: &ToplevelCa
builtins::meta::force_reset_gensym_index()?;
println!("cleared evaled states and reset gensym index.");

// Create a minimal snapshot for documentation lookup during incremental updates
// In practice, this could be enhanced to maintain documentation state

let task = if let Some(CalcitCommand::EmitJs(_)) = settings.subcommand {
run_codegen(entries, &settings.emit_path, false)
} else if let Some(CalcitCommand::EmitIr(_)) = settings.subcommand {
Expand All @@ -290,7 +297,7 @@ fn recall_program(content: &str, entries: &ProgramEntries, settings: &ToplevelCa
let warnings = check_warnings.borrow();
throw_on_warnings(&warnings)?;
}
let v = calcit::run_program(entries.reload_ns.to_owned(), entries.reload_def.to_owned(), &[]).map_err(|e| {
let v = calcit::run_program_with_docs(entries.reload_ns.to_owned(), entries.reload_def.to_owned(), &[]).map_err(|e| {
LocatedWarning::print_list(&e.warnings);
e.msg
})?;
Expand Down Expand Up @@ -334,7 +341,7 @@ fn run_codegen(entries: &ProgramEntries, emit_path: &str, ir_mode: bool) -> Resu
Ok(_) => (),
Err(failure) => {
eprintln!("\nfailed preprocessing, {failure}");
call_stack::display_stack(&failure.msg, &failure.stack, failure.location.as_ref())?;
call_stack::display_stack_with_docs(&failure.msg, &failure.stack, failure.location.as_ref())?;

let _ = fs::write(
&js_file_path,
Expand All @@ -352,7 +359,7 @@ fn run_codegen(entries: &ProgramEntries, emit_path: &str, ir_mode: bool) -> Resu
Ok(_) => (),
Err(failure) => {
eprintln!("\nfailed preprocessing, {failure}");
call_stack::display_stack(&failure.msg, &failure.stack, failure.location.as_ref())?;
call_stack::display_stack_with_docs(&failure.msg, &failure.stack, failure.location.as_ref())?;
return Err(failure.msg);
}
}
Expand All @@ -371,7 +378,7 @@ fn run_codegen(entries: &ProgramEntries, emit_path: &str, ir_mode: bool) -> Resu
Ok(_) => (),
Err(failure) => {
eprintln!("\nfailed codegen, {failure}");
call_stack::display_stack(&failure, &gen_stack::get_gen_stack(), None)?;
call_stack::display_stack_with_docs(&failure, &gen_stack::get_gen_stack(), None)?;
return Err(failure);
}
}
Expand All @@ -381,7 +388,7 @@ fn run_codegen(entries: &ProgramEntries, emit_path: &str, ir_mode: bool) -> Resu
Ok(_) => (),
Err(failure) => {
eprintln!("\nfailed codegen, {failure}");
call_stack::display_stack(&failure, &gen_stack::get_gen_stack(), None)?;
call_stack::display_stack_with_docs(&failure, &gen_stack::get_gen_stack(), None)?;
return Err(failure);
}
}
Expand Down Expand Up @@ -423,3 +430,137 @@ fn throw_on_warnings(warnings: &[LocatedWarning]) -> Result<(), String> {
Ok(())
}
}

fn run_check_examples(target_ns: &str, snapshot: &snapshot::Snapshot) -> Result<(), String> {
println!("Checking examples in namespace: {target_ns}");

// Find the target namespace
let file_data = snapshot
.files
.get(target_ns)
.ok_or_else(|| format!("Namespace '{target_ns}' not found"))?;

// Collect all functions with examples
let mut functions_with_examples = Vec::new();
let mut functions_without_examples = Vec::new();
let mut total_examples = 0;

for (def_name, code_entry) in &file_data.defs {
if !code_entry.examples.is_empty() {
functions_with_examples.push((def_name.clone(), code_entry.examples.len()));
total_examples += code_entry.examples.len();
} else {
functions_without_examples.push(def_name.clone());
}
}

if functions_with_examples.is_empty() {
println!("No functions with examples found in namespace '{target_ns}'");
return Ok(());
}

// Create a synthetic function that runs all examples
let mut example_calls = Vec::new();

for code_entry in file_data.defs.values() {
for example in &code_entry.examples {
example_calls.push(example.clone());
}
}

// Create the check function as a function definition
let check_function_code = if example_calls.is_empty() {
Cirru::List(vec![
Cirru::Leaf(Arc::from("defn")),
Cirru::Leaf(Arc::from("&calcit:check-examples")),
Cirru::List(vec![]), // empty parameter list
Cirru::Leaf(Arc::from("nil")),
])
} else {
let mut fn_body = vec![Cirru::Leaf(Arc::from("do"))];
fn_body.extend(example_calls);

Cirru::List(vec![
Cirru::Leaf(Arc::from("defn")),
Cirru::Leaf(Arc::from("&calcit:check-examples")),
Cirru::List(vec![]), // empty parameter list
Cirru::List(fn_body),
])
};

// Create a temporary snapshot with the check function
let mut temp_snapshot = snapshot.clone();
let check_fn_name = "&calcit:check-examples".to_string();

if let Some(file_data) = temp_snapshot.files.get_mut(target_ns) {
file_data.defs.insert(
check_fn_name.clone(),
snapshot::CodeEntry {
doc: "Generated function to check all examples in this namespace".to_string(),
examples: Vec::new(),
code: check_function_code,
},
);
}

// Update program data
{
let mut prgm = { program::PROGRAM_CODE_DATA.write().expect("open program data") };
*prgm = program::extract_program_data(&temp_snapshot)?;
}

// Run the check function
let started_time = Instant::now();
println!("Running {total_examples} examples...");

let result = calcit::run_program_with_docs(Arc::from(target_ns), Arc::from(check_fn_name.as_str()), &[]);

let duration = Instant::now().duration_since(started_time);

match result {
Ok(value) => {
println!("{}{}", format!("took {}ms: ", duration.as_micros() as f64 / 1000.0).dimmed(), value);

// Print summary
println!("\n{}", "=== Examples Check Summary ===".bold());
println!("Namespace: {}", target_ns.cyan());
println!("Functions with examples: {}", functions_with_examples.len().to_string().green());
println!("Total examples run: {}", total_examples.to_string().green());
println!(
"Functions without examples: {}",
functions_without_examples.len().to_string().yellow()
);

if !functions_with_examples.is_empty() {
println!("\n{}", "Functions with examples:".bold());
for (name, count) in &functions_with_examples {
println!(" {} ({} examples)", name.green(), count.to_string().cyan());
}
}

if !functions_without_examples.is_empty() {
println!("\n{}", "Functions without examples:".bold());
let display_count = std::cmp::min(functions_without_examples.len(), 32);
let names_to_show: Vec<String> = functions_without_examples
.iter()
.take(display_count)
.map(|name| name.yellow().to_string())
.collect();

let display_text = if functions_without_examples.len() > 32 {
format!(" {} ...", names_to_show.join(" "))
} else {
format!(" {}", names_to_show.join(" "))
};

println!("{display_text}");
}

Ok(())
}
Err(e) => {
LocatedWarning::print_list(&e.warnings);
Err(format!("Failed to run examples: {}", e.msg))
}
}
}
23 changes: 21 additions & 2 deletions src/call_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ pub fn show_stack(stack: &CallStackList) {
}

pub fn display_stack(failure: &str, stack: &CallStackList, location: Option<&Arc<NodeLocation>>) -> Result<(), String> {
display_stack_with_docs(failure, stack, location)
}

pub fn display_stack_with_docs(failure: &str, stack: &CallStackList, location: Option<&Arc<NodeLocation>>) -> Result<(), String> {
eprintln!("\nFailure: {failure}");
eprintln!("\ncall stack:");

Expand All @@ -119,13 +123,28 @@ pub fn display_stack(failure: &str, stack: &CallStackList, location: Option<&Arc
for v in s.args.iter() {
args.push(edn::calcit_to_edn(v)?);
}
let info = Edn::map_from_iter([
let mut info_map = vec![
(Edn::tag("def"), format!("{}/{}", s.ns, s.def).into()),
(Edn::tag("code"), cirru::calcit_to_cirru(&s.code)?.into()),
(Edn::tag("args"), args.into()),
(Edn::tag("kind"), Edn::tag(s.kind.to_string())),
]);
];

// Add documentation if available from program data
if let Some(doc) = crate::program::lookup_def_doc(&s.ns, &s.def) {
info_map.push((Edn::tag("doc"), doc.into()));
}

// Add examples if available from program data
if let Some(examples) = crate::program::lookup_def_examples(&s.ns, &s.def) {
let mut examples_list = EdnListView::default();
for example in examples {
examples_list.push(example.into());
}
info_map.push((Edn::tag("examples"), examples_list.into()));
}

let info = Edn::map_from_iter(info_map);
stack_list.push(info);
}

Expand Down
Loading