v2 acord core
This commit is contained in:
parent
cccb0378cb
commit
853a9fd0cc
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "acord-core"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ fn value_to_tree_json(val: &interp::Value) -> String {
|
|||
|
||||
pub fn evaluate_line(text: &str) -> Result<String, String> {
|
||||
let mut interp = interp::Interpreter::new();
|
||||
match interp.eval_expr_str(text) {
|
||||
match interp.eval(text) {
|
||||
Ok(v) => Ok(v.display()),
|
||||
Err(_) => {
|
||||
// cord-expr/cord-trig fallback path
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ impl Interpreter {
|
|||
return self.call_solved_fn(name, args, depth);
|
||||
}
|
||||
|
||||
// hooks override hardcoded builtins.
|
||||
let hooks_snapshot: Vec<Rc<dyn InterpreterHook>> = self.hooks.hooks.clone();
|
||||
for hook in &hooks_snapshot {
|
||||
if let Some(result) = hook.try_call(self, name, args, depth) {
|
||||
|
|
|
|||
|
|
@ -154,7 +154,20 @@ impl Interpreter {
|
|||
self.return_slot = Some(val);
|
||||
Err("\x00return".to_string())
|
||||
}
|
||||
Stmt::Use(_, _) => {
|
||||
Stmt::Use(module, item) => {
|
||||
if let Some(name) = item.as_deref().filter(|s| *s != "*") {
|
||||
let has_sub = self.module_paths.borrow().has_sub(module, name);
|
||||
if has_sub {
|
||||
let sub = [module.as_str(), name];
|
||||
self.load_module(&sub, None)?;
|
||||
return Ok(Value::Void);
|
||||
}
|
||||
}
|
||||
let top = [module.as_str()];
|
||||
if self.resolve_module_path(&top).is_some() {
|
||||
let filter = item.as_deref().filter(|s| *s != "*");
|
||||
self.load_module(&top, filter)?;
|
||||
}
|
||||
Ok(Value::Void)
|
||||
}
|
||||
Stmt::CellAssign { block, table, cell, value } => {
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ impl Interpreter {
|
|||
Ok(Value::Array(out_rows))
|
||||
}
|
||||
|
||||
pub fn exec_line(&mut self, line: &str) -> Result<Option<Value>, String> {
|
||||
pub fn exec(&mut self, line: &str) -> Result<Option<Value>, String> {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Ok(None);
|
||||
|
|
@ -326,7 +326,7 @@ impl Interpreter {
|
|||
self.eval_expr(&f.ast, 0)
|
||||
}
|
||||
|
||||
pub fn eval_expr_str(&mut self, expr: &str) -> Result<Value, String> {
|
||||
pub fn eval(&mut self, expr: &str) -> Result<Value, String> {
|
||||
let trimmed = expr.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Err("empty expression".into());
|
||||
|
|
@ -430,7 +430,7 @@ pub fn interpret_document_with(interp: &mut Interpreter, lines: &[(usize, &str,
|
|||
let block_text = block_acc.join("\n");
|
||||
block_acc.clear();
|
||||
brace_depth = 0;
|
||||
match interp.exec_line(&block_text) {
|
||||
match interp.exec(&block_text) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
results.push(InterpResult { line: idx, value: Some(Value::Error(e)), format: EvalFormat::Inline });
|
||||
|
|
@ -451,7 +451,7 @@ pub fn interpret_document_with(interp: &mut Interpreter, lines: &[(usize, &str,
|
|||
results.push(InterpResult { line: idx, value: Some(Value::Error("empty expression".into())), format });
|
||||
continue;
|
||||
}
|
||||
match interp.eval_expr_str(expr) {
|
||||
match interp.eval(expr) {
|
||||
Ok(val) => results.push(InterpResult { line: idx, value: Some(val), format }),
|
||||
Err(e) => results.push(InterpResult { line: idx, value: Some(Value::Error(e)), format }),
|
||||
}
|
||||
|
|
@ -467,7 +467,7 @@ pub fn interpret_document_with(interp: &mut Interpreter, lines: &[(usize, &str,
|
|||
let block_text = block_acc.join("\n");
|
||||
block_acc.clear();
|
||||
brace_depth = 0;
|
||||
if let Err(e) = interp.exec_line(&block_text) {
|
||||
if let Err(e) = interp.exec(&block_text) {
|
||||
results.push(InterpResult { line: idx, value: Some(Value::Error(e)), format: EvalFormat::Inline });
|
||||
}
|
||||
}
|
||||
|
|
@ -475,7 +475,7 @@ pub fn interpret_document_with(interp: &mut Interpreter, lines: &[(usize, &str,
|
|||
block_acc.push(trimmed.to_string());
|
||||
brace_depth = opens - closes;
|
||||
} else {
|
||||
if let Err(e) = interp.exec_line(trimmed) {
|
||||
if let Err(e) = interp.exec(trimmed) {
|
||||
results.push(InterpResult { line: idx, value: Some(Value::Error(e)), format: EvalFormat::Inline });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@ impl ModuleRegistry {
|
|||
Some(root)
|
||||
}
|
||||
|
||||
pub fn has_sub(&self, parent: &str, name: &str) -> bool {
|
||||
self.sub.contains_key(&(parent.to_string(), name.to_string()))
|
||||
}
|
||||
|
||||
pub fn list_top(&self) -> Vec<(String, PathBuf)> {
|
||||
let mut out: Vec<(String, PathBuf)> = self.top.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone())).collect();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::interp::ast::Stmt;
|
||||
use crate::interp::parse::Parser;
|
||||
|
|
@ -11,6 +12,8 @@ pub struct ModuleExports {
|
|||
pub vars: HashMap<String, Value>,
|
||||
pub fns: HashMap<String, FnDef>,
|
||||
pub solved_fns: HashMap<String, SolvedFnDef>,
|
||||
pub methods: HashMap<(String, String), FnDef>,
|
||||
pub traits: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
|
@ -25,6 +28,8 @@ impl Interpreter {
|
|||
vars: self.vars.clone(),
|
||||
fns: self.fns.clone(),
|
||||
solved_fns: self.solved_fns.clone(),
|
||||
methods: self.methods.clone(),
|
||||
traits: self.traits.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,6 +44,12 @@ impl Interpreter {
|
|||
for (name, def) in &exports.solved_fns {
|
||||
self.solved_fns.insert(name.clone(), def.clone());
|
||||
}
|
||||
for (key, fndef) in &exports.methods {
|
||||
self.methods.insert(key.clone(), fndef.clone());
|
||||
}
|
||||
for (name, reqs) in &exports.traits {
|
||||
self.traits.insert(name.clone(), reqs.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn import_item(&mut self, exports: &ModuleExports, item: &str) -> bool {
|
||||
|
|
@ -55,6 +66,16 @@ impl Interpreter {
|
|||
self.solved_fns.insert(item.to_string(), def.clone());
|
||||
found = true;
|
||||
}
|
||||
for (key, fndef) in &exports.methods {
|
||||
if key.0 == item {
|
||||
self.methods.insert(key.clone(), fndef.clone());
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if let Some(reqs) = exports.traits.get(item) {
|
||||
self.traits.insert(item.to_string(), reqs.clone());
|
||||
found = true;
|
||||
}
|
||||
found
|
||||
}
|
||||
}
|
||||
|
|
@ -74,3 +95,76 @@ pub fn extract_use_declarations(text: &str) -> Vec<UseDecl> {
|
|||
}
|
||||
decls
|
||||
}
|
||||
|
||||
impl Interpreter {
|
||||
/// loads a module from disk via the path registry.
|
||||
pub fn load_module(&mut self, segments: &[&str], item: Option<&str>) -> Result<(), String> {
|
||||
let path = self.resolve_module_path(segments)
|
||||
.ok_or_else(|| format!("module '{}' not in path registry", segments.join("::")))?;
|
||||
let source = read_module_source(&path)
|
||||
.map_err(|e| format!("loading '{}': {}", path.display(), e))?;
|
||||
let exports = self.exec_in_subinterp(&source)?;
|
||||
match item {
|
||||
None | Some("*") => self.import_all(&exports),
|
||||
Some(name) => {
|
||||
if !self.import_item(&exports, name) {
|
||||
return Err(format!("'{}' not exported by '{}'", name, segments.join("::")));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_in_subinterp(&self, source: &str) -> Result<ModuleExports, String> {
|
||||
let mut sub = Interpreter::new();
|
||||
sub.module_paths = self.module_paths.clone();
|
||||
sub.hooks.hooks = self.hooks.hooks.clone();
|
||||
sub.shared = std::cell::RefCell::new(self.shared.borrow().clone());
|
||||
sub.custom_builtins = self.custom_builtins.clone();
|
||||
sub.exec(source)?;
|
||||
Ok(sub.exports())
|
||||
}
|
||||
}
|
||||
|
||||
/// reads module source from a resolved registry path.
|
||||
fn read_module_source(path: &PathBuf) -> std::io::Result<String> {
|
||||
// direct .cord file
|
||||
if path.is_file() {
|
||||
return std::fs::read_to_string(path);
|
||||
}
|
||||
// resolved-path.cord sibling
|
||||
let cord_sibling = path.with_extension("cord");
|
||||
if cord_sibling.is_file() {
|
||||
return std::fs::read_to_string(&cord_sibling);
|
||||
}
|
||||
// dir/mod.cord
|
||||
if path.is_dir() {
|
||||
let mod_file = path.join("mod.cord");
|
||||
if mod_file.is_file() {
|
||||
return std::fs::read_to_string(&mod_file);
|
||||
}
|
||||
// dir/*.cord concatenated in sorted order
|
||||
let mut entries: Vec<PathBuf> = std::fs::read_dir(path)?
|
||||
.filter_map(|e| e.ok())
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|e| e.to_str()) == Some("cord"))
|
||||
.collect();
|
||||
entries.sort();
|
||||
if entries.is_empty() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("no .cord files under {}", path.display()),
|
||||
));
|
||||
}
|
||||
let mut combined = String::new();
|
||||
for e in entries {
|
||||
combined.push_str(&std::fs::read_to_string(&e)?);
|
||||
combined.push('\n');
|
||||
}
|
||||
return Ok(combined);
|
||||
}
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("no module source at {}", path.display()),
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,12 +158,11 @@ impl Parser {
|
|||
self.advance();
|
||||
if let Token::Ident(method) = self.advance() {
|
||||
let args = self.parse_arg_list()?;
|
||||
return Ok(Expr::StaticCall(type_name, method, args));
|
||||
expr = Expr::StaticCall(type_name, method, args);
|
||||
} else {
|
||||
return Err("expected method name after '::'".into());
|
||||
}
|
||||
}
|
||||
if self.peek() == &Token::LParen {
|
||||
} else if self.peek() == &Token::LParen {
|
||||
self.advance();
|
||||
let mut args = Vec::new();
|
||||
if self.peek() != &Token::RParen {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ use super::Parser;
|
|||
impl Parser {
|
||||
pub fn parse_stmt(&mut self) -> Result<Stmt, String> {
|
||||
self.skip_newlines();
|
||||
// hooks claim statement shapes first.
|
||||
for hook_idx in 0..self.hooks.len() {
|
||||
let hook = self.hooks[hook_idx].clone();
|
||||
let saved = self.pos;
|
||||
|
|
@ -326,8 +325,7 @@ impl Parser {
|
|||
Token::Ident(m) => methods.push(m),
|
||||
other => return Err(format!("expected method name in trait, got {:?}", other)),
|
||||
}
|
||||
// skip signature: consume until newline or {
|
||||
while !matches!(self.peek(), Token::Newline | Token::LBrace | Token::Eof) {
|
||||
while !matches!(self.peek(), Token::Newline | Token::LBrace | Token::RBrace | Token::Eof) {
|
||||
self.advance();
|
||||
}
|
||||
// skip body if present
|
||||
|
|
|
|||
|
|
@ -87,30 +87,30 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn implicit_mul_number_times_ident() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("2pi").unwrap();
|
||||
let v = i.eval("2pi").unwrap();
|
||||
match v { Value::Number(n) => assert!(approx(n, 2.0 * std::f64::consts::PI)), _ => panic!() }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implicit_mul_number_times_paren() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("2(3 + 4)").unwrap();
|
||||
let v = i.eval("2(3 + 4)").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 14.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implicit_mul_with_user_var() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let n = 2").unwrap();
|
||||
let v = i.eval_expr_str("2n").unwrap();
|
||||
i.exec("let n = 2").unwrap();
|
||||
let v = i.eval("2n").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implicit_mul_only_fires_adjacent() {
|
||||
let mut i = Interpreter::new();
|
||||
let v_adj = i.eval_expr_str("2pi").unwrap();
|
||||
let v_space = i.eval_expr_str("2 pi").unwrap();
|
||||
let v_adj = i.eval("2pi").unwrap();
|
||||
let v_space = i.eval("2 pi").unwrap();
|
||||
match (v_adj, v_space) {
|
||||
(Value::Number(a), Value::Number(b)) => {
|
||||
assert!(approx(a, 2.0 * std::f64::consts::PI));
|
||||
|
|
@ -123,28 +123,28 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn scientific_notation_lowercase() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("1e-9").unwrap();
|
||||
let v = i.eval("1e-9").unwrap();
|
||||
match v { Value::Number(n) => assert!(approx(n, 1e-9)), _ => panic!() }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scientific_notation_uppercase_and_plus() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("2E+3").unwrap();
|
||||
let v = i.eval("2E+3").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 2000.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scientific_notation_negative_literal() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("-1e3").unwrap();
|
||||
let v = i.eval("-1e3").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == -1000.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_str_plus_array_stringifies() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str(r#""Diff: " + [1, 2]"#).unwrap();
|
||||
let v = i.eval(r#""Diff: " + [1, 2]"#).unwrap();
|
||||
match v {
|
||||
Value::Str(s) => assert_eq!(s, "Diff: [1, 2]"),
|
||||
_ => panic!("expected Str, got {:?}", v),
|
||||
|
|
@ -154,7 +154,7 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn add_array_plus_str_stringifies() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str(r#"[1, 2] + " items""#).unwrap();
|
||||
let v = i.eval(r#"[1, 2] + " items""#).unwrap();
|
||||
match v {
|
||||
Value::Str(s) => assert_eq!(s, "[1, 2] items"),
|
||||
_ => panic!(),
|
||||
|
|
@ -164,7 +164,7 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn add_array_plus_array_concats() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("[1, 2] + [3, 4]").unwrap();
|
||||
let v = i.eval("[1, 2] + [3, 4]").unwrap();
|
||||
match v {
|
||||
Value::Array(a) => assert_eq!(a.len(), 4),
|
||||
_ => panic!(),
|
||||
|
|
@ -174,7 +174,7 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn add_array_plus_number_appends() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("[1, 2] + 3").unwrap();
|
||||
let v = i.eval("[1, 2] + 3").unwrap();
|
||||
match v {
|
||||
Value::Array(a) => assert_eq!(a.len(), 3),
|
||||
_ => panic!(),
|
||||
|
|
@ -184,7 +184,7 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn add_chains_str_array_str() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str(r#""Diff: " + [10, 20] + " + Sum: " + [30, 40]"#).unwrap();
|
||||
let v = i.eval(r#""Diff: " + [10, 20] + " + Sum: " + [30, 40]"#).unwrap();
|
||||
match v {
|
||||
Value::Str(s) => assert_eq!(s, "Diff: [10, 20] + Sum: [30, 40]"),
|
||||
_ => panic!(),
|
||||
|
|
@ -194,7 +194,7 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn float_suffix_literal() {
|
||||
let mut i = Interpreter::new();
|
||||
assert_eq!(i.eval_expr_str("0.1f").unwrap().display(), "0.1");
|
||||
assert_eq!(i.eval_expr_str("1f + 2f").unwrap().display(), "3");
|
||||
assert_eq!(i.eval_expr_str("0.5F * 4F").unwrap().display(), "2");
|
||||
assert_eq!(i.eval("0.1f").unwrap().display(), "0.1");
|
||||
assert_eq!(i.eval("1f + 2f").unwrap().display(), "3");
|
||||
assert_eq!(i.eval("0.5F * 4F").unwrap().display(), "2");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn sum_on_literal_array() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("sum([1, 2, 3, 4])").unwrap();
|
||||
let v = i.eval("sum([1, 2, 3, 4])").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 10.0));
|
||||
}
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ use super::helpers::*;
|
|||
vec!["1".into(), "2".into()],
|
||||
vec!["3".into(), "4".into()],
|
||||
]);
|
||||
let v = i.eval_expr_str("sum(@t:A1:B2)").unwrap();
|
||||
let v = i.eval("sum(@t:A1:B2)").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 10.0));
|
||||
}
|
||||
|
||||
|
|
@ -28,29 +28,29 @@ use super::helpers::*;
|
|||
vec!["label".into(), "3".into()],
|
||||
vec!["10".into(), "hello".into()],
|
||||
]);
|
||||
let v = i.eval_expr_str("sum(@t)").unwrap();
|
||||
let v = i.eval("sum(@t)").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 13.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn avg_basic() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("avg([2, 4, 6])").unwrap();
|
||||
let v = i.eval("avg([2, 4, 6])").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn avg_on_empty_errors() {
|
||||
let mut i = Interpreter::new();
|
||||
assert!(i.eval_expr_str("avg([])").is_err());
|
||||
assert!(i.eval("avg([])").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn min_and_max() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("min([5, 2, 8, 1, 9])").unwrap();
|
||||
let v = i.eval("min([5, 2, 8, 1, 9])").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 1.0));
|
||||
let v = i.eval_expr_str("max([5, 2, 8, 1, 9])").unwrap();
|
||||
let v = i.eval("max([5, 2, 8, 1, 9])").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 9.0));
|
||||
}
|
||||
|
||||
|
|
@ -61,14 +61,14 @@ use super::helpers::*;
|
|||
vec!["1".into(), "2".into(), "hello".into()],
|
||||
vec!["3".into(), "".into(), "4".into()],
|
||||
]);
|
||||
let v = i.eval_expr_str("count(@t)").unwrap();
|
||||
let v = i.eval("count(@t)").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn std_devp_matches_formula() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("std_devp([2, 4, 4, 4, 5, 5, 7, 9])").unwrap();
|
||||
let v = i.eval("std_devp([2, 4, 4, 4, 5, 5, 7, 9])").unwrap();
|
||||
match v {
|
||||
Value::Number(n) => assert!(approx(n, 2.0), "got {}", n),
|
||||
_ => panic!("not a number"),
|
||||
|
|
@ -78,11 +78,11 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn std_devs_differs_from_std_devp() {
|
||||
let mut i = Interpreter::new();
|
||||
let p = match i.eval_expr_str("std_devp([1, 2, 3, 4])").unwrap() {
|
||||
let p = match i.eval("std_devp([1, 2, 3, 4])").unwrap() {
|
||||
Value::Number(n) => n,
|
||||
_ => panic!(),
|
||||
};
|
||||
let s = match i.eval_expr_str("std_devs([1, 2, 3, 4])").unwrap() {
|
||||
let s = match i.eval("std_devs([1, 2, 3, 4])").unwrap() {
|
||||
Value::Number(n) => n,
|
||||
_ => panic!(),
|
||||
};
|
||||
|
|
@ -92,12 +92,12 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn std_devs_needs_two_values() {
|
||||
let mut i = Interpreter::new();
|
||||
assert!(i.eval_expr_str("std_devs([5])").is_err());
|
||||
assert!(i.eval("std_devs([5])").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_rejects_zero_or_many_args() {
|
||||
let mut i = Interpreter::new();
|
||||
assert!(i.eval_expr_str("sum()").is_err());
|
||||
assert!(i.eval_expr_str("avg(1, 2)").is_err());
|
||||
assert!(i.eval("sum()").is_err());
|
||||
assert!(i.eval("avg(1, 2)").is_err());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,145 +6,145 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn map_user_fn_doubles() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn double(x) {\n return x * 2\n}").unwrap();
|
||||
let v = i.eval_expr_str("map([1, 2, 3], double)").unwrap();
|
||||
i.exec("fn double(x) {\n return x * 2\n}").unwrap();
|
||||
let v = i.eval("map([1, 2, 3], double)").unwrap();
|
||||
assert_eq!(v.display(), "[2, 4, 6]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_builtin_passthrough() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("map([-1, -4, 9], abs)").unwrap();
|
||||
let v = i.eval("map([-1, -4, 9], abs)").unwrap();
|
||||
assert_eq!(v.display(), "[1, 4, 9]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_returns_void() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn noop(x) {\n return x\n}").unwrap();
|
||||
let r = i.exec_line("each([1, 2, 3], noop)").unwrap();
|
||||
i.exec("fn noop(x) {\n return x\n}").unwrap();
|
||||
let r = i.exec("each([1, 2, 3], noop)").unwrap();
|
||||
assert!(r.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fold_with_seed() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn add(a, b) {\n return a + b\n}").unwrap();
|
||||
let v = i.eval_expr_str("fold([1, 2, 3, 4], 10, add)").unwrap();
|
||||
i.exec("fn add(a, b) {\n return a + b\n}").unwrap();
|
||||
let v = i.eval("fold([1, 2, 3, 4], 10, add)").unwrap();
|
||||
assert_eq!(v.display(), "20");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fold_empty_returns_seed() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn add(a, b) {\n return a + b\n}").unwrap();
|
||||
let v = i.eval_expr_str("fold([], 42, add)").unwrap();
|
||||
i.exec("fn add(a, b) {\n return a + b\n}").unwrap();
|
||||
let v = i.eval("fold([], 42, add)").unwrap();
|
||||
assert_eq!(v.display(), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reduce_left() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn add(a, b) {\n return a + b\n}").unwrap();
|
||||
let v = i.eval_expr_str("reduce([1, 2, 3, 4], add)").unwrap();
|
||||
i.exec("fn add(a, b) {\n return a + b\n}").unwrap();
|
||||
let v = i.eval("reduce([1, 2, 3, 4], add)").unwrap();
|
||||
assert_eq!(v.display(), "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reduce_empty_errors() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn add(a, b) {\n return a + b\n}").unwrap();
|
||||
assert!(i.eval_expr_str("reduce([], add)").is_err());
|
||||
i.exec("fn add(a, b) {\n return a + b\n}").unwrap();
|
||||
assert!(i.eval("reduce([], add)").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_true_when_every_passes() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
let v = i.eval_expr_str("all([1, 2, 3], pos)").unwrap();
|
||||
i.exec("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
let v = i.eval("all([1, 2, 3], pos)").unwrap();
|
||||
assert_eq!(v.display(), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_false_short_circuits() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
let v = i.eval_expr_str("all([1, -2, 3], pos)").unwrap();
|
||||
i.exec("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
let v = i.eval("all([1, -2, 3], pos)").unwrap();
|
||||
assert_eq!(v.display(), "false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_empty_is_true() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
let v = i.eval_expr_str("all([], pos)").unwrap();
|
||||
i.exec("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
let v = i.eval("all([], pos)").unwrap();
|
||||
assert_eq!(v.display(), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_true_short_circuits() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
let v = i.eval_expr_str("any([-1, -2, 3], pos)").unwrap();
|
||||
i.exec("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
let v = i.eval("any([-1, -2, 3], pos)").unwrap();
|
||||
assert_eq!(v.display(), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_empty_is_false() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
let v = i.eval_expr_str("any([], pos)").unwrap();
|
||||
i.exec("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
let v = i.eval("any([], pos)").unwrap();
|
||||
assert_eq!(v.display(), "false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_keeps_predicate_passes() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
assert_eq!(i.eval_expr_str("filter([-1, 2, -3, 4], pos)").unwrap().display(), "[2, 4]");
|
||||
i.exec("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
assert_eq!(i.eval("filter([-1, 2, -3, 4], pos)").unwrap().display(), "[2, 4]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_returns_first_match_or_void() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn big(x) {\n return x > 5\n}").unwrap();
|
||||
assert_eq!(i.eval_expr_str("find([1, 6, 9], big)").unwrap().display(), "6");
|
||||
assert!(matches!(i.eval_expr_str("find([1, 2, 3], big)").unwrap(), Value::Void));
|
||||
i.exec("fn big(x) {\n return x > 5\n}").unwrap();
|
||||
assert_eq!(i.eval("find([1, 6, 9], big)").unwrap().display(), "6");
|
||||
assert!(matches!(i.eval("find([1, 2, 3], big)").unwrap(), Value::Void));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn take_while_stops_at_first_false() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
assert_eq!(i.eval_expr_str("take_while([1, 2, -3, 4], pos)").unwrap().display(), "[1, 2]");
|
||||
i.exec("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
assert_eq!(i.eval("take_while([1, 2, -3, 4], pos)").unwrap().display(), "[1, 2]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_while_drops_leading_passes() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
assert_eq!(i.eval_expr_str("skip_while([1, 2, -3, 4], pos)").unwrap().display(), "[-3, 4]");
|
||||
i.exec("fn pos(x) {\n return x > 0\n}").unwrap();
|
||||
assert_eq!(i.eval("skip_while([1, 2, -3, 4], pos)").unwrap().display(), "[-3, 4]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inspect_returns_input_unchanged() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn noop(x) {\n return x\n}").unwrap();
|
||||
assert_eq!(i.eval_expr_str("inspect([1, 2, 3], noop)").unwrap().display(), "[1, 2, 3]");
|
||||
i.exec("fn noop(x) {\n return x\n}").unwrap();
|
||||
assert_eq!(i.eval("inspect([1, 2, 3], noop)").unwrap().display(), "[1, 2, 3]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flat_map_splats_array_results() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn pair(x) {\n return [x, x * 10]\n}").unwrap();
|
||||
assert_eq!(i.eval_expr_str("flat_map([1, 2, 3], pair)").unwrap().display(),
|
||||
i.exec("fn pair(x) {\n return [x, x * 10]\n}").unwrap();
|
||||
assert_eq!(i.eval("flat_map([1, 2, 3], pair)").unwrap().display(),
|
||||
"[1, 10, 2, 20, 3, 30]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scan_emits_running_accumulator() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn add(a, b) {\n return a + b\n}").unwrap();
|
||||
assert_eq!(i.eval_expr_str("scan([1, 2, 3, 4], 0, add)").unwrap().display(),
|
||||
i.exec("fn add(a, b) {\n return a + b\n}").unwrap();
|
||||
assert_eq!(i.eval("scan([1, 2, 3, 4], 0, add)").unwrap().display(),
|
||||
"[1, 3, 6, 10]");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,61 +6,61 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn take_caps_at_length() {
|
||||
let mut i = Interpreter::new();
|
||||
assert_eq!(i.eval_expr_str("take([1, 2, 3], 2)").unwrap().display(), "[1, 2]");
|
||||
assert_eq!(i.eval_expr_str("take([1, 2, 3], 10)").unwrap().display(), "[1, 2, 3]");
|
||||
assert_eq!(i.eval("take([1, 2, 3], 2)").unwrap().display(), "[1, 2]");
|
||||
assert_eq!(i.eval("take([1, 2, 3], 10)").unwrap().display(), "[1, 2, 3]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_drops_n() {
|
||||
let mut i = Interpreter::new();
|
||||
assert_eq!(i.eval_expr_str("skip([1, 2, 3, 4], 2)").unwrap().display(), "[3, 4]");
|
||||
assert_eq!(i.eval_expr_str("drop([1, 2, 3, 4], 1)").unwrap().display(), "[2, 3, 4]");
|
||||
assert_eq!(i.eval("skip([1, 2, 3, 4], 2)").unwrap().display(), "[3, 4]");
|
||||
assert_eq!(i.eval("drop([1, 2, 3, 4], 1)").unwrap().display(), "[2, 3, 4]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chunk_partitions_remainder_in_tail() {
|
||||
let mut i = Interpreter::new();
|
||||
assert_eq!(i.eval_expr_str("chunk([1, 2, 3, 4, 5], 2)").unwrap().display(),
|
||||
assert_eq!(i.eval("chunk([1, 2, 3, 4, 5], 2)").unwrap().display(),
|
||||
"[[1, 2], [3, 4], [5]]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_slides_one_at_a_time() {
|
||||
let mut i = Interpreter::new();
|
||||
assert_eq!(i.eval_expr_str("window([1, 2, 3, 4], 2)").unwrap().display(),
|
||||
assert_eq!(i.eval("window([1, 2, 3, 4], 2)").unwrap().display(),
|
||||
"[[1, 2], [2, 3], [3, 4]]");
|
||||
assert_eq!(i.eval_expr_str("window([1, 2], 5)").unwrap().display(), "[]");
|
||||
assert_eq!(i.eval("window([1, 2], 5)").unwrap().display(), "[]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zip_anchors_on_left_and_pads_right() {
|
||||
let mut i = Interpreter::new();
|
||||
assert_eq!(i.eval_expr_str("zip([1, 2, 3], [10, 20, 30])").unwrap().display(),
|
||||
assert_eq!(i.eval("zip([1, 2, 3], [10, 20, 30])").unwrap().display(),
|
||||
"[[1, 10], [2, 20], [3, 30]]");
|
||||
assert_eq!(i.eval_expr_str("zip([1, 2, 3], [10])").unwrap().display(),
|
||||
assert_eq!(i.eval("zip([1, 2, 3], [10])").unwrap().display(),
|
||||
"[[1, 10], [2, null], [3, null]]");
|
||||
|
||||
assert_eq!(i.eval_expr_str("zip([1], [10, 20, 30])").unwrap().display(),
|
||||
assert_eq!(i.eval("zip([1], [10, 20, 30])").unwrap().display(),
|
||||
"[[1, 10]]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten_one_level() {
|
||||
let mut i = Interpreter::new();
|
||||
assert_eq!(i.eval_expr_str("flatten([[1, 2], [3], [4, 5]])").unwrap().display(),
|
||||
assert_eq!(i.eval("flatten([[1, 2], [3], [4, 5]])").unwrap().display(),
|
||||
"[1, 2, 3, 4, 5]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distinct_preserves_first_occurrence() {
|
||||
let mut i = Interpreter::new();
|
||||
assert_eq!(i.eval_expr_str("distinct([3, 1, 4, 1, 5, 9, 2, 6, 5])").unwrap().display(),
|
||||
assert_eq!(i.eval("distinct([3, 1, 4, 1, 5, 9, 2, 6, 5])").unwrap().display(),
|
||||
"[3, 1, 4, 5, 9, 2, 6]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_pairwise_differences() {
|
||||
let mut i = Interpreter::new();
|
||||
assert_eq!(i.eval_expr_str("delta([1, 3, 6, 10])").unwrap().display(), "[2, 3, 4]");
|
||||
assert_eq!(i.eval_expr_str("delta([5])").unwrap().display(), "[]");
|
||||
assert_eq!(i.eval("delta([1, 3, 6, 10])").unwrap().display(), "[2, 3, 4]");
|
||||
assert_eq!(i.eval("delta([5])").unwrap().display(), "[]");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,36 +14,36 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn round_with_digits() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("round(3.14159, 2)").unwrap();
|
||||
let v = i.eval("round(3.14159, 2)").unwrap();
|
||||
match v { Value::Number(n) => assert!(approx(n, 3.14)), _ => panic!() }
|
||||
let v = i.eval_expr_str("round(3.14159, 4)").unwrap();
|
||||
let v = i.eval("round(3.14159, 4)").unwrap();
|
||||
match v { Value::Number(n) => assert!(approx(n, 3.1416)), _ => panic!() }
|
||||
let v = i.eval_expr_str("round(3.7)").unwrap();
|
||||
let v = i.eval("round(3.7)").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ceil_with_digits() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("ceil(1.234, 1)").unwrap();
|
||||
let v = i.eval("ceil(1.234, 1)").unwrap();
|
||||
match v { Value::Number(n) => assert!(approx(n, 1.3)), _ => panic!() }
|
||||
let v = i.eval_expr_str("ceil(1.01)").unwrap();
|
||||
let v = i.eval("ceil(1.01)").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floor_with_digits() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("floor(1.999, 2)").unwrap();
|
||||
let v = i.eval("floor(1.999, 2)").unwrap();
|
||||
match v { Value::Number(n) => assert!(approx(n, 1.99)), _ => panic!() }
|
||||
let v = i.eval_expr_str("floor(1.9)").unwrap();
|
||||
let v = i.eval("floor(1.9)").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_digits_must_be_integer() {
|
||||
let mut i = Interpreter::new();
|
||||
assert!(i.eval_expr_str("round(3.14, 1.5)").is_err());
|
||||
assert!(i.eval("round(3.14, 1.5)").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn rand_unit_range() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("seed(42)").unwrap();
|
||||
i.exec("seed(42)").unwrap();
|
||||
for _ in 0..20 {
|
||||
let v = i.eval_expr_str("rand()").unwrap();
|
||||
let v = i.eval("rand()").unwrap();
|
||||
match v {
|
||||
Value::Number(n) => assert!(n >= 0.0 && n < 1.0, "rand() = {}", n),
|
||||
_ => panic!("rand() should return Number"),
|
||||
|
|
@ -19,9 +19,9 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn rand_integer_bound() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("seed(7)").unwrap();
|
||||
i.exec("seed(7)").unwrap();
|
||||
for _ in 0..30 {
|
||||
let v = i.eval_expr_str("rand(10)").unwrap();
|
||||
let v = i.eval("rand(10)").unwrap();
|
||||
match v {
|
||||
Value::Number(n) => assert!(n >= 0.0 && n < 10.0 && n == n.trunc()),
|
||||
_ => panic!(),
|
||||
|
|
@ -32,9 +32,9 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn rand_lo_hi_range() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("seed(99)").unwrap();
|
||||
i.exec("seed(99)").unwrap();
|
||||
for _ in 0..30 {
|
||||
let v = i.eval_expr_str("rand(5, 8)").unwrap();
|
||||
let v = i.eval("rand(5, 8)").unwrap();
|
||||
match v {
|
||||
Value::Number(n) => assert!(n >= 5.0 && n < 8.0),
|
||||
_ => panic!(),
|
||||
|
|
@ -45,8 +45,8 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn rand_picks_from_array() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("seed(1)").unwrap();
|
||||
let v = i.eval_expr_str("rand([10, 20, 30])").unwrap();
|
||||
i.exec("seed(1)").unwrap();
|
||||
let v = i.eval("rand([10, 20, 30])").unwrap();
|
||||
match v {
|
||||
Value::Number(n) => assert!(n == 10.0 || n == 20.0 || n == 30.0),
|
||||
_ => panic!(),
|
||||
|
|
@ -56,10 +56,10 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn rand_seed_is_deterministic() {
|
||||
let mut a = Interpreter::new();
|
||||
a.exec_line("seed(12345)").unwrap();
|
||||
let va = a.eval_expr_str("rand()").unwrap();
|
||||
a.exec_line("seed(12345)").unwrap();
|
||||
let vb = a.eval_expr_str("rand()").unwrap();
|
||||
a.exec("seed(12345)").unwrap();
|
||||
let va = a.eval("rand()").unwrap();
|
||||
a.exec("seed(12345)").unwrap();
|
||||
let vb = a.eval("rand()").unwrap();
|
||||
match (va, vb) {
|
||||
(Value::Number(x), Value::Number(y)) => assert_eq!(x.to_bits(), y.to_bits()),
|
||||
_ => panic!(),
|
||||
|
|
@ -69,8 +69,8 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn rand_method_call_on_array() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("seed(3)").unwrap();
|
||||
let v = i.eval_expr_str("[1, 2, 3, 4, 5].rand()").unwrap();
|
||||
i.exec("seed(3)").unwrap();
|
||||
let v = i.eval("[1, 2, 3, 4, 5].rand()").unwrap();
|
||||
match v {
|
||||
Value::Number(n) => assert!((1.0..=5.0).contains(&n)),
|
||||
_ => panic!(),
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn sort_numeric_ascending() {
|
||||
let mut i = Interpreter::new();
|
||||
assert_eq!(i.eval_expr_str("sort([3, 1, 4, 1, 5, 9, 2, 6])").unwrap().display(),
|
||||
assert_eq!(i.eval("sort([3, 1, 4, 1, 5, 9, 2, 6])").unwrap().display(),
|
||||
"[1, 1, 2, 3, 4, 5, 6, 9]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sort_by_user_key() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn neg(x) {\n return 0 - x\n}").unwrap();
|
||||
assert_eq!(i.eval_expr_str("sort_by([1, 2, 3], neg)").unwrap().display(),
|
||||
i.exec("fn neg(x) {\n return 0 - x\n}").unwrap();
|
||||
assert_eq!(i.eval("sort_by([1, 2, 3], neg)").unwrap().display(),
|
||||
"[3, 2, 1]");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,8 +89,8 @@ if (x > 10) {
|
|||
#[test]
|
||||
fn for_loop_does_not_leak_var() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let x = [1, 2, 3]").unwrap();
|
||||
i.exec_line("for x in 0..3 { let _ = x }").unwrap();
|
||||
i.exec("let x = [1, 2, 3]").unwrap();
|
||||
i.exec("for x in 0..3 { let _ = x }").unwrap();
|
||||
let after = i.get_var("x").unwrap();
|
||||
assert!(matches!(after, Value::Array(_)), "for loop leaked: x is now {:?}", after);
|
||||
}
|
||||
|
|
@ -98,10 +98,10 @@ if (x > 10) {
|
|||
#[test]
|
||||
fn for_loop_in_while_with_shadowing() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let pos = [10, 20, 30, 40]").unwrap();
|
||||
i.exec_line("let count = 0").unwrap();
|
||||
i.exec_line("let iters = 0").unwrap();
|
||||
i.exec_line("while iters < 2 {\n for pos in 0..len(pos) {\n count = count + 1\n }\n iters = iters + 1\n}").unwrap();
|
||||
i.exec("let pos = [10, 20, 30, 40]").unwrap();
|
||||
i.exec("let count = 0").unwrap();
|
||||
i.exec("let iters = 0").unwrap();
|
||||
i.exec("while iters < 2 {\n for pos in 0..len(pos) {\n count = count + 1\n }\n iters = iters + 1\n}").unwrap();
|
||||
let count = i.get_var("count").unwrap();
|
||||
assert!(matches!(count, Value::Number(n) if n == 8.0),
|
||||
"expected 8 inner iterations, got {:?}", count);
|
||||
|
|
@ -178,15 +178,15 @@ fn fib(n) {
|
|||
#[test]
|
||||
fn fn_returning_array_round_trips() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn dup(x) {\n return [x, x]\n}").unwrap();
|
||||
assert_eq!(i.eval_expr_str("dup(7)").unwrap().display(), "[7, 7]");
|
||||
i.exec("fn dup(x) {\n return [x, x]\n}").unwrap();
|
||||
assert_eq!(i.eval("dup(7)").unwrap().display(), "[7, 7]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_with_params_is_regular_fn_def() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let double(x) = x * 2").unwrap();
|
||||
i.exec("let double(x) = x * 2").unwrap();
|
||||
assert!(i.fns.contains_key("double"));
|
||||
let v = i.eval_expr_str("double(21)").unwrap();
|
||||
let v = i.eval("double(21)").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 42.0));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use super::helpers::*;
|
|||
let mut i = Interpreter::new();
|
||||
let h = Value::Extern(ExternHandle { kind: "toroid".into(), id: 42 });
|
||||
i.set_var("t", h);
|
||||
let back = i.eval_expr_str("t").unwrap();
|
||||
let back = i.eval("t").unwrap();
|
||||
match back {
|
||||
Value::Extern(h) => {
|
||||
assert_eq!(h.kind, "toroid");
|
||||
|
|
@ -33,7 +33,7 @@ use super::helpers::*;
|
|||
}
|
||||
});
|
||||
i.set_var("t1", Value::Extern(ExternHandle { kind: "node".into(), id: 7 }));
|
||||
let r = i.eval_expr_str("scene_update(t1)").unwrap();
|
||||
let r = i.eval("scene_update(t1)").unwrap();
|
||||
assert_eq!(r.display(), "updated node#7");
|
||||
assert_eq!(calls.get(), 1);
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ use super::helpers::*;
|
|||
fn custom_builtin_overrides_hardcoded() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_builtin("sin", |_interp, _args| Ok(Value::Number(42.0)));
|
||||
assert_eq!(i.eval_expr_str("sin(0)").unwrap().display(), "42");
|
||||
assert_eq!(i.eval("sin(0)").unwrap().display(), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -50,14 +50,14 @@ use super::helpers::*;
|
|||
let mut i = Interpreter::new();
|
||||
i.register_builtin("sin", |_interp, _args| Ok(Value::Number(42.0)));
|
||||
i.unregister_builtin("sin");
|
||||
let r = i.eval_expr_str("sin(0)").unwrap();
|
||||
let r = i.eval("sin(0)").unwrap();
|
||||
assert!(matches!(r, Value::Number(n) if n.abs() < 1e-9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_invokes_user_fn_by_name_with_values() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn add(a, b) {\n return a + b\n}").unwrap();
|
||||
i.exec("fn add(a, b) {\n return a + b\n}").unwrap();
|
||||
let r = i.call("add", &[Value::Number(3.0), Value::Number(4.0)]).unwrap();
|
||||
assert_eq!(r.display(), "7");
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn call_passes_extern_through_user_fn() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn pass(x) {\n return x\n}").unwrap();
|
||||
i.exec("fn pass(x) {\n return x\n}").unwrap();
|
||||
let h = Value::Extern(ExternHandle { kind: "anchor".into(), id: 9 });
|
||||
let r = i.call("pass", &[h]).unwrap();
|
||||
match r {
|
||||
|
|
@ -89,7 +89,7 @@ use super::helpers::*;
|
|||
Err("emit(extern)".into())
|
||||
}
|
||||
});
|
||||
i.exec_line("fn step(handle) {\n emit(handle)\n return handle\n}").unwrap();
|
||||
i.exec("fn step(handle) {\n emit(handle)\n return handle\n}").unwrap();
|
||||
for id in 0..3u64 {
|
||||
let h = Value::Extern(ExternHandle { kind: "node".into(), id });
|
||||
i.call("step", &[h]).unwrap();
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ pub(super) fn eval(input: &str) -> String {
|
|||
|
||||
pub(super) fn eval_one(input: &str) -> String {
|
||||
let mut interp = Interpreter::new();
|
||||
match interp.eval_expr_str(input) {
|
||||
match interp.eval(input) {
|
||||
Ok(v) => v.display(),
|
||||
Err(e) => format!("error: {}", e),
|
||||
}
|
||||
|
|
@ -33,6 +33,6 @@ pub(super) fn approx(a: f64, b: f64) -> bool {
|
|||
|
||||
pub(super) fn solve_interp() -> Interpreter {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn square(x) { x * x }").unwrap();
|
||||
i.exec("fn square(x) { x * x }").unwrap();
|
||||
i
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ impl InterpreterHook for OrderedHook {
|
|||
fn hook_try_call_dispatches_before_undefined() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_hook(Rc::new(DoubleHook)).unwrap();
|
||||
let v = i.eval_expr_str("double(21)").unwrap();
|
||||
let v = i.eval("double(21)").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 42.0));
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ fn hook_try_call_dispatches_before_undefined() {
|
|||
fn hook_try_call_overrides_hardcoded_builtin() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_hook(Rc::new(SinOverrideHook)).unwrap();
|
||||
let v = i.eval_expr_str("sin(0)").unwrap();
|
||||
let v = i.eval("sin(0)").unwrap();
|
||||
assert!(matches!(v, Value::Str(ref s) if s == "intercepted"));
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ fn hook_try_call_overrides_hardcoded_builtin() {
|
|||
fn rewrite_tokens_swaps_identifiers_before_parse() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_hook(Rc::new(TokenSwapHook)).unwrap();
|
||||
let v = i.eval_expr_str("alpha").unwrap();
|
||||
let v = i.eval("alpha").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if (n - std::f64::consts::PI).abs() < 1e-12));
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ fn hooks_introspection_lists_registered_hooks() {
|
|||
let mut i = Interpreter::new();
|
||||
i.register_hook(Rc::new(DoubleHook)).unwrap();
|
||||
i.register_hook(Rc::new(SinOverrideHook)).unwrap();
|
||||
let v = i.eval_expr_str("hooks()").unwrap();
|
||||
let v = i.eval("hooks()").unwrap();
|
||||
let arr = match v { Value::Array(a) => a, _ => panic!("expected array") };
|
||||
assert_eq!(arr.len(), 2);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn impl_static_constructor() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("impl Point {\n fn new(x, y) {\n return {__type: \"Point\", x: x, y: y}\n }\n}").unwrap();
|
||||
let v = i.eval_expr_str("Point::new(3, 4)").unwrap();
|
||||
i.exec("impl Point {\n fn new(x, y) {\n return {__type: \"Point\", x: x, y: y}\n }\n}").unwrap();
|
||||
let v = i.eval("Point::new(3, 4)").unwrap();
|
||||
match v {
|
||||
Value::Struct(s) => {
|
||||
assert!(matches!(s.borrow().get("x"), Some(Value::Number(n)) if *n == 3.0));
|
||||
|
|
@ -20,19 +20,19 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn impl_method_call() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("impl Vec2 {\n fn new(x, y) {\n return {__type: \"Vec2\", x: x, y: y}\n }\n fn length(self) {\n return sqrt(self.x^2 + self.y^2)\n }\n}").unwrap();
|
||||
i.exec_line("let v = Vec2::new(3, 4)").unwrap();
|
||||
let v = i.eval_expr_str("v.length()").unwrap();
|
||||
i.exec("impl Vec2 {\n fn new(x, y) {\n return {__type: \"Vec2\", x: x, y: y}\n }\n fn length(self) {\n return sqrt(self.x^2 + self.y^2)\n }\n}").unwrap();
|
||||
i.exec("let v = Vec2::new(3, 4)").unwrap();
|
||||
let v = i.eval("v.length()").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 5.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn impl_method_with_args() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("impl Vec2 {\n fn new(x, y) {\n return {__type: \"Vec2\", x: x, y: y}\n }\n fn add(self, other) {\n return Vec2::new(self.x + other.x, self.y + other.y)\n }\n}").unwrap();
|
||||
i.exec_line("let a = Vec2::new(1, 2)").unwrap();
|
||||
i.exec_line("let b = Vec2::new(3, 4)").unwrap();
|
||||
let v = i.eval_expr_str("a.add(b)").unwrap();
|
||||
i.exec("impl Vec2 {\n fn new(x, y) {\n return {__type: \"Vec2\", x: x, y: y}\n }\n fn add(self, other) {\n return Vec2::new(self.x + other.x, self.y + other.y)\n }\n}").unwrap();
|
||||
i.exec("let a = Vec2::new(1, 2)").unwrap();
|
||||
i.exec("let b = Vec2::new(3, 4)").unwrap();
|
||||
let v = i.eval("a.add(b)").unwrap();
|
||||
match v {
|
||||
Value::Struct(s) => {
|
||||
assert!(matches!(s.borrow().get("x"), Some(Value::Number(n)) if *n == 4.0));
|
||||
|
|
@ -45,11 +45,11 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn trait_def_and_impl() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("trait Measurable {\n fn area(self)\n}").unwrap();
|
||||
i.exec_line("impl Measurable for Circle {\n fn area(self) {\n return pi * self.r^2\n }\n}").unwrap();
|
||||
i.exec_line("impl Circle {\n fn new(r) {\n return {__type: \"Circle\", r: r}\n }\n}").unwrap();
|
||||
i.exec_line("let c = Circle::new(5)").unwrap();
|
||||
let v = i.eval_expr_str("c.area()").unwrap();
|
||||
i.exec("trait Measurable {\n fn area(self)\n}").unwrap();
|
||||
i.exec("impl Measurable for Circle {\n fn area(self) {\n return pi * self.r^2\n }\n}").unwrap();
|
||||
i.exec("impl Circle {\n fn new(r) {\n return {__type: \"Circle\", r: r}\n }\n}").unwrap();
|
||||
i.exec("let c = Circle::new(5)").unwrap();
|
||||
let v = i.eval("c.area()").unwrap();
|
||||
let expected = std::f64::consts::PI * 25.0;
|
||||
match v {
|
||||
Value::Number(n) => assert!((n - expected).abs() < 1e-10),
|
||||
|
|
@ -60,8 +60,8 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn trait_missing_method_errors() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("trait Drawable {\n fn draw(self)\n fn bounds(self)\n}").unwrap();
|
||||
let result = i.exec_line("impl Drawable for Box {\n fn draw(self) {\n return 0\n }\n}");
|
||||
i.exec("trait Drawable {\n fn draw(self)\n fn bounds(self)\n}").unwrap();
|
||||
let result = i.exec("impl Drawable for Box {\n fn draw(self) {\n return 0\n }\n}");
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("missing required method 'bounds'"));
|
||||
}
|
||||
|
|
@ -69,22 +69,22 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn method_len_on_array() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let pos = [1, 2, 3, 4]").unwrap();
|
||||
let v = i.eval_expr_str("pos.len()").unwrap();
|
||||
i.exec("let pos = [1, 2, 3, 4]").unwrap();
|
||||
let v = i.eval("pos.len()").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_call_on_array_literal() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("[1, 2, 3].len()").unwrap();
|
||||
let v = i.eval("[1, 2, 3].len()").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 3.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_on_untyped_struct_errors() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let p = {x: 1, y: 2}").unwrap();
|
||||
let result = i.eval_expr_str("p.length()");
|
||||
i.exec("let p = {x: 1, y: 2}").unwrap();
|
||||
let result = i.eval("p.length()");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn array_slot_assignment() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let arr = [1, 2, 3]").unwrap();
|
||||
i.exec_line("arr[1] = 42").unwrap();
|
||||
assert_eq!(i.eval_expr_str("arr").unwrap().display(), "[1, 42, 3]");
|
||||
i.exec("let arr = [1, 2, 3]").unwrap();
|
||||
i.exec("arr[1] = 42").unwrap();
|
||||
assert_eq!(i.eval("arr").unwrap().display(), "[1, 42, 3]");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,180 @@
|
|||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::interp::*;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use super::helpers::*;
|
||||
|
||||
fn tmp_dir(tag: &str) -> PathBuf {
|
||||
let p = std::env::temp_dir().join(format!("acord-loader-{}-{}", tag, std::process::id()));
|
||||
let _ = fs::remove_dir_all(&p);
|
||||
fs::create_dir_all(&p).unwrap();
|
||||
p
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_single_cord_file() {
|
||||
let dir = tmp_dir("single");
|
||||
fs::write(dir.join("mathy.cord"), "let pi_squared = 9.8696\nfn double(x) { x * 2 }").unwrap();
|
||||
|
||||
let mut i = Interpreter::new();
|
||||
i.add_module_path("mathy", dir.join("mathy.cord"));
|
||||
i.exec("use mathy").unwrap();
|
||||
|
||||
assert!(matches!(i.get_var("pi_squared"), Some(Value::Number(n)) if (n - 9.8696).abs() < 1e-9));
|
||||
let v = i.eval("double(21)").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 42.0));
|
||||
|
||||
fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_dir_with_mod_cord() {
|
||||
let dir = tmp_dir("withmod");
|
||||
let lib = dir.join("lib");
|
||||
fs::create_dir_all(&lib).unwrap();
|
||||
fs::write(lib.join("mod.cord"), "fn shout(s) { s + \"!\" }").unwrap();
|
||||
fs::write(lib.join("other.cord"), "// ignored when mod.cord present").unwrap();
|
||||
|
||||
let mut i = Interpreter::new();
|
||||
i.add_module_path("lib", &lib);
|
||||
i.exec("use lib").unwrap();
|
||||
|
||||
let v = i.eval("shout(\"hi\")").unwrap();
|
||||
assert!(matches!(v, Value::Str(ref s) if s == "hi!"));
|
||||
|
||||
fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_dir_concats_cord_files_alphabetically() {
|
||||
let dir = tmp_dir("concat");
|
||||
let lib = dir.join("lib");
|
||||
fs::create_dir_all(&lib).unwrap();
|
||||
fs::write(lib.join("a.cord"), "let a_val = 1").unwrap();
|
||||
fs::write(lib.join("b.cord"), "let b_val = 2").unwrap();
|
||||
|
||||
let mut i = Interpreter::new();
|
||||
i.add_module_path("lib", &lib);
|
||||
i.exec("use lib").unwrap();
|
||||
|
||||
assert!(matches!(i.get_var("a_val"), Some(Value::Number(n)) if n == 1.0));
|
||||
assert!(matches!(i.get_var("b_val"), Some(Value::Number(n)) if n == 2.0));
|
||||
|
||||
fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_item_imports_only_named_binding() {
|
||||
let dir = tmp_dir("filter");
|
||||
fs::write(dir.join("utils.cord"), "let alpha = 1\nlet beta = 2\nlet gamma = 3").unwrap();
|
||||
|
||||
let mut i = Interpreter::new();
|
||||
i.add_module_path("utils", dir.join("utils.cord"));
|
||||
i.exec("use utils::beta").unwrap();
|
||||
|
||||
assert!(i.get_var("alpha").is_none());
|
||||
assert!(matches!(i.get_var("beta"), Some(Value::Number(n)) if n == 2.0));
|
||||
assert!(i.get_var("gamma").is_none());
|
||||
|
||||
fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_wildcard_imports_everything() {
|
||||
let dir = tmp_dir("wildcard");
|
||||
fs::write(dir.join("utils.cord"), "let alpha = 1\nlet beta = 2").unwrap();
|
||||
|
||||
let mut i = Interpreter::new();
|
||||
i.add_module_path("utils", dir.join("utils.cord"));
|
||||
i.exec("use utils::*").unwrap();
|
||||
|
||||
assert!(matches!(i.get_var("alpha"), Some(Value::Number(n)) if n == 1.0));
|
||||
assert!(matches!(i.get_var("beta"), Some(Value::Number(n)) if n == 2.0));
|
||||
|
||||
fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_namespace_override_loads_distinct_source() {
|
||||
let dir = tmp_dir("subns");
|
||||
fs::write(dir.join("main.cord"), "let from_main = true").unwrap();
|
||||
fs::write(dir.join("sub.cord"), "let from_sub = true").unwrap();
|
||||
|
||||
let mut i = Interpreter::new();
|
||||
i.add_module_path("lib", dir.join("main.cord"));
|
||||
i.add_module_subpath("lib", "extra", dir.join("sub.cord"));
|
||||
i.exec("use lib::extra").unwrap();
|
||||
|
||||
assert!(matches!(i.get_var("from_sub"), Some(Value::Bool(true))));
|
||||
assert!(i.get_var("from_main").is_none());
|
||||
|
||||
fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregistered_module_no_ops() {
|
||||
let mut i = Interpreter::new();
|
||||
let result = i.exec("use spice");
|
||||
assert!(result.is_ok());
|
||||
assert!(i.spice_enabled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_item_errors() {
|
||||
let dir = tmp_dir("missing");
|
||||
fs::write(dir.join("mod.cord"), "let present = 1").unwrap();
|
||||
|
||||
let mut i = Interpreter::new();
|
||||
i.add_module_path("mod", dir.join("mod.cord"));
|
||||
let err = i.exec("use mod::absent").unwrap_err();
|
||||
assert!(err.contains("absent"));
|
||||
|
||||
fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exports_carry_methods_and_traits() {
|
||||
let mut a = Interpreter::new();
|
||||
a.exec("trait Named { fn show(self) }").unwrap();
|
||||
a.exec("impl Named for Thing { fn show(self) { return self.name } }").unwrap();
|
||||
a.exec("impl Thing { fn new(name) { return {__type: \"Thing\", name: name} } }").unwrap();
|
||||
|
||||
let exports = a.exports();
|
||||
assert!(exports.traits.contains_key("Named"));
|
||||
assert!(exports.methods.contains_key(&("Thing".to_string(), "show".to_string())));
|
||||
assert!(exports.methods.contains_key(&("Thing".to_string(), "new".to_string())));
|
||||
|
||||
let mut b = Interpreter::new();
|
||||
b.import_all(&exports);
|
||||
let v = b.eval("Thing::new(\"box\").show()").unwrap();
|
||||
assert!(matches!(v, Value::Str(ref s) if s == "box"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_module_hooks_inherit_into_subinterp() {
|
||||
use std::rc::Rc;
|
||||
|
||||
struct ConstHook;
|
||||
impl InterpreterHook for ConstHook {
|
||||
fn name(&self) -> &str { "const_hook" }
|
||||
fn try_call(&self, _i: &mut Interpreter, name: &str, _args: &[Expr], _depth: u32)
|
||||
-> Option<Result<Value, String>>
|
||||
{
|
||||
if name == "magic" { Some(Ok(Value::Number(99.0))) } else { None }
|
||||
}
|
||||
}
|
||||
|
||||
let dir = tmp_dir("hookinherit");
|
||||
fs::write(dir.join("lib.cord"), "let secret = magic()").unwrap();
|
||||
|
||||
let mut i = Interpreter::new();
|
||||
i.register_hook(Rc::new(ConstHook)).unwrap();
|
||||
i.add_module_path("lib", dir.join("lib.cord"));
|
||||
i.exec("use lib").unwrap();
|
||||
|
||||
assert!(matches!(i.get_var("secret"), Some(Value::Number(n)) if n == 99.0));
|
||||
|
||||
fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
|
@ -22,4 +22,5 @@ mod structs;
|
|||
mod impl_trait;
|
||||
mod embedding;
|
||||
mod hooks;
|
||||
mod loader;
|
||||
mod module_paths;
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ fn module_paths_builtin_returns_struct_array() {
|
|||
let mut i = Interpreter::new();
|
||||
i.add_module_path("spice", "/opt/spice");
|
||||
i.add_module_path("ring", "/opt/ring");
|
||||
let v = i.eval_expr_str("module_paths()").unwrap();
|
||||
let v = i.eval("module_paths()").unwrap();
|
||||
let arr = match v { Value::Array(a) => a, _ => panic!("expected array") };
|
||||
assert_eq!(arr.len(), 2);
|
||||
}
|
||||
|
|
@ -127,7 +127,7 @@ fn module_subpaths_builtin_returns_struct_array() {
|
|||
let mut i = Interpreter::new();
|
||||
i.add_module_path("work", "/home/x/work");
|
||||
i.add_module_subpath("work", "calc", "/elsewhere/calc");
|
||||
let v = i.eval_expr_str("module_subpaths()").unwrap();
|
||||
let v = i.eval("module_subpaths()").unwrap();
|
||||
let arr = match v { Value::Array(a) => a, _ => panic!("expected array") };
|
||||
assert_eq!(arr.len(), 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn use_statement_parses() {
|
||||
let mut interp = Interpreter::new();
|
||||
assert!(interp.exec_line("use budget").is_ok());
|
||||
assert!(interp.exec_line("use budget::ramp").is_ok());
|
||||
assert!(interp.exec("use budget").is_ok());
|
||||
assert!(interp.exec("use budget::ramp").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -32,29 +32,29 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn module_exports_and_import() {
|
||||
let mut module_a = Interpreter::new();
|
||||
module_a.exec_line("let x = 42").unwrap();
|
||||
module_a.exec_line("fn double(n) {\n n * 2\n}").unwrap();
|
||||
module_a.exec("let x = 42").unwrap();
|
||||
module_a.exec("fn double(n) {\n n * 2\n}").unwrap();
|
||||
let exports = module_a.exports();
|
||||
assert!(exports.vars.contains_key("x"));
|
||||
assert!(exports.fns.contains_key("double"));
|
||||
|
||||
let mut module_b = Interpreter::new();
|
||||
module_b.import_all(&exports);
|
||||
let val = module_b.eval_expr_str("x").unwrap();
|
||||
let val = module_b.eval("x").unwrap();
|
||||
assert!(matches!(val, Value::Number(n) if n == 42.0));
|
||||
let val = module_b.eval_expr_str("double(5)").unwrap();
|
||||
let val = module_b.eval("double(5)").unwrap();
|
||||
assert!(matches!(val, Value::Number(n) if n == 10.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_specific_item() {
|
||||
let mut module_a = Interpreter::new();
|
||||
module_a.exec_line("let x = 1").unwrap();
|
||||
module_a.exec_line("let y = 2").unwrap();
|
||||
module_a.exec("let x = 1").unwrap();
|
||||
module_a.exec("let y = 2").unwrap();
|
||||
let exports = module_a.exports();
|
||||
|
||||
let mut module_b = Interpreter::new();
|
||||
assert!(module_b.import_item(&exports, "x"));
|
||||
assert!(module_b.eval_expr_str("x").is_ok());
|
||||
assert!(module_b.eval_expr_str("y").is_err());
|
||||
assert!(module_b.eval("x").is_ok());
|
||||
assert!(module_b.eval("y").is_err());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn ring_requires_use_ring() {
|
||||
let mut i = Interpreter::new();
|
||||
assert!(i.eval_expr_str("ring(4)").is_err());
|
||||
assert!(i.eval("ring(4)").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ring_flat_single_arg() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use ring").unwrap();
|
||||
let r = i.eval_expr_str("ring(4)").unwrap();
|
||||
i.exec("use ring").unwrap();
|
||||
let r = i.eval("ring(4)").unwrap();
|
||||
match r {
|
||||
Value::Ring(buf) => {
|
||||
let b = buf.borrow();
|
||||
|
|
@ -28,8 +28,8 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn ring_rect_from_pair() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use ring").unwrap();
|
||||
let r = i.eval_expr_str("ring([3, 4])").unwrap();
|
||||
i.exec("use ring").unwrap();
|
||||
let r = i.eval("ring([3, 4])").unwrap();
|
||||
match r {
|
||||
Value::Ring(buf) => {
|
||||
let b = buf.borrow();
|
||||
|
|
@ -43,8 +43,8 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn ring_bang_sugar_parses() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use ring").unwrap();
|
||||
let r = i.eval_expr_str("ring!(4)").unwrap();
|
||||
i.exec("use ring").unwrap();
|
||||
let r = i.eval("ring!(4)").unwrap();
|
||||
match r {
|
||||
Value::Ring(buf) => assert_eq!(buf.borrow().volume, 4),
|
||||
_ => panic!("expected ring"),
|
||||
|
|
@ -54,8 +54,8 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn ring_false_volume_form() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use ring").unwrap();
|
||||
let r = i.eval_expr_str("ring(false, 5)").unwrap();
|
||||
i.exec("use ring").unwrap();
|
||||
let r = i.eval("ring(false, 5)").unwrap();
|
||||
match r {
|
||||
Value::Ring(buf) => {
|
||||
let b = buf.borrow();
|
||||
|
|
@ -69,22 +69,22 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn iter_fills_ring_in_order() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use ring").unwrap();
|
||||
i.exec_line("fn double(x) {\n return x * 2\n}").unwrap();
|
||||
i.exec_line("let buf = ring!(3)").unwrap();
|
||||
i.exec_line("iter([1, 2, 3], double, buf)").unwrap();
|
||||
let v = i.eval_expr_str("buf").unwrap();
|
||||
i.exec("use ring").unwrap();
|
||||
i.exec("fn double(x) {\n return x * 2\n}").unwrap();
|
||||
i.exec("let buf = ring!(3)").unwrap();
|
||||
i.exec("iter([1, 2, 3], double, buf)").unwrap();
|
||||
let v = i.eval("buf").unwrap();
|
||||
assert!(v.display().contains("2") && v.display().contains("4") && v.display().contains("6"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_overwrites_oldest_when_full() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use ring").unwrap();
|
||||
i.exec_line("fn id(x) {\n return x\n}").unwrap();
|
||||
i.exec_line("let buf = ring!(3)").unwrap();
|
||||
i.exec_line("iter([1, 2, 3, 4, 5], id, buf)").unwrap();
|
||||
let v = i.eval_expr_str("buf").unwrap();
|
||||
i.exec("use ring").unwrap();
|
||||
i.exec("fn id(x) {\n return x\n}").unwrap();
|
||||
i.exec("let buf = ring!(3)").unwrap();
|
||||
i.exec("iter([1, 2, 3, 4, 5], id, buf)").unwrap();
|
||||
let v = i.eval("buf").unwrap();
|
||||
let s = v.display();
|
||||
// expect [3, 4, 5]
|
||||
assert!(s.contains("3") && s.contains("4") && s.contains("5"));
|
||||
|
|
@ -94,61 +94,61 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn iter_rejects_push_into_zero_volume() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use ring").unwrap();
|
||||
i.exec_line("fn id(x) {\n return x\n}").unwrap();
|
||||
i.exec_line("let buf = ring!(0)").unwrap();
|
||||
assert!(i.exec_line("iter([1], id, buf)").is_err());
|
||||
i.exec("use ring").unwrap();
|
||||
i.exec("fn id(x) {\n return x\n}").unwrap();
|
||||
i.exec("let buf = ring!(0)").unwrap();
|
||||
assert!(i.exec("iter([1], id, buf)").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peek_requires_use_ring() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let buf = 1").unwrap();
|
||||
assert!(i.eval_expr_str("peek(buf)").is_err());
|
||||
i.exec("let buf = 1").unwrap();
|
||||
assert!(i.eval("peek(buf)").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peek_clones_full_ring() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use ring").unwrap();
|
||||
i.exec_line("fn id(x) {\n return x\n}").unwrap();
|
||||
i.exec_line("let buf = ring!(5)").unwrap();
|
||||
i.exec_line("iter([1, 2, 3], id, buf)").unwrap();
|
||||
assert_eq!(i.eval_expr_str("peek(buf)").unwrap().display(), "[1, 2, 3]");
|
||||
assert_eq!(i.eval_expr_str("peek(buf)").unwrap().display(), "[1, 2, 3]");
|
||||
i.exec("use ring").unwrap();
|
||||
i.exec("fn id(x) {\n return x\n}").unwrap();
|
||||
i.exec("let buf = ring!(5)").unwrap();
|
||||
i.exec("iter([1, 2, 3], id, buf)").unwrap();
|
||||
assert_eq!(i.eval("peek(buf)").unwrap().display(), "[1, 2, 3]");
|
||||
assert_eq!(i.eval("peek(buf)").unwrap().display(), "[1, 2, 3]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peek_with_count_returns_tail() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use ring").unwrap();
|
||||
i.exec_line("fn id(x) {\n return x\n}").unwrap();
|
||||
i.exec_line("let buf = ring!(5)").unwrap();
|
||||
i.exec_line("iter([1, 2, 3, 4, 5], id, buf)").unwrap();
|
||||
assert_eq!(i.eval_expr_str("peek(buf, 3)").unwrap().display(), "[3, 4, 5]");
|
||||
assert_eq!(i.eval_expr_str("peek(buf, 99)").unwrap().display(), "[1, 2, 3, 4, 5]");
|
||||
assert_eq!(i.eval_expr_str("peek(buf, 0)").unwrap().display(), "[]");
|
||||
i.exec("use ring").unwrap();
|
||||
i.exec("fn id(x) {\n return x\n}").unwrap();
|
||||
i.exec("let buf = ring!(5)").unwrap();
|
||||
i.exec("iter([1, 2, 3, 4, 5], id, buf)").unwrap();
|
||||
assert_eq!(i.eval("peek(buf, 3)").unwrap().display(), "[3, 4, 5]");
|
||||
assert_eq!(i.eval("peek(buf, 99)").unwrap().display(), "[1, 2, 3, 4, 5]");
|
||||
assert_eq!(i.eval("peek(buf, 0)").unwrap().display(), "[]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peek_does_not_consume_during_iter() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use ring").unwrap();
|
||||
i.exec_line("fn step(x) {\n return x * 10\n}").unwrap();
|
||||
i.exec_line("let buf = ring!(3)").unwrap();
|
||||
i.exec_line("iter([1, 2, 3], step, buf)").unwrap();
|
||||
i.eval_expr_str("peek(buf)").unwrap();
|
||||
i.exec_line("iter([4], step, buf)").unwrap();
|
||||
assert_eq!(i.eval_expr_str("peek(buf)").unwrap().display(), "[20, 30, 40]");
|
||||
i.exec("use ring").unwrap();
|
||||
i.exec("fn step(x) {\n return x * 10\n}").unwrap();
|
||||
i.exec("let buf = ring!(3)").unwrap();
|
||||
i.exec("iter([1, 2, 3], step, buf)").unwrap();
|
||||
i.eval("peek(buf)").unwrap();
|
||||
i.exec("iter([4], step, buf)").unwrap();
|
||||
assert_eq!(i.eval("peek(buf)").unwrap().display(), "[20, 30, 40]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn history_matches_peek_semantics() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use ring").unwrap();
|
||||
i.exec_line("fn id(x) {\n return x\n}").unwrap();
|
||||
i.exec_line("let buf = ring!(5)").unwrap();
|
||||
i.exec_line("iter([1, 2, 3], id, buf)").unwrap();
|
||||
assert_eq!(i.eval_expr_str("history(buf)").unwrap().display(), "[1, 2, 3]");
|
||||
assert_eq!(i.eval_expr_str("history(buf, 2)").unwrap().display(), "[2, 3]");
|
||||
i.exec("use ring").unwrap();
|
||||
i.exec("fn id(x) {\n return x\n}").unwrap();
|
||||
i.exec("let buf = ring!(5)").unwrap();
|
||||
i.exec("iter([1, 2, 3], id, buf)").unwrap();
|
||||
assert_eq!(i.eval("history(buf)").unwrap().display(), "[1, 2, 3]");
|
||||
assert_eq!(i.eval("history(buf, 2)").unwrap().display(), "[2, 3]");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn solve_macro_parses_comma() {
|
||||
let mut i = solve_interp();
|
||||
i.exec_line("let invsq = solve!(x, square)").unwrap();
|
||||
i.exec("let invsq = solve!(x, square)").unwrap();
|
||||
assert!(i.solved_fns.contains_key("invsq"));
|
||||
let def = &i.solved_fns["invsq"];
|
||||
assert_eq!(def.source_fn, "square");
|
||||
|
|
@ -17,7 +17,7 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn solve_macro_parses_from() {
|
||||
let mut i = solve_interp();
|
||||
i.exec_line("let invsq = solve!(x from square)").unwrap();
|
||||
i.exec("let invsq = solve!(x from square)").unwrap();
|
||||
let def = &i.solved_fns["invsq"];
|
||||
assert_eq!(def.source_fn, "square");
|
||||
assert_eq!(def.solve_param_idx, 0);
|
||||
|
|
@ -26,21 +26,21 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn solve_macro_unknown_source_errors() {
|
||||
let mut i = Interpreter::new();
|
||||
let err = i.exec_line("let bad = solve!(x, nonexistent)").unwrap_err();
|
||||
let err = i.exec("let bad = solve!(x, nonexistent)").unwrap_err();
|
||||
assert!(err.contains("not defined"), "error was: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solve_macro_unknown_var_errors() {
|
||||
let mut i = solve_interp();
|
||||
let err = i.exec_line("let bad = solve!(y, square)").unwrap_err();
|
||||
let err = i.exec("let bad = solve!(y, square)").unwrap_err();
|
||||
assert!(err.contains("not a parameter"), "error was: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_form_parses() {
|
||||
let mut i = solve_interp();
|
||||
i.exec_line("let invsq(out) = x where square(x) = out").unwrap();
|
||||
i.exec("let invsq(out) = x where square(x) = out").unwrap();
|
||||
let def = &i.solved_fns["invsq"];
|
||||
assert_eq!(def.source_fn, "square");
|
||||
assert_eq!(def.solve_param_idx, 0);
|
||||
|
|
@ -50,24 +50,24 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn math_form_result_not_first_errors() {
|
||||
let mut i = solve_interp();
|
||||
let err = i.exec_line("let bad(x) = x where square(x) = out").unwrap_err();
|
||||
let err = i.exec("let bad(x) = x where square(x) = out").unwrap_err();
|
||||
assert!(err.contains("first parameter"), "error was: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_form_mismatched_params_errors() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn f(a, b) { a + b }").unwrap();
|
||||
let err = i.exec_line("let bad(out, c) = a where f(a, b) = out").unwrap_err();
|
||||
i.exec("fn f(a, b) { a + b }").unwrap();
|
||||
let err = i.exec("let bad(out, c) = a where f(a, b) = out").unwrap_err();
|
||||
assert!(err.contains("parameters"), "error was: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lc_tank_inversion() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn f0(l, c) { 1 / (2 * pi * sqrt(l * c)) }").unwrap();
|
||||
i.exec_line("let lfreq = solve!(l, f0)").unwrap();
|
||||
let v = i.eval_expr_str("lfreq(1000000, 1 / 1000000000)").unwrap();
|
||||
i.exec("fn f0(l, c) { 1 / (2 * pi * sqrt(l * c)) }").unwrap();
|
||||
i.exec("let lfreq = solve!(l, f0)").unwrap();
|
||||
let v = i.eval("lfreq(1000000, 1 / 1000000000)").unwrap();
|
||||
let got = match v { Value::Number(n) => n, _ => panic!("not a number") };
|
||||
let pi = std::f64::consts::PI;
|
||||
let f = 1_000_000.0f64;
|
||||
|
|
@ -79,11 +79,11 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn math_form_and_macro_agree() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn f0(l, c) { 1 / (2 * pi * sqrt(l * c)) }").unwrap();
|
||||
i.exec_line("let a = solve!(l, f0)").unwrap();
|
||||
i.exec_line("let b(freq, c) = l where f0(l, c) = freq").unwrap();
|
||||
let av = i.eval_expr_str("a(1000000, 1 / 1000000000)").unwrap();
|
||||
let bv = i.eval_expr_str("b(1000000, 1 / 1000000000)").unwrap();
|
||||
i.exec("fn f0(l, c) { 1 / (2 * pi * sqrt(l * c)) }").unwrap();
|
||||
i.exec("let a = solve!(l, f0)").unwrap();
|
||||
i.exec("let b(freq, c) = l where f0(l, c) = freq").unwrap();
|
||||
let av = i.eval("a(1000000, 1 / 1000000000)").unwrap();
|
||||
let bv = i.eval("b(1000000, 1 / 1000000000)").unwrap();
|
||||
let (an, bn) = match (av, bv) {
|
||||
(Value::Number(a), Value::Number(b)) => (a, b),
|
||||
_ => panic!("not numbers"),
|
||||
|
|
@ -94,9 +94,9 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn non_convergent_errors() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn flat(x) { 42 }").unwrap();
|
||||
i.exec_line("let inv = solve!(x, flat)").unwrap();
|
||||
let err = i.eval_expr_str("inv(10)").unwrap_err();
|
||||
i.exec("fn flat(x) { 42 }").unwrap();
|
||||
i.exec("let inv = solve!(x, flat)").unwrap();
|
||||
let err = i.eval("inv(10)").unwrap_err();
|
||||
assert!(
|
||||
err.contains("flat derivative") || err.contains("did not converge"),
|
||||
"unexpected error: {}", err
|
||||
|
|
@ -106,17 +106,17 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn solve_macro_outside_let_errors() {
|
||||
let mut i = solve_interp();
|
||||
let err = i.eval_expr_str("solve!(x, square)").unwrap_err();
|
||||
let err = i.eval("solve!(x, square)").unwrap_err();
|
||||
assert!(err.contains("right-hand side"), "error was: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solve_through_typed_source_fn() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
i.exec_line("fn f0(l: H, c: F) -> Hz {\n return 1 / ((2 * pi) * (sqrt(l * c)))\n}").unwrap();
|
||||
i.exec_line("let L_solved = solve!(l, f0)").unwrap();
|
||||
let v = i.eval_expr_str("L_solved(2600, 1nF)").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
i.exec("fn f0(l: H, c: F) -> Hz {\n return 1 / ((2 * pi) * (sqrt(l * c)))\n}").unwrap();
|
||||
i.exec("let L_solved = solve!(l, f0)").unwrap();
|
||||
let v = i.eval("L_solved(2600, 1nF)").unwrap();
|
||||
let n = match v {
|
||||
Value::Number(n) => n,
|
||||
Value::Array(ref a) if a.len() == 2 => match &a[0] {
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn spice_off_by_default() {
|
||||
let mut i = Interpreter::new();
|
||||
assert!(i.eval_expr_str("100n").is_err());
|
||||
assert!(i.eval("100n").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spice_prefix_only() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("100n").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("100n").unwrap();
|
||||
let (n, u) = match v {
|
||||
Value::Spice(n, u) => (n, u),
|
||||
Value::Array(a) if a.len() == 2 => match (&a[0], &a[1]) {
|
||||
|
|
@ -29,155 +29,155 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn spice_prefix_with_unit() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("100nF").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("100nF").unwrap();
|
||||
assert_eq!(v.display(), "100NF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spice_unit_only_no_prefix() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("80Hz").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("80Hz").unwrap();
|
||||
assert_eq!(v.display(), "80HZ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spice_micro_sign() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("10µF").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("10µF").unwrap();
|
||||
assert_eq!(v.display(), "10UF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spice_arithmetic_preserves_unit() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("100nF + 1nF").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("100nF + 1nF").unwrap();
|
||||
assert_eq!(v.display(), "101NF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spice_cross_magnitude_renormalization() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("2500nF").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("2500nF").unwrap();
|
||||
assert_eq!(v.display(), "2.5UF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spice_scalar_op_preserves_unit() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("100nF * 2").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("100nF * 2").unwrap();
|
||||
assert_eq!(v.display(), "200NF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spice_unrecognized_suffix_falls_back_to_implicit_mul() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("2pi").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("2pi").unwrap();
|
||||
match v { Value::Number(n) => assert!(approx(n, 2.0 * std::f64::consts::PI)), _ => panic!() }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spice_display_small_value() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("0.5nF").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("0.5nF").unwrap();
|
||||
assert_eq!(v.display(), "500PF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spice_negative_literal() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("-100nF").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("-100nF").unwrap();
|
||||
assert_eq!(v.display(), "-100NF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spice_plain_number_display_unchanged() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("1 + 1").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("1 + 1").unwrap();
|
||||
assert_eq!(v.display(), "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_mul_different_labels_concatenates() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
i.exec_line("let a = 2F").unwrap();
|
||||
i.exec_line("let b = 3H").unwrap();
|
||||
let v = i.eval_expr_str("a * b").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
i.exec("let a = 2F").unwrap();
|
||||
i.exec("let b = 3H").unwrap();
|
||||
let v = i.eval("a * b").unwrap();
|
||||
assert_eq!(v.display(), "6 F·H");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_div_cancels_to_plain_number() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("6F / 3F").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("6F / 3F").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_div_different_labels() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("6F / 2H").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("6F / 2H").unwrap();
|
||||
assert_eq!(v.display(), "3 F/H");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_add_mismatched_strips() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("1F + 2H").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("1F + 2H").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 3.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_add_same_label_preserves() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let v = i.eval_expr_str("1F + 2F").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let v = i.eval("1F + 2F").unwrap();
|
||||
assert_eq!(v.display(), "3F");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_annotation_on_let() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
i.exec_line("let x: F = 22n").unwrap();
|
||||
let v = i.eval_expr_str("x").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
i.exec("let x: F = 22n").unwrap();
|
||||
let v = i.eval("x").unwrap();
|
||||
assert_eq!(v.display(), "22NF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_annotation_overrides_rhs_unit() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
i.exec_line("let x: F = 22nH").unwrap();
|
||||
assert_eq!(i.eval_expr_str("x").unwrap().display(), "22NF");
|
||||
i.exec("use spice").unwrap();
|
||||
i.exec("let x: F = 22nH").unwrap();
|
||||
assert_eq!(i.eval("x").unwrap().display(), "22NF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_annotation_wraps_plain_number() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let x: H = 5").unwrap();
|
||||
assert_eq!(i.eval_expr_str("x").unwrap().display(), "5H");
|
||||
i.exec("let x: H = 5").unwrap();
|
||||
assert_eq!(i.eval("x").unwrap().display(), "5H");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spice_lc_tank_use_case() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
i.exec_line("fn L(f, c) {\n let b = (2 * pi * f)\n return 1 / ((b*b) * c)\n}").unwrap();
|
||||
let v = i.eval_expr_str("L(2600, 1nF)").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
i.exec("fn L(f, c) {\n let b = (2 * pi * f)\n return 1 / ((b*b) * c)\n}").unwrap();
|
||||
let v = i.eval("L(2600, 1nF)").unwrap();
|
||||
let n = match v {
|
||||
Value::Number(n) => n,
|
||||
Value::Spice(n, _) => n,
|
||||
|
|
|
|||
|
|
@ -6,47 +6,47 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn struct_literal_and_field_read() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let p = {x: 1, y: 2}").unwrap();
|
||||
assert_eq!(i.eval_expr_str("p.x").unwrap().display(), "1");
|
||||
assert_eq!(i.eval_expr_str("p.y").unwrap().display(), "2");
|
||||
i.exec("let p = {x: 1, y: 2}").unwrap();
|
||||
assert_eq!(i.eval("p.x").unwrap().display(), "1");
|
||||
assert_eq!(i.eval("p.y").unwrap().display(), "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_field_write() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let p = {x: 1, y: 2}").unwrap();
|
||||
i.exec_line("p.x = 99").unwrap();
|
||||
assert_eq!(i.eval_expr_str("p.x").unwrap().display(), "99");
|
||||
i.exec("let p = {x: 1, y: 2}").unwrap();
|
||||
i.exec("p.x = 99").unwrap();
|
||||
assert_eq!(i.eval("p.x").unwrap().display(), "99");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_nested_field_write() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let n = {pos: {x: 1, y: 2}, r: 5}").unwrap();
|
||||
i.exec_line("n.pos.x = 42").unwrap();
|
||||
assert_eq!(i.eval_expr_str("n.pos.x").unwrap().display(), "42");
|
||||
assert_eq!(i.eval_expr_str("n.r").unwrap().display(), "5");
|
||||
i.exec("let n = {pos: {x: 1, y: 2}, r: 5}").unwrap();
|
||||
i.exec("n.pos.x = 42").unwrap();
|
||||
assert_eq!(i.eval("n.pos.x").unwrap().display(), "42");
|
||||
assert_eq!(i.eval("n.r").unwrap().display(), "5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_in_array_field_mutates_through_index() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let nodes = [{x: 1, r: 10}, {x: 2, r: 20}]").unwrap();
|
||||
i.exec_line("nodes[1].r = 99").unwrap();
|
||||
assert_eq!(i.eval_expr_str("nodes[1].r").unwrap().display(), "99");
|
||||
assert_eq!(i.eval_expr_str("nodes[0].r").unwrap().display(), "10");
|
||||
i.exec("let nodes = [{x: 1, r: 10}, {x: 2, r: 20}]").unwrap();
|
||||
i.exec("nodes[1].r = 99").unwrap();
|
||||
assert_eq!(i.eval("nodes[1].r").unwrap().display(), "99");
|
||||
assert_eq!(i.eval("nodes[0].r").unwrap().display(), "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_field_unknown_errors() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let p = {x: 1}").unwrap();
|
||||
assert!(i.eval_expr_str("p.missing").is_err());
|
||||
i.exec("let p = {x: 1}").unwrap();
|
||||
assert!(i.eval("p.missing").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_display_formats_in_order() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("{name: \"a\", value: 5}").unwrap();
|
||||
let v = i.eval("{name: \"a\", value: 5}").unwrap();
|
||||
assert_eq!(v.display(), "{name: \"a\", value: 5}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ use super::helpers::*;
|
|||
fn cell_address_case_insensitive_parse() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_table("budget", vec![vec!["7".into()]]);
|
||||
let v = i.eval_expr_str("@BUDGET:a1").unwrap();
|
||||
let v = i.eval("@BUDGET:a1").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 7.0));
|
||||
}
|
||||
|
||||
|
|
@ -53,9 +53,9 @@ use super::helpers::*;
|
|||
vec!["10".into(), "20".into()],
|
||||
vec!["30".into(), "40".into()],
|
||||
]);
|
||||
let v = i.eval_expr_str("@Budget:A1").unwrap();
|
||||
let v = i.eval("@Budget:A1").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 10.0));
|
||||
let v = i.eval_expr_str("@Budget:B2").unwrap();
|
||||
let v = i.eval("@Budget:B2").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 40.0));
|
||||
}
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ use super::helpers::*;
|
|||
fn read_cell_str() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_table("t", vec![vec!["hello".into(), "world".into()]]);
|
||||
let v = i.eval_expr_str("@t:A1").unwrap();
|
||||
let v = i.eval("@t:A1").unwrap();
|
||||
assert!(matches!(v, Value::Str(ref s) if s == "hello"));
|
||||
}
|
||||
|
||||
|
|
@ -71,28 +71,28 @@ use super::helpers::*;
|
|||
fn cell_arithmetic() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_table("b", vec![vec!["10".into(), "20".into()]]);
|
||||
let v = i.eval_expr_str("@b:A1 + @b:B1").unwrap();
|
||||
let v = i.eval("@b:A1 + @b:B1").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 30.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cell_ref_unknown_table_errors() {
|
||||
let mut i = Interpreter::new();
|
||||
assert!(i.eval_expr_str("@Nope:A1").is_err());
|
||||
assert!(i.eval("@Nope:A1").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cell_ref_out_of_bounds_errors() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_table("t", vec![vec!["1".into()]]);
|
||||
assert!(i.eval_expr_str("@t:Z99").is_err());
|
||||
assert!(i.eval("@t:Z99").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whole_table_snapshot() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_table("b", vec![vec!["1".into(), "2".into()], vec!["3".into(), "4".into()]]);
|
||||
let v = i.eval_expr_str("@b").unwrap();
|
||||
let v = i.eval("@b").unwrap();
|
||||
let outer = match v { Value::Array(a) => a, _ => panic!("not array") };
|
||||
assert_eq!(outer.len(), 2);
|
||||
let first = match &outer[0] { Value::Array(a) => a, _ => panic!("not array") };
|
||||
|
|
@ -104,7 +104,7 @@ use super::helpers::*;
|
|||
fn cross_block_qualified_ref() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_table("second::local", vec![vec!["7".into()]]);
|
||||
let v = i.eval_expr_str("@second::local:A1").unwrap();
|
||||
let v = i.eval("@second::local:A1").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 7.0));
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ use super::helpers::*;
|
|||
let mut i = Interpreter::new();
|
||||
i.register_table("second::local", vec![vec!["7".into()]]);
|
||||
i.set_current_block(Some("second"));
|
||||
let v = i.eval_expr_str("@local:A1").unwrap();
|
||||
let v = i.eval("@local:A1").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 7.0));
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ use super::helpers::*;
|
|||
vec!["4".into(), "5".into(), "6".into()],
|
||||
vec!["7".into(), "8".into(), "9".into()],
|
||||
]);
|
||||
let v = i.eval_expr_str("@b:A1:B2").unwrap();
|
||||
let v = i.eval("@b:A1:B2").unwrap();
|
||||
let outer = match v { Value::Array(a) => a, _ => panic!() };
|
||||
assert_eq!(outer.len(), 2);
|
||||
let row0 = match &outer[0] { Value::Array(a) => a, _ => panic!() };
|
||||
|
|
@ -153,7 +153,7 @@ use super::helpers::*;
|
|||
vec!["1".into(), "2".into()],
|
||||
vec!["3".into(), "4".into()],
|
||||
]);
|
||||
let v = i.eval_expr_str("@b[A1:B2]").unwrap();
|
||||
let v = i.eval("@b[A1:B2]").unwrap();
|
||||
let outer = match v { Value::Array(a) => a, _ => panic!() };
|
||||
assert_eq!(outer.len(), 2);
|
||||
}
|
||||
|
|
@ -162,8 +162,8 @@ use super::helpers::*;
|
|||
fn cell_assign_mutates_table() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_table("b", vec![vec!["0".into(), "0".into()]]);
|
||||
i.exec_line("@b:A1 = 42").unwrap();
|
||||
let v = i.eval_expr_str("@b:A1").unwrap();
|
||||
i.exec("@b:A1 = 42").unwrap();
|
||||
let v = i.eval("@b:A1").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 42.0));
|
||||
}
|
||||
|
||||
|
|
@ -171,7 +171,7 @@ use super::helpers::*;
|
|||
fn cell_assign_logs_write() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_table("b", vec![vec!["0".into()]]);
|
||||
i.exec_line("@b:A1 = 99").unwrap();
|
||||
i.exec("@b:A1 = 99").unwrap();
|
||||
let writes = i.drain_table_writes();
|
||||
assert_eq!(writes.len(), 1);
|
||||
assert_eq!(writes[0].table_key, "b");
|
||||
|
|
@ -183,7 +183,7 @@ use super::helpers::*;
|
|||
fn cell_assign_drain_is_idempotent() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_table("b", vec![vec!["0".into()]]);
|
||||
i.exec_line("@b:A1 = 1").unwrap();
|
||||
i.exec("@b:A1 = 1").unwrap();
|
||||
let first = i.drain_table_writes();
|
||||
assert_eq!(first.len(), 1);
|
||||
let second = i.drain_table_writes();
|
||||
|
|
@ -194,12 +194,12 @@ use super::helpers::*;
|
|||
fn cell_assign_rejects_whole_table_target() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_table("b", vec![vec!["0".into()]]);
|
||||
assert!(i.exec_line("@b = 1").is_err());
|
||||
assert!(i.exec("@b = 1").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cell_assign_rejects_range_target() {
|
||||
let mut i = Interpreter::new();
|
||||
i.register_table("b", vec![vec!["0".into(), "0".into()]]);
|
||||
assert!(i.exec_line("@b:A1:B1 = 1").is_err());
|
||||
assert!(i.exec("@b:A1:B1 = 1").is_err());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn let_with_array_type_annotation() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let pos: arr = [1, 2, 3, 4]").unwrap();
|
||||
i.exec("let pos: arr = [1, 2, 3, 4]").unwrap();
|
||||
let v = i.get_var("pos").unwrap();
|
||||
assert!(matches!(v, Value::Array(_)));
|
||||
}
|
||||
|
|
@ -129,7 +129,7 @@ use super::helpers::*;
|
|||
fn let_array_type_aliases() {
|
||||
for ty in ["arr", "array", "vec", "list"] {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line(&format!("let x: {} = [1, 2]", ty)).unwrap();
|
||||
i.exec(&format!("let x: {} = [1, 2]", ty)).unwrap();
|
||||
assert!(matches!(i.get_var("x").unwrap(), Value::Array(_)),
|
||||
"alias {} failed", ty);
|
||||
}
|
||||
|
|
@ -138,8 +138,8 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn fn_return_type_arr_accepts_array() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn which(degree) -> arr {\n return [0, 0]\n}").unwrap();
|
||||
let v = i.eval_expr_str("which(5)").unwrap();
|
||||
i.exec("fn which(degree) -> arr {\n return [0, 0]\n}").unwrap();
|
||||
let v = i.eval("which(5)").unwrap();
|
||||
match v {
|
||||
Value::Array(a) => assert_eq!(a.len(), 2),
|
||||
other => panic!("expected array, got {:?}", other),
|
||||
|
|
@ -150,8 +150,8 @@ use super::helpers::*;
|
|||
fn fn_return_type_array_aliases() {
|
||||
let mut i = Interpreter::new();
|
||||
for ty in ["arr", "array", "vec", "list"] {
|
||||
i.exec_line(&format!("fn f_{}() -> {} {{ return [1, 2, 3] }}", ty, ty)).unwrap();
|
||||
let v = i.eval_expr_str(&format!("f_{}()", ty)).unwrap();
|
||||
i.exec(&format!("fn f_{}() -> {} {{ return [1, 2, 3] }}", ty, ty)).unwrap();
|
||||
let v = i.eval(&format!("f_{}()", ty)).unwrap();
|
||||
assert!(matches!(v, Value::Array(_)), "{} return type rejected", ty);
|
||||
}
|
||||
}
|
||||
|
|
@ -159,47 +159,47 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn fn_return_type_null_accepts_void() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn noop() -> null { }").unwrap();
|
||||
let v = i.eval_expr_str("noop()").unwrap();
|
||||
i.exec("fn noop() -> null { }").unwrap();
|
||||
let v = i.eval("noop()").unwrap();
|
||||
assert!(matches!(v, Value::Void));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_return_type_struct_accepts_struct() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn make() -> struct {\n return {x: 1, y: 2}\n}").unwrap();
|
||||
let v = i.eval_expr_str("make()").unwrap();
|
||||
i.exec("fn make() -> struct {\n return {x: 1, y: 2}\n}").unwrap();
|
||||
let v = i.eval("make()").unwrap();
|
||||
assert!(matches!(v, Value::Struct(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_param_type_wraps_arg_on_entry() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn f(c: F) { return c }").unwrap();
|
||||
let v = i.eval_expr_str("f(5)").unwrap();
|
||||
i.exec("fn f(c: F) { return c }").unwrap();
|
||||
let v = i.eval("f(5)").unwrap();
|
||||
assert_eq!(v.display(), "5F");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_return_type_overrides_algebra() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
i.exec_line("fn ry(c: F, l: H) -> ohm { return l * c }").unwrap();
|
||||
let v = i.eval_expr_str("ry(2, 3)").unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
i.exec("fn ry(c: F, l: H) -> ohm { return l * c }").unwrap();
|
||||
let v = i.eval("ry(2, 3)").unwrap();
|
||||
assert_eq!(v.display(), "6ohm");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_return_type_tags_raw_result() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn square(x) -> V { x * x }").unwrap();
|
||||
let v = i.eval_expr_str("square(4)").unwrap();
|
||||
i.exec("fn square(x) -> V { x * x }").unwrap();
|
||||
let v = i.eval("square(4)").unwrap();
|
||||
assert_eq!(v.display(), "16V");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_annotation_accepts_decimal() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let x: float = 0.25f").unwrap();
|
||||
assert_eq!(i.eval_expr_str("x").unwrap().display(), "0.25");
|
||||
i.exec("let x: float = 0.25f").unwrap();
|
||||
assert_eq!(i.eval("x").unwrap().display(), "0.25");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ use super::helpers::*;
|
|||
#[test]
|
||||
fn spice_literal_is_distinct_from_array_literal() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("use spice").unwrap();
|
||||
let spice = i.eval_expr_str("1nF").unwrap();
|
||||
let arr = i.eval_expr_str(r#"[1, "nF"]"#).unwrap();
|
||||
i.exec("use spice").unwrap();
|
||||
let spice = i.eval("1nF").unwrap();
|
||||
let arr = i.eval(r#"[1, "nF"]"#).unwrap();
|
||||
assert!(matches!(spice, Value::Spice(_, _)));
|
||||
assert!(matches!(arr, Value::Array(_)));
|
||||
assert_ne!(spice.display(), arr.display());
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ fn main() {{
|
|||
list_bindings(&interp);
|
||||
continue;
|
||||
}}
|
||||
match interp.exec_line(trimmed) {{
|
||||
match interp.exec(trimmed) {{
|
||||
Ok(Some(v)) => println!("{{}}", v.display()),
|
||||
Ok(None) => {{}}
|
||||
Err(e) => eprintln!("error: {{}}", e),
|
||||
|
|
@ -159,7 +159,7 @@ fn main() {{
|
|||
fn load_block(interp: &mut Interpreter, source: &str) {{
|
||||
let body = strip_front_matter(source);
|
||||
for line in body.lines() {{
|
||||
let _ = interp.exec_line(line);
|
||||
let _ = interp.exec(line);
|
||||
}}
|
||||
}}
|
||||
|
||||
|
|
@ -207,7 +207,7 @@ fn lib_rs(block_files: &[crate::sidecar::BlockFile]) -> String {
|
|||
//! Example:
|
||||
//! ```no_run
|
||||
//! let mut interp = my_note::load();
|
||||
//! let v = interp.exec_line("my_fn(1, 2, 3)").unwrap();
|
||||
//! let v = interp.exec("my_fn(1, 2, 3)").unwrap();
|
||||
//! ```
|
||||
|
||||
use acord_core::interp::Interpreter;
|
||||
|
|
@ -223,7 +223,7 @@ pub fn load() -> Interpreter {{
|
|||
fn load_block(interp: &mut Interpreter, source: &str) {{
|
||||
let body = strip_front_matter(source);
|
||||
for line in body.lines() {{
|
||||
let _ = interp.exec_line(line);
|
||||
let _ = interp.exec(line);
|
||||
}}
|
||||
}}
|
||||
|
||||
|
|
@ -328,7 +328,7 @@ Add this crate to your `Cargo.toml` as a path dependency, then:
|
|||
```rust
|
||||
use {name}::load;
|
||||
let mut interp = load();
|
||||
let v = interp.exec_line("my_fn(1, 2, 3)").unwrap();
|
||||
let v = interp.exec("my_fn(1, 2, 3)").unwrap();
|
||||
println!("{{}}", v.unwrap().display());
|
||||
```
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue