[{
  "given":
  {
    "foo": -1,
    "zero": 0,
    "numbers": [-1, 3, 4, 5],
    "array": [-1, 3, 4, 5, "a", "100"],
    "strings": ["a", "b", "c"],
    "decimals": [1.01, 1.2, -1.5],
    "str": "Str",
    "false": false,
    "empty_list": [],
    "empty_hash": {},
    "objects": {"foo": "bar", "bar": "baz"},
    "null_key": null
  },
  "cases": [
    {
      "expression": "abs(foo)",
      "result": 1
    },
    {
      "expression": "abs(foo)",
      "result": 1
    },
    {
      "expression": "abs(str)",
      "error": "invalid-type"
    },
    {
      "expression": "abs(array[1])",
      "result": 3
    },
    {
      "expression": "abs(array[1])",
      "result": 3
    },
    {
      "expression": "abs(`false`)",
      "error": "invalid-type"
    },
    {
      "expression": "abs(`-24`)",
      "result": 24
    },
    {
      "expression": "abs(`-24`)",
      "result": 24
    },
    {
      "expression": "abs(`1`, `2`)",
      "error": "invalid-arity"
    },
    {
      "expression": "abs()",
      "error": "invalid-arity"
    },
    {
      "expression": "unknown_function(`1`, `2`)",
      "error": "unknown-function"
    },
    {
      "expression": "avg(numbers)",
      "result": 2.75
    },
    {
      "expression": "avg(array)",
      "error": "invalid-type"
    },
    {
      "expression": "avg('abc')",
      "error": "invalid-type"
    },
    {
      "expression": "avg(foo)",
      "error": "invalid-type"
    },
    {
      "expression": "avg(@)",
      "error": "invalid-type"
    },
    {
      "expression": "avg(strings)",
      "error": "invalid-type"
    },
    {
      "expression": "avg(empty_list)",
      "result": null
    },
    {
      "expression": "ceil(`1.2`)",
      "result": 2
    },
    {
      "expression": "ceil(decimals[0])",
      "result": 2
    },
    {
      "expression": "ceil(decimals[1])",
      "result": 2
    },
    {
      "expression": "ceil(decimals[2])",
      "result": -1
    },
    {
      "expression": "ceil('string')",
      "error": "invalid-type"
    },
    {
      "expression": "contains('abc', 'a')",
      "result": true
    },
    {
      "expression": "contains('abc', 'd')",
      "result": false
    },
    {
      "expression": "contains(`false`, 'd')",
      "error": "invalid-type"
    },
    {
      "expression": "contains(strings, 'a')",
      "result": true
    },
    {
      "expression": "contains(decimals, `1.2`)",
      "result": true
    },
    {
      "expression": "contains(decimals, `false`)",
      "result": false
    },
    {
      "expression": "ends_with(str, 'r')",
      "result": true
    },
    {
      "expression": "ends_with(str, 'tr')",
      "result": true
    },
    {
      "expression": "ends_with(str, 'Str')",
      "result": true
    },
    {
      "expression": "ends_with(str, 'SStr')",
      "result": false
    },
    {
      "expression": "ends_with(str, 'foo')",
      "result": false
    },
    {
      "expression": "ends_with(str, `0`)",
      "error": "invalid-type"
    },
    {
      "expression": "floor(`1.2`)",
      "result": 1
    },
    {
      "expression": "floor('string')",
      "error": "invalid-type"
    },
    {
      "expression": "floor(decimals[0])",
      "result": 1
    },
    {
      "expression": "floor(foo)",
      "result": -1
    },
    {
      "expression": "floor(str)",
      "error": "invalid-type"
    },
    {
      "expression": "length('abc')",
      "result": 3
    },
    {
      "expression": "length('✓foo')",
      "result": 4
    },
    {
      "expression": "length('')",
      "result": 0
    },
    {
      "expression": "length(@)",
      "result": 12
    },
    {
      "expression": "length(strings[0])",
      "result": 1
    },
    {
      "expression": "length(str)",
      "result": 3
    },
    {
      "expression": "length(array)",
      "result": 6
    },
    {
      "expression": "length(objects)",
      "result": 2
    },
    {
      "expression": "length(`false`)",
      "error": "invalid-type"
    },
    {
      "expression": "length(foo)",
      "error": "invalid-type"
    },
    {
      "expression": "length(strings[0])",
      "result": 1
    },
    {
      "expression": "max(numbers)",
      "result": 5
    },
    {
      "expression": "max(decimals)",
      "result": 1.2
    },
    {
      "expression": "max(strings)",
      "result": "c"
    },
    {
      "expression": "max(abc)",
      "error": "invalid-type"
    },
    {
      "expression": "max(array)",
      "error": "invalid-type"
    },
    {
      "expression": "max(decimals)",
      "result": 1.2
    },
    {
      "expression": "max(empty_list)",
      "result": null
    },
    {
      "expression": "merge(`{}`)",
      "result": {}
    },
    {
      "expression": "merge(`{}`, `{}`)",
      "result": {}
    },
    {
      "expression": "merge(`{\"a\": 1}`, `{\"b\": 2}`)",
      "result": {"a": 1, "b": 2}
    },
    {
      "expression": "merge(`{\"a\": 1}`, `{\"a\": 2}`)",
      "result": {"a": 2}
    },
    {
      "expression": "merge(`{\"a\": 1, \"b\": 2}`, `{\"a\": 2, \"c\": 3}`, `{\"d\": 4}`)",
      "result": {"a": 2, "b": 2, "c": 3, "d": 4}
    },
    {
      "expression": "min(numbers)",
      "result": -1
    },
    {
      "expression": "min(decimals)",
      "result": -1.5
    },
    {
      "expression": "min(abc)",
      "error": "invalid-type"
    },
    {
      "expression": "min(array)",
      "error": "invalid-type"
    },
    {
      "expression": "min(empty_list)",
      "result": null
    },
    {
      "expression": "min(decimals)",
      "result": -1.5
    },
    {
      "expression": "min(strings)",
      "result": "a"
    },
    {
      "expression": "type('abc')",
      "result": "string"
    },
    {
      "expression": "type(`1.0`)",
      "result": "number"
    },
    {
      "expression": "type(`2`)",
      "result": "number"
    },
    {
      "expression": "type(`true`)",
      "result": "boolean"
    },
    {
      "expression": "type(`false`)",
      "result": "boolean"
    },
    {
      "expression": "type(`null`)",
      "result": "null"
    },
    {
      "expression": "type(`[0]`)",
      "result": "array"
    },
    {
      "expression": "type(`{\"a\": \"b\"}`)",
      "result": "object"
    },
    {
      "expression": "type(@)",
      "result": "object"
    },
    {
      "expression": "sort(keys(objects))",
      "result": ["bar", "foo"]
    },
    {
      "expression": "keys(foo)",
      "error": "invalid-type"
    },
    {
      "expression": "keys(strings)",
      "error": "invalid-type"
    },
    {
      "expression": "keys(`false`)",
      "error": "invalid-type"
    },
    {
      "expression": "sort(values(objects))",
      "result": ["bar", "baz"]
    },
    {
      "expression": "keys(empty_hash)",
      "result": []
    },
    {
      "expression": "values(foo)",
      "error": "invalid-type"
    },
    {
      "expression": "join(', ', strings)",
      "result": "a, b, c"
    },
    {
      "expression": "join(', ', strings)",
      "result": "a, b, c"
    },
    {
      "expression": "join(',', `[\"a\", \"b\"]`)",
      "result": "a,b"
    },
    {
      "expression": "join(',', `[\"a\", 0]`)",
      "error": "invalid-type"
    },
    {
      "expression": "join(', ', str)",
      "error": "invalid-type"
    },
    {
      "expression": "join('|', strings)",
      "result": "a|b|c"
    },
    {
      "expression": "join(`2`, strings)",
      "error": "invalid-type"
    },
    {
      "expression": "join('|', decimals)",
      "error": "invalid-type"
    },
    {
      "expression": "join('|', decimals[].to_string(@))",
      "result": "1.01|1.2|-1.5"
    },
    {
      "expression": "join('|', empty_list)",
      "result": ""
    },
    {
      "expression": "reverse(numbers)",
      "result": [5, 4, 3, -1]
    },
    {
      "expression": "reverse(array)",
      "result": ["100", "a", 5, 4, 3, -1]
    },
    {
      "expression": "reverse(`[]`)",
      "result": []
    },
    {
      "expression": "reverse('')",
      "result": ""
    },
    {
      "expression": "reverse('hello world')",
      "result": "dlrow olleh"
    },
    {
      "expression": "starts_with(str, 'S')",
      "result": true
    },
    {
      "expression": "starts_with(str, 'St')",
      "result": true
    },
    {
      "expression": "starts_with(str, 'Str')",
      "result": true
    },
    {
      "expression": "starts_with(str, 'String')",
      "result": false
    },
    {
      "expression": "starts_with(str, `0`)",
      "error": "invalid-type"
    },
    {
      "expression": "sum(numbers)",
      "result": 11
    },
    {
      "expression": "sum(decimals)",
      "result": 0.71
    },
    {
      "expression": "sum(array)",
      "error": "invalid-type"
    },
    {
      "expression": "sum(array[].to_number(@))",
      "result": 111
    },
    {
      "expression": "sum(`[]`)",
      "result": 0
    },
    {
      "expression": "to_array('foo')",
      "result": ["foo"]
    },
    {
      "expression": "to_array(`0`)",
      "result": [0]
    },
    {
      "expression": "to_array(objects)",
      "result": [{"foo": "bar", "bar": "baz"}]
    },
    {
      "expression": "to_array(`[1, 2, 3]`)",
      "result": [1, 2, 3]
    },
    {
      "expression": "to_array(false)",
      "result": [false]
    },
    {
      "expression": "to_string('foo')",
      "result": "foo"
    },
    {
      "expression": "to_string(`1.2`)",
      "result": "1.2"
    },
    {
      "expression": "to_string(`[0, 1]`)",
      "result": "[0,1]"
    },
    {
      "expression": "to_number('1.0')",
      "result": 1.0
    },
    {
      "expression": "to_number('1.1')",
      "result": 1.1
    },
    {
      "expression": "to_number('4')",
      "result": 4
    },
    {
      "expression": "to_number('notanumber')",
      "result": null
    },
    {
      "expression": "to_number(`false`)",
      "result": null
    },
    {
      "expression": "to_number(`null`)",
      "result": null
    },
    {
      "expression": "to_number(`[0]`)",
      "result": null
    },
    {
      "expression": "to_number(`{\"foo\": 0}`)",
      "result": null
    },
    {
      "expression": "\"to_string\"(`1.0`)",
      "error": "syntax"
    },
    {
      "expression": "sort(numbers)",
      "result": [-1, 3, 4, 5]
    },
    {
      "expression": "sort(strings)",
      "result": ["a", "b", "c"]
    },
    {
      "expression": "sort(decimals)",
      "result": [-1.5, 1.01, 1.2]
    },
    {
      "expression": "sort(array)",
      "error": "invalid-type"
    },
    {
      "expression": "sort(abc)",
      "error": "invalid-type"
    },
    {
      "expression": "sort(empty_list)",
      "result": []
    },
    {
      "expression": "sort(@)",
      "error": "invalid-type"
    },
    {
      "expression": "not_null(unknown_key, str)",
      "result": "Str"
    },
    {
      "expression": "not_null(unknown_key, foo.bar, empty_list, str)",
      "result": []
    },
    {
      "expression": "not_null(unknown_key, null_key, empty_list, str)",
      "result": []
    },
    {
      "expression": "not_null(all, expressions, are_null)",
      "result": null
    },
    {
      "expression": "not_null()",
      "error": "invalid-arity"
    },
    {
      "comment": "function projection on single arg function",
      "expression": "numbers[].to_string(@)",
      "result": ["-1", "3", "4", "5"]
    },
    {
      "comment": "function projection on single arg function",
      "expression": "array[].to_number(@)",
      "result": [-1, 3, 4, 5, 100]
    }
  ]
}, {
  "given":
  {
    "foo": [
         {"b": "b", "a": "a"},
         {"c": "c", "b": "b"},
         {"d": "d", "c": "c"},
         {"e": "e", "d": "d"},
         {"f": "f", "e": "e"}
    ]
  },
  "cases": [
    {
      "comment": "function projection on variadic function",
      "expression": "foo[].not_null(f, e, d, c, b, a)",
      "result": ["b", "c", "d", "e", "f"]
    }
  ]
}, {
  "given":
  {
    "people": [
         {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"},
         {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"},
         {"age": 30, "age_str": "30", "bool": true, "name": "c"},
         {"age": 50, "age_str": "50", "bool": false, "name": "d"},
         {"age": 10, "age_str": "10", "bool": true, "name": 3}
    ]
  },
  "cases": [
    {
      "comment": "sort by field expression",
      "expression": "sort_by(people, &age)",
      "result": [
         {"age": 10, "age_str": "10", "bool": true, "name": 3},
         {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"},
         {"age": 30, "age_str": "30", "bool": true, "name": "c"},
         {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"},
         {"age": 50, "age_str": "50", "bool": false, "name": "d"}
      ]
    },
    {
      "expression": "sort_by(people, &age_str)",
      "result": [
         {"age": 10, "age_str": "10", "bool": true, "name": 3},
         {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"},
         {"age": 30, "age_str": "30", "bool": true, "name": "c"},
         {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"},
         {"age": 50, "age_str": "50", "bool": false, "name": "d"}
      ]
    },
    {
      "comment": "sort by function expression",
      "expression": "sort_by(people, &to_number(age_str))",
      "result": [
         {"age": 10, "age_str": "10", "bool": true, "name": 3},
         {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"},
         {"age": 30, "age_str": "30", "bool": true, "name": "c"},
         {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"},
         {"age": 50, "age_str": "50", "bool": false, "name": "d"}
      ]
    },
    {
      "comment": "function projection on sort_by function",
      "expression": "sort_by(people, &age)[].name",
      "result": [3, "a", "c", "b", "d"]
    },
    {
      "expression": "sort_by(people, &extra)",
      "error": "invalid-type"
    },
    {
      "expression": "sort_by(people, &bool)",
      "error": "invalid-type"
    },
    {
      "expression": "sort_by(people, &name)",
      "error": "invalid-type"
    },
    {
      "expression": "sort_by(people, name)",
      "error": "invalid-type"
    },
    {
      "expression": "sort_by(people, &age)[].extra",
      "result": ["foo", "bar"]
    },
    {
      "expression": "sort_by(`[]`, &age)",
      "result": []
    },
    {
      "expression": "max_by(people, &age)",
      "result": {"age": 50, "age_str": "50", "bool": false, "name": "d"}
    },
    {
      "expression": "max_by(people, &age_str)",
      "result": {"age": 50, "age_str": "50", "bool": false, "name": "d"}
    },
    {
      "expression": "max_by(people, &bool)",
      "error": "invalid-type"
    },
    {
      "expression": "max_by(people, &extra)",
      "error": "invalid-type"
    },
    {
      "expression": "max_by(people, &to_number(age_str))",
      "result": {"age": 50, "age_str": "50", "bool": false, "name": "d"}
    },
    {
      "expression": "min_by(people, &age)",
      "result": {"age": 10, "age_str": "10", "bool": true, "name": 3}
    },
    {
      "expression": "min_by(people, &age_str)",
      "result": {"age": 10, "age_str": "10", "bool": true, "name": 3}
    },
    {
      "expression": "min_by(people, &bool)",
      "error": "invalid-type"
    },
    {
      "expression": "min_by(people, &extra)",
      "error": "invalid-type"
    },
    {
      "expression": "min_by(people, &to_number(age_str))",
      "result": {"age": 10, "age_str": "10", "bool": true, "name": 3}
    }
  ]
}, {
  "given":
  {
    "people": [
         {"age": 10, "order": "1"},
         {"age": 10, "order": "2"},
         {"age": 10, "order": "3"},
         {"age": 10, "order": "4"},
         {"age": 10, "order": "5"},
         {"age": 10, "order": "6"},
         {"age": 10, "order": "7"},
         {"age": 10, "order": "8"},
         {"age": 10, "order": "9"},
         {"age": 10, "order": "10"},
         {"age": 10, "order": "11"}
    ]
  },
  "cases": [
    {
      "comment": "stable sort order",
      "expression": "sort_by(people, &age)",
      "result": [
         {"age": 10, "order": "1"},
         {"age": 10, "order": "2"},
         {"age": 10, "order": "3"},
         {"age": 10, "order": "4"},
         {"age": 10, "order": "5"},
         {"age": 10, "order": "6"},
         {"age": 10, "order": "7"},
         {"age": 10, "order": "8"},
         {"age": 10, "order": "9"},
         {"age": 10, "order": "10"},
         {"age": 10, "order": "11"}
      ]
    }
  ]
}, {
  "given":
  {
    "people": [
         {"a": 10, "b": 1, "c": "z"},
         {"a": 10, "b": 2, "c": null},
         {"a": 10, "b": 3},
         {"a": 10, "b": 4, "c": "z"},
         {"a": 10, "b": 5, "c": null},
         {"a": 10, "b": 6},
         {"a": 10, "b": 7, "c": "z"},
         {"a": 10, "b": 8, "c": null},
         {"a": 10, "b": 9}
    ],
    "empty": []
  },
  "cases": [
    {
      "expression": "map(&a, people)",
      "result": [10, 10, 10, 10, 10, 10, 10, 10, 10]
    },
    {
      "expression": "map(&c, people)",
      "result": ["z", null, null, "z", null, null, "z", null, null]
    },
    {
      "expression": "map(&a, badkey)",
      "error": "invalid-type"
    },
    {
      "expression": "map(&foo, empty)",
      "result": []
    }
  ]
}, {
  "given": {
    "array": [
      {
          "foo": {"bar": "yes1"}
      },
      {
          "foo": {"bar": "yes2"}
      },
      {
          "foo1": {"bar": "no"}
      }
  ]},
  "cases": [
    {
      "expression": "map(&foo.bar, array)",
      "result": ["yes1", "yes2", null]
    },
    {
      "expression": "map(&foo1.bar, array)",
      "result": [null, null, "no"]
    },
    {
      "expression": "map(&foo.bar.baz, array)",
      "result": [null, null, null]
    }
  ]
}, {
  "given": {
    "array": [[1, 2, 3, [4]], [5, 6, 7, [8, 9]]]
  },
  "cases": [
    {
      "expression": "map(&[], array)",
      "result": [[1, 2, 3, 4], [5, 6, 7, 8, 9]]
    }
  ]
}
]
