Skip to content

Commit

Permalink
standard library
Browse files Browse the repository at this point in the history
Summary:
This diff introduces the "Whisker standard library" — a place to put *extremely* generic functionality around template data.

The initial implementation just has the following functions:
- `array.of(...)` — array literals
- `array.len`
- `array.empty?`
- `str.len`

I picked a small set of (non-controversial) functions just to validate the approach.

Reviewed By: yoney

Differential Revision: D67958812

fbshipit-source-id: 36200dc21c1f053ad31664d8d3ee562ab641109a
  • Loading branch information
praihan authored and facebook-github-bot committed Jan 11, 2025
1 parent f983897 commit 8009217
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 2 deletions.
1 change: 1 addition & 0 deletions third-party/thrift/src/thrift/compiler/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ add_library(
whisker/parser.cc
whisker/print_ast.cc
whisker/render.cc
whisker/standard_library.cc
whisker/token.cc
whisker/tree_printer.cc
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

#include <thrift/compiler/generate/t_mstch_generator.h>

#include <algorithm>
#include <cassert>
#include <cstdio>
#include <fstream>
Expand All @@ -34,6 +33,7 @@
#include <thrift/compiler/whisker/mstch_compat.h>
#include <thrift/compiler/whisker/parser.h>
#include <thrift/compiler/whisker/source_location.h>
#include <thrift/compiler/whisker/standard_library.h>

#include <thrift/compiler/detail/system.h>
#include <thrift/compiler/generate/t_generator.h>
Expand Down Expand Up @@ -559,6 +559,7 @@ t_mstch_generator::gen_whisker_render_state(whisker_options whisker_opts) {
render_options.strict_printable_types = whisker::diagnostic_level::debug;
render_options.strict_undefined_variables = whisker::diagnostic_level::error;

whisker::load_standard_library(render_options.globals);
for (const auto& undefined_name : whisker_opts.allowed_undefined_variables) {
render_options.globals.insert({undefined_name, whisker::make::null});
}
Expand Down
166 changes: 166 additions & 0 deletions third-party/thrift/src/thrift/compiler/whisker/standard_library.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <thrift/compiler/whisker/standard_library.h>

namespace w = whisker::make;

namespace whisker {

namespace {

class named_native_function : public native_function {
public:
explicit named_native_function(std::string_view name)
: name_(std::move(name)) {}

void print_to(
tree_printer::scope scope, const object_print_options&) const final {
scope.println("<function {}>", name_);
}

private:
std::string_view name_;
};

map::value_type create_array_functions() {
map array_functions;

{
/**
* Creates an array with the provided arguments in order. This function can
* be used to form an "array literal".
*
* Name: array.of
*
* Arguments:
* 0 or more objects (variadic)
*
* Returns:
* [array] provided arguments in order.
*/
class array_of : public named_native_function {
public:
array_of() : named_native_function("array.of") {}

object::ptr invoke(context ctx) override {
ctx.declare_named_arguments({});
array result;
result.reserve(ctx.arity());
for (const object::ptr& arg : ctx.arguments()) {
result.emplace_back(object(*arg));
}
return object::owned(w::array(std::move(result)));
}
};
array_functions["of"] = w::make_native_function<array_of>();
}

{
/**
* Produces the length of an array or array-like object.
*
* Name: array.len
*
* Arguments:
* - [array] — The array to find length of.
*
* Returns:
* [i64] length of the provided array.
*/
class array_len : public named_native_function {
public:
array_len() : named_native_function("array.len") {}

object::ptr invoke(context ctx) override {
ctx.declare_named_arguments({});
ctx.declare_arity(1);
auto len = i64(ctx.argument<array>(0).size());
return object::owned(w::i64(len));
}
};
array_functions["len"] = w::make_native_function<array_len>();
}

{
/**
* Checks an array for emptiness.
*
* Name: array.empty?
*
* Arguments:
* - [array] — The array to check for emptiness.
*
* Returns:
* [boolean] indicating whether the array is empty.
*/
class array_empty : public named_native_function {
public:
array_empty() : named_native_function("array.empty?") {}

object::ptr invoke(context ctx) override {
ctx.declare_named_arguments({});
ctx.declare_arity(1);
return object::as_static(
ctx.argument<array>(0).size() == 0 ? w::true_ : w::false_);
}
};
array_functions["empty?"] = w::make_native_function<array_empty>();
}

return map::value_type{"array", std::move(array_functions)};
}

map::value_type create_string_functions() {
map string_functions;

{
/**
* Produces the length of string.
*
* Name: string.len
*
* Arguments:
* - [string] — The string to find length of.
*
* Returns:
* [i64] length of the provided string.
*/
class string_len : public named_native_function {
public:
string_len() : named_native_function("string.len") {}

object::ptr invoke(context ctx) override {
ctx.declare_named_arguments({});
ctx.declare_arity(1);
auto len = i64(ctx.argument<string>(0)->length());
return object::owned(w::i64(len));
}
};
string_functions["len"] = w::make_native_function<string_len>();
}

return map::value_type{"string", std::move(string_functions)};
}

} // namespace

void load_standard_library(map& module) {
module.emplace(create_array_functions());
module.emplace(create_string_functions());
}

} // namespace whisker
31 changes: 31 additions & 0 deletions third-party/thrift/src/thrift/compiler/whisker/standard_library.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include <thrift/compiler/whisker/object.h>

namespace whisker {

/**
* Loads Whisker's standard library into the provided map. A common use case is
* calling this function on render_options::globals map object.
*
* See standard_library.cc for available functions.
*/
void load_standard_library(map& module);

} // namespace whisker
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <thrift/compiler/whisker/parser.h>
#include <thrift/compiler/whisker/render.h>

#include <functional>
#include <optional>
#include <sstream>
#include <stdexcept>
Expand Down Expand Up @@ -89,6 +90,7 @@ class RenderTest : public testing::Test {
std::optional<diagnostic_level> strict_undefined_variables;
// Backtraces are disabled by default since they add generally add noise.
bool show_source_backtrace_on_failure = false;
std::vector<std::function<void(map&)>> libraries_to_load;

void apply_to(render_options& options) const {
if (strict_boolean_conditional.has_value()) {
Expand All @@ -103,10 +105,14 @@ class RenderTest : public testing::Test {
options.show_source_backtrace_on_failure =
show_source_backtrace_on_failure ? diagnostic_level::error
: diagnostic_level::info;
for (const auto& load : libraries_to_load) {
load(options.globals);
}
}
};
render_test_options render_test_options_;

protected:
void SetUp() override {
last_render_ = std::nullopt;
render_test_options_ = {};
Expand All @@ -125,6 +131,9 @@ class RenderTest : public testing::Test {
void show_source_backtrace_on_failure(bool enabled) {
render_test_options_.show_source_backtrace_on_failure = enabled;
}
void use_library(std::function<void(map&)> library_loader) {
render_test_options_.libraries_to_load.push_back(std::move(library_loader));
}

struct partials_by_path {
/**
Expand Down Expand Up @@ -165,7 +174,6 @@ class RenderTest : public testing::Test {
}

render_options options;
render_test_options_.apply_to(options);
if (!partials.value.empty()) {
auto partial_resolver =
std::make_unique<in_memory_template_resolver>(current.src_manager);
Expand All @@ -175,6 +183,7 @@ class RenderTest : public testing::Test {
options.partial_resolver = std::move(partial_resolver);
}
options.globals = std::move(globals.value);
render_test_options_.apply_to(options);

std::ostringstream out;
if (whisker::render(
Expand Down
Loading

0 comments on commit 8009217

Please sign in to comment.