use openjd_expr::*;
fn host_lib_empty() -> std::sync::Arc<FunctionLibrary> {
FunctionLibrary::for_profile(
&ExprProfile::current()
.with_host_context(HostContext::with_rules(Vec::<PathMappingRule>::new())),
)
}
#[allow(dead_code)]
fn eval(expr: &str) -> ExprValue {
ParsedExpression::new(expr)
.and_then(|p| p.evaluate(&SymbolTable::new()))
.unwrap()
}
fn eval_with_host_context(expr: &str) -> ExprValue {
let parsed = ParsedExpression::new(expr).unwrap();
let st = SymbolTable::new();
let symtabs = [&st];
let lib = host_lib_empty();
parsed.with_library(&lib).evaluate(&symtabs).unwrap()
}
#[allow(dead_code)]
fn eval_with_host_context_fails(expr: &str) -> bool {
let parsed = ParsedExpression::new(expr).unwrap();
let st = SymbolTable::new();
let symtabs = [&st];
let lib = host_lib_empty();
parsed.with_library(&lib).evaluate(&symtabs).is_err()
}
#[test]
fn path_functions_available_without_host_context() {
assert!(ParsedExpression::new("path('/a/b').name")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.is_ok());
}
#[test]
fn string_functions_available() {
assert!(ParsedExpression::new("'hello'.upper()")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.is_ok());
}
#[test]
fn math_functions_available() {
assert!(ParsedExpression::new("abs(-5)")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.is_ok());
}
#[test]
fn repr_functions_available() {
assert!(ParsedExpression::new("repr_py(42)")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.is_ok());
}
#[test]
fn regex_functions_available() {
assert!(ParsedExpression::new(r"re_search('abc', r'\w+')")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.is_ok());
}
#[test]
fn list_functions_available() {
assert!(ParsedExpression::new("sorted([3, 1, 2])")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.is_ok());
}
#[test]
fn conversion_functions_available() {
assert!(ParsedExpression::new("int('42')")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.is_ok());
}
#[test]
fn default_library_no_host_context() {
let lib = FunctionLibrary::for_profile(&ExprProfile::current());
assert!(!lib.host_context_enabled);
}
#[test]
fn with_host_context_returns_new_library() {
let base = FunctionLibrary::for_profile(&ExprProfile::current());
let with_host = host_lib_empty();
assert!(with_host.host_context_enabled);
assert!(!base.host_context_enabled);
}
#[test]
fn with_host_context_chaining() {
let lib = host_lib_empty();
assert!(lib.host_context_enabled);
}
#[test]
fn not_available_without_host_context() {
let e = ParsedExpression::new("apply_path_mapping('/path')")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.unwrap_err()
.to_string();
assert!(
e.contains(
&[
"Unknown function: 'apply_path_mapping'\n",
" apply_path_mapping('/path')\n",
" ^~~~~~~~~~~~~~~~~~~~~~~~~~~"
]
.concat()
),
"got:\n{e}"
);
}
#[test]
fn not_available_with_default_library() {
let e = ParsedExpression::new("apply_path_mapping('/path')")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.unwrap_err()
.to_string();
assert!(
e.contains("Unknown function: 'apply_path_mapping'"),
"got:\n{e}"
);
}
#[test]
fn available_with_host_context() {
let r = eval_with_host_context("apply_path_mapping('/some/path')");
assert!(matches!(r, ExprValue::Path { .. }));
}
#[test]
fn method_syntax_without_host_context() {
let mut st = SymbolTable::new();
st.set("P", ExprValue::String("/path".into())).unwrap();
let e = ParsedExpression::new("P.apply_path_mapping()")
.and_then(|p| p.evaluate(&st))
.unwrap_err()
.to_string();
assert!(
e.contains(
&[
"Unknown function: 'apply_path_mapping'\n",
" P.apply_path_mapping()\n",
" ~~^~~~~~~~~~~~~~~~~~~~"
]
.concat()
),
"got:\n{e}"
);
}
#[test]
fn method_syntax_with_host_context() {
let parsed = ParsedExpression::new("P.apply_path_mapping()").unwrap();
let mut st = SymbolTable::new();
st.set("P", ExprValue::String("/some/path".into())).unwrap();
let lib = host_lib_empty();
let r = parsed.with_library(&lib).evaluate(&[&st]).unwrap();
assert!(matches!(r, ExprValue::Path { .. }));
}
#[test]
fn with_path_mapping_rules() {
let rule = PathMappingRule {
source_path_format: PathFormat::Posix,
source_path: "/old".into(),
destination_path: "/new".into(),
};
let mut st = SymbolTable::new();
st.set("P", ExprValue::String("/old/file.txt".into()))
.unwrap();
let lib = FunctionLibrary::for_profile(
&ExprProfile::current().with_host_context(HostContext::with_rules(vec![rule])),
);
let parsed = ParsedExpression::new("P.apply_path_mapping()").unwrap();
let symtabs = [&st];
let r = parsed
.with_library(&lib)
.with_path_format(PathFormat::Posix)
.evaluate(&symtabs)
.unwrap();
assert_eq!(r.to_display_string(), "/new/file.txt");
}
#[test]
fn unmatched_path_unchanged() {
let rule = PathMappingRule {
source_path_format: PathFormat::Posix,
source_path: "/old".into(),
destination_path: "/new".into(),
};
let mut st = SymbolTable::new();
st.set("P", ExprValue::String("/other/file.txt".into()))
.unwrap();
let lib = FunctionLibrary::for_profile(
&ExprProfile::current().with_host_context(HostContext::with_rules(vec![rule])),
);
let parsed = ParsedExpression::new("P.apply_path_mapping()").unwrap();
let symtabs = [&st];
let r = parsed
.with_library(&lib)
.with_path_format(PathFormat::Posix)
.evaluate(&symtabs)
.unwrap();
assert_eq!(r.to_display_string(), "/other/file.txt");
}
#[test]
fn no_rules_returns_path_unchanged() {
let lib = host_lib_empty();
let parsed = ParsedExpression::new("apply_path_mapping('/any/path')").unwrap();
let st = SymbolTable::new();
let symtabs = [&st];
let r = parsed
.with_library(&lib)
.with_path_format(PathFormat::Posix)
.evaluate(&symtabs)
.unwrap();
assert_eq!(r.to_display_string(), "/any/path");
}
#[test]
fn submission_functions_available() {
assert!(ParsedExpression::new("len('hello')")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.is_ok());
assert!(ParsedExpression::new("sorted([3, 1, 2])")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.is_ok());
assert!(ParsedExpression::new("path('/a/b').name")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.is_ok());
}
#[test]
fn submission_arithmetic_value() {
let r = ParsedExpression::new("1 + 2")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.unwrap();
assert_eq!(r, ExprValue::Int(3));
}
#[test]
fn submission_min_value() {
let r = ParsedExpression::new("min(5, 3)")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.unwrap();
assert_eq!(r, ExprValue::Int(3));
}
#[test]
fn submission_upper_value() {
let r = ParsedExpression::new("upper('hello')")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.unwrap();
assert_eq!(r.to_display_string(), "HELLO");
}
#[test]
fn submission_len_value() {
let r = ParsedExpression::new("len('test')")
.and_then(|p| p.evaluate(&SymbolTable::new()))
.unwrap();
assert_eq!(r, ExprValue::Int(4));
}
#[test]
fn path_stem_without_host_context() {
let mut st = SymbolTable::new();
st.set(
"P",
ExprValue::new_path("/projects/render.exr", PathFormat::Posix),
)
.unwrap();
let parsed = ParsedExpression::new("P.stem").unwrap();
let symtabs = [&st];
let r = parsed
.with_path_format(PathFormat::Posix)
.evaluate(&symtabs)
.unwrap();
assert_eq!(r.to_display_string(), "render");
}
#[test]
fn path_suffix_without_host_context() {
let mut st = SymbolTable::new();
st.set(
"P",
ExprValue::new_path("/projects/render.exr", PathFormat::Posix),
)
.unwrap();
let parsed = ParsedExpression::new("P.suffix").unwrap();
let symtabs = [&st];
let r = parsed
.with_path_format(PathFormat::Posix)
.evaluate(&symtabs)
.unwrap();
assert_eq!(r.to_display_string(), ".exr");
}
#[test]
fn path_with_suffix_without_host_context() {
let mut st = SymbolTable::new();
st.set(
"P",
ExprValue::new_path("/projects/render.exr", PathFormat::Posix),
)
.unwrap();
let parsed = ParsedExpression::new("with_suffix(P, '.png')").unwrap();
let symtabs = [&st];
let r = parsed
.with_path_format(PathFormat::Posix)
.evaluate(&symtabs)
.unwrap();
assert!(
r.to_display_string().ends_with("render.png"),
"got: {}",
r.to_display_string()
);
}
#[test]
fn function_syntax_with_path_mapping_rules() {
let rule = PathMappingRule {
source_path_format: PathFormat::Posix,
source_path: "/old/path".into(),
destination_path: "/new/path".into(),
};
let lib = FunctionLibrary::for_profile(
&ExprProfile::current().with_host_context(HostContext::with_rules(vec![rule])),
);
let parsed = ParsedExpression::new("apply_path_mapping('/old/path/file.txt')").unwrap();
let st = SymbolTable::new();
let symtabs = [&st];
let r = parsed
.with_library(&lib)
.with_path_format(PathFormat::Posix)
.evaluate(&symtabs)
.unwrap();
assert_eq!(r.to_display_string(), "/new/path/file.txt");
}
#[test]
fn function_syntax_unmatched_path_unchanged() {
let rule = PathMappingRule {
source_path_format: PathFormat::Posix,
source_path: "/specific/path".into(),
destination_path: "/mapped/path".into(),
};
let lib = FunctionLibrary::for_profile(
&ExprProfile::current().with_host_context(HostContext::with_rules(vec![rule])),
);
let parsed = ParsedExpression::new("apply_path_mapping('/other/path/file.txt')").unwrap();
let st = SymbolTable::new();
let symtabs = [&st];
let r = parsed
.with_library(&lib)
.with_path_format(PathFormat::Posix)
.evaluate(&symtabs)
.unwrap();
assert_eq!(r.to_display_string(), "/other/path/file.txt");
}