use std::collections::BTreeMap;

use mq_lang::{DefaultEngine, Engine, Ident, MqResult, RuntimeValue};
use rstest::{fixture, rstest};

#[fixture]
fn engine() -> DefaultEngine {
    let mut engine = mq_lang::DefaultEngine::default();
    engine.load_builtin_module();
    engine
}

#[rstest]
#[case::def_("
    # comments
    def test_fn(s):
       let test = \"WORLD\" | ltrimstr(s, \"hello\") | upcase() | ltrimstr(test);
    | test_fn(\"helloWorld2025\")
    ",
      vec![RuntimeValue::String("helloWorld".to_string())],
      Ok(vec![RuntimeValue::String("2025".to_string())].into()))]
#[case::while_("
    var x = 5 |
    while (x > 0):
      # test
      x -= 1 | x;
    ",
      vec![RuntimeValue::Number(10.into())],
      Ok(vec![RuntimeValue::Number(0.into())].into()))]
#[case::foreach("
    foreach(x, array(1, 2, 3)):
      add(x, 1);
    ",
      vec![RuntimeValue::Number(10.into())],
      Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into())])].into()))]
#[case::while_break("
    var x = 0 |
    while(x < 10):
      x += 1
      | if(x == 3):
        break
      else:
        x;
    ",
      vec![RuntimeValue::Number(10.into())],
      Ok(vec![RuntimeValue::Number(2.into())].into()))]
#[case::while_break_with_value("
    var x = 0 |
    while(x < 10):
      x += 1
      | if(x == 5):
        break: \"found\"
      else:
        x;
    ",
      vec![RuntimeValue::Number(10.into())],
      Ok(vec![RuntimeValue::String("found".to_string())].into()))]
#[case::while_continue("
    var x = 0 |
    while(x < 4):
      x += 1
      | if(x == 3):
        continue
      else:
        x;
    ",
      vec![RuntimeValue::Number(10.into())],
      Ok(vec![RuntimeValue::Number(4.into())].into()))]
#[case::foreach_break("
    foreach(x, array(1, 2, 3, 4, 5)):
      if(x == 3):
        break
      else:
        x + 10;
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(11.into()), RuntimeValue::Number(12.into())])].into()))]
#[case::foreach_break_with_value("
    foreach(x, array(1, 2, 3, 4, 5)):
      if(x == 3):
        break: \"stopped at 3\"
      else:
        x + 10;
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::String("stopped at 3".to_string())].into()))]
#[case::foreach_continue("
    foreach(x, array(1, 2, 3, 4, 5)):
      if(x == 3):
        continue
      else:
        x + 10;
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(11.into()), RuntimeValue::Number(12.into()), RuntimeValue::Number(14.into()), RuntimeValue::Number(15.into())])].into()))]
#[case::while_do_end("
    var x = 5 |
    while (x > 0) do
      x -= 1 | x
    end
    ",
      vec![RuntimeValue::Number(10.into())],
      Ok(vec![RuntimeValue::Number(0.into())].into()))]
#[case::foreach_do_end("
    foreach(x, array(1, 2, 3)) do
      add(x, 1)
    end
    ",
      vec![RuntimeValue::Number(10.into())],
      Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into())])].into()))]
#[case::while_do_end_break("
    var x = 0 |
    while(x < 10) do
      x += 1
      | if(x == 3):
        break
      else:
        x
      end
    end
    ",
      vec![RuntimeValue::Number(10.into())],
      Ok(vec![RuntimeValue::Number(2.into())].into()))]
#[case::foreach_do_end_continue("
    foreach(x, array(1, 2, 3, 4, 5)) do
      if(x == 3):
        continue
      else:
        x + 10
      end
    end
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(11.into()), RuntimeValue::Number(12.into()), RuntimeValue::Number(14.into()), RuntimeValue::Number(15.into())])].into()))]
#[case::loop_break_with_value("
    loop:
      break: 42;
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::Number(42.into())].into()))]
#[case::loop_break_with_value_complex("
    var x = 0 |
    loop:
      x += 1
      | if(x >= 5):
          break: x * 10
        else:
          x;
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::Number(50.into())].into()))]
#[case::nested_do_end("
    let arr = array(array(1, 2), array(3, 4)) |
    foreach(row, arr) do
      foreach(x, row) do
        x * 2
      end
    end
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Array(vec![RuntimeValue::Number(2.into()), RuntimeValue::Number(4.into())]), RuntimeValue::Array(vec![RuntimeValue::Number(6.into()), RuntimeValue::Number(8.into())])])].into()))]
#[case::match_do_end("
    match (2) do
      | 1: \"one\"
      | 2: \"two\"
      | _: \"other\"
    end
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::String("two".to_string())].into()))]
#[case::match_do_end_type_pattern("
    match (array(1, 2, 3)) do
      | :array: \"is_array\"
      | :number: \"is_number\"
      | _: \"other\"
    end
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::String("is_array".to_string())].into()))]
#[case::if_("
    def fibonacci(x):
      if(eq(x, 0)):
        0
      elif(eq(x, 1)):
        1
      else:
        add(fibonacci(sub(x, 1)), fibonacci(sub(x, 2)))
      ; | fibonacci(10)
    ",
      vec![RuntimeValue::Number(10.into())],
      Ok(vec![RuntimeValue::Number(55.into())].into()))]
#[case::if_("let x = 1
      | let y = if (eq(x, 1)): 2 else: 3
      | y
      ",
        vec![RuntimeValue::Number(0.into())],
              Ok(vec![RuntimeValue::Number(2.into())].into()))]
#[case::if_("let x = 2
      | let y = if (eq(x, 1)): 1
      | y
      ",
        vec![RuntimeValue::Number(0.into())], Ok(vec![RuntimeValue::NONE].into()))]
#[case::elif_("
      def test_fn(x):
        if (eq(x, 0)):
            0
        elif (eq(x, 1)):
            1
        else:
            2;
      | test_fn(0)
      ",
        vec![RuntimeValue::Number(0.into())],
        Ok(vec![RuntimeValue::Number(0.into())].into()))]
#[case::elif_("
      def test_fn(x):
        if (eq(x, 0)):
            0
        elif (eq(x, 1)):
            1
        else:
            2;
      | test_fn(1)
      ",
        vec![RuntimeValue::Number(1.into())],
        Ok(vec![RuntimeValue::Number(1.into())].into()))]
#[case::elif_("
      def test_fn(x):
        if (eq(x, 0)):
            0
        elif (eq(x, 1)):
            1
        else:
            2;
      | test_fn(2)
      ",
        vec![RuntimeValue::Number(2.into())],
        Ok(vec![RuntimeValue::Number(2.into())].into()))]
#[case::contains("contains(\"test\")",
      vec![RuntimeValue::String("testString".to_string())],
      Ok(vec![RuntimeValue::TRUE].into()))]
#[case::contains("contains(\"test\")",
      vec![RuntimeValue::String("String".to_string())],
      Ok(vec![RuntimeValue::FALSE].into()))]
#[case::is_array("is_array()",
      vec![RuntimeValue::Array(Vec::new())],
      Ok(vec![RuntimeValue::TRUE].into()))]
#[case::is_array("is_array(array(\"test\"))",
      vec![RuntimeValue::Array(Vec::new())],
      Ok(vec![RuntimeValue::TRUE].into()))]
#[case::is_array("is_string(array(\"test\"))",
      vec![RuntimeValue::Array(Vec::new())],
      Ok(vec![RuntimeValue::FALSE].into()))]
#[case::is_dict_true("is_dict()",
      vec![RuntimeValue::new_dict()],
      Ok(vec![RuntimeValue::TRUE].into()))]
#[case::is_dict_false("is_dict()",
      vec![RuntimeValue::Array(Vec::new())],
      Ok(vec![RuntimeValue::FALSE].into()))]
#[case::is_none_true("is_none(None)",
      vec!["text".into()],
      Ok(vec![RuntimeValue::TRUE].into()))]
#[case::is_none_false("is_none()",
      vec![RuntimeValue::Number(1.into())],
      Ok(vec![RuntimeValue::FALSE].into()))]
#[case::is_bool_true("is_bool(true)",
        vec![RuntimeValue::Boolean(true)],
        Ok(vec![RuntimeValue::TRUE].into()))]
#[case::is_bool_false("is_bool(false)",
        vec![RuntimeValue::Boolean(false)],
        Ok(vec![RuntimeValue::TRUE].into()))]
#[case::is_bool_non_bool("is_bool(1)",
        vec![RuntimeValue::Number(1.into())],
        Ok(vec![RuntimeValue::FALSE].into()))]
#[case::ltrimstr("ltrimstr(\"test\")",
      vec![RuntimeValue::String("testString".to_string())],
      Ok(vec![RuntimeValue::String("String".to_string())].into()))]
#[case::rtrimstr("rtrimstr(\"test\")",
      vec![RuntimeValue::String("Stringtest".to_string())],
      Ok(vec![RuntimeValue::String("String".to_string())].into()))]
#[case::ltrim("ltrim()",
      vec![RuntimeValue::String(" test ".to_string())],
      Ok(vec![RuntimeValue::String("test ".to_string())].into()))]
#[case::rtrim("rtrim()",
      vec![RuntimeValue::String(" test ".to_string())],
      Ok(vec![RuntimeValue::String(" test".to_string())].into()))]
#[case::is_empty("is_empty(\"\")",
      vec![RuntimeValue::String("String".to_string())],
      Ok(vec![RuntimeValue::TRUE].into()))]
#[case::is_empty("is_empty(\"test\")",
      vec![RuntimeValue::String("String".to_string())],
      Ok(vec![RuntimeValue::FALSE].into()))]
#[case::is_empty("is_empty(array(\"test\"))",
      vec![RuntimeValue::String("String".to_string())],
      Ok(vec![RuntimeValue::FALSE].into()))]
#[case::test1("test(\"^hello.*\")",
      vec![RuntimeValue::String("helloWorld".to_string())],
      Ok(vec![RuntimeValue::TRUE].into()))]
#[case::test2("test(\"^world.*\")",
      vec![RuntimeValue::String("helloWorld".to_string())],
      Ok(vec![RuntimeValue::FALSE].into()))]
#[case::test3("select(contains(\"hello\"))",
      vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text{value: "hello world".to_string(), position: None}))],
      Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text{value: "hello world".to_string(), position: None}))].into()))]
#[case::first("first(array(1, 2, 3))",
      vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])],
      Ok(vec![RuntimeValue::Number(1.into())].into()))]
#[case::first("first(array())",
      vec![RuntimeValue::Array(Vec::new())],
      Ok(vec![RuntimeValue::None].into()))]
#[case::last("last(array(1, 2, 3))",
      vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])],
      Ok(vec![RuntimeValue::Number(3.into())].into()))]
#[case::last("last(array())",
      vec![RuntimeValue::Array(Vec::new())],
      Ok(vec![RuntimeValue::None].into()))]
#[case::select("select(contains(\"hello\"))",
      vec![RuntimeValue::String("hello world".to_string())],
      Ok(vec![RuntimeValue::String("hello world".to_string())].into()))]
#[case::closure("
      def make_adder(x):
        fn(y): add(x, y);
      end
      let add_five = make_adder(5)
      | add_five(10)
      ",
        vec![RuntimeValue::Number(10.into())],
        Ok(vec![RuntimeValue::Number(15.into())].into()))]
#[case::closure("
      def make_adder(x):
        fn(y): add(x, y);
      end
      let add_five = fn(x): add(x, 5);
      | add_five(10)
      ",
        vec![RuntimeValue::Number(10.into())],
        Ok(vec![RuntimeValue::Number(15.into())].into()))]
#[case::map("def test(x): add(x, 1); | map(array(1, 2, 3), test)",
            vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])],
            Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into())])].into()))]
#[case::filter("
            def is_even(x):
              eq(mod(x, 2), 0);
            | filter(array(1, 2, 3, 4, 5, 6), is_even)
            ",
              vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into()), RuntimeValue::Number(5.into()), RuntimeValue::Number(6.into())])],
                    Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(2.into()), RuntimeValue::Number(4.into()), RuntimeValue::Number(6.into())])].into()))]
#[case::filter("
            def is_odd(x):
              eq(mod(x, 2), 1);
            | filter(array(1, 2, 3, 4, 5, 6), is_odd)
            ",
              vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into()), RuntimeValue::Number(5.into()), RuntimeValue::Number(6.into())])],
              Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(5.into())])].into()))]
#[case::func("let func1 = fn(): 1;
      | let func2 = fn(): 2;
      | add(func1(), func2())",
        vec![RuntimeValue::Number(0.into())],
              Ok(vec![RuntimeValue::Number(3.into())].into()))]
#[case::interpolated_string("let val1 = \"Hello\"
      | s\"${val1} World!\"",
        vec![RuntimeValue::Number(0.into())],
             Ok(vec!["Hello World!".to_string().into()].into()))]
#[case::interpolated_string("s\"${self} World!\"",
        vec![RuntimeValue::String("Hello".into())],
             Ok(vec!["Hello World!".to_string().into()].into()))]
#[case::matches_url("matches_url(\"https://github.com\")",
      vec![RuntimeValue::new_markdown(mq_markdown::Node::Definition(mq_markdown::Definition { position: None, url: mq_markdown::Url::new("https://github.com".to_string()), title: None, ident: "ident".to_string(), label: None }))],
      Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text { position: None, value: "true".to_string() }))].into()))]
#[case::matches_url("matches_url(\"https://github.com\")",
      vec![RuntimeValue::new_markdown(mq_markdown::Node::Link(mq_markdown::Link{ position: None, url: mq_markdown::Url::new("https://github.com".to_string()), title: None, values: Vec::new()}))],
      Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text { position: None, value: "true".to_string() }))].into()))]
