Refactor Examples to use assert_eq
This commit updates all example files to use `assert_eq!` for verifying output, replacing the previous `Output:` comments. This change makes the examples more robust and self-testing. The benchmark results in some examples have also been slightly adjusted, reflecting minor performance variations after the refactoring. Additionally, a runtime panic handling mechanism has been introduced in the VM for function calls, which improves error reporting for unexpected panics.
This commit is contained in:
+109
-1
@@ -1,5 +1,5 @@
|
||||
use crate::ast::environment::Environment;
|
||||
use crate::ast::types::{Purity, Signature, StaticType, Value};
|
||||
use crate::ast::types::{Keyword, Purity, Signature, StaticType, Value};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn register(env: &Environment) {
|
||||
@@ -8,6 +8,7 @@ pub fn register(env: &Environment) {
|
||||
register_comparison(env);
|
||||
register_logic(env);
|
||||
register_io(env);
|
||||
register_testing(env);
|
||||
}
|
||||
|
||||
fn register_constants(env: &Environment) {
|
||||
@@ -523,3 +524,110 @@ fn register_io(env: &Environment) {
|
||||
}).doc("Prints all arguments to stdout followed by a newline. Returns void.")
|
||||
.examples(&["(print \"Hello World\")", "(print x y z)"]);
|
||||
}
|
||||
|
||||
/// Structural equality that compares record values by field content,
|
||||
/// not by Arc pointer identity. Required because the compiler may fold
|
||||
/// record literals at compile-time, yielding a different Arc than one
|
||||
/// created at runtime via make_record.
|
||||
fn values_equal(a: &Value, b: &Value) -> bool {
|
||||
match (a, b) {
|
||||
(Value::Record(la, va), Value::Record(lb, vb)) => {
|
||||
la.fields.len() == lb.fields.len()
|
||||
&& la
|
||||
.fields
|
||||
.iter()
|
||||
.zip(lb.fields.iter())
|
||||
.all(|((ka, _), (kb, _))| ka == kb)
|
||||
&& va
|
||||
.iter()
|
||||
.zip(vb.iter())
|
||||
.all(|(x, y)| values_equal(x, y))
|
||||
}
|
||||
(Value::Tuple(a), Value::Tuple(b)) => {
|
||||
a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
|
||||
}
|
||||
_ => a == b,
|
||||
}
|
||||
}
|
||||
|
||||
fn register_testing(env: &Environment) {
|
||||
let any_ty = StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Any,
|
||||
ret: StaticType::Void,
|
||||
}));
|
||||
|
||||
// (assert cond) / (assert cond "msg")
|
||||
env.register_native_fn("assert", any_ty.clone(), Purity::Impure, |args| {
|
||||
let cond = match args.first() {
|
||||
Some(Value::Bool(b)) => *b,
|
||||
Some(other) => panic!("assert expects a bool, got {}", other),
|
||||
None => panic!("assert requires at least 1 argument"),
|
||||
};
|
||||
if !cond {
|
||||
match args.get(1) {
|
||||
Some(Value::Text(msg)) => panic!("assertion failed: {}", msg),
|
||||
_ => panic!("assertion failed"),
|
||||
}
|
||||
}
|
||||
Value::Void
|
||||
})
|
||||
.doc("Asserts that a condition is true. Panics with an optional message on failure.")
|
||||
.examples(&["(assert (= 1 1))", "(assert (> x 0) \"x must be positive\")"]);
|
||||
|
||||
// (assert-eq expected actual) / (assert-eq expected actual "msg")
|
||||
env.register_native_fn("assert-eq", any_ty.clone(), Purity::Impure, |args| {
|
||||
if args.len() < 2 {
|
||||
panic!("assert-eq requires at least 2 arguments");
|
||||
}
|
||||
let expected = &args[0];
|
||||
let actual = &args[1];
|
||||
if !values_equal(expected, actual) {
|
||||
match args.get(2) {
|
||||
Some(Value::Text(msg)) => panic!(
|
||||
"assertion failed: {}\n expected: {}\n got: {}",
|
||||
msg, expected, actual
|
||||
),
|
||||
_ => panic!(
|
||||
"assertion failed: expected {}, got {}",
|
||||
expected, actual
|
||||
),
|
||||
}
|
||||
}
|
||||
Value::Void
|
||||
})
|
||||
.doc("Asserts that expected == actual. Panics with a diff message on failure.")
|
||||
.examples(&["(assert-eq 42 (factorial 5 1))", "(assert-eq :ok (status) \"wrong status\")"]);
|
||||
|
||||
// (type-of x) -> keyword
|
||||
let type_of_ty = StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Any]),
|
||||
ret: StaticType::Keyword,
|
||||
}));
|
||||
env.register_native_fn("type-of", type_of_ty, Purity::Pure, |args| {
|
||||
let name = match args.first() {
|
||||
Some(v) => match v.static_type() {
|
||||
StaticType::Void => "void",
|
||||
StaticType::Bool => "bool",
|
||||
StaticType::Int => "int",
|
||||
StaticType::Float => "float",
|
||||
StaticType::DateTime => "datetime",
|
||||
StaticType::Text => "text",
|
||||
StaticType::Keyword => "keyword",
|
||||
StaticType::Tuple(_)
|
||||
| StaticType::Vector(_, _)
|
||||
| StaticType::Matrix(_, _) => "tuple",
|
||||
StaticType::Record(_) => "record",
|
||||
StaticType::Series(_) => "series",
|
||||
StaticType::Stream(_) => "stream",
|
||||
StaticType::Function(_) | StaticType::FunctionOverloads(_) => "function",
|
||||
StaticType::FieldAccessor(_) => "field-accessor",
|
||||
StaticType::Object(name) => name,
|
||||
_ => "unknown",
|
||||
},
|
||||
None => panic!("type-of requires 1 argument"),
|
||||
};
|
||||
Value::Keyword(Keyword::intern(name))
|
||||
})
|
||||
.doc("Returns the type of a value as a keyword (e.g. :int, :float, :stream).")
|
||||
.examples(&["(type-of 42)", "(type-of my-stream)"]);
|
||||
}
|
||||
|
||||
+24
-2
@@ -441,7 +441,19 @@ impl VM {
|
||||
self.tail_call = Some((func_val, arg_vals));
|
||||
return Ok(Value::Void);
|
||||
}
|
||||
Value::Function(f) => return Ok((f.func)(&arg_vals)),
|
||||
Value::Function(f) => {
|
||||
return std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
(f.func)(&arg_vals)
|
||||
}))
|
||||
.map_err(|e| {
|
||||
e.downcast_ref::<String>()
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
e.downcast_ref::<&str>().map(|s| s.to_string())
|
||||
})
|
||||
.unwrap_or_else(|| "runtime panic".to_string())
|
||||
});
|
||||
}
|
||||
Value::FieldAccessor(k) => {
|
||||
if arg_vals.len() != 1 {
|
||||
return Err(format!(
|
||||
@@ -510,7 +522,17 @@ impl VM {
|
||||
loop {
|
||||
let result = match ¤t_func {
|
||||
Value::Function(f) => {
|
||||
let res = (f.func)(&self.stack[base..]);
|
||||
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
(f.func)(&self.stack[base..])
|
||||
}))
|
||||
.map_err(|e| {
|
||||
e.downcast_ref::<String>()
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
e.downcast_ref::<&str>().map(|s| s.to_string())
|
||||
})
|
||||
.unwrap_or_else(|| "runtime panic".to_string())
|
||||
})?;
|
||||
self.stack.truncate(base);
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user