diff --git a/thrift/compiler/whisker/standard_library.cc b/thrift/compiler/whisker/standard_library.cc index 5de15868ad2..5d55421a43d 100644 --- a/thrift/compiler/whisker/standard_library.cc +++ b/thrift/compiler/whisker/standard_library.cc @@ -121,6 +121,41 @@ map::value_type create_array_functions() { array_functions["empty?"] = w::make_native_function(); } + { + /** + * Gets the object from an array at a given index. If the index is negative, + * or larger than the size of the array, then an error is thrown. + * + * Name: array.at + * + * Arguments: + * - [whisker::array] — The array to get from + * - [i64] — The index of the item to get + * + * Returns: + * [object] the item at the given index. + */ + class array_at : public named_native_function { + public: + array_at() : named_native_function("array.at") {} + + object::ptr invoke(context ctx) override { + ctx.declare_named_arguments({}); + ctx.declare_arity(2); + + auto a = ctx.argument(0); + auto index = ctx.argument(1); + + if (index < 0 || std::size_t(index) >= a.size()) { + ctx.error( + "Index '{}' is out of bounds (size is {}).", index, a.size()); + } + return a.at(index); + } + }; + array_functions["at"] = w::make_native_function(); + } + return map::value_type{"array", std::move(array_functions)}; } diff --git a/thrift/compiler/whisker/test/standard_library_test.cc b/thrift/compiler/whisker/test/standard_library_test.cc index aa223a20d97..590eb348b38 100644 --- a/thrift/compiler/whisker/test/standard_library_test.cc +++ b/thrift/compiler/whisker/test/standard_library_test.cc @@ -102,6 +102,48 @@ TEST_F(StandardLibraryTest, array_empty) { } } +TEST_F(StandardLibraryTest, array_at) { + { + auto result = render( + "{{ (array.at (array.of 1 \"foo\" 2) 0) }}\n" + "{{ (array.at (array.of 1 \"foo\" 2) 1) }}\n" + "{{ (array.at (array.of 1 \"foo\" 2) 2) }}\n", + w::null); + EXPECT_THAT(diagnostics(), testing::IsEmpty()); + EXPECT_EQ( + *result, + "1\n" + "foo\n" + "2\n"); + } + + { + auto result = render("{{ (array.at (array.of) 0) }}\n", w::null); + EXPECT_FALSE(result.has_value()); + EXPECT_THAT( + diagnostics(), + testing::ElementsAre(diagnostic( + diagnostic_level::error, + "Function 'array.at' threw an error:\n" + "Index '0' is out of bounds (size is 0).", + path_to_file, + 1))); + } + + { + auto result = render("{{ (array.at (array.of 0 1 2) -1) }}\n", w::null); + EXPECT_FALSE(result.has_value()); + EXPECT_THAT( + diagnostics(), + testing::ElementsAre(diagnostic( + diagnostic_level::error, + "Function 'array.at' threw an error:\n" + "Index '-1' is out of bounds (size is 3).", + path_to_file, + 1))); + } +} + TEST_F(StandardLibraryTest, string_len) { { auto result = render(