#[case::matches_url("matches_url(\"https://github.com\")",
      vec![RuntimeValue::new_markdown(mq_markdown::Node::Image(mq_markdown::Image{ alt: "".to_string(), position: None, url: "https://github.com".to_string(), title: None }))],
      Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text { position: None, value: "true".to_string() }))].into()))]
#[case::matches_url("matches_url(\"https://gitlab.com\")",
      vec![RuntimeValue::String("https://gitlab.com".to_string())],
      Ok(vec![RuntimeValue::FALSE].into()))]
#[case::nest(".link | update(\"test\")",
      vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading{ values: vec![
           mq_markdown::Node::Link(mq_markdown::Link { url: mq_markdown::Url::new("url".to_string()), title: None, values: Vec::new(), position: None }),
           mq_markdown::Node::Image(mq_markdown::Image{ alt: "".to_string(), url: "url".to_string(), title: None, position: None })
      ], position: None, depth: 1 }))],
      Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Link(mq_markdown::Link { url: mq_markdown::Url::new("test".to_string()), title: None, values: Vec::new(), position: None }))].into()))]
#[case::selector("nodes | .h",
      vec![
        RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading{ values: vec![mq_markdown::Node::Text(mq_markdown::Text { value: "text".to_string(), position: None }),], position: None, depth: 1 })),
        RuntimeValue::String("test".to_string()),
      ],
      Ok(vec![
        RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading{ values: vec![mq_markdown::Node::Text(mq_markdown::Text { value: "text".to_string(), position: None }),], position: None, depth: 1 })),
        RuntimeValue::NONE
      ].into()))]
#[case::selector("nodes | .h",
      vec![
        RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text { value: "text".to_string(), position: None })),
        RuntimeValue::String("test".to_string()),
      ],
      Ok(vec![RuntimeValue::NONE, RuntimeValue::NONE].into()))]
#[case::sort_by("sort_by(get_title)",
      vec![RuntimeValue::Array(vec![
          RuntimeValue::new_markdown(mq_markdown::Node::Link(mq_markdown::Link{ url: mq_markdown::Url::new("http://mqlang1".to_string()), title: Some(mq_markdown::Title::new("2".to_string())), values: vec![
            mq_markdown::Node::Text(mq_markdown::Text { value: "text".to_string(), position: None })
          ], position: None })),
          RuntimeValue::new_markdown(mq_markdown::Node::Link(mq_markdown::Link{ url: mq_markdown::Url::new("http://mqlang2".to_string()), title: Some(mq_markdown::Title::new("1".to_string())), values: vec![
            mq_markdown::Node::Text(mq_markdown::Text { value: "text".to_string(), position: None })
          ], position: None })),
      ])],
      Ok(vec![RuntimeValue::Array(vec![
          RuntimeValue::new_markdown(mq_markdown::Node::Link(mq_markdown::Link{ url: mq_markdown::Url::new("http://mqlang2".to_string()), title: Some(mq_markdown::Title::new("1".to_string())), values: vec![
            mq_markdown::Node::Text(mq_markdown::Text { value: "text".to_string(), position: None })
          ], position: None })),
          RuntimeValue::new_markdown(mq_markdown::Node::Link(mq_markdown::Link{ url: mq_markdown::Url::new("http://mqlang1".to_string()), title: Some(mq_markdown::Title::new("2".to_string())), values: vec![
            mq_markdown::Node::Text(mq_markdown::Text { value: "text".to_string(), position: None })
          ], position: None })),
      ])].into()))]
#[case::sort_by("sort_by(get_url)",
      vec![RuntimeValue::Array(vec![
          RuntimeValue::new_markdown(mq_markdown::Node::Link(mq_markdown::Link{ url: mq_markdown::Url::new("http://mqlang2".to_string()), title: Some(mq_markdown::Title::new("1".to_string())), values: vec![
            mq_markdown::Node::Text(mq_markdown::Text { value: "text".to_string(), position: None })
          ], position: None })),
          RuntimeValue::new_markdown(mq_markdown::Node::Link(mq_markdown::Link{ url: mq_markdown::Url::new("http://mqlang1".to_string()), title: Some(mq_markdown::Title::new("2".to_string())), values: vec![
            mq_markdown::Node::Text(mq_markdown::Text { value: "text".to_string(), position: None })
          ], position: None })),
      ])],
      Ok(vec![RuntimeValue::Array(vec![
          RuntimeValue::new_markdown(mq_markdown::Node::Link(mq_markdown::Link{ url: mq_markdown::Url::new("http://mqlang1".to_string()), title: Some(mq_markdown::Title::new("2".to_string())), values: vec![
            mq_markdown::Node::Text(mq_markdown::Text { value: "text".to_string(), position: None })
          ], position: None })),
          RuntimeValue::new_markdown(mq_markdown::Node::Link(mq_markdown::Link{ url: mq_markdown::Url::new("http://mqlang2".to_string()), title: Some(mq_markdown::Title::new("1".to_string())), values: vec![
            mq_markdown::Node::Text(mq_markdown::Text { value: "text".to_string(), position: None })
          ], position: None })),
      ])].into()))]
#[case::sort_by(r#"def sort_test(v): if (eq(v, "3")): "1" elif (eq(v, "1")): "3" else: v; sort_by(sort_test)"#,
      vec![RuntimeValue::Array(vec![
         "2".to_string().into(),
         "1".to_string().into(),
         "3".to_string().into(),
      ])],
      Ok(vec![RuntimeValue::Array(vec![
         "3".to_string().into(),
         "2".to_string().into(),
         "1".to_string().into(),
      ])].into()))]
#[case::find_index("
      def is_even(x):
        eq(mod(x, 2), 0);
      | find_index(array(1, 3, 5, 6, 7), is_even)
      ",
        vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(5.into()), RuntimeValue::Number(6.into()), RuntimeValue::Number(7.into())])],
        Ok(vec![RuntimeValue::Number(3.into())].into()))]
#[case::find_index("
      def is_greater_than_five(x):
        gt(x, 5);
      | find_index(array(1, 3, 5, 6, 7), is_greater_than_five)
      ",
        vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(5.into()), RuntimeValue::Number(6.into()), RuntimeValue::Number(7.into())])],
        Ok(vec![RuntimeValue::Number(3.into())].into()))]
#[case::find_index_no_match("
      def is_negative(x):
        lt(x, 0);
      | find_index(array(1, 3, 5, 6, 7), is_negative)
      ",
        vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(5.into()), RuntimeValue::Number(6.into()), RuntimeValue::Number(7.into())])],
        Ok(vec![RuntimeValue::Number((-1).into())].into()))]
#[case::find_index_empty_array("
      def is_even(x):
        eq(mod(x, 2), 0);
      | find_index(array(), is_even)
      ",
        vec![RuntimeValue::Array(vec![])],
        Ok(vec![RuntimeValue::Number((-1).into())].into()))]
#[case::skip("
          skip([1, 2, 3, 4, 5], 2)
          ",
          vec![RuntimeValue::Array(vec![
              RuntimeValue::Number(1.into()),
              RuntimeValue::Number(2.into()),
              RuntimeValue::Number(3.into()),
              RuntimeValue::Number(4.into()),
              RuntimeValue::Number(5.into()),
          ])],
          Ok(vec![RuntimeValue::Array(vec![
              RuntimeValue::Number(3.into()),
              RuntimeValue::Number(4.into()),
              RuntimeValue::Number(5.into()),
          ])].into()))]
#[case::skip_zero("
          skip([1, 2, 3], 0)
          ",
          vec![RuntimeValue::Array(vec![
              RuntimeValue::Number(1.into()),
              RuntimeValue::Number(2.into()),
              RuntimeValue::Number(3.into()),
          ])],
          Ok(vec![RuntimeValue::Array(vec![
              RuntimeValue::Number(1.into()),
              RuntimeValue::Number(2.into()),
              RuntimeValue::Number(3.into()),
          ])].into()))]
#[case::skip_all("
          skip([1, 2, 3], 3)
          ",
          vec![RuntimeValue::Array(vec![
              RuntimeValue::Number(1.into()),
              RuntimeValue::Number(2.into()),
              RuntimeValue::Number(3.into()),
          ])],
          Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::skip_more_than_length("
          skip([1, 2, 3], 5)
          ",
          vec![RuntimeValue::Array(vec![
              RuntimeValue::Number(1.into()),
              RuntimeValue::Number(2.into()),
              RuntimeValue::Number(3.into()),
          ])],
          Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::skip_empty("
          skip([], 2)
          ",
          vec![RuntimeValue::Array(vec![])],
          Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::skip_while("
      def is_less_than_four(x):
        lt(x, 4);
      | skip_while(array(1, 2, 3, 4, 5, 1, 2), is_less_than_four)
      ",
        vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into()), RuntimeValue::Number(5.into()), RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())])],
        Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(4.into()), RuntimeValue::Number(5.into()), RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())])].into()))]
#[case::skip_while_all_match("
      def is_positive(x):
        gt(x, 0);
      | skip_while(array(1, 2, 3), is_positive)
      ",
        vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])],
        Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::skip_while_empty_array("
      def is_positive(x):
        gt(x, 0);
      | skip_while(array(), is_positive)
      ",
        vec![RuntimeValue::Array(vec![])],
        Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::take("
        take([1, 2, 3, 4, 5], 3)
        ",
        vec![RuntimeValue::Array(vec![
            RuntimeValue::Number(1.into()),
            RuntimeValue::Number(2.into()),
            RuntimeValue::Number(3.into()),
            RuntimeValue::Number(4.into()),
            RuntimeValue::Number(5.into()),
        ])],
        Ok(vec![RuntimeValue::Array(vec![
            RuntimeValue::Number(1.into()),
            RuntimeValue::Number(2.into()),
            RuntimeValue::Number(3.into()),
        ])].into()))]
#[case::take_zero("
        take([1, 2, 3], 0)
        ",
        vec![RuntimeValue::Array(vec![
            RuntimeValue::Number(1.into()),
            RuntimeValue::Number(2.into()),
            RuntimeValue::Number(3.into()),
        ])],
        Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::take_all("
        take([1, 2, 3], 3)
        ",
        vec![RuntimeValue::Array(vec![
            RuntimeValue::Number(1.into()),
            RuntimeValue::Number(2.into()),
            RuntimeValue::Number(3.into()),
        ])],
        Ok(vec![RuntimeValue::Array(vec![
            RuntimeValue::Number(1.into()),
            RuntimeValue::Number(2.into()),
            RuntimeValue::Number(3.into()),
        ])].into()))]
#[case::take_more_than_length("
        take([1, 2, 3], 5)
        ",
        vec![RuntimeValue::Array(vec![
            RuntimeValue::Number(1.into()),
            RuntimeValue::Number(2.into()),
            RuntimeValue::Number(3.into()),
        ])],
        Ok(vec![RuntimeValue::Array(vec![
            RuntimeValue::Number(1.into()),
            RuntimeValue::Number(2.into()),
            RuntimeValue::Number(3.into()),
        ])].into()))]
#[case::take_empty("
        take([], 2)
        ",
        vec![RuntimeValue::Array(vec![])],
        Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::take_while("
      def is_less_than_four(x):
        lt(x, 4);
      | take_while(array(1, 2, 3, 4, 5, 1, 2), is_less_than_four)
      ",
        vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into()), RuntimeValue::Number(5.into()), RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())])],
        Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])].into()))]
#[case::take_while_none_match("
      def is_negative(x):
        lt(x, 0);
      | take_while(array(1, 2, 3), is_negative)
      ",
        vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])],
        Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::take_while_all_match("
      def is_positive(x):
        gt(x, 0);
      | take_while(array(1, 2, 3), is_positive)
      ",
        vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])],
        Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])].into()))]
#[case::take_while_empty_array("
      def is_positive(x):
        gt(x, 0);
      | take_while(array(), is_positive)
      ",
        vec![RuntimeValue::Array(vec![])],
        Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::anonymous_fn("
        let f = fn(x): add(x, 1);
        | f(10)
        ",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Number(11.into())].into()))]
#[case::anonymous_fn_passed("
          def apply_func(f, x):
            f(x);
          | apply_func(fn(x): mul(x, 2);, 5)
          ",
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![RuntimeValue::Number(10.into())].into()))]
#[case::anonymous_fn_return("
          def make_multiplier(factor):
            fn(x): mul(x, factor);;
          | let double = make_multiplier(2)
          | double(5)
          ",
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![RuntimeValue::Number(10.into())].into()))]
#[case::array_empty("[]",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::array_with_elements("[1, 2, 3]",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])].into()))]
#[case::array_nested("[[1, 2], [3, 4]]",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Array(vec![
            RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())]),
            RuntimeValue::Array(vec![RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into())])
          ])].into()))]
#[case::array_mixed_types("[1, \"test\", []]",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Array(vec![
            RuntimeValue::Number(1.into()),
            RuntimeValue::String("test".to_string()),
            RuntimeValue::Array(vec![])
          ])].into()))]
