use crate::error::ErrorKind;
use crate::run;
fn ri(s: &str) -> i64 {
run(s).expect("run").to_int()
}
#[test]
fn list_range_string_say_join_alphabet() {
assert_eq!(rs(r#"join "", ("a".."z");"#), "abcdefghijklmnopqrstuvwxyz");
}
#[test]
fn list_range_numeric_string_endpoints_say() {
assert_eq!(rs(r#"join ",", ("9".."11");"#), "9,10,11");
}
#[test]
fn range_hash_slice_string_keys_read() {
assert_eq!(
rs(r#"my %h = (a => 1, b => 2, c => 3, d => 4);
join ",", @h{"a".."c"};"#),
"1,2,3"
);
}
#[test]
fn range_hash_slice_string_keys_assign() {
assert_eq!(
rs(r#"my %h;
@h{"a".."c"} = (10, 20, 30);
"$h{a},$h{b},$h{c}";"#),
"10,20,30"
);
}
#[test]
fn range_hash_slice_deref_string_keys_read() {
assert_eq!(
rs(r#"my $r = {a => 1, b => 2, c => 3, d => 4};
join ",", @$r{"a".."c"};"#),
"1,2,3"
);
}
#[test]
fn range_return_list_context() {
assert_eq!(
rs(r#"fn foo { return 1..$_0; }
join ",", foo(4);"#),
"1,2,3,4"
);
}
#[test]
fn range_array_slice_reverse_range_index() {
assert_eq!(
rs(r#"my @a = (10, 20, 30, 40, 50);
join ",", @a[rev 1..3];"#),
"40,30,20"
);
}
#[test]
fn range_sub_call_args_flatten_range() {
assert_eq!(
ri(r#"fn foo { return scalar @_; }
foo(1..10);"#),
10
);
}
#[test]
fn range_precedence_below_additive() {
assert_eq!(
rs(r#"my $x = 2;
join ",", ($x..$x+3);"#),
"2,3,4,5"
);
}
#[test]
fn range_anon_array_ref_flattens() {
assert_eq!(
rs(r#"my $r = [1..5];
join ",", @$r;"#),
"1,2,3,4,5"
);
}
#[test]
fn range_anon_hash_ref_value_flattens_inner_ref() {
assert_eq!(
rs(r#"my $h = { a => [1..3] };
join ",", @{$h->{a}};"#),
"1,2,3"
);
}
#[test]
fn range_map_block_comma_list_expands() {
assert_eq!(
rs(r#"join ",", map { ($_, $_*10) } 1..3;"#),
"1,10,2,20,3,30"
);
}
#[test]
fn range_printf_list_context_args() {
assert_eq!(rs(r#"sprintf "%d %d %d", 1..3;"#), "1 2 3");
}
#[test]
fn range_scalar_flip_flop_false_is_empty_string() {
assert_eq!(rs(r#"my $x = 1..2; "[$x]";"#), "[]");
}
#[test]
fn range_descending_numeric_is_empty() {
assert_eq!(ri(r#"my @a = (10..1); 0+@a;"#), 0);
}
#[test]
fn range_anon_array_ref_reverse_range() {
assert_eq!(
rs(r#"my $r = [rev 1..5];
join ",", @$r;"#),
"5,4,3,2,1"
);
}
#[test]
fn range_constant_array_ref() {
assert_eq!(
rs(r#"use constant FOO => [1..5];
join ",", @{FOO()};"#),
"1,2,3,4,5"
);
}
fn rf(s: &str) -> f64 {
let v = run(s).expect("run");
if let Some(f) = v.as_float() {
return f;
}
if let Some(n) = v.as_integer() {
return n as f64;
}
v.to_number()
}
fn rs(s: &str) -> String {
run(s).expect("run").to_string()
}
#[test]
fn sprintf_basic_decimal() {
assert_eq!(rs(r#"sprintf "%d", 42;"#), "42");
}
#[test]
fn sprintf_padded_zero() {
assert_eq!(rs(r#"sprintf "%04d", 7;"#), "0007");
}
#[test]
fn index_finds_substring() {
assert_eq!(ri(r#"index("foobar", "bar");"#), 3);
}
#[test]
fn rindex_finds_last() {
assert_eq!(ri(r#"rindex("abab", "b");"#), 3);
}
#[test]
fn substr_two_arg() {
assert_eq!(rs(r#"substr("abcdef", 2);"#), "cdef");
}
#[test]
fn substr_three_arg() {
assert_eq!(rs(r#"substr("abcdef", 1, 3);"#), "bcd");
}
#[test]
fn hex_literal_and_hex_builtin() {
assert_eq!(ri("0xFF;"), 255);
assert_eq!(ri(r#"hex("FF");"#), 255);
}
#[test]
fn oct_literal_and_oct_builtin() {
assert_eq!(ri("010;"), 8);
assert_eq!(ri(r#"oct("10");"#), 8);
}
#[test]
fn ucfirst_lcfirst() {
assert_eq!(rs(r#"ucfirst("hello");"#), "Hello");
assert_eq!(rs(r#"lcfirst("HELLO");"#), "hELLO");
}
#[test]
fn split_space_default() {
assert_eq!(ri(r#"scalar split(" ", "a b c");"#), 3);
}
#[test]
fn grep_block_list() {
assert_eq!(ri(r#"scalar grep { $_ > 2 } (1, 2, 3, 4);"#), 2);
}
#[test]
fn map_block_double() {
assert_eq!(ri(r#"my @m = map { $_ * 2 } (1, 2, 3); $m[2];"#), 6);
}
#[test]
fn qw_word_list() {
assert_eq!(ri("scalar qw(a b c d);"), 4);
}
#[test]
fn array_slice_negative_index() {
assert_eq!(ri("my @a = (10, 20, 30); $a[-1];"), 30);
}
#[test]
fn hash_exists_delete() {
assert_eq!(ri(r#"my %h = (x => 1); exists $h{'x'} ? 1 : 0;"#), 1);
}
#[test]
fn ref_type_array() {
assert_eq!(rs(r#"ref([]);"#), "ARRAY");
}
#[test]
fn scalar_context_hash_count_string() {
let v = run(r#"my %h = (a => 1, b => 2); scalar %h;"#).expect("run");
let s = v.to_string();
assert!(
s.contains('/') || v.to_int() >= 2,
"unexpected scalar %h: {:?}",
v
);
}
#[test]
fn perl_compat_named_array_rvalue_in_scalar_assign_is_length() {
assert_eq!(ri(r#"my @y = (10, 20, 30); my $x = @y; $x"#), 3);
}
#[test]
fn perl_compat_named_hash_rvalue_in_scalar_assign_is_fill_string() {
assert_eq!(rs(r#"my %h = (a => 1, b => 2); my $x = %h; $x"#), "2/3");
}
#[test]
fn perl_compat_named_array_exists_delete() {
assert_eq!(
rs(r#"my @a = (10, 20, 30);
my $e0 = exists $a[99] ? 1 : 0;
my $e1 = exists $a[1] ? 1 : 0;
my $d = delete $a[1];
my $e2 = exists $a[1] ? 1 : 0;
$e0 . "," . $e1 . "," . $d . "," . $e2;"#,),
"0,1,20,1"
);
}
#[test]
fn perl_compat_arrow_array_exists_delete() {
assert_eq!(
rs(r#"no strict 'vars';
my $a = [10, 20, 30];
my $e0 = exists $a->[99] ? 1 : 0;
my $e1 = exists $a->[1] ? 1 : 0;
my $d = delete $a->[1];
my $e2 = exists $a->[1] ? 1 : 0;
$e0 . "," . $e1 . "," . $d . "," . $e2;"#,),
"0,1,20,1"
);
}
#[test]
fn perl_compat_arrow_array_compound_add() {
assert_eq!(
ri(r#"no strict 'vars'; my $v = [1, 2, 3]; $v->[1] += 10; $v->[1]"#),
12
);
}
#[test]
fn perl_compat_arrow_hash_compound_add_and_concat() {
assert_eq!(
ri(r#"no strict 'vars'; my $h = { a => 10 }; $h->{a} += 2; $h->{a}"#),
12
);
assert_eq!(
rs(r#"no strict 'vars'; my $h = { a => "z" }; $h->{a} .= "9"; $h->{a}"#),
"z9"
);
}
#[test]
fn perl_compat_scalar_deref_hash_string_concat_assign() {
assert_eq!(
rs(r#"my %h = (a => "z"); my $r = \%h; $$r{a} .= "9"; $h{a}"#),
"z9"
);
}
#[test]
fn unless_else_branch() {
assert_eq!(
ri("my $r = 0; unless (0) { $r = 7 } else { $r = 9 }; $r;"),
7
);
}
#[test]
fn if_elsif_else_chain() {
assert_eq!(
ri("my $r = 0; if (0) { $r = 1 } elsif (0) { $r = 2 } else { $r = 42 }; $r;"),
42
);
}
#[test]
fn for_range_sum() {
assert_eq!(ri("my $s = 0; for my $i (1..5) { $s = $s + $i; } $s;"), 15);
}
#[test]
fn compound_assign_plus() {
assert_eq!(ri("my $x = 10; $x += 32; $x;"), 42);
}
#[test]
fn compound_assign_mul() {
assert_eq!(ri("my $x = 6; $x *= 7; $x;"), 42);
}
#[test]
fn postincrement_scalar() {
assert_eq!(ri("my $i = 41; $i++; $i;"), 42);
}
#[test]
fn preincrement_scalar() {
assert_eq!(ri("my $i = 41; ++$i;"), 42);
}
#[test]
fn string_equality_eq() {
assert_eq!(ri(r#""foo" eq "foo" ? 1 : 0;"#), 1);
}
#[test]
fn string_inequality_ne() {
assert_eq!(ri(r#""a" ne "b" ? 1 : 0;"#), 1);
}
#[test]
fn numeric_and_word_ops() {
assert_eq!(ri("1 and 2 and 3;"), 3);
assert_eq!(ri("0 or 99;"), 99);
}
#[test]
fn repeat_operator_string() {
assert_eq!(rs("'-' x 5;"), "-----");
}
#[test]
fn range_in_list_context_count() {
assert_eq!(ri("my @a = (1..10); 0+@a;"), 10);
}
#[test]
fn list_range_singleton_endpoint() {
assert_eq!(rs(r#"join ",", (5..5);"#), "5");
}
#[test]
fn list_range_descending_numeric_is_empty() {
assert_eq!(ri("my @a = (5..2); 0+@a;"), 0);
assert_eq!(rs(r#"join "-", (5..2);"#), "");
}
#[test]
fn list_range_includes_negatives_and_zero() {
assert_eq!(rs(r#"join ",", (-2..1);"#), "-2,-1,0,1");
}
#[test]
fn list_range_float_endpoints_use_integer_bounds() {
assert_eq!(rs(r#"join ",", (1.2..3.7);"#), "1,2,3");
}
#[test]
fn list_range_leading_zero_strings_not_coerced_to_octal() {
assert_eq!(rs(r#"join ",", ("01".."03");"#), "01,02,03");
}
#[test]
fn list_range_double_letter_magic_increment() {
assert_eq!(rs(r#"join ",", ("aa".."ac");"#), "aa,ab,ac");
}
#[test]
fn list_range_empty_string_left_endpoint_yields_one_empty_element() {
assert_eq!(ri(r#"my @x = ("".."a"); 0+@x;"#), 1);
}
#[test]
fn nested_arithmetic_parens() {
assert_eq!(ri("((2 + 3) * (4 + 2));"), 30);
}
#[test]
fn float_compare_loose() {
assert_eq!(ri("3.0 == 3 ? 1 : 0;"), 1);
}
#[test]
fn negative_zero_add() {
assert_eq!(ri("-0 + 7;"), 7);
}
#[test]
fn backslash_array_hash_ref_alias() {
assert_eq!(
rs("my @a = (1,2,3); my $r = \\@a; ref($r)"),
"ARRAY".to_string()
);
assert_eq!(ri("my @a = (1,2,3); my $r = \\@a; $r->[0] = 99; $a[0]"), 99);
assert_eq!(
ri("my @a = (1,2,3); my $r = \\@a; my @c = @$r; $c[0] + $c[1] + $c[2]"),
6
);
assert_eq!(
rs("my %h = (a=>7); my $r = \\%h; ref($r)"),
"HASH".to_string()
);
assert_eq!(ri("my %h = (a=>7); my $r = \\%h; $r->{a}"), 7);
}
#[test]
fn scalar_deref_hashref_brace_subscript() {
assert_eq!(ri(r#"my %h=(a=>1); my $r=\%h; $$r{a}"#), 1);
}
#[test]
fn scalar_deref_aref_bracket_subscript() {
assert_eq!(ri(r#"my @a=(10,20,30); my $r=\@a; $$r[1]"#), 20);
}
#[test]
fn interpolated_string_at_scalar_aref() {
assert_eq!(
rs(r#"my $r = [1, 2, 3]; my $s = "@$r"; $s"#),
"1 2 3".to_string()
);
}
#[test]
fn pop_push_peeled_array_deref_operand() {
assert_eq!(
rs(r#"my @a = (9, 8); my $r = \@a; pop((@$r)); join "-", @a"#),
"9".to_string()
);
assert_eq!(
rs(r#"my @a = (1); my $r = \@a; push((@$r), 2); join "", @a"#),
"12".to_string()
);
}
#[test]
fn splice_offset_length_perl_rules() {
assert_eq!(
rs("my @a = (1,2,3,4,5); my @rem = splice(@a, -2); join(' ', @rem) . '|' . join(' ', @a);"),
"4 5|1 2 3"
);
assert_eq!(
rs("my @a = (1,2,3,4,5); my @rem = splice(@a, 0, -2); join(' ', @rem) . '|' . join(' ', @a);"),
"1 2 3|4 5"
);
assert_eq!(
rs("my @a = (1,2,3); my @r = splice(@a, 100); join(' ', @a) . '|' . scalar @r;"),
"1 2 3|0"
);
}
#[test]
fn perl_compat_regression_ref_deref_splice_qq_scalar_deref_subscript() {
assert_eq!(rs(r#"my @a=(1,2,3); my $r=\@a; ref($r)"#), "ARRAY");
assert_eq!(rs(r#"my %h=(a=>1); my $r=\%h; ref($r)"#), "HASH");
assert_eq!(
ri(r#"my @a=(1,2,3); my $r=\@a; my @c=@$r; $c[0]+$c[1]+$c[2]"#),
6
);
assert_eq!(ri(r#"my @a=(1,2,3); my $r=\@a; $r->[1]=42; $a[1]"#), 42);
assert_eq!(
rs(r#"my @a=(1,2,3); my @rem=splice(@a,100); join(" ",@a)."|".scalar @rem"#),
"1 2 3|0"
);
assert_eq!(
rs(r#"my @a=(1,2,3,4,5); my @rem=splice(@a,-2); join(" ",@rem)."|".join(" ",@a)"#),
"4 5|1 2 3"
);
assert_eq!(
rs(r#"my @a=(1,2,3,4,5); my @rem=splice(@a,0,-2); join(" ",@rem)."|".join(" ",@a)"#),
"1 2 3|4 5"
);
assert_eq!(
rs(r#"my $v=[1,2,3]; splice @$v,100; join "-", @$v"#),
"1-2-3"
);
assert_eq!(ri(r#"my @a=(9,8); my $r=\@a; my $x=pop @$r; $x+$a[0]"#), 17);
assert_eq!(ri(r#"my @a=(9,8); my $r=\@a; shift @$r; $a[0]"#), 8);
assert_eq!(
rs(r#"my @a=(1); my $r=\@a; push @$r,2,3; join "",@a"#),
"123"
);
assert_eq!(
rs(r#"my @a=(2); my $r=\@a; unshift @$r,1; join "-",@a"#),
"1-2"
);
assert_eq!(rs(r#"my $r=[1,2,3]; my $s="@$r\n"; $s"#), "1 2 3\n");
assert_eq!(ri(r#"my %h=(a=>1); my $r=\%h; $$r{a}"#), 1);
assert_eq!(ri(r#"my %h=(a=>1); my $r=\%h; $$r{b}=7; $h{b}"#), 7);
assert_eq!(ri(r#"my @a=(10,20,30); my $r=\@a; $$r[1]"#), 20);
}
#[test]
fn sort_numeric_guess() {
assert_eq!(ri("my @a = (3, 1, 2); $a[0] + $a[1] + $a[2];"), 6);
}
#[test]
fn reverse_array_list() {
assert_eq!(ri("my @a = (1, 2, 3); $a[0] + $a[2];"), 4);
}
#[test]
fn join_empty_separator() {
assert_eq!(rs(r#"join("", 1, 2, 3);"#), "123");
}
#[test]
fn sprintf_string_percent_s() {
assert_eq!(rs(r#"sprintf "%s-%s", "a", "b";"#), "a-b");
}
#[test]
fn ord_multibyte_first_byte_or_char() {
assert!(ri(r#"ord("Z");"#) > 0);
}
#[test]
fn chr_roundtrip_small() {
assert_eq!(ri(r#"ord(chr(33));"#), 33);
}
#[test]
fn abs_zero() {
assert_eq!(ri("abs(0);"), 0);
}
#[test]
fn sqrt_zero() {
assert_eq!(ri("sqrt(0);"), 0);
}
#[test]
fn int_truncates_negative() {
assert_eq!(ri("int(-3.9);"), -3);
}
#[test]
fn logical_xor_bitwise() {
assert_eq!(ri("0b101 ^ 0b011;"), 6);
}
#[test]
fn shift_left_if_compileable() {
assert_eq!(ri("4 >> 1;"), 2);
}
#[test]
fn diamond_operator_parses() {
crate::parse("<>").expect("parse diamond");
}
#[test]
fn stat_returns_thirteen_fields_in_scalar_context() {
assert_eq!(ri(r#"scalar stat "Cargo.toml";"#), 13);
}
#[test]
fn stat_missing_path_is_empty_list() {
assert_eq!(ri(r#"scalar stat "/no/such/path/stryke-test-xyz";"#), 0);
}
#[test]
fn glob_finds_rs_sources_under_src() {
let n = ri(r#"scalar glob "src/*.rs";"#);
assert!(
n > 0,
"glob src/*.rs should match at least one file, got {n}"
);
}
#[test]
fn glob_par_plain_list_context_count() {
let n = ri(r#"glob_par "src/*.rs";"#);
assert!(
n > 0,
"glob_par without scalar should yield array len as to_int, got {n}"
);
}
#[test]
fn glob_par_finds_rs_sources_under_src() {
let n = ri(r#"scalar glob_par "src/*.rs";"#);
assert!(
n > 0,
"glob_par src/*.rs should match at least one file, got {n}"
);
}
#[test]
fn glob_par_tree_walker_plain_matches_count() {
let program = crate::parse(r#"glob_par "src/*.rs";"#).expect("parse");
let mut interp = crate::interpreter::Interpreter::new();
let n = interp
.execute_tree(&program)
.expect("execute_tree")
.to_int();
assert!(
n > 0,
"tree-walker glob_par (plain) should match at least one file, got {n}"
);
}
#[test]
fn glob_par_tree_walker_matches_count() {
let program = crate::parse(r#"scalar glob_par "src/*.rs";"#).expect("parse");
let mut interp = crate::interpreter::Interpreter::new();
let n = interp
.execute_tree(&program)
.expect("execute_tree")
.to_int();
assert!(
n > 0,
"tree-walker glob_par should match at least one file, got {n}"
);
}
#[test]
fn ppool_collect_order_and_results() {
let n = ri(r#"
my $p = ppool(2);
$p->submit(fn { $_ * 3 }, 2);
$p->submit(fn { $_ * 3 }, 4);
my @r = $p->collect();
$r[0] + $r[1];
"#);
assert_eq!(n, 18);
}
#[test]
fn ppool_submit_optional_paren_block_postfix_for() {
let n = ri(r#"
my $p = ppool(2);
$p->submit({ $_ * 3 }, $_) for (2, 4);
my @r = $p->collect();
$r[0] + $r[1];
"#);
assert_eq!(n, 18);
}
#[test]
fn ppool_submit_single_arg_postfix_for_uses_callers_topic() {
let n = ri(r#"
my $pool = ppool(4);
my @tasks = (2, 4);
$pool->submit({ $_ * 3 }) for @tasks;
my @results = $pool->collect();
$results[0] + $results[1];
"#);
assert_eq!(n, 18);
}
#[test]
fn opendir_readdir_returns_name() {
assert_eq!(
ri(r#"opendir D, "."; my $x = readdir D; closedir D; $x ne "" ? 1 : 0;"#),
1
);
}
#[test]
fn readdir_list_context_returns_all_remaining_entries() {
let base = std::env::temp_dir().join(format!("stryke_sem_rdl_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
std::fs::create_dir_all(&base).expect("mkdir");
std::fs::write(base.join("a.txt"), b"x").expect("write");
std::fs::write(base.join("b.txt"), b"y").expect("write");
let pd = base.to_string_lossy();
let script = format!(
r#"opendir H, "{pd}" or die;
my @f = readdir H;
closedir H;
(scalar grep {{ $_ eq "a.txt" }} @f) && (scalar grep {{ $_ eq "b.txt" }} @f) ? 1 : 0"#,
);
assert_eq!(ri(&script), 1);
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn rewinddir_resets_read_position() {
assert_eq!(
ri(r#"opendir D, "."; readdir D; rewinddir D; (telldir D) == 0 ? 1 : 0;"#),
1
);
}
#[test]
fn perl_compat_opendir_finds_known_entry_in_temp_dir() {
let base = std::env::temp_dir().join(format!("stryke_sem_od_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
std::fs::create_dir_all(&base).expect("mkdir");
std::fs::write(base.join("mark.txt"), b"x").expect("write");
let pd = base.to_string_lossy();
let script = format!(
r#"opendir H, "{pd}" or die;
my @n;
for (1..16) {{
my $e = readdir H;
last unless defined $e;
push @n, $e;
}}
closedir H;
scalar grep {{ $_ eq "mark.txt" }} @n;"#,
);
assert_eq!(ri(&script), 1);
let _ = std::fs::remove_dir_all(&base);
}
#[cfg(unix)]
#[test]
fn perl_compat_readlink_symlink_target_string() {
use std::os::unix::fs::symlink;
let base = std::env::temp_dir().join(format!("stryke_sem_rl_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
std::fs::create_dir_all(&base).expect("mkdir");
let link = base.join("rl");
symlink("expected_tgt", &link).expect("symlink");
let sl = link.to_string_lossy();
assert_eq!(
ri(&format!(r#"(readlink "{sl}") eq "expected_tgt" ? 1 : 0;"#)),
1
);
let _ = std::fs::remove_dir_all(&base);
}
#[cfg(unix)]
#[test]
fn perl_compat_hard_link_reads_same_bytes() {
let dir = std::env::temp_dir();
let a = dir.join(format!("stryke_sem_hl_a_{}", std::process::id()));
let b = dir.join(format!("stryke_sem_hl_b_{}", std::process::id()));
let _ = std::fs::remove_file(&a);
let _ = std::fs::remove_file(&b);
std::fs::write(&a, b"hl").expect("write");
let sa = a.to_string_lossy();
let sb = b.to_string_lossy();
let script = format!(
r#"link "{sa}", "{sb}";
slurp "{sb}";"#,
);
assert_eq!(rs(&script), "hl");
let _ = std::fs::remove_file(&a);
let _ = std::fs::remove_file(&b);
}
#[test]
fn tell_stdout_unbuffered_slot_returns_negative_one() {
assert_eq!(ri(r#"tell STDOUT;"#), -1);
}
#[test]
fn tell_writable_open_file_reports_byte_offset() {
let dir = std::env::temp_dir();
let path = dir.join("stryke_tell_semantics_test");
let _ = std::fs::remove_file(&path);
let ps = path.to_string_lossy();
let script = format!(r#"open F, ">", "{ps}"; print F "abc"; my $p = tell F; close F; $p"#);
assert_eq!(ri(&script), 3);
let _ = std::fs::remove_file(&path);
}
#[test]
fn perl_compat_quotemeta_escapes_dot() {
assert_eq!(rs(r#"quotemeta("a.c");"#), r"a\.c");
}
#[test]
fn perl_compat_quotemeta_escapes_path_slashes() {
assert_eq!(rs(r#"quotemeta("/usr/bin");"#), r"\/usr\/bin");
}
#[test]
fn perl_compat_chomp_returns_chars_removed() {
assert_eq!(ri(r#"my $s = "ab\n"; my $n = chomp $s; $n;"#), 1);
}
#[test]
fn perl_compat_subst_replacement_expands_env_brace() {
let home = std::env::var("HOME").expect("HOME");
let script = r#"$_ = "~"; s@^~@$ENV{HOME}@; $_;"#.to_string();
assert_eq!(rs(&script), home);
}
#[test]
fn perl_compat_my_array_from_do_block_list_context() {
assert_eq!(
ri(r#"my @a = do { (10, 20, 30) };
$a[0] + $a[1] + $a[2];"#),
60
);
}
#[test]
fn perl_compat_my_hash_from_do_block_list_context() {
assert_eq!(
ri(r#"my %h = do { ("x", 3, "y", 4) };
$h{"x"} * 10 + $h{"y"};"#),
34
);
}
#[test]
fn perl_compat_array_assign_flattens_hash_to_key_value_list() {
assert_eq!(
ri(r#"my %h = ("p", 10, "q", 20);
my @a = %h;
scalar @a;"#),
4
);
}
#[test]
fn perl_compat_getc_reads_bytes_from_open_file() {
let dir = std::env::temp_dir();
let path = dir.join("stryke_getc_semantics_test");
let _ = std::fs::remove_file(&path);
std::fs::write(&path, b"mn").expect("write temp");
let ps = path.to_string_lossy();
let script = format!(
r#"open G, "<", "{ps}";
my $t = (getc G) . (getc G);
close G;
$t;"#
);
assert_eq!(rs(&script), "mn");
let _ = std::fs::remove_file(&path);
}
#[test]
fn perl_compat_join_with_custom_list_separator() {
assert_eq!(
rs(r#"no strict 'vars';
$" = ":";
join $", ("x", "y");"#),
"x:y"
);
}
#[test]
fn perl_compat_qq_array_with_custom_list_separator() {
assert_eq!(
rs(r#"no strict 'vars';
my @t = (5, 6);
$" = "_";
"@t";"#),
"5_6"
);
}
#[test]
fn perl_compat_sysseek_then_tell_on_open_file() {
let dir = std::env::temp_dir();
let path = dir.join("stryke_sysseek_semantics_test");
let _ = std::fs::remove_file(&path);
std::fs::write(&path, b"ABCDE").expect("write temp");
let ps = path.to_string_lossy();
let script = format!(
r#"open S, "<", "{ps}";
sysseek S, 3, 0;
my $n = tell S;
close S;
$n;"#
);
assert_eq!(ri(&script), 3);
let _ = std::fs::remove_file(&path);
}
#[test]
fn perl_compat_scalar_values_hash() {
assert_eq!(
ri(r#"my %g = ("x", 9, "y", 8);
scalar values %g;"#),
2
);
}
#[test]
fn perl_compat_eof_string_handle_open_vs_closed() {
let dir = std::env::temp_dir();
let path = dir.join("stryke_eof_semantics_test");
let _ = std::fs::remove_file(&path);
std::fs::write(&path, b"z").expect("write temp");
let ps = path.to_string_lossy();
let open_then = format!(
r#"open Q, "<", "{ps}";
my $a = eof("Q");
close Q;
$a;"#
);
let after_close = format!(
r#"open Q, "<", "{ps}";
close Q;
eof("Q");"#
);
assert_eq!(ri(&open_then), 0);
assert_eq!(ri(&after_close), 1);
let _ = std::fs::remove_file(&path);
}
#[test]
fn perl_compat_truncate_shortens_file_by_path() {
let dir = std::env::temp_dir();
let path = dir.join("stryke_truncate_semantics_test");
let _ = std::fs::remove_file(&path);
let ps = path.to_string_lossy();
let script = format!(
r#"open W, ">", "{ps}";
print W "abcd";
close W;
truncate "{ps}", 1;
open R, "<", "{ps}";
my $t = readline R;
close R;
length $t;"#
);
assert_eq!(ri(&script), 1);
let _ = std::fs::remove_file(&path);
}
#[test]
fn perl_compat_split_comma_with_limit() {
assert_eq!(ri(r#"scalar split(",", "u,v,w,x", 2);"#), 2);
}
#[test]
fn perl_compat_study_empty_vs_nonempty() {
assert_eq!(ri(r#"study "";"#), 0);
assert_eq!(ri(r#"study "n";"#), 1);
}
#[test]
fn perl_compat_hex_oct_builtins() {
assert_eq!(ri(r#"hex("FF");"#), 255);
assert_eq!(ri(r#"oct("10");"#), 8);
}
#[test]
fn perl_compat_eval_string_and_block() {
assert_eq!(ri(r#"eval '17 + 25';"#), 42);
assert_eq!(ri(r#"eval { 9 * 4 + 6 };"#), 42);
}
#[test]
fn perl_compat_unpack_after_pack_byte() {
assert_eq!(ri(r#"scalar unpack("C", pack("C", 88));"#), 88);
}
#[test]
fn perl_compat_filetest_s_nonempty_and_z_empty() {
let dir = std::env::temp_dir();
let nonempty = dir.join("stryke_sem_filetest_s");
let empty = dir.join("stryke_sem_filetest_z");
let _ = std::fs::remove_file(&nonempty);
let _ = std::fs::remove_file(&empty);
std::fs::write(&nonempty, b"x").expect("write");
std::fs::write(&empty, b"").expect("write");
let sn = nonempty.to_string_lossy();
let se = empty.to_string_lossy();
assert_eq!(ri(&format!(r#"-s "{sn}";"#)), 1);
assert_eq!(ri(&format!(r#"-z "{se}";"#)), 1);
let _ = std::fs::remove_file(&nonempty);
let _ = std::fs::remove_file(&empty);
}
#[test]
fn perl_compat_sleep_zero() {
assert_eq!(ri("sleep 0;"), 0);
}
#[test]
fn perl_compat_glob_txt_in_directory() {
let base = std::env::temp_dir().join(format!("stryke_sem_glob_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
std::fs::create_dir_all(&base).expect("mkdir");
std::fs::write(base.join("p.txt"), b"1").expect("write");
std::fs::write(base.join("q.txt"), b"2").expect("write");
std::fs::write(base.join("r.rst"), b"3").expect("write");
let d = base.to_string_lossy();
let script = format!(
r#"my $dir = "{d}";
my @g = glob("$dir/*.txt");
scalar @g;"#
);
assert_eq!(ri(&script), 2);
let _ = std::fs::remove_dir_all(&base);
}
#[cfg(unix)]
#[test]
fn perl_compat_qx_printf_stdout() {
assert_eq!(rs(r#"scalar `printf '%s' sem_qx`;"#), "sem_qx");
}
#[test]
fn perl_compat_select_roundtrip_default_handle() {
assert_eq!(
rs(r#"my $a = select(STDERR);
my $b = select($a);
$a . "/" . $b;"#),
"STDOUT/STDERR"
);
}
#[test]
fn perl_compat_ref_bless_and_delete_exists() {
assert_eq!(
rs(r#"no strict 'vars';
my $o = bless {}, "Box";
ref $o;"#),
"Box"
);
assert_eq!(
ri(r#"my %m = ("u", 5);
my $x = delete $m{"u"};
my $y = exists $m{"u"};
$x * 100 + $y;"#),
500
);
}
#[test]
fn perl_compat_index_rindex_substr_splice() {
assert_eq!(ri(r#"index("xyx", "x") + 5 * rindex("xyx", "x");"#), 10);
assert_eq!(rs(r#"substr("Perl", 1, 3);"#), "erl");
assert_eq!(
rs(r#"my @s = (1, 2, 3, 4);
join(",", splice @s, 0, 2);"#),
"1,2"
);
}
#[test]
fn perl_compat_splice_aref_list_replacement() {
assert_eq!(
ri(r#"no strict 'vars';
my $v = [10, 20, 30, 40];
my $r = join "-", splice @$v, 1, 2, 7, 8;
($r eq "20-30" && $v->[1] == 7 && $v->[2] == 8) ? 1 : 0;"#,),
1
);
}
#[test]
fn perl_compat_scalar_splice_aref_last_removed() {
assert_eq!(
ri(r#"no strict 'vars';
my $a = [3, 4, 5];
scalar splice @$a, 0, 2;"#,),
4
);
}
#[test]
fn perl_compat_splice_aref_negative_offset_length() {
assert_eq!(
rs(r#"no strict 'vars';
my $v = [1, 2, 3, 4, 5];
my @rem = splice @$v, -2;
join(" ", @$v) . "|" . join(" ", @rem);"#),
"1 2 3|4 5"
);
assert_eq!(
rs(r#"no strict 'vars';
my $v = [1, 2, 3, 4, 5];
my @rem = splice @$v, 0, -2;
join(" ", @$v) . "|" . join(" ", @rem);"#),
"4 5|1 2 3"
);
}
#[test]
fn perl_compat_splice_aref_negative_offset_with_replacement() {
assert_eq!(
rs(r#"no strict 'vars';
my $v = [1, 2, 3, 4, 5];
splice @$v, -2, 1, 99;
join "-", @$v;"#),
"1-2-3-99-5"
);
}
#[test]
fn perl_compat_scalar_deref_hash_compound_assign_and_postinc() {
assert_eq!(
ri(r#"my %h = (a => 1); my $r = \%h; $$r{a} += 5; $h{a}"#),
6
);
assert_eq!(
ri(r#"my %h = (a => 5); my $r = \%h; my $x = $$r{a}++; $x * 10 + $h{a}"#),
56
);
}
#[test]
fn perl_compat_scalar_deref_hash_logassign() {
assert_eq!(
ri(r#"my %h = (a => 2); my $r = \%h; $$r{a} &&= 7; $h{a}"#),
7
);
assert_eq!(ri(r#"my %h = (); my $r = \%h; $$r{b} ||= 9; $h{b}"#), 9);
}
#[test]
fn perl_compat_scalar_deref_hash_defined_or_assign() {
assert_eq!(ri(r#"my %h = (); my $r = \%h; $$r{x} //= 42; $h{x}"#), 42);
assert_eq!(
ri(r#"my %h = (a => 1); my $r = \%h; $$r{a} //= 99; $h{a}"#),
1
);
assert_eq!(
ri(r#"my %h = (a => 1); my $r = \%h; my $runs = 0; $$r{a} //= ($runs = 1); $runs"#,),
0
);
}
#[test]
fn perl_compat_scalar_splice_aref_remove_one() {
assert_eq!(
rs(r#"no strict 'vars';
my $v = [1, 2, 3];
my $s = scalar splice @$v, 1, 1;
$s . "|" . join "-", @$v"#),
"2|1-3"
);
}
#[test]
fn perl_compat_scalar_at_aref_is_array_length() {
assert_eq!(
ri(r#"no strict 'vars';
my $r = [7, 8, 9, 0];
scalar @$r;"#,),
4
);
}
#[test]
fn perl_compat_scalar_at_aref_empty_ref_is_zero() {
assert_eq!(
ri(r#"no strict 'vars';
my $e = [];
scalar @$e;"#,),
0
);
}
#[test]
fn perl_compat_scalar_braced_aref_is_length() {
assert_eq!(
ri(r#"no strict 'vars';
my $r = [1, 2, 3];
scalar @{$r};"#,),
3
);
}
#[test]
fn perl_compat_scalar_braced_sub_return_aref_count() {
assert_eq!(
ri(r#"no strict 'vars';
sub row { [0, 0, 0, 0, 0, 0] }
scalar @{row()};"#,),
6
);
}
#[test]
fn perl_compat_assignment_list_deref_aref_to_scalar() {
assert_eq!(
ri(r#"no strict 'vars';
my $x = [1, 1, 1];
my $c = @$x;
$c;"#,),
3
);
}
#[test]
fn perl_compat_join_receives_scalar_at_aref_as_one_field() {
assert_eq!(
rs(r#"no strict 'vars';
my $v = [2, 4, 6];
join "x", scalar @$v;"#,),
"3"
);
}
#[test]
fn perl_compat_scalar_named_array_length() {
assert_eq!(
ri(r#"my @w = (0, 0, 0, 0, 0);
scalar @w;"#,),
5
);
}
#[test]
fn perl_compat_scalar_percent_href_fill_metric() {
assert_eq!(
rs(r#"no strict 'vars';
my $h = { x => 1, y => 2 };
scalar %$h;"#,),
"2/3"
);
}
#[test]
fn perl_compat_scalar_percent_href_empty_zero() {
assert_eq!(
ri(r#"no strict 'vars';
my $e = {};
scalar %$e;"#,),
0
);
}
#[test]
fn perl_compat_slurp_and_unlink_temp_file() {
let dir = std::env::temp_dir();
let path = dir.join("stryke_slurp_unlink_semantics");
let _ = std::fs::remove_file(&path);
std::fs::write(&path, b"ok").expect("write temp");
let ps = path.to_string_lossy();
let slurp = format!(r#"slurp "{ps}";"#);
assert_eq!(rs(&slurp), "ok");
let rm = format!(r#"unlink "{ps}";"#);
assert_eq!(ri(&rm), 1);
assert!(!path.exists());
}
#[test]
fn perl_compat_stat_size_and_missing_list() {
let dir = std::env::temp_dir();
let path = dir.join("stryke_sem_stat_sz");
let _ = std::fs::remove_file(&path);
std::fs::write(&path, b"abcdef").expect("write temp");
let ps = path.to_string_lossy();
let sz = format!(
r#"my @st = stat("{ps}");
$st[7];"#,
);
assert_eq!(ri(&sz), 6);
assert_eq!(
ri(r#"my @st = stat("stryke___no___stat___"); scalar @st;"#),
0
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn perl_compat_readline_scalar_length_includes_newline() {
let dir = std::env::temp_dir();
let path = dir.join("stryke_sem_readline_scalar");
let _ = std::fs::remove_file(&path);
std::fs::write(&path, b"a\nbb\n").expect("write temp");
let ps = path.to_string_lossy();
let script = format!(
r#"open SRL, "<", "{ps}";
my $x = readline SRL;
close SRL;
length $x;"#
);
assert_eq!(ri(&script), 2);
let _ = std::fs::remove_file(&path);
}
#[test]
fn perl_compat_mkdir_and_d_test() {
let base = std::env::temp_dir().join(format!("stryke_sem_mkdir_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
let pb = base.to_string_lossy();
let script = format!(
r#"mkdir "{pb}";
(-d "{pb}") + (-e "{pb}");"#,
);
assert_eq!(ri(&script), 2);
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn perl_compat_capture_true_exitcode() {
assert_eq!(ri(r#"my $c = capture("true"); $c->exitcode;"#), 0);
}
#[test]
fn perl_compat_filetest_e_f_regular_file() {
let dir = std::env::temp_dir();
let path = dir.join("stryke_sem_ef");
let _ = std::fs::remove_file(&path);
std::fs::write(&path, b"x").expect("write");
let ps = path.to_string_lossy();
assert_eq!(ri(&format!(r#"(-e "{ps}") + (-f "{ps}");"#)), 2);
let _ = std::fs::remove_file(&path);
}
#[cfg(unix)]
#[test]
fn perl_compat_lstat_symlink_st_size_not_followed() {
use std::os::unix::fs::symlink;
let base = std::env::temp_dir().join(format!("stryke_sem_lstat_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
std::fs::create_dir_all(&base).expect("mkdir");
let target = base.join("longtargetfilename");
std::fs::write(&target, b"z").expect("write");
let link = base.join("sym");
symlink("longtargetfilename", &link).expect("symlink");
let sl = link.to_string_lossy();
let script = format!(
r#"my @st = stat("{sl}");
my @l = lstat("{sl}");
$st[7] * 100 + $l[7];"#,
);
assert_eq!(ri(&script), 118);
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn perl_compat_wantarray_rename_lc_uc() {
assert_eq!(
ri(r#"sub ctx { wantarray ? 2 : 8 }
my $x = ctx();
my @y = ctx();
$x * 10 + $y[0];"#),
82
);
let dir = std::env::temp_dir();
let a = dir.join("stryke_sem_rename_a");
let b = dir.join("stryke_sem_rename_b");
let _ = std::fs::remove_file(&a);
let _ = std::fs::remove_file(&b);
std::fs::write(&a, b"ok").expect("write");
let sa = a.to_string_lossy();
let sb = b.to_string_lossy();
let script = format!(r#"rename "{sa}", "{sb}"; length(slurp "{sb}");"#);
assert_eq!(ri(&script), 2);
let _ = std::fs::remove_file(&b);
assert_eq!(rs(r#"lc("Hi") . uc("m");"#), "hiM");
}
#[test]
fn pchannel_fan_send_recv() {
let s = r#"
my ($tx, $rx) = pchannel();
fan 3 { $tx->send($_) }
my $sum = 0;
$sum += $rx->recv() for 1..3;
$sum;
"#;
assert_eq!(ri(s), 3);
}
#[test]
fn fan_progress_optional_parses_and_runs() {
let v = run(r#"fan 2 { 1 }, progress => 0;"#).expect("run");
assert!(v.is_undef());
let v2 = run(r#"fan { 1 }, progress => 0;"#).expect("run");
assert!(v2.is_undef());
}
#[test]
fn postfix_pfor_after_backtick_without_semicolon_runs() {
assert_eq!(ri(r#"my $x = 1; `true` pfor (1, 2, 3); 42"#), 42,);
}
#[test]
fn fan_block_backtick_postfix_pfor_progress_runs() {
run(r#"fan { my $x = "tommy"; `true` pfor (1, 2, 3); sleep 0 }, progress => 0;"#).expect("run");
}
#[test]
fn glob_par_progress_optional_runs() {
let base = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let sub = format!("target/glob_par_test_{}", std::process::id());
let dir = base.join(&sub);
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("create target/ glob_par scratch dir");
std::fs::write(dir.join("probe.rs"), b"// glob_par test\n").expect("write probe.rs");
let pat = format!("{sub}/*.rs");
let n = ri(&format!(r#"scalar glob_par "{pat}", progress => 0;"#));
let _ = std::fs::remove_dir_all(&dir);
assert!(
n >= 1,
"glob_par with progress => 0 should match at least one file, got {n}"
);
}
#[test]
fn fan_cap_returns_list_in_index_order() {
let s = r#"
my $s = join ",", fan_cap 4 { $_ * 2 };
$s eq "0,2,4,6" ? 1 : 0;
"#;
assert_eq!(ri(s), 1);
}
#[test]
fn pselect_multiplex_recv() {
let s = r#"
my ($tx1, $rx1) = pchannel();
my ($tx2, $rx2) = pchannel();
$tx1->send(7);
my ($v, $i) = pselect($rx1, $rx2);
$v == 7 && $i == 0 ? 1 : 0;
"#;
assert_eq!(ri(s), 1);
}
#[test]
fn deque_push_front_back_pop_order() {
let s = r#"
my $q = deque();
$q->push_back(1);
$q->push_front(0);
$q->pop_front() + $q->pop_front();
"#;
assert_eq!(ri(s), 1);
}
#[test]
fn heap_numeric_comparator_pops_sorted() {
let s = r#"
my $pq = heap(fn { $a <=> $b });
$pq->push(3);
$pq->push(1);
$pq->push(2);
$pq->pop() + $pq->pop() + $pq->pop();
"#;
assert_eq!(ri(s), 6);
}
#[test]
fn heap_block_comparator_readme_strykerm() {
let s = r#"
my $pq = heap({ $a <=> $b });
$pq->push(3);
my $min = $pq->pop();
$min;
"#;
assert_eq!(ri(s), 3);
}
#[test]
fn heap_sub_comparator_sees_outer_lexical() {
let s = r#"
my $bias = 0;
my $pq = heap(fn { $a <=> ($b + $bias) });
$pq->push(1);
$pq->push(100);
$pq->pop() + 0;
"#;
assert_eq!(ri(s), 1);
}
#[test]
fn trace_fan_mysync_runs() {
let s = r#"
mysync $counter = 0;
trace { fan 4 { $counter++ } };
$counter;
"#;
assert_eq!(ri(s), 4);
}
#[test]
fn outer_topic_in_fan_closure() {
let s = r#"
$_ = 100;
my @r = fan_cap 3 { $_< };
$r[0] + $r[1] + $r[2];
"#;
assert_eq!(ri(s), 300);
}
#[test]
fn outer_topic_in_nested_closure() {
let s = r#"
my $cr = fn { $_< };
$_ = 42;
fan 1 { $cr->(999) } == 0 && $_< == 42;
"#;
assert_eq!(ri(s), 0);
}
#[test]
fn outer_topic_double_level() {
let s = r#"
$_ = 100;
my @r = fan_cap 2 {
my $outer = $_<;
my $cr = fn { $outer + $_< };
$cr->($_);
};
$r[0] + $r[1];
"#;
assert_eq!(ri(s), 201);
}
#[test]
fn outer_topic_in_string_interpolation() {
let s = r#"
$_ = "outer";
my @r = fan_cap 1 { $_ = "inner"; "$_< $_" eq "outer inner" ? 1 : 0 };
$r[0];
"#;
assert_eq!(ri(s), 1);
}
#[test]
fn timer_returns_elapsed_ms() {
let ms = rf(r#"timer { my $x = 1 + 1; }"#);
assert!(ms >= 0.0);
assert!(ms < 60_000.0, "timer should be wall-clock ms, got {ms}");
}
#[test]
fn pmap_chunked_preserves_order_and_values() {
let s = r#"
my @a = pmap_chunked 2 { $_ * 2 } (1, 2, 3, 4);
$a[0] + $a[1] + $a[2] + $a[3];
"#;
assert_eq!(ri(s), 20);
}
#[test]
fn reduce_left_fold_sum_and_concat() {
assert_eq!(ri(r#"(1, 2, 3, 4) |> reduce { $a + $b };"#), 10);
assert_eq!(rs(r#"("a", "b", "c") |> reduce { $a . $b };"#), "abc");
}
#[test]
fn collect_after_pipe_chain_materializes_list() {
let s = r#"
my @x = ("a".."z") |> map { $_ } |> take_while { $_ le "c" } |> collect;
join ",", @x;
"#;
assert_eq!(rs(s), "a,b,c");
}
#[test]
fn collect_eager_repacks_list_literal_actuals() {
assert_eq!(ri(r#"my @x = collect((40, 2)); $x[0] + $x[1];"#), 42);
}
#[test]
fn collect_after_map_pipe_chain() {
assert_eq!(
rs(r#"my @x = (1, 2, 3) |> map { $_ * 2 } |> collect;
join ",", @x;"#,),
"2,4,6"
);
}
#[test]
fn collect_after_grep_pipe_chain() {
assert_eq!(
ri(r#"my @x = (1, 2, 3, 4) |> grep { $_ % 2 == 0 } |> collect;
scalar @x;"#,),
2
);
}
#[test]
fn collect_without_arguments_is_runtime_error() {
let e = run(r#"collect();"#).expect_err("collect()");
assert_eq!(e.kind, ErrorKind::Runtime);
assert!(
e.message.contains("at least one argument"),
"unexpected message: {}",
e.message
);
}
#[test]
fn pipeline_filter_map_take_collect() {
let s = r#"
my @a = pipeline(1, 9, 10, 15)
->filter(fn { $_ > 5 })
->map(fn { $_ * 2 })
->take(2)
->collect();
$a[0] + $a[1];
"#;
assert_eq!(ri(s), 38);
}
#[test]
fn pipeline_chain_bare_blocks_and_array_source() {
let s = r#"
my @data = (5, 11, 12, 9);
my @result = pipeline(@data)
->filter({ $_ > 10 })
->map({ $_ * 2 })
->take(100)
->collect();
$result[0] + $result[1];
"#;
assert_eq!(ri(s), 46);
}
#[test]
fn pipeline_parallel_pgrep_pmap_psort() {
let s = r#"
my @r = pipeline(3, 1, 4)
->pgrep(fn { $_ > 1 })
->pmap(fn { $_ + 10 })
->psort(fn { $a <=> $b })
->collect();
scalar @r;
"#;
assert_eq!(ri(s), 2);
}
#[test]
fn pipeline_preduce_collect_scalar() {
assert_eq!(
ri(r#"pipeline(1, 2, 3, 4)->preduce(fn { $a + $b })->collect();"#),
10
);
}
#[test]
fn pipeline_chaining_rejects_ops_after_preduce() {
assert!(run(r#"pipeline(1, 2)->preduce(fn { $a + $b })->map(fn { $_ });"#,).is_err());
}
#[test]
fn pipeline_user_sub_in_chain() {
let s = r#"
sub times2 { $_ * 2 }
my @r = pipeline(1, 2, 3)->times2->collect();
$r[0] + $r[1] + $r[2];
"#;
assert_eq!(ri(s), 12);
}
#[test]
fn pipeline_grep_alias_matches_filter() {
let s = r#"
my @r = pipeline(1, 2, 3, 4)->grep(fn { $_ % 2 == 0 })->collect();
scalar @r;
"#;
assert_eq!(ri(s), 2);
}
#[test]
fn pipeline_qualified_sub_chain() {
let s = r#"
package P;
sub tripl { $_ * 3 }
package main;
my @r = pipeline(1, 2)->P::tripl->collect();
$r[0] + $r[1];
"#;
assert_eq!(ri(s), 9);
}
#[test]
fn async_await_returns_block_value() {
assert_eq!(ri(r#"my $t = async { 40 + 2 }; await($t);"#), 42);
}
#[test]
fn async_await_two_tasks() {
assert_eq!(
ri(r#"my $a = async { 10 }; my $b = async { 32 }; await($a) + await($b);"#),
42
);
}
#[test]
fn spawn_await_same_as_async() {
assert_eq!(ri(r#"my $t = spawn { 40 + 2 }; await($t);"#), 42);
}
#[test]
fn await_passes_through_non_task() {
assert_eq!(ri(r#"await(7);"#), 7);
}
#[test]
fn capture_structured_exit_and_failed() {
assert_eq!(
ri(r#"my $r = capture("true"); $r->exitcode + $r->failed;"#),
0
);
assert_eq!(ri(r#"my $r = capture("false"); $r->exitcode;"#), 1);
assert_eq!(ri(r#"my $r = capture("false"); $r->failed;"#), 1);
}
#[test]
fn typed_my_int_str_float_runtime_checks() {
assert_eq!(ri(r#"typed my $x : Int = 7; $x = 3; $x"#), 3);
assert_eq!(rs(r#"typed my $s : Str = "a"; $s = "b"; $s"#), "b");
assert_eq!(rf(r#"typed my $f : Float = 2; $f = 3.5; $f"#), 3.5);
assert!(run(r#"typed my $x : Int = "nope";"#).is_err());
let e = run(r#"typed my $x : Int = 1; $x = "y";"#).expect_err("type mismatch");
assert_eq!(e.kind, ErrorKind::Type);
}
#[test]
fn typed_sub_params_runtime_checks() {
assert_eq!(
ri(r#"my $f = fn ($a: Int, $b: Int) { $a + $b }; $f->(3, 4)"#),
7
);
assert_eq!(
rs(r#"sub greet ($name: Str) { "hello $name" } greet("world")"#),
"hello world"
);
assert_eq!(rf(r#"my $f = fn ($x: Float) { $x * 2.0 }; $f->(1.5)"#), 3.0);
let e = run(r#"my $f = fn ($a: Int) { $a }; $f->("oops")"#).expect_err("type mismatch");
assert!(
format!("{}", e).contains("expected Int"),
"error should mention Int"
);
let e2 = run(r#"fn foo ($s: Str) { $s } foo(123)"#).expect_err("type mismatch");
assert!(
format!("{}", e2).contains("expected Str"),
"error should mention Str"
);
}
#[test]
fn pack_unpack_and_length() {
assert_eq!(ri(r#"unpack("C", "A");"#), 65);
assert_eq!(ri(r#"length pack("C3", 1, 2, 3);"#), 3);
}
#[test]
fn open_read_pipe_echo() {
let out = rs(r#"
open(FH, "-|", "echo hi");
my $x = <FH>;
close FH;
$x;
"#);
assert!(out.contains("hi"));
}
#[test]
fn open_read_pipe_two_arg_trailing_pipe() {
let out = rs(r#"
open(FH, "echo hi |") or die "open";
my $x = <FH>;
close FH;
$x;
"#);
assert!(out.contains("hi"));
}
#[test]
fn open_write_pipe_two_arg_leading_pipe() {
let out = rs(r#"
open(FH, "| tr a-z A-Z") or die "open";
print FH "abc\n";
close FH;
"done";
"#);
assert_eq!(out, "done");
}
#[test]
fn autoload_sets_missing_sub_name() {
assert_eq!(
rs(r#"
sub AUTOLOAD { $AUTOLOAD }
not_defined_yet();
"#,),
"main::not_defined_yet"
);
}
#[test]
fn autoload_method_sets_package_method_in_autoload() {
assert_eq!(
rs(r#"
package D;
sub AUTOLOAD { $AUTOLOAD }
package main;
bless({}, "D")->missing_meth();
"#),
"D::missing_meth"
);
}
#[test]
fn compile_phase_blocks_run_before_main() {
assert_eq!(
rs(r#"
BEGIN { $main::order = ""; $main::order .= "B" }
UNITCHECK { $main::order .= "U" }
CHECK { $main::order .= "C" }
INIT { $main::order .= "I" }
$main::order .= "M";
$main::order
"#),
"BUCIM"
);
}
#[test]
fn runtime_push_isa_updates_method_resolution() {
assert_eq!(
ri(r#"
package P;
sub pong { 42 }
package C;
our @ISA;
push @C::ISA, "P";
package main;
my $o = bless {}, "C";
$o->pong()
"#),
42
);
}
#[test]
fn typeglob_assign_scalar_ref_to_coderef_aliases_sub() {
assert_eq!(
ri(r#"
package P;
sub orig { 7 }
*alias = \&orig;
package main;
P::alias()
"#),
7
);
}
#[test]
fn use_open_encoding_utf8_sets_open_caret() {
assert_eq!(ri(r#"use open qw(:utf8); ${^OPEN}"#), 1);
}
#[cfg(unix)]
#[test]
fn perl_compat_dollar_question_reflects_system() {
assert_eq!(ri(r#"system("true"); $?"#), 0);
}
#[cfg(unix)]
#[test]
fn dollar_question_reflects_capture() {
assert_eq!(ri(r#"capture("true"); $?"#), 0);
assert_eq!(ri(r#"capture("false"); $?"#), 256);
}
#[test]
fn our_isa_populates_package_stash() {
assert_eq!(ri(r#"package C; our @ISA = ("P"); scalar @ISA"#), 1);
}
#[test]
fn qualified_sub_call_across_packages() {
assert_eq!(
ri(r#"package P; sub meth { 10 } package main; P::meth()"#),
10
);
}
#[test]
fn isa_visible_from_main_after_package_blocks() {
assert_eq!(
ri(r#"
package P;
sub meth { 10 }
package C;
our @ISA = ("P");
package main;
scalar @C::ISA
"#),
1
);
}
#[test]
fn perl_compat_super_calls_parent_method() {
assert_eq!(
ri(r#"
package P;
sub meth { 10 }
package C;
our @ISA = ("P");
sub meth { my $s = shift; $s->SUPER::meth + 5 }
package main;
my $o = bless {}, "C";
$o->meth();
"#),
15
);
}
#[test]
fn perl_compat_use_overload_dispatches_add() {
assert_eq!(
ri(r#"
package O;
use overload '+' => 'add';
sub add { my ($a, $b) = @_; $a->{n} + $b->{n} }
package main;
my $a = O->new(n => 2);
my $b = O->new(n => 3);
$a + $b;
"#),
5
);
}
#[test]
fn perl_compat_use_overload_coderef_handler() {
assert_eq!(
ri(r#"
package O;
use overload '+' => \&add;
sub add { my ($a, $b) = @_; $a->{n} * $b->{n} }
package main;
my $a = O->new(n => 2);
my $b = O->new(n => 3);
$a + $b;
"#),
6
);
}
#[test]
fn perl_compat_use_overload_stringify() {
assert_eq!(
rs(r#"
package O;
use overload '""' => 'as_string';
sub as_string { "x7" }
package main;
my $o = bless { n => 7 }, "O";
"$o"
"#),
"x7"
);
}
#[test]
fn perl_compat_use_overload_combined_coderef() {
assert_eq!(
ri(r#"
package O;
use overload '+' => \&add_op, '""' => \&to_str;
sub add_op { my ($a, $b) = @_; $a->{n} + $b->{n} }
sub to_str { "v" . $_0->{n} }
package main;
my $a = O->new(n => 2);
my $b = O->new(n => 3);
$a + $b;
"#),
5
);
assert_eq!(
rs(r#"
package O;
use overload '+' => \&add_op, '""' => \&to_str;
sub add_op { my ($a, $b) = @_; $a->{n} + $b->{n} }
sub to_str { "v" . $_0->{n} }
package main;
my $o = bless { n => 7 }, "O";
"$o"
"#),
"v7"
);
}
#[test]
fn perl_compat_sprintf_percent_s_uses_overload_stringify() {
assert_eq!(
rs(r#"
package O;
use overload '""' => 'as_string';
sub as_string { "Z" }
package main;
my $o = bless {}, "O";
sprintf "%s", $o;
"#),
"Z"
);
}
#[test]
fn perl_compat_regex_caret_match_vars() {
assert_eq!(
rs(r#"
"abc" =~ /b/;
"${^MATCH}" . ":" . "${^PREMATCH}" . ":" . "${^POSTMATCH}";
"#),
"b:a:c"
);
}
#[test]
fn perl_compat_tie_hash_exists_delete() {
assert_eq!(
ri(r#"
package T;
sub TIEHASH { bless { h => {} }, shift }
sub FETCH { $_0->{h}->{$_1} }
sub STORE { $_0->{h}->{$_1} = $_2 }
sub EXISTS { 7 }
sub DELETE { 8 }
package main;
my %t;
tie %t, "T";
$t{a} = 1;
exists $t{a};
"#),
7
);
assert_eq!(
ri(r#"
package T;
sub TIEHASH { bless { h => {} }, shift }
sub FETCH { $_0->{h}->{$_1} }
sub STORE { $_0->{h}->{$_1} = $_2 }
sub EXISTS { 1 }
sub DELETE { 8 }
package main;
my %t;
tie %t, "T";
$t{a} = 1;
delete $t{a};
"#),
8
);
}
#[test]
fn perl_compat_use_overload_nomethod_binop() {
assert_eq!(
ri(r#"
package O;
use overload nomethod => 'catch_all', fallback => 1;
sub catch_all { my ($a, $b, $op) = @_; 99 }
package main;
my $a = O->new(n => 1);
my $b = O->new(n => 2);
$a + $b;
"#),
99
);
}
#[test]
fn perl_compat_use_overload_unary_neg() {
assert_eq!(
ri(r#"
package O;
use overload 'neg' => 'negate_op';
sub negate_op { my ($x) = @_; 42 }
package main;
my $o = bless {}, "O";
-$o;
"#),
42
);
}
#[test]
fn perl_compat_use_overload_bool_for_logical_not() {
assert_eq!(
ri(r#"
package O;
use overload 'bool' => 'as_bool';
sub as_bool { $_0->{t} }
package main;
my $f = bless { t => 0 }, "O";
my $t = bless { t => 1 }, "O";
(!$f) * 10 + (!$t);
"#),
10
);
}
#[test]
fn perl_compat_use_overload_bool_for_not_keyword() {
assert_eq!(
ri(r#"
package O;
use overload 'bool' => 'as_bool';
sub as_bool { $_0->{t} }
package main;
my $f = bless { t => 0 }, "O";
my $t = bless { t => 1 }, "O";
(not $f) * 10 + (not $t);
"#),
10
);
}
#[test]
fn perl_compat_join_stringifies_overloaded_objects() {
assert_eq!(
rs(r#"
package O;
use overload '""' => 'as_str';
sub as_str { "@" . $_0->{n} }
package main;
my $a = bless { n => 1 }, "O";
my $b = bless { n => 2 }, "O";
join ":", $a, $b,3;
"#),
"@1:@2:3"
);
}
#[test]
fn perl_compat_use_overload_empty_list_runs() {
assert_eq!(
ri(r#"
package O;
sub add { 1 }
use overload ();
package main;
9;
"#),
9
);
}
#[test]
fn perl_compat_use_overload_dispatches_sub_mul_cmp_ops() {
assert_eq!(
ri(r#"
package O;
use overload '-' => 'osub', '*' => 'omul', '==' => 'onumeq', 'eq' => 'ostreq', cmp => 'ocmp';
sub osub { my ($a, $b) = @_; $a->{n} - $b->{n} }
sub omul { my ($a, $b) = @_; $a->{n} * $b->{n} }
sub onumeq { my ($a, $b) = @_; $a->{n} == $b ? 1 : 0 }
sub ostreq { my ($a, $b) = @_; $b eq "rhs" ? 1 : 0 }
sub ocmp { my ($a, $b) = @_; $a->{n} <=> $b }
package main;
my $x = O->new(n => 10);
my $y = O->new(n => 4);
my $s = $x - $y;
my $m = $x * $y;
my $e = $x == 10;
my $q = $x eq "rhs";
my $c = $x cmp 0;
$s * 1000 + $m * 100 + $e * 10 + $q + $c;
"#),
10_012
);
}
#[test]
fn perl_compat_use_overload_dispatches_concat_op() {
assert_eq!(
rs(r#"
package O;
use overload '.' => 'odot';
sub odot { my ($a, $b) = @_; "[" . $a->{n} . "+" . $b . "]" }
package main;
my $a = O->new(n => "x");
$a . "z";
"#),
"[x+z]"
);
}
#[test]
fn perl_compat_use_overload_dispatches_concat_op_string_on_lhs() {
assert_eq!(
rs(r#"
package O;
use overload '.' => 'odot';
sub odot { my ($a, $b) = @_; "[" . $a->{n} . "+" . $b . "]" }
package main;
my $a = O->new(n => "x");
"z" . $a;
"#),
"[x+z]"
);
}
#[test]
fn perl_compat_qq_interpolates_lone_scalar_without_overload() {
assert_eq!(
rs(r#"
my $u = 40;
"$u";
"#),
"40"
);
}
#[test]
fn perl_compat_qq_interpolates_lone_array_uses_list_separator() {
assert_eq!(
rs(r#"
no strict 'vars';
my @a = (9, 8, 7);
"@a";
"#),
"9 8 7"
);
}
#[test]
fn perl_compat_use_overload_dispatches_div_mod_pow() {
assert_eq!(
ri(r#"
package O;
use overload '/' => 'odiv', '%' => 'omod', '**' => 'opow';
sub odiv { my ($a, $b) = @_; $a->{n} / $b }
sub omod { my ($a, $b) = @_; $a->{n} % $b }
sub opow { my ($a, $b) = @_; $a->{n} ** $b }
package main;
my $a = O->new(n => 20);
my $c = O->new(n => 2);
($a / 4) + ($a % 7) + ($c ** 3);
"#),
19
);
}
#[test]
fn perl_compat_use_overload_nomethod_dispatches_concat() {
assert_eq!(
ri(r#"
package O;
use overload nomethod => 'nm', fallback => 1;
sub nm { my ($a, $b, $op) = @_; $op eq "." ? 777 : 0 }
package main;
my $a = O->new(n => 1);
$a . "tail";
"#),
777
);
}
#[test]
fn perl_compat_use_overload_dispatches_add_blessed_on_rhs() {
assert_eq!(
ri(r#"
package O;
use overload '+' => 'add';
sub add { my ($a, $b) = @_; $a->{n} + $b }
package main;
my $x = O->new(n => 7);
5 + $x;
"#),
12
);
}
#[test]
fn perl_compat_use_overload_dispatches_ne_and_spaceship() {
assert_eq!(
ri(r#"
package O;
use overload 'ne' => 'cmp_ne', '<=>' => 'osp';
sub cmp_ne { my ($a, $b) = @_; 1 }
sub osp { my ($a, $b) = @_; $a->{n} <=> $b }
package main;
my $o = O->new(n => 9);
($o <=> 4) * 10 + ($o ne "x");
"#),
11
);
}
#[test]
fn perl_compat_qq_interpolates_lone_hash_element_expr() {
assert_eq!(
rs(r#"
no strict 'vars';
my %h = (k => 42);
"$h{k}";
"#),
"42"
);
}
#[test]
fn perl_compat_use_overload_dispatches_str_lt_and_num_gt() {
assert_eq!(
ri(r#"
package O;
use overload 'lt' => 'olt', '>' => 'ogt';
sub olt { my ($a, $b) = @_; $a->{"s"} lt $b ? 1 : 0 }
sub ogt { my ($a, $b) = @_; $a->{"n"} > $b ? 1 : 0 }
package main;
my $o = bless { "n" => 5, "s" => "a" }, "O";
($o lt "b") * 10 + ($o > 3);
"#),
11
);
}
#[test]
fn perl_compat_use_overload_sub_blessed_on_rhs() {
assert_eq!(
ri(r#"
package O;
use overload '-' => 'osub';
sub osub { my ($a, $b) = @_; $b - $a->{n} }
package main;
my $o = O->new(n => 7);
10 - $o;
"#),
3
);
}
#[test]
fn perl_compat_use_overload_mul_and_pow_blessed_on_rhs() {
assert_eq!(
ri(r#"
package O;
use overload '*' => 'omul', '**' => 'opow';
sub omul { my ($a, $b) = @_; $a->{n} * $b }
sub opow { my ($a, $b) = @_; $b ** $a->{n} }
package main;
my $a = O->new(n => 6);
my $b = O->new(n => 3);
(4 * $a) * 10 + (2 ** $b);
"#),
248
);
}
#[test]
fn perl_compat_use_overload_num_ne_and_num_le() {
assert_eq!(
ri(r#"
package O;
use overload '!=' => 'oine', '<=' => 'ole';
sub oine { my ($a, $b) = @_; $a->{n} != $b ? 1 : 0 }
sub ole { my ($a, $b) = @_; $a->{n} <= $b ? 1 : 0 }
package main;
my $o = O->new(n => 7);
($o != 10) * 10 + ($o <= 7);
"#),
11
);
}
#[test]
fn perl_compat_use_overload_str_le_and_str_ge() {
assert_eq!(
ri(r#"
package O;
use overload 'le' => 'ole', 'ge' => 'oge';
sub ole { my ($a, $b) = @_; $a->{"t"} le $b ? 1 : 0 }
sub oge { my ($a, $b) = @_; $a->{"t"} ge $b ? 1 : 0 }
package main;
my $o = bless { "t" => "m" }, "O";
($o le "n") * 10 + ($o ge "a");
"#),
11
);
}
#[test]
fn perl_compat_use_overload_div_and_mod_blessed_on_rhs() {
assert_eq!(
ri(r#"
package O;
use overload '/' => 'odiv', '%' => 'omod';
sub odiv { my ($a, $b) = @_; $b / $a->{n} }
sub omod { my ($a, $b) = @_; $b % $a->{n} }
package main;
my $a = O->new(n => 4);
my $b = O->new(n => 3);
(20 / $a) * 10 + (10 % $b);
"#),
51
);
}
#[test]
fn perl_compat_use_overload_num_ge_and_num_lt() {
assert_eq!(
ri(r#"
package O;
use overload '>=' => 'oge', '<' => 'olt';
sub oge { my ($a, $b) = @_; $a->{n} >= $b ? 1 : 0 }
sub olt { my ($a, $b) = @_; $a->{n} < $b ? 1 : 0 }
package main;
my $o = O->new(n => 7);
($o >= 6) * 10 + ($o < 8);
"#),
11
);
}
#[test]
fn perl_compat_use_overload_str_cmp_op() {
assert_eq!(
ri(r#"
package O;
use overload 'cmp' => 'ocmp';
sub ocmp { my ($a, $b) = @_; $a->{"t"} cmp $b }
package main;
my $o = bless { "t" => "b" }, "O";
$o cmp "c";
"#),
-1
);
}
#[test]
fn perl_compat_qq_stringify_blessed_hash_value() {
assert_eq!(
rs(r#"
package O;
use overload '""' => 'as_str';
sub as_str { "Zy" }
package main;
no strict 'vars';
my %h = ("k" => bless {}, "O");
"$h{k}";
"#),
"Zy"
);
}
#[test]
fn perl_compat_qq_interpolates_literal_then_two_scalars() {
assert_eq!(
rs(r#"
no strict 'vars';
my $x = 7;
my $y = 8;
"p${x}x$y";
"#),
"p7x8"
);
}
#[test]
fn perl_compat_qq_interpolates_named_array_element() {
assert_eq!(
rs(r#"
no strict 'vars';
my @a = (11, 22);
"v$a[0]";
"#),
"v11"
);
}
#[test]
fn perl_compat_qq_braced_scalar_trailing_literal() {
assert_eq!(
rs(r#"
no strict 'vars';
my $u = 3;
"n${u}px";
"#),
"n3px"
);
}
#[test]
fn perl_compat_qq_braced_scalar_leading_and_trailing_literals() {
assert_eq!(
rs(r#"
no strict 'vars';
my $u = 5;
"L${u}R";
"#),
"L5R"
);
}
#[test]
fn perl_compat_qq_mixed_braced_and_plain_scalar() {
assert_eq!(
rs(r#"
no strict 'vars';
my $x = 1;
my $y = 2;
"a${x}b$y";
"#),
"a1b2"
);
}
#[test]
fn perl_compat_named_array_slice_list_assign() {
assert_eq!(
ri(r#"
no strict 'vars';
my @a = (0, 0);
@a[0, 1] = (5, 6);
$a[0] + $a[1];
"#),
11
);
}
#[test]
fn perl_compat_named_array_slice_single_index_list_rhs() {
assert_eq!(
ri(r#"
no strict 'vars';
my @a = (0, 7);
@a[0] = (9);
$a[0] + $a[1];
"#),
16
);
}
#[test]
fn perl_compat_named_array_slice_three_indices_list_assign() {
assert_eq!(
ri(r#"
no strict 'vars';
my @a = (0, 0, 0);
@a[0, 1, 2] = (10, 20, 30);
$a[0] + $a[1] + $a[2];
"#),
60
);
}
#[test]
fn perl_compat_tie_scalar_fetch_store() {
assert_eq!(
ri(r#"
package T;
sub TIESCALAR { bless { v => 0 }, shift }
sub FETCH { $_[0]->{v} }
sub STORE { $_[0]->{v} = $_1 }
package main;
my $x;
tie $x, "T";
$x = 7;
$x;
"#),
7
);
}
#[test]
fn perl_compat_splice_aref_zero_length_insert() {
assert_eq!(
rs(r#"no strict 'vars'; my $v = [1, 2, 3]; splice @$v, 1, 0, 9; join "-", @$v"#),
"1-9-2-3"
);
}
#[test]
fn perl_compat_splice_named_zero_length_insert() {
assert_eq!(
rs(r#"my @a = (1, 2, 3); splice @a, 1, 0, 9; join "-", @a"#),
"1-9-2-3"
);
}
#[test]
fn perl_compat_splice_aref_offset_only_returns_tail_removed() {
assert_eq!(
rs(r#"no strict 'vars';
my $v = [1, 2, 3, 4];
my @r = splice @$v, 2;
join("-", @$v) . "|" . join("-", @r)"#),
"1-2|3-4"
);
}
#[test]
fn perl_compat_scalar_splice_aref_two_arg_empties_target() {
assert_eq!(
rs(r#"no strict 'vars';
my $v = [1, 2, 3];
my $s = scalar splice @$v;
$s . "|" . scalar(@$v)"#),
"3|0"
);
}
#[test]
fn perl_compat_splice_aref_three_replacements() {
assert_eq!(
rs(r#"no strict 'vars';
my $v = [10, 20, 30, 40];
splice @$v, 1, 2, 1, 2, 3;
join "-", @$v"#),
"10-1-2-3-40"
);
}
#[test]
fn perl_compat_push_aref_splice_list_chain() {
assert_eq!(
rs(r#"no strict 'vars';
my $v = [1, 2, 3];
push @$v, splice @$v, 0, 1;
join "-", @$v"#),
"2-3-1"
);
}
#[test]
fn perl_compat_list_literal_leading_aref_flatten() {
assert_eq!(
rs(r#"no strict 'vars'; my $v = [1, 2]; my @x = (@$v, 3, 4); join "-", @x"#),
"1-2-3-4"
);
}
#[test]
fn perl_compat_scalar_deref_hash_compound_sub_mul() {
assert_eq!(
ri(r#"my %h = (a => 10); my $r = \%h; $$r{a} -= 3; $h{a}"#),
7
);
assert_eq!(
ri(r#"my %h = (a => 4); my $r = \%h; $$r{a} *= 3; $h{a}"#),
12
);
}
#[test]
fn perl_compat_arrow_array_compound_sub_mul_concat() {
assert_eq!(
ri(r#"no strict 'vars'; my $v = [10, 20, 30]; $v->[1] -= 5; $v->[1]"#),
15
);
assert_eq!(
ri(r#"no strict 'vars'; my $v = [2, 3, 4]; $v->[1] *= 5; $v->[1]"#),
15
);
assert_eq!(
rs(r#"no strict 'vars'; my $v = ["a"]; $v->[0] .= "b"; $v->[0]"#),
"ab"
);
}
#[test]
fn perl_compat_arrow_hash_exists_delete_sequence() {
assert_eq!(
rs(r#"no strict 'vars';
my $h = { x => 1 };
my $e0 = exists $h->{x} ? 1 : 0;
my $d = delete $h->{x};
my $e1 = exists $h->{x} ? 1 : 0;
$e0 . "," . $d . "," . $e1"#),
"1,1,0"
);
}
#[test]
fn perl_compat_arrow_hash_compound_sub_mul() {
assert_eq!(
ri(r#"no strict 'vars'; my $h = { a => 10 }; $h->{a} -= 3; $h->{a}"#),
7
);
assert_eq!(
ri(r#"no strict 'vars'; my $h = { a => 4 }; $h->{a} *= 3; $h->{a}"#),
12
);
}
#[test]
fn perl_compat_scalar_deref_hash_log_or_default() {
assert_eq!(
ri(r#"my %h = (a => 0); my $r = \%h; my $x = $$r{a} || 7; $x"#),
7
);
}
#[test]
fn perl_compat_arrow_hash_log_or_default() {
assert_eq!(
ri(r#"no strict 'vars'; my $h = { a => 0 }; my $x = $h->{a} || 7; $x"#),
7
);
}
#[test]
fn perl_compat_named_array_splice_negative_offset_with_replacement() {
assert_eq!(
rs(r#"my @a = (1, 2, 3, 4, 5);
splice @a, -2, 1, 88;
join "-", @a"#),
"1-2-3-88-5"
);
}
#[test]
fn perl_compat_unshift_aref_splice_returns_removed_to_front() {
assert_eq!(
rs(r#"no strict 'vars';
my $v = [1, 2, 3];
unshift @$v, splice(@$v, 0, 1);
join "-", @$v"#),
"1-2-3"
);
}
#[test]
fn perl_compat_reverse_sort_aref_list_context() {
assert_eq!(
ri(r#"no strict 'vars';
my $v = [1, 2, 3];
my @t = rev @$v;
$t[0];"#),
3
);
assert_eq!(
rs(r#"no strict 'vars';
my $v = [3, 1, 2];
join "-", sort @$v"#),
"1-2-3"
);
}
#[test]
fn perl_compat_keys_values_sort_on_hashref() {
assert_eq!(
rs(r#"no strict 'vars';
my $h = { a => 1, b => 2 };
join "", sort keys %$h"#),
"ab"
);
assert_eq!(
rs(r#"no strict 'vars';
my $h = { a => 1, b => 2 };
join "-", sort values %$h"#),
"1-2"
);
}
#[test]
fn perl_compat_scalar_keys_on_hashref() {
assert_eq!(
ri(r#"no strict 'vars'; my $h = { u => 1, v => 2, w => 3 }; scalar keys %$h"#),
3
);
}
#[test]
fn perl_compat_grep_map_blocks_receive_aref_list() {
assert_eq!(
ri(r#"no strict 'vars';
my $v = [1, 2, 3, 4];
scalar grep { $_ > 1 } @$v"#),
3
);
assert_eq!(
ri(r#"no strict 'vars';
my $v = [1, 2, 3];
my @m = map { $_ * 2 } @$v;
$m[2];"#),
6
);
}
#[test]
fn perl_compat_arrow_array_postincrement() {
assert_eq!(
ri(r#"no strict 'vars'; my $v = [10, 20]; $v->[0]++; $v->[0]"#),
11
);
}
#[test]
fn perl_compat_arrow_hash_and_scalar_deref_hash_decrement() {
assert_eq!(
ri(r#"no strict 'vars'; my $h = { k => 5 }; $h->{k}--; $h->{k}"#),
4
);
assert_eq!(ri(r#"my %h = (a => 3); my $r = \%h; $$r{a}--; $h{a}"#), 2);
}
#[test]
fn perl_compat_for_loop_sum_over_arrayref() {
assert_eq!(
ri(r#"no strict 'vars';
my $v = [1, 2, 3];
my $s = 0;
for my $x (@$v) { $s = $s + $x; }
$s"#),
6
);
}
#[test]
fn perl_compat_splice_aref_variable_offset_replacement() {
assert_eq!(
rs(r#"no strict 'vars';
my $v = [1, 2, 3, 4];
my $o = 1;
splice @$v, $o, 2, 9;
join "-", @$v"#),
"1-9-4"
);
}
#[test]
fn perl_compat_defined_exists_hash_elems_through_ref() {
assert_eq!(
ri(r#"my %h = (a => 1); my $r = \%h; defined $$r{a} ? 1 : 0"#),
1
);
assert_eq!(
ri(r#"no strict 'vars'; my $h = { a => 1 }; exists $h->{a} ? 1 : 0"#),
1
);
}
#[test]
fn perl_compat_arrayref_slice_index_list() {
assert_eq!(
ri(r#"no strict 'vars';
my $v = [5, 6, 7];
my @s = @$v[0, 2];
$s[0] + $s[1]"#),
12
);
}
#[test]
fn perl_compat_keys_list_from_hashref() {
assert_eq!(
ri(r#"no strict 'vars';
my $h = { "p" => 1, "q" => 2 };
my @k = keys %$h;
scalar @k"#),
2
);
}
#[test]
fn perl_compat_bracket_copy_of_aref_is_shallow_new_array() {
assert_eq!(
ri(r#"no strict 'vars';
my $v = [1, 2, 3];
my $c = [@$v];
$c->[0] = 9;
$v->[0]"#),
1
);
}
#[test]
fn perl_compat_ref_returns_array_and_hash() {
assert_eq!(rs(r#"no strict 'vars'; my $v = [1]; ref($v)"#), "ARRAY");
assert_eq!(rs(r#"my %h = (a => 1); ref(\%h)"#), "HASH");
}
#[test]
fn perl_compat_scalar_splice_aref_returns_last_removed_middle() {
assert_eq!(
rs(r#"no strict 'vars';
my $v = [10, 20, 30, 40];
my $n = scalar splice @$v, 1, 2;
$n . "|" . join("-", @$v)"#),
"30|10-40"
);
}
#[test]
fn perl_compat_scalar_splice_named_negative_offset_two_removed() {
assert_eq!(
rs(r#"my @a = (1, 2, 3, 4, 5);
my $n = scalar splice @a, -3, 2;
$n . "|" . join("-", @a)"#),
"4|1-2-5"
);
}
#[test]
fn perl_compat_hash_elem_falsy_andassign_short_circuit() {
assert_eq!(
ri(r#"no strict 'vars'; my $h = { a => 0 }; $h->{a} &&= 1; $h->{a}"#),
0
);
assert_eq!(
ri(r#"my %h = (a => 0); my $r = \%h; $$r{a} &&= 1; $h{a}"#),
0
);
}
#[test]
fn perl_compat_concat_two_arefs_in_list() {
assert_eq!(
rs(r#"no strict 'vars';
my $a = [1, 2];
my $b = [3, 4];
my @x = (@$a, @$b);
join "-", @x"#),
"1-2-3-4"
);
}
#[test]
fn perl_compat_arrow_elem_assign_and_dynamic_index_mult() {
assert_eq!(
rs(r#"no strict 'vars'; my $v = [1, 2, 3]; $v->[1] = 9; join "-", @$v"#),
"1-9-3"
);
assert_eq!(
rs(r#"no strict 'vars';
my $v = [1, 2, 3];
my $i = 0;
$v->[$i] *= 10;
join "-", @$v"#),
"10-2-3"
);
}
#[test]
fn perl_compat_arrow_elem_last_index_assign() {
assert_eq!(
rs(r#"no strict 'vars';
my $v = [0, 0, 0];
$v->[$#v] = 5;
join "-", @$v"#),
"0-0-5"
);
}
#[test]
fn perl_compat_named_array_last_index_elem() {
assert_eq!(ri(r#"my @a = (1, 2, 3); $a[$#a]"#), 3);
}
#[test]
fn perl_compat_new_hash_keys_via_arrow_and_scalar_deref() {
assert_eq!(
ri(r#"no strict 'vars'; my $h = { "a" => 1 }; $h->{"b"} = 2; $h->{"a"} + $h->{"b"}"#,),
3
);
assert_eq!(
ri(r#"my %h = (a => 1); my $r = \%h; $$r{b} = 3; $h{a} + $h{b}"#),
4
);
}
#[test]
fn perl_compat_empty_hashref_or_default_fills_key() {
assert_eq!(
ri(r#"no strict 'vars'; my $h = {}; $h->{z} ||= 7; $h->{z}"#),
7
);
assert_eq!(ri(r#"my %h = (); my $r = \%h; $$r{z} ||= 8; $h{z}"#), 8);
}
#[test]
fn perl_compat_empty_aref_push() {
assert_eq!(
ri(r#"no strict 'vars'; my $v = []; push @$v, 1, 2; scalar @$v"#),
2
);
}
#[test]
fn perl_compat_empty_hashref_assign_then_key_count() {
assert_eq!(
ri(r#"no strict 'vars'; my $h = {}; $h->{x} = 1; scalar keys %$h"#),
1
);
}
#[test]
fn perl_compat_splice_named_three_replacements_returns_removed() {
assert_eq!(
rs(r#"my @a = (1, 2, 3, 4);
my @b = splice @a, 1, 2, 9, 8, 7;
join("-", @a) . "|" . join("-", @b)"#),
"1-9-8-7-4|2-3"
);
}
#[test]
fn perl_compat_splice_aref_three_replacements_returns_removed() {
assert_eq!(
rs(r#"no strict 'vars';
my $v = [1, 2, 3, 4];
my @b = splice @$v, 1, 2, 9, 8, 7;
join("-", @$v) . "|" . join("-", @b)"#),
"1-9-8-7-4|2-3"
);
}
#[test]
fn perl_compat_shift_pop_aref() {
assert_eq!(
rs(r#"no strict 'vars';
my $v = [1, 2, 3];
shift @$v;
join "-", @$v"#),
"2-3"
);
assert_eq!(
rs(r#"no strict 'vars';
my $v = [1, 2, 3];
my $x = pop @$v;
$x . "|" . join("-", @$v)"#),
"3|1-2"
);
}
#[test]
fn perl_compat_arrow_preinc_and_named_array_blshift_assign() {
assert_eq!(
ri(r#"no strict 'vars'; my $v = [3]; ++$v->[0]; $v->[0]"#),
4
);
assert_eq!(ri(r#"my @a = (1, 2, 4); $a[1] <<= 1; $a[1]"#), 4);
}
#[cfg(unix)]
#[test]
fn perl_compat_local_typeglob_aliases_handle() {
let out = rs(r#"
my $f = "/tmp/stryke_tg_" . $$;
open OUT, ">", $f;
local *G = *OUT;
print G "xyz";
close OUT;
open IN, "<", $f;
my $s = <IN>;
close IN;
unlink $f;
$s;
"#);
assert!(out.contains("xyz"));
}
#[test]
fn parity_311_full_form_deref() {
assert_eq!(ri(r#"my $sr = \10; ${$sr}"#), 10);
assert_eq!(rs(r#"my $ar = [1,2]; join ",", @{$ar}"#), "1,2");
assert_eq!(rs(r#"my $hr = {a=>1}; join ",", %{$hr}"#), "a,1");
}
#[test]
fn parity_312_postfix_deref() {
assert_eq!(rs(r#"my $r = [1, 2, 3]; join ",", $r->@*"#), "1,2,3");
assert_eq!(
rs(r#"my $r = {a => 1}; my %h = $r->%*; join ",", %h"#),
"a,1"
);
assert_eq!(
rs(r#"my $r = [10, 20, 30]; join ",", $r->@[0, 2]"#),
"10,30"
);
assert_eq!(
rs(r#"my $r = {a => 10, b => 20}; join ",", $r->@{"a", "b"}"#),
"10,20"
);
}
#[test]
fn parity_315_local_input_record_separator() {
assert_eq!(ri(r#"defined($/) ? 1 : 0"#), 1);
assert_eq!(ri(r#"{ local $/; defined($/) ? 1 : 0 }"#), 0);
assert_eq!(ri(r#"{ local $/; 1 } defined($/) ? 1 : 0"#), 1);
}
#[test]
fn parity_316_local_output_separators() {
let script = r#"
my $f = "test_316.tmp";
open my $fh, ">", $f;
{
local $, = ":";
local $\ = "END";
print $fh 1, 2, 3;
}
close $fh;
open my $fh2, "<", $f;
my $out = <$fh2>;
close $fh2;
unlink $f;
$out;
"#;
assert_eq!(rs(script), "1:2:3END");
assert_eq!(rs(r#"my @a = (1, 2); { local $" = "|"; "@a" }"#), "1|2");
}
#[test]
fn block_last_stmt_returns_value() {
assert_eq!(rs(r#"my $x = 10; { $x = 20; 42 }"#), "42");
}
#[test]
fn nested_block_last_stmt_returns_value() {
assert_eq!(rs(r#"{ { 123 } }"#), "123");
}
#[test]
fn block_with_multiple_stmts_returns_last() {
assert_eq!(rs(r#"{ my $y = 1; my $z = 2; $y + $z }"#), "3");
}
#[test]
fn deeply_nested_blocks_return_value() {
assert_eq!(rs(r#"{ { { { { 99 } } } } }"#), "99");
}
#[test]
fn block_ending_in_non_expr_returns_undef() {
assert_eq!(rs(r#"{ 1; 2; my $x = 3; }"#), "");
}
#[test]
fn if_else_as_last_stmt_with_multi_stmt_blocks() {
assert_eq!(
rs(r#"my $x = 1; if ($x) { my $a = 10; $a + 5 } else { 0 }"#),
"15"
);
assert_eq!(
rs(r#"my $x = 0; if ($x) { 10 } else { my $b = 20; $b + 5 }"#),
"25"
);
}
#[test]
fn local_in_last_stmt_block_affects_interpolation() {
assert_eq!(rs(r#"my @a = (1, 2); { local $" = "-"; "@a" }"#), "1-2");
}
#[test]
fn local_in_nested_last_stmt_block() {
assert_eq!(rs(r#"my @a = (1, 2); { { local $" = ":"; "@a" } }"#), "1:2");
}
#[test]
fn block_with_conditional_return_path() {
assert_eq!(rs(r#"{ if (1) { 77 } else { 88 } }"#), "77");
}
#[test]
fn mixed_stmts_and_blocks_tail() {
assert_eq!(rs(r#"1; { 2; { 3 } }"#), "3");
}
#[test]
fn local_returns_assigned_value() {
assert_eq!(
rs(r#"{ our $x = 10; my $val = do { local $x = 42 }; $val + 1 }"#),
"43"
);
}
#[test]
fn local_multiple_scalars() {
assert_eq!(
rs(
r#"our ($a, $b) = (1, 2); my $inner = do { local ($a, $b) = (10, 20); "$a $b" }; "$inner|$a $b""#
),
"10 20|1 2"
);
}
#[test]
fn local_array_clears_and_restores() {
assert_eq!(
rs(
r#"our @a = (1, 2); my $inner = do { local @a = (3, 4, 5); join ",", @a }; "$inner|" . join ",", @a"#
),
"3,4,5|1,2"
);
}
#[test]
fn local_hash_clears_and_restores() {
assert_eq!(
rs(
r#"our %h = (k1 => 'v1'); my $inner = do { local %h = (k2 => 'v2'); $h{k2} . (exists $h{k1} ? 1 : 0) }; $inner . $h{k1}"#
),
"v20v1"
);
}
#[test]
fn local_special_irs_slurp() {
assert_eq!(rs(r#"{ local $/; defined($/) ? 1 : 0 }"#), "0");
}
#[test]
fn local_special_ofs_and_ors() {
assert_eq!(
rs(r#"{ local $, = ":"; my @a = (1, 2); join $,, @a }"#),
"1:2"
);
}
#[test]
fn local_in_foreach_loop_variable() {
assert_eq!(
rs(r#"our $x = "global"; for $x (1..3) { last if $x == 2 }; $x"#),
"global"
);
}
#[test]
fn local_with_typeglob_filehandle_alias() {
assert_eq!(
rs(r#"
sub read_it {
local *FH;
open FH, "<", "src/lib.rs" or return "fail";
my $line = <FH>;
close FH;
"ok"
}
read_it();
"#),
"ok"
);
}
#[test]
fn local_env_restoration() {
assert_eq!(
rs(r#"$ENV{TEST_VAR} = "orig"; { local $ENV{TEST_VAR} = "new"; }; $ENV{TEST_VAR}"#),
"orig"
);
}
#[test]
fn local_array_element() {
assert_eq!(
rs(
r#"our @a = (1, 2, 3); my $inner = do { local $a[1] = 99; join ",", @a }; "$inner|" . $a[1]"#
),
"1,99,3|2"
);
}
#[test]
fn local_hash_element() {
assert_eq!(
rs(r#"our %h = (k => "v"); my $inner = do { local $h{k} = "new"; $h{k} }; $inner . $h{k}"#),
"newv"
);
}
#[test]
fn local_multiple_special_vars() {
assert_eq!(
rs(r#"{ local ($", $,) = ("|", ":"); my @a = (1, 2); "@a" . join $,, (3, 4) }"#),
"1|23:4"
);
}
#[test]
fn local_in_subroutine() {
assert_eq!(
rs(r#"
our $x = 10;
sub wrap { local $x = 20; inner_fn() }
sub inner_fn { $x }
wrap() . "|" . $x
"#),
"20|10"
);
}
#[test]
fn local_scalar_ref() {
assert_eq!(
rs(r#"our $x = 1; { local $x = 2; my $r = \$x; $$r = 3; }; $x"#),
"1"
);
}
#[test]
fn local_array_push() {
assert_eq!(
rs(
r#"our @a = (1); my $in; { local @a = (2); push @a, 3; $in = join ",", @a } $in . "|" . join ",", @a"#
),
"2,3|1"
);
}
#[test]
fn local_hash_delete() {
assert_eq!(
rs(
r#"our %h = (a => 1); my $in; { local %h = (a => 2, b => 3); delete $h{a}; $in = exists $h{a} ? 1 : 0 } $in . $h{a}"#
),
"01"
);
}
#[test]
fn local_undef_slurp_mode() {
assert_eq!(rs(r#"{ local $/; defined($/) ? 1 : 0 }"#), "0");
}
#[test]
fn local_with_dynamic_name() {
}
#[test]
fn local_in_eval() {
assert_eq!(
rs(r#"our $x = 1; eval { local $x = 2; die "err" }; $x"#),
"1"
);
}
#[test]
fn local_ors_affects_say() {
assert_eq!(rs(r#"{ local $\ = "!"; p "hi" } "#), "1");
}
#[test]
fn local_ofs_affects_print() {
}
#[test]
fn parity_302_heredoc_single_quote_no_interpolation() {
assert_eq!(
rs(r#"my $x = 42; <<'EOF'
value is $x
EOF
"#),
"value is $x\n"
);
}
#[test]
fn parity_302_heredoc_double_quote_interpolation() {
assert_eq!(
rs(r#"my $x = 42; <<"EOF"
value is $x
EOF
"#),
"value is 42\n"
);
}
#[test]
fn parity_302_heredoc_no_quote_interpolation() {
assert_eq!(
rs(r#"my $x = 42; <<EOF
value is $x
EOF
"#),
"value is 42\n"
);
}
#[test]
fn compose_right_to_left() {
let s = r#"
my $f = compose(fn { $_[0] + 1 }, fn { $_[0] * 2 });
$f->(5);
"#;
assert_eq!(ri(s), 11); }
#[test]
fn compose_single_fn() {
let s = r#"
my $f = compose(fn { $_[0] * 3 });
$f->(7);
"#;
assert_eq!(ri(s), 21);
}
#[test]
fn partial_prepends_args() {
let s = r#"
my $add = fn { $_[0] + $_1 };
my $add5 = partial($add, 5);
$add5->(3);
"#;
assert_eq!(ri(s), 8);
}
#[test]
fn constantly_ignores_args() {
let s = r#"
my $c = constantly(42);
$c->(1, 2, 3);
"#;
assert_eq!(ri(s), 42);
}
#[test]
fn complement_negates() {
let s = r#"
my $even = fn { $_[0] % 2 == 0 };
my $odd = complement($even);
$odd->(3);
"#;
assert_eq!(ri(s), 1);
}
#[test]
fn juxt_collects_results() {
let s = r#"
my $j = juxt(fn { $_[0] + 1 }, fn { $_[0] * 2 });
join ",", $j->(5);
"#;
assert_eq!(rs(s), "6,10");
}
#[test]
fn memoize_caches() {
let s = r#"
my $f = memoize(fn { $_[0] * $_[0] });
my $a = $f->(5);
my $b = $f->(5);
my $c = $f->(3);
"$a,$b,$c";
"#;
assert_eq!(rs(s), "25,25,9");
}
#[test]
fn curry_accumulates_args() {
let s = r#"
my $add = curry(fn { $_[0] + $_1 }, 2);
my $add5 = $add->(5);
$add5->(3);
"#;
assert_eq!(ri(s), 8);
}
#[test]
fn curry_immediate_when_enough_args() {
let s = r#"
my $add = curry(fn { $_[0] + $_1 }, 2);
$add->(5, 3);
"#;
assert_eq!(ri(s), 8);
}
#[test]
fn curry_multiple_stages() {
let s = r#"
my $add3 = curry(fn { $_[0] + $_1 + $_2 }, 3);
my $f1 = $add3->(1);
my $f2 = $f1->(10);
$f2->(100);
"#;
assert_eq!(ri(s), 111);
}
#[test]
fn fnil_replaces_undef() {
let s = r#"
my $f = fnil(fn { $_[0] + $_1 }, 10, 20);
join ",", $f->(undef, undef), $f->(5, undef), $f->(undef, 5), $f->(5, 5);
"#;
assert_eq!(rs(s), "30,25,15,10");
}
#[test]
fn fnil_with_string_defaults() {
let s = r#"
my $greet = fnil(fn { "Hello, $_[0]!" }, "World");
$greet->(undef) . " " . $greet->("Alice");
"#;
assert_eq!(rs(s), "Hello, World! Hello, Alice!");
}
#[test]
fn once_calls_only_once() {
let s = r#"
my $f = once(fn { 42 });
my $a = $f->();
my $b = $f->();
"$a,$b";
"#;
assert_eq!(rs(s), "42,42");
}
#[test]
fn deep_clone_is_independent() {
let s = r#"
my $a = {x => [1, 2, 3]};
my $b = deep_clone($a);
$b->{x}[0] = 99;
$a->{x}[0];
"#;
assert_eq!(ri(s), 1);
}
#[test]
fn deep_merge_recursive() {
let s = r#"
my $a = {a => 1, n => {x => 1, y => 2}};
my $b = {b => 2, n => {y => 9, z => 3}};
my $m = deep_merge($a, $b);
join ",", $m->{a}, $m->{b}, $m->{n}{x}, $m->{n}{y}, $m->{n}{z};
"#;
assert_eq!(rs(s), "1,2,1,9,3");
}
#[test]
fn deep_equal_match() {
assert_eq!(ri(r#"deep_equal([1,2,3], [1,2,3]);"#), 1);
assert_eq!(ri(r#"deep_equal([1,2,3], [1,2,4]);"#), 0);
assert_eq!(ri(r#"deep_equal({a=>1}, {a=>1});"#), 1);
assert_eq!(ri(r#"deep_equal({a=>1}, {a=>2});"#), 0);
assert_eq!(ri(r#"deep_equal({a=>[1,2]}, {a=>[1,2]});"#), 1);
assert_eq!(ri(r#"deep_equal({a=>[1,2]}, {a=>[1,3]});"#), 0);
}
#[test]
fn deep_clone_with_circular_ref() {
let s = r#"
my $a = { n => undef };
my $curr = $a;
for (1..100) {
$curr->{n} = { n => undef };
$curr = $curr->{n};
}
my $b = deep_clone($a);
$b->{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n}{n} ? 1 : 0;
"#;
assert_eq!(ri(s), 1);
}
#[test]
fn collection_flip_args() {
let s = r#"
join ",", flip_args("a", "b", "c");
"#;
assert_eq!(rs(s), "c,b,a");
}
#[test]
fn collection_juxt_slices() {
let s = r#"
my $j2 = juxt2(1, 2, 3, 4);
my $j3 = juxt3(1, 2, 3, 4);
join ":", (join ",", @$j2), (join ",", @$j3);
"#;
assert_eq!(rs(s), "1,2:1,2,3");
}
#[test]
fn functional_coalesce() {
let s = r#"
join ",", coalesce(undef, undef, 42, 100), coalesce(0, 42), coalesce("", "hi");
"#;
assert_eq!(rs(s), "42,0,");
}
#[test]
fn functional_const_fn() {
let s = r#"
const_fn(42);
"#;
assert_eq!(ri(s), 42);
}
#[test]
fn collection_transpose() {
let s = r#"
my @m = ([1, 2], [3, 4], [5, 6]);
my @t = transpose(@m);
join ":", (join ",", @{$t[0]}), (join ",", @{$t[1]});
"#;
assert_eq!(rs(s), "1,3,5:2,4,6");
}
#[test]
fn collection_batch() {
let s = r#"
my @b = batch(2, 1, 2, 3, 4, 5);
join ":", (join ",", @{$b[0]}), (join ",", @{$b[1]}), (join ",", @{$b[2]});
"#;
assert_eq!(rs(s), "1,2:3,4:5");
}
#[test]
fn collection_sliding_pairs() {
let s = r#"
my @p = sliding_pairs(1, 2, 3, 4);
join ":", (join ",", @{$p[0]}), (join ",", @{$p[1]}), (join ",", @{$p[2]});
"#;
assert_eq!(rs(s), "1,2:2,3:3,4");
}
#[test]
fn math_is_prime() {
let s = r#"
join ",", map { is_prime($_) ? 1 : 0 } (1..10);
"#;
assert_eq!(rs(s), "0,1,1,0,1,0,1,0,0,0");
}
#[test]
fn math_factorial() {
assert_eq!(ri("factorial(5)"), 120);
assert_eq!(ri("factorial(0)"), 1);
}
#[test]
fn functional_complement_variadic() {
let s = r#"
my $none_even = complement(fn {
my $any = 0;
for (@_) { if ($_ % 2 == 0) { $any = 1; last } }
$any;
});
join ",", $none_even->(1, 3, 5), $none_even->(1, 2, 5);
"#;
assert_eq!(rs(s), "1,0");
}
#[test]
fn functional_constantly_variadic() {
let s = r#"
my $f = constantly(42);
join ",", $f->(), $f->(1), $f->(1, 2, 3);
"#;
assert_eq!(rs(s), "42,42,42");
}
#[test]
fn functional_juxt_multi_args() {
let s = r#"
my $stats = juxt(fn { $_[0] + $_1 }, fn { $_[0] * $_1 });
my @res = $stats->(10, 5);
join ",", @res;
"#;
assert_eq!(rs(s), "15,50");
}
#[test]
fn tally_counts() {
let s = r#"
my $t = tally("a", "b", "a", "c", "a", "b");
join ",", $t->{a}, $t->{b}, $t->{c};
"#;
assert_eq!(rs(s), "3,2,1");
}
#[test]
fn struct_basic_decl_and_named_construct() {
let s = r#"
struct Point { x => Float, y => Float };
my $p = Point(x => 1.5, y => 2.0);
$p->x + $p->y;
"#;
assert_eq!(rf(s), 3.5);
}
#[test]
fn struct_positional_construct() {
let s = r#"
struct Point { x => Float, y => Float };
my $p = Point(10.0, 20.0);
$p->x . "," . $p->y;
"#;
assert_eq!(rs(s), "10,20");
}
#[test]
fn struct_default_values() {
let s = r#"
struct Config { timeout => Int = 30, retries => Int = 3 };
my $c = Config(retries => 5);
$c->timeout . "," . $c->retries;
"#;
assert_eq!(rs(s), "30,5");
}
#[test]
fn struct_field_setter() {
let s = r#"
struct Point { x => Float, y => Float };
my $p = Point(1.0, 2.0);
$p->x(5.5);
$p->x;
"#;
assert_eq!(rf(s), 5.5);
}
#[test]
fn struct_functional_update_with() {
let s = r#"
struct Point { x => Float, y => Float };
my $p1 = Point(1.0, 2.0);
my $p2 = $p1->with(y => 10.0);
$p1->y . "," . $p2->y;
"#;
assert_eq!(rs(s), "2,10");
}
#[test]
fn struct_user_defined_method() {
let s = r#"
struct Circle {
radius => Float,
fn area { 3 * $self->radius ** 2 }
};
my $c = Circle(radius => 5);
$c->area;
"#;
assert_eq!(ri(s), 75);
}
#[test]
fn struct_structural_equality() {
let s = r#"
struct Point { x => Int, y => Int };
my $a = Point(1, 2);
my $b = Point(1, 2);
my $c = Point(1, 3);
($a == $b) . "," . ($a == $c);
"#;
assert_eq!(rs(s), "1,0");
}
#[test]
fn struct_to_hash() {
let s = r#"
struct Point { x => Int, y => Int };
my $p = Point(10, 20);
my $h = $p->to_hash;
$h->{x} + $h->{y};
"#;
assert_eq!(ri(s), 30);
}
#[test]
fn struct_fields_method() {
let s = r#"
struct Point { x=0, y=0, z=0 };
join ",", Point()->fields;
"#;
assert_eq!(rs(s), "x,y,z");
}
#[test]
fn struct_type_checking_at_construction() {
let s = r#"
struct Point { x => Int };
Point(x => "not an int");
"#;
let res = run(s);
assert!(res.is_err());
assert!(format!("{}", res.unwrap_err()).contains("expected Int"));
}
#[test]
fn struct_type_checking_at_mutation() {
let s = r#"
struct Point { x => Int };
my $p = Point(x => 1);
$p->x("oops");
"#;
let res = run(s);
assert!(res.is_err());
}
#[test]
fn struct_any_field_allows_undef() {
let s = r#"
struct Box { val => Any };
my $b = Box(val => undef);
$b->val(42);
$b->val(undef);
defined($b->val) ? 1 : 0;
"#;
assert_eq!(ri(s), 0);
}
#[test]
fn struct_nested() {
let s = r#"
struct Point { x => Int, y => Int };
struct Rect { top_left => Point, bottom_right => Point };
my $r = Rect(
top_left => Point(0, 0),
bottom_right => Point(10, 10)
);
$r->bottom_right->x;
"#;
assert_eq!(ri(s), 10);
}
#[test]
fn struct_complex_defaults() {
let s = r#"
sub get_default { 42 }
struct S { x => Int = get_default() };
my $inst = S();
$inst->x;
"#;
assert_eq!(ri(s), 42);
}
#[test]
fn struct_implicit_any() {
let s = r#"
struct S { x, y };
my $inst = S(x => "hi", y => [1,2,3]);
$inst->x . "," . (ref $inst->y);
"#;
assert_eq!(rs(s), "hi,ARRAY");
}
#[test]
fn struct_method_shadowing_field() {
let s = r#"
struct S {
foo => Int;
fn foo($self) { $self->{foo} + 1 }
};
my $inst = S(foo => 10);
# $inst->foo() will return the field value 10, not call the method.
$inst->foo();
"#;
assert_eq!(ri(s), 10);
}
#[test]
fn struct_duplicate_decl_error() {
let s = r#"
struct S { x };
struct S { y };
"#;
let res = run(s);
assert!(res.is_err());
assert!(format!("{}", res.unwrap_err()).contains("duplicate struct `S`"));
}
#[test]
fn struct_deep_non_circular() {
let s = r#"
struct Node { val => Int, next => Any };
my $root = Node(val => 0, next => undef);
my $curr = $root;
for (1..10) {
$curr->next(Node(val => $_, next => undef));
$curr = $curr->next;
}
$root->next->next->next->val;
"#;
assert_eq!(ri(s), 3);
}
#[test]
fn struct_recursion_limitation() {
let s = r#"
struct Node { val => Int, next => Any };
my $n = Node(val => 1, next => undef);
$n->val;
"#;
assert_eq!(ri(s), 1);
}
#[test]
fn struct_recursion_deep() {
let s = r#"
struct Node { val => Int, next => Any };
my $n2 = Node(val => 2, next => undef);
my $n1 = Node(val => 1, next => $n2);
$n1->next->val;
"#;
assert_eq!(ri(s), 2);
}
#[test]
fn struct_oo_style_new() {
let s = r#"
struct Point { x => Int, y => Int };
my $p = Point->new(x => 5, y => 10);
$p->x;
"#;
assert_eq!(ri(s), 5);
}
#[test]
fn struct_clone() {
let s = r#"
struct Point { x => Int };
my $p1 = Point(1);
my $p2 = $p1->clone;
$p2->x(2);
$p1->x;
"#;
assert_eq!(ri(s), 1);
}
#[test]
fn struct_stringify_and_eval() {
let s = r#"
struct Point { x => Int, y => Int };
my $p = Point(1, 2);
my $s = str $p;
my $p2 = eval $s;
$p == $p2;
"#;
assert_eq!(ri(s), 1);
}
#[test]
fn compat_mode_udf_shadowing() {
use crate::{compat_mode, set_compat_mode};
assert!(!compat_mode());
let _ = set_compat_mode; }
#[test]
fn pipeline_filter_alias_to_grep() {
let s = r#"
my @r = pipeline(1, 2, 3, 4, 5)->filter(fn { $_ % 2 == 1 })->collect();
join ",", @r;
"#;
assert_eq!(rs(s), "1,3,5");
}
#[test]
fn dataframe_filter_alias() {
let s = r#"
my $df = df [{a => 1}, {a => 2}, {a => 3}];
my $df2 = $df->filter(fn { $_->{a} > 1 });
$df2->nrow;
"#;
assert_eq!(ri(s), 2);
}
#[test]
fn grep_with_regex_block() {
let s = r#"
my @r = grep { /a/ } ("apple", "banana", "cherry");
join ",", @r;
"#;
assert_eq!(rs(s), "apple,banana");
}
#[test]
fn fi_keyword_with_block() {
let s = r#"
my @r = fi { /a/ } ("apple", "banana", "cherry");
join ",", @r;
"#;
assert_eq!(rs(s), "apple,banana");
}
#[test]
fn pipeline_f_with_regex() {
let s = r#"
my @r = pipeline("apple", "banana", "cherry")->f(fn { /a/ })->collect();
join ",", @r;
"#;
assert_eq!(rs(s), "apple,banana");
}
#[test]
fn pipeline_f_alias_for_filter() {
let s = r#"
my @r = pipeline(1, 2, 3, 4)->f(fn { $_ > 2 })->collect();
join ",", @r;
"#;
assert_eq!(rs(s), "3,4");
}
#[test]
fn core_builtins_hex_oct() {
assert_eq!(ri("hex('10')"), 16);
assert_eq!(ri("hex('0x10')"), 16);
assert_eq!(ri("hex('0X10')"), 16);
assert_eq!(ri("oct('10')"), 8);
assert_eq!(ri("oct('010')"), 8);
assert_eq!(ri("oct('0x10')"), 16);
assert_eq!(ri("oct('0b10')"), 2);
}
#[test]
fn core_builtins_case_mapping() {
assert_eq!(rs("lc('HELLO')"), "hello");
assert_eq!(rs("uc('hello')"), "HELLO");
assert_eq!(rs("lcfirst('HELLO')"), "hELLO");
assert_eq!(rs("ucfirst('hello')"), "Hello");
}
#[test]
fn core_builtins_reverse() {
assert_eq!(rs("scalar rev('abc')"), "cba");
assert_eq!(rs("join('', rev('a', 'b', 'c'))"), "cba");
}
#[test]
fn core_builtins_abs_chr_ord() {
assert_eq!(ri("abs(-42)"), 42);
assert_eq!(ri("abs(42)"), 42);
assert_eq!(ri("ord(chr(65))"), 65);
assert_eq!(rs("chr(65)"), "A");
}
#[test]
fn core_builtins_string_alignment() {
assert_eq!(rs("ljust('abc', 5)"), "abc ");
assert_eq!(rs("rjust('abc', 5)"), " abc");
assert_eq!(rs("zfill('123', 5)"), "00123");
assert_eq!(rs("zfill('12345', 3)"), "12345");
}
#[test]
fn core_builtins_quotemeta() {
assert_eq!(rs("quotemeta('a.b*c')"), "a\\.b\\*c");
assert_eq!(rs("qm('a.b*c')"), "a\\.b\\*c");
}
#[test]
fn core_builtins_index_rindex() {
assert_eq!(ri("index('hello world', 'o')"), 4);
assert_eq!(ri("index('hello world', 'o', 5)"), 7);
assert_eq!(ri("index('hello world', 'x')"), -1);
assert_eq!(ri("rindex('hello world', 'o')"), 7);
assert_eq!(ri("rindex('hello world', 'o', 6)"), 4);
}
#[test]
fn core_builtins_splice_basic() {
let s = r#"
my @a = (1, 2, 3, 4, 5);
my @r = splice(@a, 1, 2, 8, 9);
join(',', @a) . ';' . join(',', @r);
"#;
assert_eq!(rs(s), "1,8,9,4,5;2,3");
}
#[test]
fn core_builtins_defined_ref_variations() {
assert_eq!(ri("defined(undef)"), 0);
assert_eq!(ri("defined(0)"), 1);
assert_eq!(ri("defined('')"), 1);
assert_eq!(rs("ref([])"), "ARRAY");
assert_eq!(rs("ref({})"), "HASH");
assert_eq!(rs("ref(fn {})"), "CODE");
}
#[test]
fn core_builtins_more_math() {
assert_eq!(ri("floor(3.9)"), 3);
assert_eq!(ri("ceil(3.1)"), 4);
assert_eq!(ri("pow2(3)"), 8);
assert_eq!(ri("pow2(10)"), 1024);
assert!((run("exp2(3)").expect("run").to_number() - 8.0).abs() < 1e-9);
assert!((run("log2(8)").expect("run").to_number() - 3.0).abs() < 1e-9);
assert!((run("log10(100)").expect("run").to_number() - 2.0).abs() < 1e-9);
}
#[test]
fn core_builtins_hyperbolic() {
assert_eq!(ri("sinh(0)"), 0);
assert_eq!(ri("cosh(0)"), 1);
}
#[test]
fn core_builtins_math_each() {
assert_eq!(rs("join(',', floor_each(1.1, 2.9, 3.5))"), "1,2,3");
assert_eq!(rs("join(',', ceil_each(1.1, 2.9, 3.1))"), "2,3,4");
}
#[test]
fn core_builtins_path_helpers() {
assert_eq!(rs("path_ext('test.txt')"), "txt");
assert_eq!(rs("path_ext('no_ext')"), "");
assert_eq!(rs("path_join('usr', 'local', 'bin')"), "usr/local/bin");
assert_eq!(
rs("join(',', @{path_split('usr/local/bin')})"),
"usr,local,bin"
);
}
#[test]
fn core_builtins_string_utilities() {
assert_eq!(rs("strip_prefix('foobar', 'foo')"), "bar");
assert_eq!(rs("strip_suffix('foobar', 'bar')"), "foo");
assert_eq!(rs("trim(' hello ')"), "hello");
assert_eq!(ri("contains('hello world', 'world')"), 1);
assert_eq!(ri("contains('hello world', 'mars')"), 0);
}
#[test]
fn core_builtins_list_collection() {
assert_eq!(rs("join(',', take(1, 2, 3, 4, 2))"), "1,2");
assert_eq!(rs("join(',', drop(1, 2, 3, 4, 2))"), "3,4");
assert_eq!(rs("join(',', tail(1, 2, 3, 4, 2))"), "3,4");
assert_eq!(
rs("join(',', map { join(':', @$_) } collect(enumerate('a', 'b')))"),
"0:a,1:b"
);
assert_eq!(
rs("join(',', map { join(':', @$_) } zip([1, 2], ['a', 'b']))"),
"1:a,2:b"
);
assert_eq!(
rs("join(',', map { join(':', @$_) } zip_all([1, 2, 3], ['a', 'b']))"),
"1:a,2:b"
);
}
#[test]
fn core_builtins_predicates() {
assert_eq!(ri("all_eq(1, 1, 1)"), 1);
assert_eq!(ri("all_eq(1, 2, 1)"), 0);
assert_eq!(ri("all_distinct(1, 2, 3)"), 1);
assert_eq!(ri("all_distinct(1, 2, 1)"), 0);
}
#[test]
fn core_builtins_aggregates_more() {
assert_eq!(ri("sum_of(1, 2, 3)"), 6);
assert_eq!(ri("product_of(2, 3, 4)"), 24);
assert_eq!(ri("max_of(1, 10, 5)"), 10);
assert_eq!(ri("min_of(1, 10, 5)"), 1);
}
#[test]
fn core_builtins_list_util_more() {
assert_eq!(rs("join(',', uniq(1, 2, 2, 3, 1, 4))"), "1,2,3,4");
assert_eq!(rs("my @s = sample(2, 1, 2, 3); scalar @s"), "2");
assert!(run("defined(shuffle(1, 2, 3))").expect("run").is_true());
}
#[test]
fn core_builtins_list_util_pairs() {
assert_eq!(
rs("my @p = pairs(a => 1, b => 2); join(',', map { $_->[0] . ':' . $_->[1] } @p)"),
"a:1,b:2"
);
assert_eq!(
rs("my @p = pairs(a => 1, b => 2); join(',', unpairs(@p))"),
"a,1,b,2"
);
assert_eq!(rs("join(',', pairkeys(a => 1, b => 2))"), "a,b");
assert_eq!(rs("join(',', pairvalues(a => 1, b => 2))"), "1,2");
assert_eq!(
rs("join(',', pairmap fn { $a . $b }, (a => 1, b => 2))"),
"a1,b2"
);
}
#[test]
fn core_builtins_validation() {
assert_eq!(ri("is_valid_ipv4('127.0.0.1')"), 1);
assert_eq!(ri("is_valid_ipv4('256.0.0.1')"), 0);
assert_eq!(ri("is_valid_ipv6('::1')"), 1);
assert_eq!(ri("is_valid_email('test@example.com')"), 1);
assert_eq!(ri("is_valid_email('invalid-email')"), 0);
assert_eq!(ri("is_valid_url('https://google.com')"), 1);
assert_eq!(ri("is_valid_url('not-a-url')"), 0);
}
#[test]
fn core_builtins_stats_more_means() {
assert_eq!(run("harmonic_mean(1, 4)").expect("run").to_number(), 1.6);
assert_eq!(run("geometric_mean(2, 8)").expect("run").to_number(), 4.0);
}
#[test]
fn core_builtins_crypto_encoding() {
assert_eq!(rs("md5('')"), "d41d8cd98f00b204e9800998ecf8427e");
assert_eq!(
rs("sha256('abc')"),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
assert_eq!(rs("base64_encode('hello')"), "aGVsbG8=");
assert_eq!(rs("base64_decode('aGVsbG8=')"), "hello");
assert_eq!(rs("hex_encode('ABC')"), "414243");
assert_eq!(rs("hex_decode('414243')"), "ABC");
}
#[test]
fn core_builtins_pack_unpack_extended() {
assert_eq!(rs("unpack('H*', pack('N', 0x12345678))"), "12345678");
assert_eq!(rs("unpack('H*', pack('V', 0x12345678))"), "78563412");
assert_eq!(rs("unpack('Z*', pack('Z*', 'hello'))"), "hello");
assert_eq!(rs("join(',', unpack('C3', pack('C3', 1, 2, 3)))"), "1,2,3");
}
#[test]
fn core_builtins_string_distance() {
assert_eq!(ri("levenshtein('kitten', 'sitting')"), 3);
assert_eq!(ri("hamming_distance('abc', 'abd')"), 1);
assert_eq!(rs("soundex('Robert')"), "R163");
assert_eq!(rs("soundex('Rupert')"), "R163");
}
#[test]
fn core_builtins_math_more_trig() {
assert_eq!(ri("abs_diff(10, 7)"), 3);
assert_eq!(ri("abs_diff(7, 10)"), 3);
assert!((run("asin(sin(0.5))").expect("run").to_number() - 0.5).abs() < 1e-9);
assert!((run("acos(cos(0.5))").expect("run").to_number() - 0.5).abs() < 1e-9);
assert!((run("atan(tan(0.5))").expect("run").to_number() - 0.5).abs() < 1e-9);
}
#[test]
fn core_builtins_functional_accumulate() {
assert_eq!(rs("join(',', accumulate(1, 2, 3, 4))"), "1,3,6,10");
assert_eq!(
rs("join(',', accumulate(fn { $_[0] * $_1 }, 1, 2, 3, 4))"),
"1,2,6,24"
);
}
#[test]
fn core_builtins_text_analysis() {
assert_eq!(rs("join('', digits('a1b2c3'))"), "123");
assert_eq!(
rs("join('|', numbers('temp 98.6F, -20C, ver 3'))"),
"98.6|-20|3"
);
assert_eq!(rs("join(',', graphemes('café'))"), "c,a,f,é");
assert_eq!(
rs("join('|', sentences('Hello world. This is a test! Is it working?'))"),
"Hello world.|This is a test!|Is it working?"
);
assert_eq!(
rs("join('|', paragraphs(\"para 1\\n\\npara 2\\n\\npara 3\"))"),
"para 1|para 2|para 3"
);
}
#[test]
fn core_builtins_sparkline() {
assert_eq!(rs("sparkline(1, 2, 3, 4, 5, 6, 7, 8)"), "▁▂▃▄▅▆▇█");
}
#[test]
fn core_builtins_digits_of() {
assert_eq!(rs("join(',', digits_of(12345))"), "1,2,3,4,5");
}
#[test]
fn core_builtins_dataframe_basic() {
let s = r#"
my $df = dataframe([{a => 1, b => 2}, {a => 3, b => 4}])
$df->nrow() . ',' . $df->ncol()
"#;
assert_eq!(rs(s), "2,2");
}
#[test]
fn core_builtins_which() {
assert!(run("defined(which('sh'))").expect("run").is_true());
}
#[test]
fn core_builtins_datetime() {
assert_eq!(ri("add_days(0, 1)"), 86400);
assert_eq!(ri("add_days(86400, -1)"), 0);
assert_eq!(ri("add_hours(0, 1)"), 3600);
assert_eq!(ri("diff_days(0, 86400)"), 1);
assert_eq!(ri("diff_hours(0, 3600)"), 1);
assert_eq!(ri("start_of_day(90000)"), 86400);
}
#[test]
fn core_builtins_list_functional_more() {
assert_eq!(rs("join(',', flatten([1, 2], [3, 4]))"), "1,2,3,4");
assert_eq!(rs("join(',', flatten_deep([1, [2, [3, 4]]]))"), "1,2,3,4");
assert_eq!(
rs("join(',', collect(grep_v('a', 'apple', 'banana', 'cherry')))"),
"cherry"
);
assert_eq!(rs("first_or('default', 1, 2, 3)"), "1");
assert_eq!(rs("first_or('default')"), "default");
let s = r#"
my $h = {a => 1, b => 2, c => 3}
my $s = select_keys($h, 'a', 'c')
join(',', sort keys %$s)
"#;
assert_eq!(rs(s), "a,c");
}
#[test]
fn core_builtins_list_functional_batch3() {
assert_eq!(
rs("join(',', collect(compact(1, undef, '', 2, 0, 3)))"),
"1,2,0,3"
);
assert_eq!(rs("join(',', collect(concat((1, 2), (3, 4))))"), "1,2,3,4");
assert_eq!(rs("join(',', collect(dedup(1, 1, 2, 2, 3, 1)))"), "1,2,3,1");
let s = r#"
my @r = chunk_while(fn { $_0 <= $_1 }, 1, 2, 4, 9, 5, 6, 8)
join('|', map { join(',', @$_) } @r)
"#;
assert_eq!(rs(s), "1,2,4,9|5,6,8");
}
#[test]
fn core_builtins_collection_frequencies() {
let s = r#"
my $f = frequencies('a', 'b', 'a', 'c', 'b', 'a')
join(',', map { "$_:" . $f->{$_} } sort keys %$f)
"#;
assert_eq!(rs(s), "a:3,b:2,c:1");
}
#[test]
fn core_builtins_path_manipulation() {
assert_eq!(rs("basename('/usr/local/bin/perl')"), "perl");
assert_eq!(rs("dirname('/usr/local/bin/perl')"), "/usr/local/bin");
let s = r#"
my ($base, $dir, $sfx) = fileparse('/usr/local/bin/perl.pl', '.pl');
"$base|$dir|$sfx"
"#;
assert_eq!(rs(s), "perl|/usr/local/bin|.pl");
}
#[test]
fn core_builtins_list_interleave() {
assert_eq!(
rs("join(',', interleave([1, 2, 3], ['a', 'b']))"),
"1,a,2,b,3"
);
}
#[test]
fn core_builtins_data_avg_columns() {
assert_eq!(run("avg(10, 20, 30)").expect("run").to_number(), 20.0);
assert_eq!(
rs("join('|', columns(' val1 val2 val3 '))"),
"val1|val2|val3"
);
}
#[test]
fn core_builtins_palindrome() {
assert_eq!(ri("is_palindrome('racecar')"), 1);
assert_eq!(ri("is_palindrome('hello')"), 0);
assert_eq!(ri("is_palindrome('A man a plan a canal Panama')"), 1);
}
#[test]
fn core_builtins_random_uuid() {
assert!(run("defined(coin_flip())").expect("run").is_true());
assert!(run("dice_roll(6)").expect("run").to_int() >= 1);
assert!(run("dice_roll(6)").expect("run").to_int() <= 6);
assert_eq!(run("length(uuid_v4())").expect("run").to_int(), 36);
}
#[test]
fn core_builtins_distance_similarity() {
assert_eq!(
run("euclidean_distance([0, 0], [3, 4])")
.expect("run")
.to_number(),
5.0
);
assert_eq!(
run("manhattan_distance([0, 0], [3, 4])")
.expect("run")
.to_number(),
7.0
);
assert_eq!(
run("jaccard_index([1, 2, 3], [2, 3, 4])")
.expect("run")
.to_number(),
2.0 / 4.0
);
assert_eq!(
run("cosine_similarity([1, 0], [1, 0])")
.expect("run")
.to_number(),
1.0
);
assert_eq!(
run("cosine_similarity([1, 0], [0, 1])")
.expect("run")
.to_number(),
0.0
);
}
#[test]
fn core_builtins_stats_entropy_zscore() {
assert!((run("entropy(1, 1)").expect("run").to_number() - 2.0f64.ln()).abs() < 1e-9);
assert_eq!(run("zscore(10, 5, 10, 15)").expect("run").to_number(), 0.0);
}