#[case::array_length("len([])",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Number(0.into())].into()))]
#[case::array_length("len([1, 2, 3, 4])",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Number(4.into())].into()))]
#[case::dict_new_empty("dict()",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::new_dict()].into()))]
#[case::dict_set_get_string("let m = dict() | let m = set(m, \"name\", \"Jules\") | get(m, \"name\")",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::String("Jules".to_string())].into()))]
#[case::dict_set_get_number("let m = set(dict(), \"age\", 30) | get(m, \"age\")",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Number(30.into())].into()))]
#[case::dict_set_get_array("let m = set(dict(), \"data\", [1, 2, 3]) | get(m, \"data\")",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])].into()))]
#[case::dict_set_get_bool("let m = set(dict(), \"active\", true) | get(m, \"active\")",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::dict_set_get_none("let m = set(dict(), \"nothing\", None) | get(m, \"nothing\")",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::None].into()))]
#[case::dict_get_non_existent("let m = dict() | get(m, \"missing\")",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::None].into()))]
#[case::dict_set_overwrite("let m = set(dict(), \"name\", \"Jules\") | let m = set(m, \"name\", \"Vincent\") | get(m, \"name\")",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::String("Vincent".to_string())].into()))]
#[case::dict_nested_set_get("let m1 = dict() | let m2 = set(dict(), \"level\", 2) | let m = set(m1, \"nested\", m2) | get(get(m, \"nested\"), \"level\")",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Number(2.into())].into()))]
#[case::dict_keys_empty("keys(dict())",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::dict_keys_non_empty("let m = set(set(dict(), \"a\", 1), \"b\", 2) | keys(m)",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Array(vec![RuntimeValue::String("a".to_string()), RuntimeValue::String("b".to_string())])].into()))]
#[case::dict_values_empty("values(dict())",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::dict_values_non_empty("let m = set(set(dict(), \"a\", 1), \"b\", \"hello\") | values(m)",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::String("hello".to_string())])].into()))]
#[case::dict_len_empty("len(dict())",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Number(0.into())].into()))]
#[case::dict_len_non_empty("len(set(set(dict(), \"a\", 1), \"b\", 2))",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Number(2.into())].into()))]
#[case::dict_type_is_dict("type(dict())",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::String("dict".to_string())].into()))]
#[case::dict_contains_existing_key(r#"let m = set(dict(), "name", "Jules") | contains(m, "name")"#,
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::dict_contains_non_existing_key(r#"let m = set(dict(), "name", "Jules") | contains(m, "age")"#,
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Boolean(false)].into()))]
#[case::dict_contains_empty(r#"contains(dict(), "any_key")"#,
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Boolean(false)].into()))]
#[case::dict_contains_multiple_keys(r#"let m = set(set(set(dict(), "a", 1), "b", 2), "c", 3) | contains(m, "b")"#,
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::dict_map_identity(r#"let m = dict(["a", 1], ["b", 2]) | map(m, fn(kv): kv;)"#,
        vec![RuntimeValue::Number(0.into())],
        Ok(vec![{
          let mut dict = BTreeMap::new();
          dict.insert(Ident::new("a"), RuntimeValue::Number(1.into()));
          dict.insert(Ident::new("b"), RuntimeValue::Number(2.into()));
          dict.into()
        }].into()))]
#[case::dict_map_transform_values("
        def double_value(kv):
          array(first(kv), mul(last(kv), 2));
        | let m = set(set(dict(), \"x\", 5), \"y\", 10)
        | map(m, double_value)
        ",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![{
            let mut dict = BTreeMap::new();
            dict.insert(Ident::new("x"), RuntimeValue::Number(10.into()));
            dict.insert(Ident::new("y"), RuntimeValue::Number(20.into()));
            dict.into()
          }].into()))]
#[case::dict_map_transform_keys(r#"
          def prefix_key(kv):
            array(add("prefix_", first(kv)), last(kv));
          | let m = set(set(dict(), "a", 1), "b", 2)
          | map(m, prefix_key)
          "#,
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![{
              let mut dict = BTreeMap::new();
              dict.insert(Ident::new("prefix_a"), RuntimeValue::Number(1.into()));
              dict.insert(Ident::new("prefix_b"), RuntimeValue::Number(2.into()));
              dict.into()
            }].into()))]
#[case::dict_map_empty("map(dict(), fn(kv): kv;)",
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![RuntimeValue::new_dict()].into()))]
#[case::dict_map_complex_transform(r#"
          def transform_entry(kv):
            let key = first(kv)
            | let value = last(kv)
            | array(add(key, "_transformed"), add(value, 100));
          | let m = set(set(dict(), "num1", 1), "num2", 2)
          | map(m, transform_entry)
          "#,
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![{
              let mut dict = BTreeMap::new();
              dict.insert(Ident::new("num1_transformed"), RuntimeValue::Number(101.into()));
              dict.insert(Ident::new("num2_transformed"), RuntimeValue::Number(102.into()));
              dict.into()
            }].into()))]
#[case::dict_filter_even_values(r#"
            def is_even_value(kv):
              last(kv) | mod(2) | eq(0);
            | let m = dict(["a", 1], ["b", 2], ["c", 4])
            | filter(m, is_even_value)
            "#,
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![{
              let mut dict = BTreeMap::new();
              dict.insert(Ident::new("b"), RuntimeValue::Number(2.into()));
              dict.insert(Ident::new("c"), RuntimeValue::Number(4.into()));
              dict.into()
            }].into()))]
#[case::dict_filter_empty("filter(dict(), fn(kv): true;)",
           vec![RuntimeValue::Number(0.into())],
           Ok(vec![RuntimeValue::new_dict()].into()))]
#[case::group_by_numbers("
            def get_remainder(x):
              mod(x, 3);
            | group_by(array(1, 2, 3, 4, 5, 6, 7, 8, 9), get_remainder)
            ",
              vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into()), RuntimeValue::Number(5.into()), RuntimeValue::Number(6.into()), RuntimeValue::Number(7.into()), RuntimeValue::Number(8.into()), RuntimeValue::Number(9.into())])],
              Ok(vec![{
                let mut dict = BTreeMap::new();
                dict.insert(Ident::new("0"), RuntimeValue::Array(vec![RuntimeValue::Number(3.into()), RuntimeValue::Number(6.into()), RuntimeValue::Number(9.into())]));
                dict.insert(Ident::new("1"), RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(4.into()), RuntimeValue::Number(7.into())]));
                dict.insert(Ident::new("2"), RuntimeValue::Array(vec![RuntimeValue::Number(2.into()), RuntimeValue::Number(5.into()), RuntimeValue::Number(8.into())]));
                dict.into()
              }].into()))]
#[case::group_by_strings(r#"
            def get_length(s):
              len(s);
            | group_by(array("cat", "dog", "bird", "fish", "elephant"), get_length)
            "#,
              vec![RuntimeValue::Array(vec![RuntimeValue::String("cat".to_string()), RuntimeValue::String("dog".to_string()), RuntimeValue::String("bird".to_string()), RuntimeValue::String("fish".to_string()), RuntimeValue::String("elephant".to_string())])],
              Ok(vec![{
                let mut dict = BTreeMap::new();
                dict.insert(Ident::new("3"), RuntimeValue::Array(vec![RuntimeValue::String("cat".to_string()), RuntimeValue::String("dog".to_string())]));
                dict.insert(Ident::new("4"), RuntimeValue::Array(vec![RuntimeValue::String("bird".to_string()), RuntimeValue::String("fish".to_string())]));
                dict.insert(Ident::new("8"), RuntimeValue::Array(vec![RuntimeValue::String("elephant".to_string())]));
                dict.into()
              }].into()))]
#[case::group_by_empty_array("
            def identity(x):
              x;
            | group_by(array(), identity)
            ",
              vec![RuntimeValue::Array(vec![])],
              Ok(vec![RuntimeValue::new_dict()].into()))]
#[case::group_by_single_element("
            def identity(x):
              x;
            | group_by(array(42), identity)
            ",
              vec![RuntimeValue::Array(vec![RuntimeValue::Number(42.into())])],
              Ok(vec![{
                let mut dict = BTreeMap::new();
                dict.insert(Ident::new("42"), RuntimeValue::Array(vec![RuntimeValue::Number(42.into())]));
                dict.into()
              }].into()))]
#[case::group_by_all_same_key(r#"
            def always_same(x):
              "same";
            | group_by(array(1, 2, 3, 4), always_same)
            "#,
              vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into())])],
              Ok(vec![{
                let mut dict = BTreeMap::new();
                dict.insert(Ident::new("same"), RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into())]));
                dict.into()
              }].into()))]
#[case::group_by_boolean_result("
            def is_even(x):
              eq(mod(x, 2), 0);
            | group_by(array(1, 2, 3, 4, 5, 6), is_even)
            ",
              vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into()), RuntimeValue::Number(5.into()), RuntimeValue::Number(6.into())])],
              Ok(vec![{
                let mut dict = BTreeMap::new();
                dict.insert(Ident::new("false"), RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(5.into())]));
                dict.insert(Ident::new("true"), RuntimeValue::Array(vec![RuntimeValue::Number(2.into()), RuntimeValue::Number(4.into()), RuntimeValue::Number(6.into())]));
                dict.into()
              }].into()))]
#[case::is_h_true("is_h()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
            values: vec![],
            position: None,
            depth: 1,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "true".to_string(),
            position: None,
        }))].into()))]
#[case::is_h_false("is_h()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))].into()))]
#[case::is_h1_true("is_h1()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
            values: vec![],
            position: None,
            depth: 1,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "true".to_string(),
            position: None,
        }))].into()))]
#[case::is_h1_false("is_h1()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
          values: vec![],
          position: None,
          depth: 2,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))].into()))]
#[case::is_h1_false("is_h1()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))].into()))]
#[case::is_h2_true("is_h2()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
            values: vec![],
            position: None,
            depth: 2,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "true".to_string(),
            position: None,
        }))].into()))]
#[case::is_h2_false("is_h2()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
          values: vec![],
          position: None,
          depth: 3,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))].into()))]
#[case::is_h2_false("is_h2()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))].into()))]
#[case::is_h3_true("is_h3()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
            values: vec![],
            position: None,
            depth: 3,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "true".to_string(),
            position: None,
        }))].into()))]
#[case::is_h3_false("is_h3()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
          values: vec![],
          position: None,
          depth: 4,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))].into()))]
#[case::is_h3_false("is_h3()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))].into()))]
#[case::is_h4_true("is_h4()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
            values: vec![],
            position: None,
            depth: 4,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "true".to_string(),
            position: None,
        }))].into()))]
#[case::is_h4_false("is_h4()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
          values: vec![],
          position: None,
          depth: 5,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))].into()))]
#[case::is_h4_false("is_h4()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))].into()))]
#[case::is_h5_true("is_h5()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
            values: vec![],
            position: None,
            depth: 5,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "true".to_string(),
            position: None,
        }))].into()))]
#[case::is_h5_false("is_h5()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
          values: vec![],
          position: None,
          depth: 4,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))].into()))]
#[case::is_h5_false("is_h5()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))].into()))]
#[case::is_h6_true("is_h6()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
            values: vec![],
            position: None,
            depth: 6,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "true".to_string(),
            position: None,
        }))].into()))]
#[case::is_h6_false("is_h6()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
          values: vec![],
          position: None,
          depth: 5,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))].into()))]
#[case::is_h6_false("is_h6()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))].into()))]
#[case::is_em_true("is_em()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Emphasis(mq_markdown::Emphasis {
          values: vec![],
          position: None,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "true".to_string(),
          position: None,
        }))].into()))]
#[case::is_em_false("is_em()",
        vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))],
        Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
          value: "false".to_string(),
          position: None,
        }))].into()))]
#[case::is_html_true("is_html()",
          vec![RuntimeValue::new_markdown(mq_markdown::Node::Html(mq_markdown::Html {
              value: "<b>bold</b>".to_string(),
              position: None,
          }))],
          Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "true".to_string(),
              position: None,
          }))].into()))]
#[case::is_html_false("is_html()",
          vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "not html".to_string(),
              position: None,
          }))],
          Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "false".to_string(),
              position: None,
          }))].into()))]
#[case::is_yaml_true("is_yaml()",
          vec![RuntimeValue::new_markdown(mq_markdown::Node::Yaml(mq_markdown::Yaml {
            value: "---\nkey: value\n".to_string(),
            position: None,
          }))],
          Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "true".to_string(),
            position: None,
          }))].into()))]
#[case::is_yaml_false("is_yaml()",
          vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "not yaml".to_string(),
            position: None,
          }))],
          Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "false".to_string(),
            position: None,
          }))].into()))]
#[case::is_toml_true("is_toml()",
          vec![RuntimeValue::new_markdown(mq_markdown::Node::Toml(mq_markdown::Toml {
            value: "[section]\nkey = \"value\"\n".to_string(),
            position: None,
          }))],
          Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "true".to_string(),
            position: None,
          }))].into()))]
#[case::is_toml_false("is_toml()",
          vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "not toml".to_string(),
            position: None,
          }))],
          Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "false".to_string(),
            position: None,
          }))].into()))]
#[case::is_code_true("is_code()",
          vec![RuntimeValue::new_markdown(mq_markdown::Node::Code(mq_markdown::Code {
            value: "let x = 1;".to_string(),
            position: None,
            fence: true,
            meta: None,
            lang: Some("rust".to_string()),
          }))],
          Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "true".to_string(),
            position: None,
          }))].into()))]
#[case::is_code_false("is_code()",
          vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "not code".to_string(),
            position: None,
          }))],
          Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "false".to_string(),
            position: None,
          }))].into()))]
#[case::is_text_true("is_text()",
          vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "sample".to_string(),
            position: None,
          }))],
          Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "true".to_string(),
            position: None,
          }))].into()))]
#[case::is_text_false("is_text()",
          vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
            values: vec![],
            position: None,
            depth: 1,
          }))],
          Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
            value: "false".to_string(),
            position: None,
          }))].into()))]
#[case::is_list_true("is_list()",
            vec![RuntimeValue::new_markdown(mq_markdown::Node::List(mq_markdown::List {
              values: vec![],
              position: None,
              ordered: false,
              level: 1,
              index: 1,
              checked: Some(false),
            }))],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "true".to_string(),
              position: None,
            }))].into()))]
#[case::is_list_false("is_list()",
            vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "not a list".to_string(),
              position: None,
            }))],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "false".to_string(),
              position: None,
            }))].into()))]
#[case::is_mdx_flow_expression_true("is_mdx_flow_expression()",
            vec![RuntimeValue::new_markdown(mq_markdown::Node::MdxFlowExpression(mq_markdown::MdxFlowExpression {
              value: "1 + 2".into(),
              position: None,
            }))],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "true".to_string(),
              position: None,
            }))].into()))]
#[case::is_mdx_flow_expression_false("is_mdx_flow_expression()",
            vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "not mdx".to_string(),
              position: None,
            }))],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "false".to_string(),
              position: None,
            }))].into()))]
#[case::is_mdx_jsx_flow_element_true("is_mdx_jsx_flow_element()",
            vec![RuntimeValue::new_markdown(mq_markdown::Node::MdxJsxFlowElement(mq_markdown::MdxJsxFlowElement {
              name: Some("Component".to_string()),
              attributes: vec![],
              children: vec![],
              position: None,
            }))],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "true".to_string(),
              position: None,
            }))].into()))]
#[case::is_mdx_jsx_flow_element_false("is_mdx_jsx_flow_element()",
            vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "not jsx".to_string(),
              position: None,
            }))],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "false".to_string(),
              position: None,
            }))].into()))]
#[case::is_mdx_jsx_text_element_true("is_mdx_jsx_text_element()",
            vec![RuntimeValue::new_markdown(mq_markdown::Node::MdxJsxTextElement(mq_markdown::MdxJsxTextElement {
              name: Some("InlineComponent".into()),
              attributes: vec![],
              children: vec![],
              position: None,
            }))],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "true".to_string(),
              position: None,
            }))].into()))]
#[case::is_mdx_jsx_text_element_false("is_mdx_jsx_text_element()",
            vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "not jsx text".to_string(),
              position: None,
            }))],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "false".to_string(),
              position: None,
            }))].into()))]
#[case::is_mdx_text_expression_true("is_mdx_text_expression()",
            vec![RuntimeValue::new_markdown(mq_markdown::Node::MdxTextExpression(mq_markdown::MdxTextExpression {
              value: "foo + bar".into(),
              position: None,
            }))],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "true".to_string(),
              position: None,
            }))].into()))]
#[case::is_mdx_text_expression_false("is_mdx_text_expression()",
            vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "not mdx text expr".to_string(),
              position: None,
            }))],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "false".to_string(),
              position: None,
            }))].into()))]
#[case::is_mdx_js_esm_true("is_mdx_js_esm()",
            vec![RuntimeValue::new_markdown(mq_markdown::Node::MdxJsEsm(mq_markdown::MdxJsEsm {
              value: "export const foo = 1;".into(),
              position: None,
            }))],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "true".to_string(),
              position: None,
            }))].into()))]
#[case::is_mdx_js_esm_false("is_mdx_js_esm()",
            vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "not esm".to_string(),
              position: None,
            }))],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "false".to_string(),
              position: None,
            }))].into()))]
#[case::is_mdx_true("is_mdx()",
            vec![RuntimeValue::new_markdown(mq_markdown::Node::MdxFlowExpression(mq_markdown::MdxFlowExpression {
              value: "1 + 2".into(),
              position: None,
            }))],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "true".to_string(),
              position: None,
            }))].into()))]
#[case::is_mdx_false("is_mdx()",
            vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "not mdx".to_string(),
              position: None,
            }))],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "false".to_string(),
              position: None,
            }))].into()))]
#[case::any_true("
              any([1, 2, 3], fn(x): x == 2;)",
              vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])],
              Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::any_false("
              any([1, 2, 3], fn(x): x == 4;)",
              vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])],
              Ok(vec![RuntimeValue::Boolean(false)].into()))]
#[case::any_empty_array("
              any([], fn(x): x == 1;)",
              vec![RuntimeValue::Array(vec![])],
              Ok(vec![RuntimeValue::Boolean(false)].into()))]
#[case::any_dict_true(r#"any(dict(["a", 1], ["b", 2]), fn(kv): last(kv) == 2;)"#,
              vec![{
                let mut dict = BTreeMap::new();
                dict.insert(Ident::new("a"), RuntimeValue::Number(1.into()));
                dict.insert(Ident::new("b"), RuntimeValue::Number(2.into()));
                dict.into()
              }],
              Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::any_dict_false(r#"any(dict(["a", 1], ["b", 2]), fn(kv): last(kv) == 3;)"#,
              vec![{
                let mut dict = BTreeMap::new();
                dict.insert(Ident::new("a"), RuntimeValue::Number(1.into()));
                dict.insert(Ident::new("b"), RuntimeValue::Number(2.into()));
                dict.into()
              }],
              Ok(vec![RuntimeValue::Boolean(false)].into()))]
#[case::all_true("
              all([2, 4, 6], fn(x): mod(x, 2) == 0;)",
              vec![RuntimeValue::Array(vec![RuntimeValue::Number(2.into()), RuntimeValue::Number(4.into()), RuntimeValue::Number(6.into())])],
              Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::all_false("
              all([2, 3, 6], fn(x): mod(x, 2) == 0;)",
              vec![RuntimeValue::Array(vec![RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(6.into())])],
              Ok(vec![RuntimeValue::Boolean(false)].into()))]
#[case::all_empty_array("
              all([], fn(x): x == 1;)",
              vec![RuntimeValue::Array(vec![])],
              Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::all_dict_true(r#"all(dict(["a", 2], ["b", 4]), fn(kv): mod(last(kv), 2) == 0;)"#,
              vec![{
              let mut dict = BTreeMap::new();
              dict.insert(Ident::new("a"), RuntimeValue::Number(2.into()));
              dict.insert(Ident::new("b"), RuntimeValue::Number(4.into()));
              dict.into()
              }],
              Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::all_dict_false(r#"all(dict(["a", 2], ["b", 3]), fn(kv): mod(last(kv), 2) == 0;)"#,
              vec![{
              let mut dict = BTreeMap::new();
              dict.insert(Ident::new("a"), RuntimeValue::Number(2.into()));
              dict.insert(Ident::new("b"), RuntimeValue::Number(3.into()));
              dict.into()
              }],
              Ok(vec![RuntimeValue::Boolean(false)].into()))]
#[case::in_array_true("in([1, 2, 3], 2)",
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::in_array_false("in([1, 2, 3], 4)",
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![RuntimeValue::Boolean(false)].into()))]
#[case::in_string_true(r#"in("hello", "ell")"#,
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::in_string_false(r#"in("hello", "xyz")"#,
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![RuntimeValue::Boolean(false)].into()))]
#[case::in_array_true(r#"in(["a", "b", "c"], ["a", "b"])"#,
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::in_array_false(r#"in(["a", "c"], ["a", "b"])"#,
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![RuntimeValue::Boolean(false)].into()))]
#[case::fold_sum("
            def sum(acc, x):
              add(acc, x);
            | fold([1, 2, 3, 4], 0, sum)
            ",
            vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into())])],
            Ok(vec![RuntimeValue::Number(10.into())].into()))]
#[case::fold_concat(r#"
            def concat(acc, x):
              add(acc, x);
            | fold(["a", "b", "c"], "", concat)
            "#,
            vec![RuntimeValue::Array(vec![RuntimeValue::String("a".into()), RuntimeValue::String("b".into()), RuntimeValue::String("c".into())])],
            Ok(vec![RuntimeValue::String("abc".into())].into()))]
#[case::fold_empty("
            def sum(acc, x):
              add(acc, x);
            | fold([], 0, sum)
            ",
            vec![RuntimeValue::Array(vec![])],
            Ok(vec![RuntimeValue::Number(0.into())].into()))]
#[case::unique_by_numbers("
            def get_remainder(x):
              mod(x, 3);
            | unique_by([1, 2, 3, 4, 5, 6, 7, 8, 9], get_remainder)
            ",
              vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into()), RuntimeValue::Number(5.into()), RuntimeValue::Number(6.into()), RuntimeValue::Number(7.into()), RuntimeValue::Number(8.into()), RuntimeValue::Number(9.into())])],
              Ok(vec![RuntimeValue::Array(vec![
              RuntimeValue::Number(1.into()),
              RuntimeValue::Number(2.into()),
              RuntimeValue::Number(3.into()),
              ])].into()))]
#[case::unique_by_strings(r#"
            def get_length(s):
              len(s);
            | unique_by(["cat", "dog", "bird", "fish", "elephant"], get_length)
            "#,
              vec![RuntimeValue::Array(vec![RuntimeValue::String("cat".to_string()), RuntimeValue::String("dog".to_string()), RuntimeValue::String("bird".to_string()), RuntimeValue::String("fish".to_string()), RuntimeValue::String("elephant".to_string())])],
              Ok(vec![RuntimeValue::Array(vec![
              RuntimeValue::String("cat".to_string()),
              RuntimeValue::String("bird".to_string()),
              RuntimeValue::String("elephant".to_string()),
              ])].into()))]
#[case::unique_by_empty_array("
            def identity(x):
              x;
            | unique_by([], identity)
            ",
              vec![RuntimeValue::Array(vec![])],
              Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::unique_by_all_same_key(r#"
            def always_same(x):
              "same";
            | unique_by([1, 2, 3, 4], always_same)
            "#,
              vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into())])],
              Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into())])].into()))]
#[case::dict_literal_empty("let d = {} | d",
            vec![RuntimeValue::Number(0.into())], // Dummy input
            Ok(vec![RuntimeValue::new_dict()].into()))]
#[case::dict_literal_simple(r#"let d = {"a": 1, "b": "two"} | d"#, // Mixing string and ident keys
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![{
                let mut dict = BTreeMap::new();
                dict.insert(Ident::new("a"), RuntimeValue::Number(1.into()));
                dict.insert(Ident::new("b"), RuntimeValue::String("two".to_string()));
                dict.into()
            }].into()))]
#[case::dict_literal_access_after_creation(r#"let d = {"name": "Jules", "occupation": "Philosopher"} | get(d, "name")"#,
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![RuntimeValue::String("Jules".to_string())].into()))]
#[case::dict_literal_trailing_comma(r#"let d = {"a":1, "b":2,} | len(d)"#,
            vec![RuntimeValue::Number(0.into())],
            Ok(vec![RuntimeValue::Number(2.into())].into()))]
#[case::selector_attr(".code.lang",
            vec![
              RuntimeValue::new_markdown(mq_markdown::Node::Code(mq_markdown::Code{ lang: Some("rust".to_string()), meta: None, fence: true, value: "value".to_string(),  position: None })),
            ],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "rust".to_string(),
              position: None,
            }))].into()))]
#[case::standalone_attr_selector_lang(".lang",
            vec![
              RuntimeValue::new_markdown(mq_markdown::Node::Code(mq_markdown::Code{ lang: Some("rust".to_string()), meta: None, fence: true, value: "value".to_string(),  position: None })),
            ],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "rust".to_string(),
              position: None,
            }))].into()))]
#[case::standalone_attr_selector_set(".lang |= \"python\" | .lang",
            vec![
              RuntimeValue::new_markdown(mq_markdown::Node::Code(mq_markdown::Code{ lang: Some("rust".to_string()), meta: None, fence: true, value: "".to_string(),  position: None })),
            ],
            Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text {
              value: "python".to_string(),
              position: None,
            }))].into()))]
#[case::recursive_selector_with_children("nodes | ..",
            vec![
              RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading{
                  values: vec![
                      mq_markdown::Node::Text(mq_markdown::Text { value: "hello".to_string(), position: None }),
                      mq_markdown::Node::Link(mq_markdown::Link { url: mq_markdown::Url::new("url".to_string()), title: None, values: Vec::new(), position: None }),
                  ],
                  position: None,
                  depth: 1,
              })),
            ],
            Ok(vec![
              RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text { value: "hello".to_string(), position: None })),
              RuntimeValue::new_markdown(mq_markdown::Node::Link(mq_markdown::Link { url: mq_markdown::Url::new("url".to_string()), title: None, values: Vec::new(), position: None })),
            ].into()))]
#[case::recursive_selector_leaf_node("nodes | ..",
            vec![
              RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text { value: "leaf".to_string(), position: None })),
            ],
            Ok(vec![].into()))]
#[case::recursive_selector_nested("nodes | ..",
            vec![
              RuntimeValue::new_markdown(mq_markdown::Node::Blockquote(mq_markdown::Blockquote{
                  values: vec![
                      mq_markdown::Node::Heading(mq_markdown::Heading{
                          values: vec![
                              mq_markdown::Node::Text(mq_markdown::Text { value: "nested".to_string(), position: None }),
                          ],
                          position: None,
                          depth: 2,
                      }),
                  ],
                  position: None,
              })),
            ],
            Ok(vec![
              RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text { value: "nested".to_string(), position: None })),
              RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading{
                  values: vec![
                      mq_markdown::Node::Text(mq_markdown::Text { value: "nested".to_string(), position: None }),
                  ],
                  position: None,
                  depth: 2,
              })),
            ].into()))]
#[case::recursive_selector_pipe_filter("nodes | .. | filter(fn(x): select(x, .text);)",
            vec![
              RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading{
                  values: vec![
                      mq_markdown::Node::Text(mq_markdown::Text { value: "hello".to_string(), position: None }),
                      mq_markdown::Node::Link(mq_markdown::Link { url: mq_markdown::Url::new("url".to_string()), title: None, values: Vec::new(), position: None }),
                  ],
                  position: None,
                  depth: 1,
              })),
            ],
            Ok(vec![
              RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text { value: "hello".to_string(), position: None })),
            ].into()))]
#[case::do_block_simple("
    do
      \"hello world\"
    end
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::String("hello world".to_string())].into()))]
#[case::do_block_multiple_statements("
    do
      let x = 5
      | let y = 10
      | add(x, y)
    end
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::Number(15.into())].into()))]
#[case::do_block_with_input("
    do
      add(\"processed: \", self)
    end
    ",
      vec![RuntimeValue::String("input".to_string())],
      Ok(vec![RuntimeValue::String("processed: input".to_string())].into()))]
#[case::do_block_nested("
    do
      let x = 5
      | do
          let y = 10
          | add(x, y)
        end
      | add(x, 100)
    end
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::Number(105.into())].into()))]
#[case::do_block_with_function("
    def double(x):
      mul(x, 2);
    | do
        let value = 21
        | double(value)
      end
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::Number(42.into())].into()))]
#[case::if_no_end("
    def test_fn(x):
      if (eq(x, 1)):
        \"one\"
      elif (eq(x, 2)):
        \"two\"
      else:
        \"other\";
    | test_fn(1)
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::String("one".to_string())].into()))]
#[case::if_elif_no_end("
    def test_fn(x):
      if (eq(x, 1)):
        \"one\"
      elif (eq(x, 2)):
        \"two\"
      else:
        \"other\";
    | test_fn(2)
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::String("two".to_string())].into()))]
#[case::if_else_no_end("
    def test_fn(x):
      if (eq(x, 1)):
        \"one\"
      elif (eq(x, 2)):
        \"two\"
      else:
        \"other\";
    | test_fn(3)
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::String("other".to_string())].into()))]
#[case::nested_if_no_end("
    def test_nested(x, y):
      if (gt(x, 0)):
        if (gt(y, 0)):
          \"both positive\"
        else:
          \"x positive, y non-positive\"
      else:
        \"x non-positive\";
    | test_nested(1, 1)
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::String("both positive".to_string())].into()))]
#[case::if_in_do_block("
    do
      let x = 5
      | if (gt(x, 3)):
          \"greater than 3\"
        else:
          \"not greater than 3\"
    end
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::String("greater than 3".to_string())].into()))]
#[case::if_do_block_simple("
    let v = 1
    | if (v > 0):
      do
        \"hello world\" | upcase()
      end
    ",
      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::String("HELLO WORLD".to_string())].into()))]
#[case::let_do_block_simple("
    let v = do \"hello world\" | upcase() end | v
    ",      vec![RuntimeValue::Number(0.into())],
      Ok(vec![RuntimeValue::String("HELLO WORLD".to_string())].into()))]
#[case::array_with_comment("[1 # comment\n, 2, 3]",
        vec![RuntimeValue::Number(0.into())],
        Ok(vec![RuntimeValue::Array(vec![
          RuntimeValue::Number(1.into()),
          RuntimeValue::Number(2.into()),
          RuntimeValue::Number(3.into())])].into()))]
#[case::dict_literal_with_comment(
  r#"let d = {
    "a": 1, # comment for a
    "b": 2, # comment for b
    # comment line
    "c": 3
  } | d"#,
  vec![RuntimeValue::Number(0.into())],
  Ok(vec![{
    let mut dict = BTreeMap::new();
    dict.insert(Ident::new("a"), RuntimeValue::Number(1.into()));
    dict.insert(Ident::new("b"), RuntimeValue::Number(2.into()));
    dict.insert(Ident::new("c"), RuntimeValue::Number(3.into()));
    dict.into()
  }].into())
)]
#[case::empty_array_iterator_expand("[]*.[]",
        vec![RuntimeValue::Number(0.into())],
        Ok(vec![RuntimeValue::Number(0.into())].into()))]
#[case::array_mul_decimal("[2,1]*0.2",
        vec![RuntimeValue::Number(0.into())],
        Ok(vec![RuntimeValue::Array(vec![
            RuntimeValue::Number(0.4.into()),
            RuntimeValue::Number(0.2.into())
        ])].into()))]
#[case::array_mul_large_number("[0.4,0.2]*5E9",
        vec![RuntimeValue::Number(0.into())],
        Ok(vec![RuntimeValue::Array(vec![
            RuntimeValue::Number(2000000000.0.into()),
            RuntimeValue::Number(1000000000.0.into())
        ])].into()))]
#[case::array_mul_small_integer("[1,2]*5",
        vec![RuntimeValue::Number(0.into())],
        Ok(vec![RuntimeValue::Array(vec![
            RuntimeValue::Number(1.into()),
            RuntimeValue::Number(2.into()),
            RuntimeValue::Number(1.into()),
            RuntimeValue::Number(2.into()),
            RuntimeValue::Number(1.into()),
            RuntimeValue::Number(2.into()),
            RuntimeValue::Number(1.into()),
            RuntimeValue::Number(2.into()),
            RuntimeValue::Number(1.into()),
            RuntimeValue::Number(2.into())
        ])].into()))]
#[case::array_mul_at_boundary("[1,2]*1000",
        vec![RuntimeValue::Number(0.into())],
        Ok(vec![{
            let mut arr = Vec::with_capacity(2000);
            for _ in 0..1000 {
                arr.push(RuntimeValue::Number(1.into()));
                arr.push(RuntimeValue::Number(2.into()));
            }
            RuntimeValue::Array(arr)
        }].into()))]
#[case::array_mul_over_boundary("[1,2]*1001",
        vec![RuntimeValue::Number(0.into())],
        Ok(vec![RuntimeValue::Array(vec![
            RuntimeValue::Number(1001.into()),
            RuntimeValue::Number(2002.into())
        ])].into()))]
#[case::range_mul_decimal_large("2..1*.2*5E9",
        vec![RuntimeValue::Number(0.into())],
        Ok(vec![RuntimeValue::Array(vec![
            RuntimeValue::Number(2000000000.0.into()),
            RuntimeValue::Number(1000000000.0.into())
        ])].into()))]
#[case::array_concat_normal("[1,2]+[3,4]",
        vec![RuntimeValue::Number(0.into())],
        Ok(vec![RuntimeValue::Array(vec![
            RuntimeValue::Number(1.into()),
            RuntimeValue::Number(2.into()),
            RuntimeValue::Number(3.into()),
            RuntimeValue::Number(4.into())
        ])].into()))]
#[case::get_variable_simple("
          let x = 42
          | get_variable(\"x\")
          ",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Number(42.into())].into()))]
#[case::set_variable_simple("
          set_variable(\"x\", 99)
          | get_variable(\"x\")
          ",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Number(99.into())].into()))]
#[case::set_variable_overwrite("
          set_variable(\"x\", 1)
          | set_variable(\"x\", 2)
          | get_variable(\"x\")
          ",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::Number(2.into())].into()))]
#[case::set_and_get_multiple_variables("
          set_variable(\"a\", \"foo\")
          | set_variable(\"b\", \"bar\")
          | get_variable(\"a\") + get_variable(\"b\")
          ",
          vec![RuntimeValue::Number(0.into())],
          Ok(vec![RuntimeValue::String("foobar".to_string())].into()))]
#[case::to_mdx_single_text(
            r#""<Component />" | to_mdx() | first() | to_string()"#,
            vec![RuntimeValue::None],
            Ok(vec![RuntimeValue::String("<Component />".to_string())].into())
          )]
#[case::var_basic("
    var x = 10 | x
    ",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Number(10.into())].into()))]
#[case::var_and_assign("
    var x = 10 | x = 20 | x
    ",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Number(20.into())].into()))]
#[case::var_multiple_assigns("
    var count = 0 | count = count + 1 | count = count + 2 | count
    ",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Number(3.into())].into()))]
#[case::var_with_string("
    var name = \"Alice\" | name = \"Bob\" | name
    ",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("Bob".to_string())].into()))]
#[case::var_in_loop("
    var sum = 0 |
    foreach (i, array(1, 2, 3, 4, 5)):
        sum = sum + i
        | sum;
    ",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(3.into()), RuntimeValue::Number(6.into()), RuntimeValue::Number(10.into()), RuntimeValue::Number(15.into())])].into()))]
#[case::macro_basic("
    macro double(x) do
      x + x
    end
    | double(5)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(10.into())].into()))]
#[case::macro_with_string("
    macro greet(name) do
      s\"Hello, ${name}!\"
    end
    | greet(\"World\")
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::String("Hello, World!".to_string())].into()))]
#[case::macro_multiple_params("
    macro add_three(a, b, c) do
      a + b + c
    end
    | add_three(1, 2, 3)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(6.into())].into()))]
#[case::macro_with_function_call("
    macro apply_twice(f, x) do
      f(f(x))
    end
    | def inc(n): n + 1;
    | apply_twice(inc, 5)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(7.into())].into()))]
#[case::macro_nested_calls("
    macro double(x): x + x
    | macro quadruple(x): double(double(x))
    | quadruple(3)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(12.into())].into()))]
#[case::macro_with_let("
    macro let_double(x) do
      let y = x | y + y
    end
    | let_double(7)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(14.into())].into()))]
#[case::macro_with_if("
    macro max(a, b) do
        if(a > b): a else: b
    end
    | max(10, 5)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(10.into())].into()))]
#[case::macro_with_array("
    macro first_two(arr) do
      arr[0:2]
    end
    | first_two([1, 2, 3, 4, 5])
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())])].into()))]
#[case::macro_parameter_shadowing("
    let x = 100 |
    macro use_param(x) do
      x * 2
    end
    | use_param(5)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(10.into())].into()))]
#[case::macro_quote_basic("
    macro make_expr(x) do
      quote: unquote(x) + 1
    end
    | make_expr(5)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(6.into())].into()))]
#[case::macro_quote_multiple_expressions("
    macro wrap_expr(x) do
      quote do
        let result = unquote(x) | result * 2
      end
    end
    | wrap_expr(5)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(10.into())].into()))]
#[case::macro_quote_with_function("
    macro define_double() do
        quote: def double(x): x * 2 end
    end
    | define_double() | double(7)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(14.into())].into()))]
#[case::macro_quote_nested("
    macro compute(a, b) do
        quote: unquote(a) + unquote(b) * 2
    end
    | compute(10, 5)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(20.into())].into()))]
#[case::macro_quote_with_if("
    macro conditional_expr(x) do
        quote: if(unquote(x) > 10): \"large\" else: \"small\"
    end
    | conditional_expr(15)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::String("large".to_string())].into()))]
#[case::macro_quote_preserve_structure("
    macro make_array(a, b, c) do
        quote: [unquote(a), unquote(b), unquote(c)]
    end
    | make_array(1, 2, 3)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])].into()))]
#[case::macro_quote_with_let_outside("
    macro test(x) do
        let y = x + 1 |
        quote: unquote(y) * 2
    end
    | test(5)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(12.into())].into()))]
#[case::macro_quote_mixed_code("
    macro compute(x) do
        let a = x * 2 |
        let b = x + 10 |
        quote: unquote(a) + unquote(b)
    end
    | compute(5)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(25.into())].into()))]
#[case::macro_quote_variable_reference("
    macro make_computation(x) do
        let base = x |
        | quote: unquote(base) * 3 + unquote(x)
    end
    | make_computation(4)
    ",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(16.into())].into()))]
#[case::default_params_with_all_args1(r#"
    def greet(name, greeting="Hello"): greeting + " " + name; | greet("Alice")"#,
    vec!["test".into()],
    Ok(vec!["Hello Alice".into()].into()))]
#[case::default_params_with_all_args2(r#"
    def greet(name, greeting="Hello"): greeting + " " + name; | greet("Alice", "Hi")"#,
    vec!["test".into()],
    Ok(vec!["Hi Alice".into()].into()))]
#[case::default_params_with_self(r#"
    def greet(name, greeting="Hello"): greeting + " " + name; | greet()"#,
    vec!["Alice".into()],
    Ok(vec!["Hello Alice".into()].into()))]
#[case::default_params_with_expr(r#"
    def greet(name, greeting="Hello" + " Hi"): greeting + " " + name; | greet()"#,
    vec!["Alice".into()],
    Ok(vec!["Hello Hi Alice".into()].into()))]
#[case::quote_ast_get_args("let a = 10 | let b = 20 | _ast_get_args(quote: a + b) | len()",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Number(2.into())].into()))]
#[case::quote_ast_to_code("let a = 10 | _ast_to_code(quote: a)",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::String("a".into())].into()))]
#[case::double_not_true("!!true",
    vec![RuntimeValue::Boolean(false)],
    Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::double_not_false("!!false",
    vec![RuntimeValue::Boolean(false)],
    Ok(vec![RuntimeValue::Boolean(false)].into()))]
#[case::plus_equal(r#"
    var i = 1 | i += 10 | i"#,
    vec!["".into()],
    Ok(vec![RuntimeValue::Number(11.into())].into()))]
#[case::minus_equal(r#"
    var i = 10 | i -= 1 | i"#,
    vec!["".into()],
    Ok(vec![RuntimeValue::Number(9.into())].into()))]
#[case::slash_equal(r#"
    var i = 4 | i /= 2 | i"#,
    vec!["".into()],
    Ok(vec![RuntimeValue::Number(2.into())].into()))]
#[case::star_equal(r#"
    var i = 4 | i *= 2 | i"#,
    vec!["".into()],
    Ok(vec![RuntimeValue::Number(8.into())].into()))]
#[case::percent_equal(r#"
    var i = 3 | i %= 2 | i"#,
    vec!["".into()],
    Ok(vec![RuntimeValue::Number(1.into())].into()))]
#[case::double_slash_equal(r#"
    var i = 5 | i //= 2 | i"#,
    vec!["".into()],
    Ok(vec![RuntimeValue::Number(2.into())].into()))]
#[case::set_attr(r#"
    to_markdown("```\ntest\n```") | .code | first() | .code.value |= "updated" | .code.value | to_text()"#,
    vec!["".into()],
    Ok(vec!["updated".into()].into()))]
#[case::variadic_all_args("def f(*args): args; | f(1, 2, 3)",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])].into()))]
#[case::variadic_with_regular_param("def f(a, *rest): rest; | f(1, 2, 3)",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])].into()))]
#[case::variadic_empty_rest("def f(a, *rest): rest; | f(1)",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::variadic_no_args("def f(*args): args; | f()",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::variadic_with_pipe("def f(a, *rest): [a, rest]; | 10 | f(20, 30)",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(20.into()), RuntimeValue::Array(vec![RuntimeValue::Number(30.into())])])].into()))]
#[case::variadic_with_two_regular_params("def f(a, b, *rest): [a, b, rest]; | f(1, 2, 3, 4)",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Array(vec![RuntimeValue::Number(3.into()), RuntimeValue::Number(4.into())])])].into()))]
#[case::variadic_fn_syntax("let g = fn(*args): args; | g(1, 2)",
    vec![RuntimeValue::Number(0.into())],
    Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())])].into()))]
#[case::is_regex_match_match(r#"is_regex_match("test1", "[a-z0-9]+")"#,
    vec![RuntimeValue::None],
    Ok(vec![true.into()].into()))]
#[case::is_regex_match_no_match(r#"is_regex_match("abc", "[0-9]+")"#,
    vec![RuntimeValue::None],
    Ok(vec![false.into()].into()))]
#[case::is_regex_match_none_input(r#"is_regex_match(., "[a-z]+")"#,
    vec![RuntimeValue::None],
    Ok(vec![false.into()].into()))]
#[case::is_regex_match_markdown(r#"is_regex_match(., "hello")"#,
    vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text { value: "hello world".to_string(), position: None }))],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text { position: None, value: "true".to_string() }))].into()))]
#[case::regex_op(r#""test1" =~ "[a-z0-9]+""#,
    vec![RuntimeValue::None],
    Ok(vec![true.into()].into()))]
#[case::regex_non_match(r#""abc" =~ "[0-9]+""#,
    vec![RuntimeValue::None],
    Ok(vec![false.into()].into()))]
#[case::regex_complex_pattern(r#""abc123XYZ" =~ "[a-z]+[0-9]+[A-Z]+""#,
    vec![RuntimeValue::None],
    Ok(vec![true.into()].into()))]
#[case::regex_none_input(r#". =~ "foo""#,
    vec![RuntimeValue::None],
    Ok(vec![false.into()].into()))]
#[case::regex_markdown_match(r#". =~ "hello""#,
    vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text { value: "hello world".to_string(), position: None }))],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text { position: None, value: "true".to_string() }))].into()))]
#[case::regex_markdown_non_match(r#". =~ "foo""#,
    vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text { value: "hello world".to_string(), position: None }))],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text { position: None, value: "false".to_string() }))].into()))]
#[case::regex_in_if(r#""test" | if (. =~ "test"): true else: false"#,
    vec![RuntimeValue::None],
    Ok(vec![true.into()].into()))]
#[case::shift_left_number("shift_left(1, 2)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(4.into())].into()),)]
#[case::shift_left_number_operator("1 << 2", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(4.into())].into()),)]
#[case::shift_right_number("shift_right(4, 2)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.into())].into()),)]
#[case::shift_right_number_operator("4 >> 2", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.into())].into()),)]
#[case::shift_left_array_operator("[1] << 2", vec![RuntimeValue::None], Ok(vec![vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())].into()].into()),)]
#[case::shift_right_array_operator("2 >> [1]", vec![RuntimeValue::None], Ok(vec![vec![RuntimeValue::Number(2.into()), RuntimeValue::Number(1.into())].into()].into()),)]
#[case::shift_right_header_level_h1("to_markdown(\"# Heading 1\") | first() | shift_right(1)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
        depth: 2,
        values: vec!["Heading 1".to_string().into()],
        position: None
    }))].into()))]
#[case::shift_right_header_level_h1_operator("let md = do to_markdown(\"# Heading 1\") | first(); | md >> 1",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
        depth: 2,
        values: vec!["Heading 1".to_string().into()],
        position: None
    }))].into()))]
#[case::shift_right_header_level_h6("to_markdown(\"###### Heading 6\") | first() | shift_right(1)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
        depth: 6,
        values: vec!["Heading 6".to_string().into()],
        position: None
    }))].into()))]
#[case::shift_left_header_level_h2("to_markdown(\"## Heading 2\") | first() | shift_left(1)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
        depth: 1,
        values: vec!["Heading 2".to_string().into()],
        position: None
    }))].into()))]
#[case::shift_left_header_level_h2_via_binding("let md = do to_markdown(\"## Heading 2\") | first(); | md << 1",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
        depth: 1,
        values: vec!["Heading 2".to_string().into()],
        position: None
    }))].into()))]
#[case::shift_left_header_level_h1("to_markdown(\"# Heading 1\") | first() | shift_left(1)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
        depth: 1,
        values: vec!["Heading 1".to_string().into()],
        position: None
    }))].into()))]
#[case::shift_left_string_basic("\"abcdef\" | shift_left(2)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("cdef".to_string())].into()))]
#[case::shift_right_string_basic("\"abcdef\" | shift_right(2)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("abcd".to_string())].into()))]
#[case::shift_left_string_amount_greater_than_length("\"abc\" | shift_left(10)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("".to_string())].into()))]
#[case::shift_right_string_amount_equal_to_length("\"abc\" | shift_right(3)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("".to_string())].into()))]
#[case::convert_string_to_h1_function("convert(\"Hello\", :h1)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
        depth: 1,
        values: vec!["Hello".to_string().into()],
        position: None,
    }))].into()))]
#[case::convert_string_to_h1_operator("\"Hello\" @ :h1",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
        depth: 1,
        values: vec!["Hello".to_string().into()],
        position: None,
    }))].into()))]
#[case::convert_string_to_h2_operator("\"Hello\" @ :h2",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
        depth: 2,
        values: vec!["Hello".to_string().into()],
        position: None,
    }))].into()))]
#[case::convert_string_to_h1_via_string_operator("\"Hello\" @ \"#\"",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
        depth: 1,
        values: vec!["Hello".to_string().into()],
        position: None,
    }))].into()))]
#[case::convert_string_to_h2_via_string_operator("\"Hello\" @ \"##\"",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
        depth: 2,
        values: vec!["Hello".to_string().into()],
        position: None,
    }))].into()))]
#[case::convert_string_to_base64_operator("\"text\" @ :base64",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("dGV4dA==".to_string())].into()))]
#[case::convert_string_to_uri_operator("\"hello world\" @ :uri",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("hello%20world".to_string())].into()))]
#[case::convert_string_to_urid_operator("\"hello%20world\" @ :urid",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("hello world".to_string())].into()))]
#[case::convert_string_to_sh_operator("\"hello world\" @ :sh",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("'hello world'".to_string())].into()))]
#[case::convert_string_to_blockquote_operator("\"Hello\" @ \">\"",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Blockquote(mq_markdown::Blockquote {
        values: vec!["Hello".to_string().into()],
        position: None,
    }))].into()))]
#[case::convert_string_to_list_item_operator("\"Hello\" @ \"-\"",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::List(mq_markdown::List {
        values: vec!["Hello".to_string().into()],
        index: 0,
        ordered: false,
        level: 1,
        checked: None,
        position: None,
    }))].into()))]
#[case::convert_string_to_strikethrough_operator("\"Hello\" @ \"~~\"",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Delete(mq_markdown::Delete {
        values: vec!["Hello".to_string().into()],
        position: None,
    }))].into()))]
#[case::convert_string_to_strong_operator("\"Hello\" @ \"**\"",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Strong(mq_markdown::Strong {
        values: vec!["Hello".to_string().into()],
        position: None,
    }))].into()))]
#[case::convert_string_to_horizontal_rule_operator("\"Hello\" @ \"--\"",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::HorizontalRule(mq_markdown::HorizontalRule {
        position: None,
    }))].into()))]
#[case::skip_while_basic("skip_while([1,2,3,4,5], fn(x): x < 3;)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![
        RuntimeValue::Number(3.into()),
        RuntimeValue::Number(4.into()),
        RuntimeValue::Number(5.into()),
    ])].into()))]
#[case::skip_while_all_match("skip_while([1,2,3], fn(x): x < 10;)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::skip_while_none_match("skip_while([1,2,3], fn(x): x > 10;)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![
        RuntimeValue::Number(1.into()),
        RuntimeValue::Number(2.into()),
        RuntimeValue::Number(3.into()),
    ])].into()))]
#[case::skip_while_empty_array("skip_while([], fn(x): x < 3;)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::skip_while_stops_at_first_non_match("skip_while([1,3,2,4], fn(x): x < 3;)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![
        RuntimeValue::Number(3.into()),
        RuntimeValue::Number(2.into()),
        RuntimeValue::Number(4.into()),
    ])].into()))]
#[case::take_while_basic("take_while([1,2,3,4,5], fn(x): x < 3;)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![
        RuntimeValue::Number(1.into()),
        RuntimeValue::Number(2.into()),
    ])].into()))]
#[case::take_while_all_match("take_while([1,2,3], fn(x): x < 10;)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![
        RuntimeValue::Number(1.into()),
        RuntimeValue::Number(2.into()),
        RuntimeValue::Number(3.into()),
    ])].into()))]
#[case::take_while_none_match("take_while([1,2,3], fn(x): x > 10;)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::take_while_empty_array("take_while([], fn(x): x < 3;)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::take_while_stops_at_first_non_match("take_while([1,3,2,4], fn(x): x < 3;)",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![
        RuntimeValue::Number(1.into()),
    ])].into()))]
#[case::slice_end_only("let x = [1, 2, 3] | x[:2]",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![
        RuntimeValue::Number(1.into()),
        RuntimeValue::Number(2.into()),
    ])].into()))]
#[case::slice_end_only_single("let x = [1, 2, 3] | x[:1]",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![
        RuntimeValue::Number(1.into()),
    ])].into()))]
#[case::slice_end_only_empty("let x = [1, 2, 3] | x[:0]",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::slice_full_colon("let x = [1, 2, 3] | x[:]",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![
        RuntimeValue::Number(1.into()),
        RuntimeValue::Number(2.into()),
        RuntimeValue::Number(3.into()),
    ])].into()))]
#[case::dict_symbol_access_unchanged("let d = {type: :section} | d[:type]",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Symbol(Ident::new("section"))].into()))]
// --- or-pattern integration tests ---
#[case::match_or_number_first_alt(
    r#"match(1) do | 1 || 2 || 3: "small" | _: "other" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("small".to_string())].into()))]
#[case::match_or_number_second_alt(
    r#"match(2) do | 1 || 2 || 3: "small" | _: "other" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("small".to_string())].into()))]
#[case::match_or_number_third_alt(
    r#"match(3) do | 1 || 2 || 3: "small" | _: "other" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("small".to_string())].into()))]
#[case::match_or_number_no_match_wildcard(
    r#"match(5) do | 1 || 2 || 3: "small" | _: "other" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("other".to_string())].into()))]
#[case::match_or_number_no_arm_matches(
    r#"match(9) do | 1 || 2: "one or two" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::None].into()))]
#[case::match_or_string_first_alt(
    r#"match("a") do | "a" || "b": "letter" | _: "other" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("letter".to_string())].into()))]
#[case::match_or_string_second_alt(
    r#"match("b") do | "a" || "b": "letter" | _: "other" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("letter".to_string())].into()))]
#[case::match_or_string_no_match(
    r#"match("c") do | "a" || "b": "letter" | _: "other" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("other".to_string())].into()))]
#[case::match_or_bool_true(
    r#"match(true) do | true || false: "bool" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("bool".to_string())].into()))]
#[case::match_or_bool_false(
    r#"match(false) do | true || false: "bool" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("bool".to_string())].into()))]
#[case::match_or_none_literal(
    r#"match(.) do | none || 1: "matched" | _: "other" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("matched".to_string())].into()))]
#[case::match_or_type_string_matches(
    r#"match("hello") do | :string || :bool: "str or bool" | :number: "number" | _: "other" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("str or bool".to_string())].into()))]
#[case::match_or_type_number_matches(
    r#"match(42) do | :string || :bool: "str or bool" | :number: "number" | _: "other" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("number".to_string())].into()))]
#[case::match_or_type_no_match(
    r#"match(42) do | :string || :bool: "str or bool" | _: "other" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("other".to_string())].into()))]
#[case::match_or_type_array_or_dict(
    r#"match(array(1,2)) do | :array || :dict: "collection" | _: "other" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("collection".to_string())].into()))]
#[case::match_or_piped_input_first_alt(
    r#"match(.) do | 1 || 2: "one or two" | _: "other" end"#,
    vec![RuntimeValue::Number(1.into())],
    Ok(vec![RuntimeValue::String("one or two".to_string())].into()))]
#[case::match_or_piped_input_second_alt(
    r#"match(.) do | 1 || 2: "one or two" | _: "other" end"#,
    vec![RuntimeValue::Number(2.into())],
    Ok(vec![RuntimeValue::String("one or two".to_string())].into()))]
#[case::match_or_piped_input_no_match(
    r#"match(.) do | 1 || 2: "one or two" | _: "other" end"#,
    vec![RuntimeValue::Number(3.into())],
    Ok(vec![RuntimeValue::String("other".to_string())].into()))]
#[case::match_or_with_guard_passes(
    r#"let x = 5 | match(x) do | 4 || 5 || 6 if (x > 4): "big" | _: "other" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("big".to_string())].into()))]
#[case::match_or_with_guard_fails(
    r#"let x = 4 | match(x) do | 4 || 5 || 6 if (x > 4): "big" | _: "other" end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("other".to_string())].into()))]
#[case::match_or_two_alts_with_ident_binding(
    r#"match(3) do | 1 || 2: "small" | x: to_string(x) end"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("3".to_string())].into()))]
#[case::from_html_heading(
    r#""<h1>Hello</h1>" | from_html() | first()"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading {
        depth: 1,
        values: vec!["Hello".to_string().into()],
        position: None,
    }))].into()))]
#[case::from_html_paragraph(
    r#""<p>Hello world</p>" | from_html() | first() | to_text()"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::String("Hello world".to_string())].into()))]
#[case::from_html_empty(
    r#""" | from_html()"#,
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Array(vec![])].into()))]
#[case::pow_integer("pow(2, 3)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(8.into())].into()),)]
#[case::pow_zero_exp("pow(5, 0)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.into())].into()),)]
#[case::pow_negative_exp("pow(2, -1)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(0.5f64.into())].into()),)]
#[case::pow_float_exp("pow(4, 0.5)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(2.0f64.into())].into()),)]
#[case::sqrt_perfect_square("sqrt(4)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(2.0f64.into())].into()),)]
#[case::sqrt_nine("sqrt(9)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(3.0f64.into())].into()),)]
#[case::sqrt_one("sqrt(1)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.0f64.into())].into()),)]
#[case::ln_one("ln(1)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(0.0f64.into())].into()),)]
#[case::ln_e("ln(2.718281828459045)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.0f64.into())].into()),)]
#[case::log10_one("log10(1)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(0.0f64.into())].into()),)]
#[case::log10_hundred("log10(100)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(2.0f64.into())].into()),)]
#[case::exp_zero("exp(0)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.0f64.into())].into()),)]
#[case::negate_simple("negate(1)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number((-1).into())].into()))]
#[case::div_float("div(5, 2)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(2.5.into())].into()))]
#[case::gte_simple("gte(2, 1)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::lte_simple("lte(1, 2)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::ne_simple("ne(1, 2)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::csv_parse_simple(r##"_csv_parse("a,b\n1,2", ",", true)"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Array(vec![
    RuntimeValue::Dict(BTreeMap::from([
        (Ident::new("a"), RuntimeValue::String("1".to_string())),
        (Ident::new("b"), RuntimeValue::String("2".to_string())),
    ]))
])].into()))]
#[case::json_parse_simple(r##"_json_parse("{\"a\": 1}")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Dict(BTreeMap::from([
    (Ident::new("a"), RuntimeValue::Number(1.into())),
]))].into()))]
#[case::yaml_parse_simple(r##"_yaml_parse("a: 1")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Dict(BTreeMap::from([
    (Ident::new("a"), RuntimeValue::Number(1.into())),
]))].into()))]
#[case::toml_parse_simple(r##"_toml_parse("a = 1")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Dict(BTreeMap::from([
    (Ident::new("a"), RuntimeValue::Number(1.into())),
]))].into()))]
#[case::xml_parse_simple(r##"_xml_parse("<root>text</root>")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Dict(BTreeMap::from([
    (Ident::new("tag"), RuntimeValue::String("root".to_string())),
    (Ident::new("attributes"), RuntimeValue::new_dict()),
    (Ident::new("children"), RuntimeValue::Array(vec![])),
    (Ident::new("text"), RuntimeValue::String("text".to_string())),
]))].into()))]
#[case::and_builtin("and(true, false)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Boolean(false)].into()))]
#[case::or_builtin("or(false, true)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::coalesce_simple("coalesce(None, 1)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.into())].into()))]
#[case::compact_array("compact([1, None, 2])", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())])].into()))]
#[case::uniq_array("uniq([1, 2, 1])", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())])].into()))]
#[case::sort_array("sort([3, 1, 2])", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into()), RuntimeValue::Number(3.into())])].into()))]
#[case::utf8bytelen_simple(r##"utf8bytelen("あ")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(3.into())].into()))]
#[case::rindex_simple(r##"rindex("banana", "a")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(5.into())].into()))]
#[case::explode_simple(r##"explode("abc")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(97.into()), RuntimeValue::Number(98.into()), RuntimeValue::Number(99.into())])].into()))]
#[case::implode_simple(r##"implode([97, 98, 99])"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("abc".to_string())].into()))]
#[case::intern_simple(r##"intern("foo")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("foo".to_string())].into()))]
#[case::nan_builtin("nan() | is_nan()", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::infinite_builtin("infinite() > 0", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::to_md_text_simple(r##"to_md_text("hello")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Text(mq_markdown::Text{value: "hello".to_string(), position: None}))].into()))]
#[case::to_h_simple(r##"to_h("title", 1)"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Heading(mq_markdown::Heading{depth: 1, values: vec!["title".to_string().into()], position: None}))].into()))]
#[case::to_hr_simple("to_hr()", vec![RuntimeValue::None], Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::HorizontalRule(mq_markdown::HorizontalRule{position: None}))].into()))]
#[case::to_strong_simple(r##"to_strong("bold")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Strong(mq_markdown::Strong{values: vec!["bold".to_string().into()], position: None}))].into()))]
#[case::to_em_simple(r##"to_em("italic")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Emphasis(mq_markdown::Emphasis{values: vec!["italic".to_string().into()], position: None}))].into()))]
#[case::to_code_inline_simple(r##"to_code_inline("code")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::CodeInline(mq_markdown::CodeInline{value: "code".to_string().into(), position: None}))].into()))]
#[case::get_title_simple(r##"to_markdown("[link](url 'title')") | first() | get_title()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("title".to_string())].into()))]
#[case::get_url_simple(r##"to_markdown("[link](url)") | first() | get_url()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("url".to_string())].into()))]
#[case::set_code_block_lang_simple(r##"to_markdown("```\ncode\n```") | first() | set_code_block_lang("rust") | .code.lang"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("rust".to_string())].into()))]
#[case::set_list_ordered_simple(r##"to_markdown("- item") | first() | set_list_ordered(true) | .list.ordered"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::diff_simple(r##"_diff("abc", "abd") | len()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(2.into())].into()))]
#[case::get_markdown_position_simple(r##"to_markdown("# title") | first() | _get_markdown_position() | get("start_line")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.into())].into()))]
#[case::toon_parse_simple(r##"_toon_parse("a: 1")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Dict(BTreeMap::from([
    (Ident::new("a"), RuntimeValue::Number(1.into())),
]))].into()))]
#[case::capture_simple(r##"capture("abc123def", "(?P<num>\\d+)")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Dict(BTreeMap::from([
    (Ident::new("num"), RuntimeValue::String("123".to_string())),
]))].into()))]
#[case::is_debug_mode_simple("is_debug_mode()", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Boolean(cfg!(feature = "debugger"))].into()))]
#[case::set_check_simple(r##"to_markdown("- [ ] task") | first() | set_check(true) | .list.checked"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Boolean(true)].into()))]
#[case::stderr_simple(r##"stderr("test stderr")"##, vec![RuntimeValue::String("val".to_string())], Ok(vec![RuntimeValue::String("val".to_string())].into()))]
#[case::to_image_simple(r##"to_image("url", "alt", "title")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Image(mq_markdown::Image{url: "url".to_string(), alt: "alt".to_string(), title: Some("title".to_string()), position: None}))].into()))]
#[case::to_link_simple(r##"to_link("url", "text", "title")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Link(mq_markdown::Link{url: mq_markdown::Url::new("url".to_string()), title: Some(mq_markdown::Title::new("title".to_string())), values: vec!["text".to_string().into()], position: None}))].into()))]
#[case::to_math_simple(r##"to_math("E=mc^2")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::Math(mq_markdown::Math{value: "E=mc^2".to_string(), position: None}))].into()))]
#[case::to_math_inline_simple(r##"to_math_inline("E=mc^2")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::MathInline(mq_markdown::MathInline{value: "E=mc^2".to_string().into(), position: None}))].into()))]
#[case::to_md_list_simple(r##"to_md_list("item", 1)"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::List(mq_markdown::List{values: vec!["item".to_string().into()], index: 0, ordered: false, level: 1, checked: None, position: None}))].into()))]
#[case::to_md_table_row_simple(r##"to_md_table_row("a", "b")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::TableRow(mq_markdown::TableRow{values: vec![
    mq_markdown::Node::TableCell(mq_markdown::TableCell{row: 0, column: 0, values: vec!["a".to_string().into()], position: None}),
    mq_markdown::Node::TableCell(mq_markdown::TableCell{row: 0, column: 1, values: vec!["b".to_string().into()], position: None}),
], position: None}))].into()))]
#[case::to_md_table_cell_simple(r##"to_md_table_cell("val", 1, 2)"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::new_markdown(mq_markdown::Node::TableCell(mq_markdown::TableCell{row: 1, column: 2, values: vec!["val".to_string().into()], position: None}))].into()))]
#[case::to_md_name_simple(r##"to_markdown("# title") | first() | to_md_name()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("h1".to_string())].into()))]
#[case::entries_simple(r##"entries({"a": 1})"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Array(vec![RuntimeValue::String("a".to_string()), RuntimeValue::Number(1.into())])])].into()))]
#[case::del_array_simple("del([1, 2, 3], 1)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(3.into())])].into()))]
#[case::index_string_simple(r##"index("hello", "e")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.into())].into()))]
#[case::set_ref_simple(r##"to_markdown("[link][id]\n\n[id]: url") | first() | set_ref("newlabel") | .link_ref.label"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("newlabel".to_string())].into()))]
#[case::downcase_simple(r##"downcase("ABC")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("abc".to_string())].into()))]
#[case::gsub_simple(r##"gsub("a1b2", "\\d", "x")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("axbx".to_string())].into()))]
#[case::regex_match_simple(r##"regex_match("a1b2", "\\d")"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Array(vec![RuntimeValue::String("1".to_string()), RuntimeValue::String("2".to_string())])].into()))]
#[case::slice_simple(r##"slice("abcdef", 1, 4)"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("bcd".to_string())].into()))]
#[case::sort_by_impl_simple(r##"_sort_by_impl([[2, "b"], [1, "a"]])"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Array(vec![
    RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::String("a".to_string())]),
    RuntimeValue::Array(vec![RuntimeValue::Number(2.into()), RuntimeValue::String("b".to_string())]),
])].into()))]
#[case::selector_task(r##"to_markdown("- [ ] todo\n- [x] done") | .task | compact() | len()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(2.into())].into()))]
#[case::selector_todo(r##"to_markdown("- [ ] todo\n- [x] done") | .todo | compact() | len()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.into())].into()))]
#[case::selector_done(r##"to_markdown("- [ ] todo\n- [x] done") | .done | compact() | len()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.into())].into()))]
#[case::selector_mdx_jsx_flow(r##"to_mdx("<Component />") | .mdx_jsx_flow_element | compact() | len()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.into())].into()))]
#[case::selector_mdx_flow_expr(r##"to_mdx("{1 + 1}") | .mdx_flow_expression | compact() | len()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.into())].into()))]
#[case::selector_footnote(r##"to_markdown("[^1]: note\n\n# title") | .footnote | compact() | len()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.into())].into()))]
#[case::selector_definition(r##"to_markdown("[id]: url") | .definition | compact() | len()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.into())].into()))]
#[case::selector_dict_empty_returns_none("{} | .h | is_none()", vec![RuntimeValue::None], Ok(vec![RuntimeValue::TRUE].into()))]
#[case::selector_dict_with_markdown_values(r##"{"docs": to_markdown("# Title\n\ntext")} | .h | is_dict()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::TRUE].into()))]
#[case::selector_dict_preserves_type_key(r##"{"type": "mytype"} | .h | entries() | first() | last()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("mytype".to_string())].into()))]
#[case::selector_array_of_dicts_preserves_dict(r##"array({"docs": to_markdown("# Title")}) | .h | first() | is_dict()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::TRUE].into()))]
#[case::selector_call_h(r##"to_markdown("# h1\n\n## h2\n\ntest") | .h(2).depth | compact() | first()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(2.into())].into()))]
#[case::selector_call_code_lang(r##"to_markdown("```rust\ncode\n```") | .code("rust").lang | compact() | first()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("rust".to_string())].into()))]
#[case::selector_bracket_variable_list(r##"let v = 1 | to_markdown("- a\n- b\n- c") | .[v] | compact() | first() | to_string()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("- b".to_string())].into()))]
#[case::selector_bracket_variable_table_row(r##"let v = 1 | to_markdown("| a | b |\n|---|---|\n| c | d |") | .[v][] | compact() | first() | to_string()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("c".to_string())].into()))]
#[case::selector_bracket_variable_table_col(r##"let v = 1 | to_markdown("| a | b |\n|---|---|\n| c | d |") | .[][v] | compact() | first() | to_string()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("b".to_string())].into()))]
#[case::selector_bracket_expr_list(r##"let v = 0 | to_markdown("- a\n- b\n- c") | .[v + 1] | compact() | first() | to_string()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("- b".to_string())].into()))]
#[case::selector_bracket_expr_table_col(r##"let v = 0 | to_markdown("| a | b |\n|---|---|\n| c | d |") | .[][v + 1] | compact() | first() | to_string()"##, vec![RuntimeValue::None], Ok(vec![RuntimeValue::String("b".to_string())].into()))]
#[case::module_inline_function("
    module math:
        def mysum(a, b): a + b;
    end
    | math::mysum(1, 2)
    ",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Number(3.into())].into()))]
#[case::module_inline_let("
    module constants:
        let pi = 314
    end
    | constants::pi
    ",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Number(314.into())].into()))]
#[case::module_hoisting("
    def call_math(): math::mysum(10, 5);
    | call_math()
    | module math:
        def mysum(a, b): a + b;
    end
    ",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Number(15.into())].into()))]
#[case::module_extension("
    module math:
        def mysum(a, b): a + b;
    end
    module math:
        def mymul(a, b): a * b;
    end
    | math::mysum(2, 3) + math::mymul(2, 3)
    ",
    vec![RuntimeValue::None],
    Ok(vec![RuntimeValue::Number(11.into())].into()))]
// partial: explicitly create a partial function with the first arg pre-filled
#[case::partial_basic("def f(a, b, c): c; | let p = partial(f, 10) | p(42)", vec![RuntimeValue::Number(5.into())], Ok(vec![RuntimeValue::Number(42.into())].into()))]
// partial: the pre-filled arg is accessible in the body of the partial function
#[case::partial_captured_arg("def f(a, b, c): a; | let p = partial(f, 42) | p(99, 1)", vec![RuntimeValue::Number(5.into())], Ok(vec![RuntimeValue::Number(42.into())].into()))]
// partial: pre-fill multiple args and verify they combine correctly in the final call
#[case::partial_multiple_args("def f(a, b, c): a + b + c; | let p = partial(f, 10, 20) | p(30)", vec![RuntimeValue::Number(5.into())], Ok(vec![RuntimeValue::Number(60.into())].into()))]
// partial: 2-param function can be partially applied — the scenario that triggered the redesign
#[case::partial_two_param("def plus(a, b): a + b; | let plus10 = partial(plus, 10) | plus10(5)", vec![RuntimeValue::Number(0.into())], Ok(vec![RuntimeValue::Number(15.into())].into()))]
// property selector: quoted form (."key") is the only way to access dict keys
#[case::property_selector_quoted_h1(r#"."h1""#, vec![{let mut d = std::collections::BTreeMap::new(); d.insert(Ident::new("h1"), RuntimeValue::String("title".to_string())); RuntimeValue::Dict(d)}], Ok(vec![RuntimeValue::String("title".to_string())].into()))]
#[case::property_selector_quoted_url(r#"."url""#, vec![{let mut d = std::collections::BTreeMap::new(); d.insert(Ident::new("url"), RuntimeValue::String("https://example.com".to_string())); RuntimeValue::Dict(d)}], Ok(vec![RuntimeValue::String("https://example.com".to_string())].into()))]
#[case::property_selector_quoted_text(r#"."text""#, vec![{let mut d = std::collections::BTreeMap::new(); d.insert(Ident::new("text"), RuntimeValue::String("hello".to_string())); RuntimeValue::Dict(d)}], Ok(vec![RuntimeValue::String("hello".to_string())].into()))]
// property selector: quoted form with spaces in key
#[case::property_selector_quoted_space(r#"."my key""#, vec![{let mut d = std::collections::BTreeMap::new(); d.insert(Ident::new("my key"), RuntimeValue::String("val".to_string())); RuntimeValue::Dict(d)}], Ok(vec![RuntimeValue::String("val".to_string())].into()))]
// property selector: missing key returns None
#[case::property_selector_quoted_missing(r#"."h1""#, vec![{let d = std::collections::BTreeMap::new(); RuntimeValue::Dict(d)}], Ok(vec![RuntimeValue::None].into()))]
// nested property selector: ."a"."b" accesses {"a": {"b": 1}}
#[case::property_selector_nested(r#"."a"."b""#, vec![{let mut outer = std::collections::BTreeMap::new(); let mut inner = std::collections::BTreeMap::new(); inner.insert(Ident::new("b"), RuntimeValue::Number(1.into())); outer.insert(Ident::new("a"), RuntimeValue::Dict(inner)); RuntimeValue::Dict(outer)}], Ok(vec![RuntimeValue::Number(1.into())].into()))]
// nested property selector: ."a"."b"."c" accesses three levels deep
#[case::property_selector_nested_three(r#"."a"."b"."c""#, vec![{let mut outer = std::collections::BTreeMap::new(); let mut mid = std::collections::BTreeMap::new(); let mut inner = std::collections::BTreeMap::new(); inner.insert(Ident::new("c"), RuntimeValue::Number(42.into())); mid.insert(Ident::new("b"), RuntimeValue::Dict(inner)); outer.insert(Ident::new("a"), RuntimeValue::Dict(mid)); RuntimeValue::Dict(outer)}], Ok(vec![RuntimeValue::Number(42.into())].into()))]
// nested property selector: missing intermediate key returns None
#[case::property_selector_nested_missing(r#"."a"."b""#, vec![{let mut d = std::collections::BTreeMap::new(); d.insert(Ident::new("a"), RuntimeValue::Number(1.into())); RuntimeValue::Dict(d)}], Ok(vec![RuntimeValue::None].into()))]
fn test_eval(mut engine: Engine, #[case] program: &str, #[case] input: Vec<RuntimeValue>, #[case] expected: MqResult) {
    assert_eq!(engine.eval(program, input.into_iter()), expected);
}

#[rstest]
#[case::invalid_function_syntax("f()def f(): 1", vec![RuntimeValue::Number(0.into())])]
#[case::func("def func1(): 1 | func1(); | func1()", vec![RuntimeValue::Number(0.into())])]
#[case::func("def func1(x): 1; | func1(1, 2)", vec![RuntimeValue::Number(0.into())])]
#[case::func_invalid_definition("def f(x): 1; | f2(1, 2)", vec![RuntimeValue::Number(0.into())])]
#[case::invalid_definition("func1(1, 2)", vec![RuntimeValue::Number(0.into())])]
#[case::interpolated_string("s\"${val1} World!\"", vec![RuntimeValue::Number(0.into())])]
#[case::foreach("foreach(x, 1): add(x, 1);", vec![RuntimeValue::Number(10.into())])]
#[case::dict_get_on_non_map("get(\"not_a_map\", \"key\")", vec![RuntimeValue::Number(0.into())],)]
#[case::dict_set_on_non_map("set(123, \"key\", \"value\")", vec![RuntimeValue::Number(0.into())],)]
#[case::dict_keys_on_non_map("keys([1,2,3])", vec![RuntimeValue::Number(0.into())],)]
#[case::dict_values_on_non_map("values(true)", vec![RuntimeValue::Number(0.into())],)]
#[case::dict_get_wrong_key_type("let m = new_dict() | get(m, 123)", vec![RuntimeValue::Number(0.into())],)]
#[case::dict_set_wrong_key_type("let m = new_dict() | set(m, false, \"value\")", vec![RuntimeValue::Number(0.into())],)]
#[case::dict_get_wrong_arg_count("let m = new_dict() | get(m)", vec![RuntimeValue::Number(0.into())],)]
#[case::dict_set_wrong_arg_count("let m = new_dict() | set(m, \"key\")", vec![RuntimeValue::Number(0.into())],)]
#[case::assign_to_immutable("let x = 10 | x = 20", vec![RuntimeValue::Number(0.into())],)]
#[case::macro_undefined("undefined_macro(5)", vec![RuntimeValue::Number(0.into())],)]
#[case::macro_arity_mismatch_too_few("
    macro add_two(a, b):
        a + b;
    | add_two(1)
    ", vec![RuntimeValue::Number(0.into())],)]
#[case::macro_arity_mismatch_too_many("
    macro double(x):
        x + x;
    | double(1, 2, 3)
    ", vec![RuntimeValue::Number(0.into())],)]
#[case::unquote_outside_quote("unquote(5)", vec![RuntimeValue::Number(0.into())],)]
#[case::variadic_not_last_param("def f(*a, b): a", vec![RuntimeValue::Number(0.into())],)]
#[case::multiple_variadic_params("def f(*a, *b): a", vec![RuntimeValue::Number(0.into())],)]
#[case::macro_variadic_param("macro m(*args): args", vec![RuntimeValue::Number(0.into())],)]
#[case::regex_invalid_pattern(r#""abc" =~ "[invalid""#, vec![RuntimeValue::None],)]
#[case::is_regex_match_invalid_pattern(r#"is_regex_match("abc", "[invalid")"#, vec![RuntimeValue::None],)]
fn test_eval_error(mut engine: Engine, #[case] program: &str, #[case] input: Vec<RuntimeValue>) {
    assert!(engine.eval(program, input.into_iter()).is_err());
}

#[rstest]
#[case::unclosed_brace("{key: val", vec![RuntimeValue::None], "Expected a closing brace")]
#[case::unclosed_paren("(upcase", vec![RuntimeValue::None], "Expected a closing parenthesis")]
#[case::unclosed_bracket("[1, 2", vec![RuntimeValue::None], "Expected a closing bracket")]
#[case::eof_after_not("!", vec![RuntimeValue::None], "Expected an expression after `!`")]
#[case::eof_after_plus("1 +", vec![RuntimeValue::None], "Expected an expression after `+`")]
#[case::eof_after_minus("-", vec![RuntimeValue::None], "Expected an expression after `-`")]
fn test_eof_error_messages(
    mut engine: Engine,
    #[case] program: &str,
    #[case] input: Vec<RuntimeValue>,
    #[case] expected_msg: &str,
) {
    let err = engine.eval(program, input.into_iter()).unwrap_err();
    let msg = format!("{}", err);
    assert!(
        msg.contains(expected_msg),
        "Expected error message to contain {:?}, got {:?}",
        expected_msg,
        msg
    );
}

#[cfg(feature = "ast-json")]
mod ast_json {
    use mq_lang::{ArenaId, AstExpr, AstLiteral, AstNode, Program, Shared};
    use rstest::rstest;
    use smallvec::smallvec;

    fn default_token_id() -> ArenaId<Shared<mq_lang::Token>> {
        ArenaId::new(0)
    }

    #[rstest]
    #[case(
    Shared::new(AstNode {
        token_id: default_token_id(),
        expr: Shared::new(AstExpr::Literal(AstLiteral::String("hello".to_string()))),
    }),
    Some(vec!["Literal", "String", "hello"]),
    true
)]
    #[case(
    Shared::new(AstNode {
        token_id: default_token_id(),
        expr: Shared::new(AstExpr::Literal(AstLiteral::Number(123.45.into()))),
    }),
    Some(vec!["Literal", "Number", "123.45"]),
    true
)]
    #[case(
    Shared::new(AstNode {
        token_id: default_token_id(),
        expr: Shared::new(AstExpr::Ident(mq_lang::IdentWithToken::new("my_var"))),
    }),
    Some(vec!["Ident", "my_var"]),
    true
)]
    #[case(
    Shared::new(AstNode {
        token_id: default_token_id(),
        expr: Shared::new(AstExpr::Call(
            mq_lang::IdentWithToken::new("my_func"),
            smallvec![Shared::new(AstNode {
                token_id: default_token_id(),
                expr: Shared::new(AstExpr::Literal(AstLiteral::Number(1.into()))),
            })],
        )),
    }),
    Some(vec!["Call", "my_func", "Literal", "Number", "1.0"]),
    true
)]
    #[case(
    Shared::new(AstNode {
        token_id: default_token_id(),
        expr: Shared::new(AstExpr::If(smallvec![
            (
                Some(Shared::new(AstNode {
                    token_id: default_token_id(),
                    expr: Shared::new(AstExpr::Literal(AstLiteral::Bool(true))),
                })),
                Shared::new(AstNode {
                    token_id: default_token_id(),
                    expr: Shared::new(AstExpr::Literal(AstLiteral::String("then_branch".to_string()))),
                })
            )
        ])),
    }),
    Some(vec!["If", "Bool", "true", "String", "then_branch"]),
    false
)]
    fn test_astnode_serialization_deserialization(
        #[case] original_node: Shared<AstNode>,
        #[case] expected_json_parts: Option<Vec<&str>>,
        #[case] check_token_id: bool,
    ) {
        let json_string = original_node.to_json().unwrap();
        if let Some(parts) = expected_json_parts {
            for part in parts {
                assert!(json_string.contains(part), "json does not contain: {}", part);
            }
        }
        let deserialized_node: AstNode = AstNode::from_json(&json_string).unwrap();
        assert_eq!(deserialized_node.expr, original_node.expr);
        if check_token_id {
            assert_eq!(deserialized_node.token_id, default_token_id());
        }
        if let AstExpr::Ident(ident) = &*deserialized_node.expr {
            assert_eq!(ident.token, None);
        }
    }

    #[test]
    fn test_program_serialization_deserialization() {
        let node1 = Shared::new(AstNode {
            token_id: default_token_id(),
            expr: Shared::new(AstExpr::Literal(AstLiteral::String("first".to_string()))),
        });
        let node2 = Shared::new(AstNode {
            token_id: default_token_id(),
            expr: Shared::new(AstExpr::Literal(AstLiteral::Number(10.into()))),
        });
        let original_program: Program = vec![node1, node2];

        let json_string = serde_json::to_string_pretty(&original_program)
            .unwrap()
            .replace(" ", "");

        assert!(json_string.starts_with('['));
        assert!(json_string.contains("\"String\":\"first\""));
        assert!(json_string.contains("\"Number\":10.0"));
        assert!(json_string.ends_with("\n]"));

        let deserialized_program: Program = serde_json::from_str(&json_string).unwrap();

        assert_eq!(deserialized_program.len(), original_program.len());
        for (orig, deser) in original_program.iter().zip(deserialized_program.iter()) {
            assert_eq!(deser.expr, orig.expr);
            assert_eq!(deser.token_id, default_token_id());
        }
    }

    #[rstest]
    #[case("{invalid_json}")]
    #[case(r#"{\"expr\": {\"UnknownVariant\": \"some_data\"}}"#)]
    fn test_invalid_or_malformed_json_deserialization(#[case] json_string: &str) {
        let result: Result<AstNode, _> = AstNode::from_json(json_string);
        assert!(result.is_err());
    }
}
