// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gn/functions.h"

#include <stddef.h>
#include <cctype>
#include <memory>
#include <utility>

#include "base/environment.h"
#include "base/sha2.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "gn/build_settings.h"
#include "gn/config.h"
#include "gn/config_values_generator.h"
#include "gn/err.h"
#include "gn/input_file.h"
#include "gn/parse_node_value_adapter.h"
#include "gn/parse_tree.h"
#include "gn/pool.h"
#include "gn/scheduler.h"
#include "gn/scope.h"
#include "gn/settings.h"
#include "gn/standard_out.h"
#include "gn/template.h"
#include "gn/token.h"
#include "gn/value.h"
#include "gn/value_extractors.h"
#include "gn/variables.h"

namespace {

// Some functions take a {} following them, and some don't. For the ones that
// don't, this is used to verify that the given block node is null and will
// set the error accordingly if it's not. Returns true if the block is null.
bool VerifyNoBlockForFunctionCall(const FunctionCallNode* function,
                                  const BlockNode* block,
                                  Err* err) {
  if (!block)
    return true;

  *err =
      Err(block, "Unexpected '{'.",
          "This function call doesn't take a {} block following it, and you\n"
          "can't have a {} block that's not connected to something like an if\n"
          "statement or a target declaration.");
  err->AppendRange(function->function().range());
  return false;
}

// This key is set as a scope property on the scope of a declare_args() block,
// in order to prevent reading a variable defined earlier in the same call
// (see `gn help declare_args` for more).
const void* kInDeclareArgsKey = nullptr;

}  // namespace

bool EnsureNotReadingFromSameDeclareArgs(const ParseNode* node,
                                         const Scope* cur_scope,
                                         const Scope* val_scope,
                                         Err* err) {
  // If the value didn't come from a scope at all, we're safe.
  if (!val_scope)
    return true;

  const Scope* val_args_scope = nullptr;
  val_scope->GetProperty(&kInDeclareArgsKey, &val_args_scope);

  const Scope* cur_args_scope = nullptr;
  cur_scope->GetProperty(&kInDeclareArgsKey, &cur_args_scope);
  if (!val_args_scope || !cur_args_scope || (val_args_scope != cur_args_scope))
    return true;

  *err =
      Err(node,
          "Reading a variable defined in the same declare_args() call.\n"
          "\n"
          "If you need to set the value of one arg based on another, put\n"
          "them in two separate declare_args() calls, one after the other.\n");
  return false;
}

bool EnsureNotProcessingImport(const ParseNode* node,
                               const Scope* scope,
                               Err* err) {
  if (scope->IsProcessingImport()) {
    *err =
        Err(node, "Not valid from an import.",
            "Imports are for defining defaults, variables, and rules. The\n"
            "appropriate place for this kind of thing is really in a normal\n"
            "BUILD file.");
    return false;
  }
  return true;
}

bool EnsureNotProcessingBuildConfig(const ParseNode* node,
                                    const Scope* scope,
                                    Err* err) {
  if (scope->IsProcessingBuildConfig()) {
    *err = Err(node, "Not valid from the build config.",
               "You can't do this kind of thing from the build config script, "
               "silly!\nPut it in a regular BUILD file.");
    return false;
  }
  return true;
}

bool FillTargetBlockScope(const Scope* scope,
                          const FunctionCallNode* function,
                          const std::string& target_type,
                          const BlockNode* block,
                          const std::vector<Value>& args,
                          Scope* block_scope,
                          Err* err) {
  if (!block) {
    FillNeedsBlockError(function, err);
    return false;
  }

  // Copy the target defaults, if any, into the scope we're going to execute
  // the block in.
  const Scope* default_scope = scope->GetTargetDefaults(target_type);
  if (default_scope) {
    Scope::MergeOptions merge_options;
    merge_options.skip_private_vars = true;
    if (!default_scope->NonRecursiveMergeTo(block_scope, merge_options,
                                            function, "target defaults", err))
      return false;
  }

  // The name is the single argument to the target function.
  if (!EnsureSingleStringArg(function, args, err))
    return false;

  // Set the target name variable to the current target, and mark it used
  // because we don't want to issue an error if the script ignores it.
  const std::string_view target_name(variables::kTargetName);
  block_scope->SetValue(target_name, Value(function, args[0].string_value()),
                        function);
  block_scope->MarkUsed(target_name);
  return true;
}

void FillNeedsBlockError(const FunctionCallNode* function, Err* err) {
  *err = Err(function->function(), "This function call requires a block.",
             "The block's \"{\" must be on the same line as the function "
             "call's \")\".");
}

bool EnsureSingleStringArg(const FunctionCallNode* function,
                           const std::vector<Value>& args,
                           Err* err) {
  if (args.size() != 1) {
    *err = Err(function->function(), "Incorrect arguments.",
               "This function requires a single string argument.");
    return false;
  }
  return args[0].VerifyTypeIs(Value::STRING, err);
}

const Label& ToolchainLabelForScope(const Scope* scope) {
  return scope->settings()->toolchain_label();
}

Label MakeLabelForScope(const Scope* scope,
                        const FunctionCallNode* function,
                        const std::string& name) {
  const Label& toolchain_label = ToolchainLabelForScope(scope);
  return Label(scope->GetSourceDir(), name, toolchain_label.dir(),
               toolchain_label.name());
}

// static
const int NonNestableBlock::kKey = 0;

NonNestableBlock::NonNestableBlock(Scope* scope,
                                   const FunctionCallNode* function,
                                   const char* type_description)
    : scope_(scope),
      function_(function),
      type_description_(type_description),
      key_added_(false) {}

NonNestableBlock::~NonNestableBlock() {
  if (key_added_)
    scope_->SetProperty(&kKey, nullptr);
}

bool NonNestableBlock::Enter(Err* err) {
  void* scope_value = scope_->GetProperty(&kKey, nullptr);
  if (scope_value) {
    // Existing block.
    const NonNestableBlock* existing =
        reinterpret_cast<const NonNestableBlock*>(scope_value);
    *err = Err(function_, "Can't nest these things.",
               std::string("You are trying to nest a ") + type_description_ +
                   " inside a " + existing->type_description_ + ".");
    err->AppendSubErr(Err(existing->function_, "The enclosing block."));
    return false;
  }

  scope_->SetProperty(&kKey, this);
  key_added_ = true;
  return true;
}

namespace functions {

// assert ----------------------------------------------------------------------

const char kAssert[] = "assert";
const char kAssert_HelpShort[] =
    "assert: Assert an expression is true at generation time.";
const char kAssert_Help[] =
    R"(assert: Assert an expression is true at generation time.

  assert(<condition> [, <error string>])

  If the condition is false, the build will fail with an error. If the
  optional second argument is provided, that string will be printed
  with the error message.

Examples

  assert(is_win)
  assert(defined(sources), "Sources must be defined");
)";

Value RunAssert(Scope* scope,
                const FunctionCallNode* function,
                const std::vector<Value>& args,
                Err* err) {
  // Check usage: Assert takes 1 or 2 arguments.
  if (args.size() != 1 && args.size() != 2) {
    *err = Err(function->function(), "Wrong number of arguments.",
               "assert() takes one or two arguments, "
               "were you expecting something else?");
    return Value();
  }

  // Check usage: The first argument must be a boolean.
  if (args[0].type() != Value::BOOLEAN) {
    *err = Err(function->function(), "Assertion value not a bool.");
    return Value();
  }
  bool assertion_passed = args[0].boolean_value();

  // Check usage: The second argument, if present, must be a string.
  if (args.size() == 2 && args[1].type() != Value::STRING) {
    *err = Err(function->function(), "Assertion message is not a string.");
    return Value();
  }

  // Assertion passed: there is nothing to do, so return an empty value.
  if (assertion_passed) {
    return Value();
  }

  // Assertion failed; try to make a useful message and report it.
  if (args.size() == 2) {
    *err =
        Err(function->function(), "Assertion failed.", args[1].string_value());
  } else {
    *err = Err(function->function(), "Assertion failed.");
  }
  if (args[0].origin()) {
    // If you do "assert(foo)" we'd ideally like to show you where foo was
    // set, and in this case the origin of the args will tell us that.
    // However, if you do "assert(foo && bar)" the source of the value will
    // be the assert like, which isn't so helpful.
    //
    // So we try to see if the args are from the same line or not. This will
    // break if you do "assert(\nfoo && bar)" and we may show the second line
    // as the source, oh well. The way around this is to check to see if the
    // origin node is inside our function call block.
    Location origin_location = args[0].origin()->GetRange().begin();
    if (origin_location.file() != function->function().location().file() ||
        origin_location.line_number() !=
            function->function().location().line_number()) {
      err->AppendSubErr(
          Err(args[0].origin()->GetRange(), "", "This is where it was set."));
    }
  }
  return Value();
}

// config ----------------------------------------------------------------------

const char kConfig[] = "config";
const char kConfig_HelpShort[] = "config: Defines a configuration object.";
const char kConfig_Help[] =
    R"(config: Defines a configuration object.

  Configuration objects can be applied to targets and specify sets of compiler
  flags, includes, defines, etc. They provide a way to conveniently group sets
  of this configuration information.

  A config is referenced by its label just like a target.

  The values in a config are additive only. If you want to remove a flag you
  need to remove the corresponding config that sets it. The final set of flags,
  defines, etc. for a target is generated in this order:

   1. The values specified directly on the target (rather than using a config).
   2. The configs specified in the target's "configs" list, in order.
   3. Public_configs from a breadth-first traversal of the dependency tree in
      the order that the targets appear in "deps".
   4. All dependent configs from a breadth-first traversal of the dependency
      tree in the order that the targets appear in "deps".

More background

  Configs solve a problem where the build system needs to have a higher-level
  understanding of various compiler settings. For example, some compiler flags
  have to appear in a certain order relative to each other, some settings like
  defines and flags logically go together, and the build system needs to
  de-duplicate flags even though raw command-line parameters can't always be
  operated on in that way.

  The config gives a name to a group of settings that can then be reasoned
  about by GN. GN can know that configs with the same label are the same thing
  so can be de-duplicated. It allows related settings to be grouped so they
  are added or removed as a unit. And it allows targets to refer to settings
  with conceptual names ("no_rtti", "enable_exceptions", etc.) rather than
  having to hard-coding every compiler's flags each time they are referred to.

Variables valid in a config definition

)"

    CONFIG_VALUES_VARS_HELP

    R"(  Nested configs: configs
  General: visibility

Variables on a target used to apply configs

  all_dependent_configs, configs, public_configs

Example

  config("myconfig") {
    include_dirs = [ "include/common" ]
    defines = [ "ENABLE_DOOM_MELON" ]
  }

  executable("mything") {
    configs = [ ":myconfig" ]
  }
)";

Value RunConfig(const FunctionCallNode* function,
                const std::vector<Value>& args,
                Scope* scope,
                Err* err) {
  NonNestableBlock non_nestable(scope, function, "config");
  if (!non_nestable.Enter(err))
    return Value();

  if (!EnsureSingleStringArg(function, args, err) ||
      !EnsureNotProcessingImport(function, scope, err))
    return Value();

  Label label(MakeLabelForScope(scope, function, args[0].string_value()));

  if (g_scheduler->verbose_logging())
    g_scheduler->Log("Defining config", label.GetUserVisibleName(true));

  // Create the new config.
  std::unique_ptr<Config> config = std::make_unique<Config>(
      scope->settings(), label, scope->CollectBuildDependencyFiles());
  config->set_defined_from(function);
  if (!Visibility::FillItemVisibility(config.get(), scope, err))
    return Value();

  // Fill the flags and such.
  const SourceDir& input_dir = scope->GetSourceDir();
  ConfigValuesGenerator gen(&config->own_values(), scope, input_dir, err);
  gen.Run();
  if (err->has_error())
    return Value();

  // Read sub-configs.
  const Value* configs_value = scope->GetValue(variables::kConfigs, true);
  if (configs_value) {
    ExtractListOfUniqueLabels(scope->settings()->build_settings(),
                              *configs_value, scope->GetSourceDir(),
                              ToolchainLabelForScope(scope), &config->configs(),
                              err);
  }
  if (err->has_error())
    return Value();

  // Save the generated item.
  Scope::ItemVector* collector = scope->GetItemCollector();
  if (!collector) {
    *err = Err(function, "Can't define a config in this context.");
    return Value();
  }
  collector->push_back(std::move(config));

  return Value();
}

// declare_args ----------------------------------------------------------------

const char kDeclareArgs[] = "declare_args";
const char kDeclareArgs_HelpShort[] = "declare_args: Declare build arguments.";
const char kDeclareArgs_Help[] =
    R"(declare_args: Declare build arguments.

  Introduces the given arguments into the current scope. If they are not
  specified on the command line or in a toolchain's arguments, the default
  values given in the declare_args block will be used. However, these defaults
  will not override command-line values.

  See also "gn help buildargs" for an overview.

  The precise behavior of declare args is:

   1. The declare_args() block executes. Any variable defined in the enclosing
      scope is available for reading, but any variable defined earlier in
      the current scope is not (since the overrides haven't been applied yet).

   2. At the end of executing the block, any variables set within that scope
      are saved, with the values specified in the block used as the "default value"
      for that argument. Once saved, these variables are available for override
      via args.gn.

   3. User-defined overrides are applied. Anything set in "gn args" now
      overrides any default values. The resulting set of variables is promoted
      to be readable from the following code in the file.

  This has some ramifications that may not be obvious:

    - You should not perform difficult work inside a declare_args block since
      this only sets a default value that may be discarded. In particular,
      don't use the result of exec_script() to set the default value. If you
      want to have a script-defined default, set some default "undefined" value
      like [], "", or -1, and after the declare_args block, call exec_script if
      the value is unset by the user.

    - Because you cannot read the value of a variable defined in the same
      block, if you need to make the default value of one arg depend
      on the possibly-overridden value of another, write two separate
      declare_args() blocks:

        declare_args() {
          enable_foo = true
        }
        declare_args() {
          # Bar defaults to same user-overridden state as foo.
          enable_bar = enable_foo
        }

Example

  declare_args() {
    enable_teleporter = true
    enable_doom_melon = false
  }

  If you want to override the (default disabled) Doom Melon:
    gn --args="enable_doom_melon=true enable_teleporter=true"
  This also sets the teleporter, but it's already defaulted to on so it will
  have no effect.
)";

Value RunDeclareArgs(Scope* scope,
                     const FunctionCallNode* function,
                     const std::vector<Value>& args,
                     BlockNode* block,
                     Err* err) {
  NonNestableBlock non_nestable(scope, function, "declare_args");
  if (!non_nestable.Enter(err))
    return Value();

  Scope block_scope(scope);
  block_scope.SetProperty(&kInDeclareArgsKey, &block_scope);
  block->Execute(&block_scope, err);
  if (err->has_error())
    return Value();

  // Pass the values from our scope into the Args object for adding to the
  // scope with the proper values (taking into account the defaults given in
  // the block_scope, and arguments passed into the build).
  Scope::KeyValueMap values;
  block_scope.GetCurrentScopeValues(&values);
  scope->settings()->build_settings()->build_args().DeclareArgs(values, scope,
                                                                err);
  return Value();
}

// defined ---------------------------------------------------------------------

const char kDefined[] = "defined";
const char kDefined_HelpShort[] =
    "defined: Returns whether an identifier is defined.";
const char kDefined_Help[] =
    R"(defined: Returns whether an identifier is defined.

  Returns true if the given argument is defined. This is most useful in
  templates to assert that the caller set things up properly.

  You can pass an identifier:
    defined(foo)
  which will return true or false depending on whether foo is defined in the
  current scope.

  You can also check a named scope:
    defined(foo.bar)
  which will return true or false depending on whether bar is defined in the
  named scope foo. It will throw an error if foo is not defined or is not a
  scope.

  You can also check a named scope using a subscript string expression:
    defined(foo[bar + "_name"])
  which will return true or false depending on whether the subscript
  expression expands to the name of a member of the scope foo. It will
  throw an error if foo is not defined or is not a scope, or if the
  expression does not expand to a string, or if it is an empty string.

Example

  template("mytemplate") {
    # To help users call this template properly...
    assert(defined(invoker.sources), "Sources must be defined")

    # If we want to accept an optional "values" argument, we don't
    # want to dereference something that may not be defined.
    if (defined(invoker.values)) {
      values = invoker.values
    } else {
      values = "some default value"
    }
  }
)";

Value RunDefined(Scope* scope,
                 const FunctionCallNode* function,
                 const ListNode* args_list,
                 Err* err) {
  const auto& args_vector = args_list->contents();
  if (args_vector.size() != 1) {
    *err = Err(function, "Wrong number of arguments to defined().",
               "Expecting exactly one.");
    return Value();
  }

  const IdentifierNode* identifier = args_vector[0]->AsIdentifier();
  if (identifier) {
    // Passed an identifier "defined(foo)".
    if (scope->GetValue(identifier->value().value()))
      return Value(function, true);
    return Value(function, false);
  }

  const AccessorNode* accessor = args_vector[0]->AsAccessor();
  if (accessor) {
    // The base of the accessor must be a scope if it's defined.
    const Value* base = scope->GetValue(accessor->base().value());
    if (!base) {
      *err = Err(accessor, "Undefined identifier");
      return Value();
    }
    if (!base->VerifyTypeIs(Value::SCOPE, err))
      return Value();

    std::string scope_member;

    if (accessor->member()) {
      // Passed an accessor "defined(foo.bar)".
      scope_member = accessor->member()->value().value();
    } else if (accessor->subscript()) {
      // Passed an accessor "defined(foo["bar"])".
      Value subscript_value = accessor->subscript()->Execute(scope, err);
      if (err->has_error())
        return Value();
      if (!subscript_value.VerifyTypeIs(Value::STRING, err))
        return Value();
      scope_member = subscript_value.string_value();
    }
    if (!scope_member.empty()) {
      // Check the member inside the scope to see if its defined.
      bool result = base->scope_value()->GetValue(scope_member) != nullptr;
      return Value(function, result);
    }
  }

  // Argument is invalid.
  *err = Err(function, "Bad thing passed to defined().",
             "It should be of the form defined(foo), defined(foo.bar) or "
             "defined(foo[<string-expression>]).");
  return Value();
}

// getenv ----------------------------------------------------------------------

const char kGetEnv[] = "getenv";
const char kGetEnv_HelpShort[] = "getenv: Get an environment variable.";
const char kGetEnv_Help[] =
    R"(getenv: Get an environment variable.

  value = getenv(env_var_name)

  Returns the value of the given environment variable. If the value is not
  found, it will try to look up the variable with the "opposite" case (based on
  the case of the first letter of the variable), but is otherwise
  case-sensitive.

  If the environment variable is not found, the empty string will be returned.
  Note: it might be nice to extend this if we had the concept of "none" in the
  language to indicate lookup failure.

Example

  home_dir = getenv("HOME")
)";

Value RunGetEnv(Scope* scope,
                const FunctionCallNode* function,
                const std::vector<Value>& args,
                Err* err) {
  if (!EnsureSingleStringArg(function, args, err))
    return Value();

  std::unique_ptr<base::Environment> env(base::Environment::Create());

  std::string result;
  if (!env->GetVar(args[0].string_value().c_str(), &result))
    return Value(function, "");  // Not found, return empty string.
  return Value(function, result);
}

// import ----------------------------------------------------------------------

const char kImport[] = "import";
const char kImport_HelpShort[] =
    "import: Import a file into the current scope.";
const char kImport_Help[] =
    R"(import: Import a file into the current scope.

  The import command loads the rules and variables resulting from executing the
  given file into the current scope.

  By convention, imported files are named with a .gni extension.

  An import is different than a C++ "include". The imported file is executed in
  a standalone environment from the caller of the import command. The results
  of this execution are cached for other files that import the same .gni file.

  Note that you can not import a BUILD.gn file that's otherwise used in the
  build. Files must either be imported or implicitly loaded as a result of deps
  rules, but not both.

  The imported file's scope will be merged with the scope at the point import
  was called. If there is a conflict (both the current scope and the imported
  file define some variable or rule with the same name but different value), a
  runtime error will be thrown. Therefore, it's good practice to minimize the
  stuff that an imported file defines.

  Variables and templates beginning with an underscore '_' are considered
  private and will not be imported. Imported files can use such variables for
  internal computation without affecting other files.

Examples

  import("//build/rules/idl_compilation_rule.gni")

  # Looks in the current directory.
  import("my_vars.gni")
)";

Value RunImport(Scope* scope,
                const FunctionCallNode* function,
                const std::vector<Value>& args,
                Err* err) {
  if (!EnsureSingleStringArg(function, args, err))
    return Value();

  const SourceDir& input_dir = scope->GetSourceDir();
  SourceFile import_file = input_dir.ResolveRelativeFile(
      args[0], err, scope->settings()->build_settings()->root_path_utf8());
  scope->AddBuildDependencyFile(import_file);
  if (!err->has_error()) {
    scope->settings()->import_manager().DoImport(import_file, function, scope,
                                                 err);
  }
  return Value();
}

// not_needed -----------------------------------------------------------------

const char kNotNeeded[] = "not_needed";
const char kNotNeeded_HelpShort[] =
    "not_needed: Mark variables from scope as not needed.";
const char kNotNeeded_Help[] =
    R"(not_needed: Mark variables from scope as not needed.

  not_needed(variable_list_or_star, variable_to_ignore_list = [])
  not_needed(from_scope, variable_list_or_star,
             variable_to_ignore_list = [])

  Mark the variables in the current or given scope as not needed, which means
  you will not get an error about unused variables for these. The
  variable_to_ignore_list allows excluding variables from "all matches" if
  variable_list_or_star is "*".

Example

  not_needed("*", [ "config" ])
  not_needed([ "data_deps", "deps" ])
  not_needed(invoker, "*", [ "config" ])
  not_needed(invoker, [ "data_deps", "deps" ])
)";

Value RunNotNeeded(Scope* scope,
                   const FunctionCallNode* function,
                   const ListNode* args_list,
                   Err* err) {
  const auto& args_vector = args_list->contents();
  if (args_vector.size() < 1 || args_vector.size() > 3) {
    *err = Err(function, "Wrong number of arguments.",
               "Expecting one, two or three arguments.");
    return Value();
  }
  auto args_cur = args_vector.begin();

  Value* value = nullptr;  // Value to use, may point to result_value.
  Value result_value;      // Storage for the "evaluate" case.
  Value scope_value;       // Storage for an evaluated scope.
  const IdentifierNode* identifier = (*args_cur)->AsIdentifier();
  if (identifier) {
    // Optimize the common case where the input scope is an identifier. This
    // prevents a copy of a potentially large Scope object.
    value = scope->GetMutableValue(identifier->value().value(),
                                   Scope::SEARCH_NESTED, true);
    if (!value) {
      *err = Err(identifier, "Undefined identifier.");
      return Value();
    }
  } else {
    // Non-optimized case, just evaluate the argument.
    result_value = (*args_cur)->Execute(scope, err);
    if (err->has_error())
      return Value();
    value = &result_value;
  }
  args_cur++;

  // Extract the source scope if different from current one.
  Scope* source = scope;
  if (value->type() == Value::SCOPE) {
    if (args_cur == args_vector.end()) {
      *err = Err(
          function, "Wrong number of arguments.",
          "The first argument is a scope, expecting two or three arguments.");
      return Value();
    }
    // Copy the scope value if it will be overridden.
    if (value == &result_value) {
      scope_value = Value(nullptr, value->scope_value()->MakeClosure());
      source = scope_value.scope_value();
    } else {
      source = value->scope_value();
    }
    result_value = (*args_cur)->Execute(scope, err);
    if (err->has_error())
      return Value();
    value = &result_value;
    args_cur++;
  } else if (args_vector.size() > 2) {
    *err = Err(
        function, "Wrong number of arguments.",
        "The first argument is not a scope, expecting one or two arguments.");
    return Value();
  }

  // Extract the exclusion list if defined.
  Value exclusion_value;
  std::set<std::string> exclusion_set;
  if (args_cur != args_vector.end()) {
    exclusion_value = (*args_cur)->Execute(source, err);
    if (err->has_error())
      return Value();

    if (exclusion_value.type() != Value::LIST) {
      *err = Err(exclusion_value, "Not a valid list of variables to exclude.",
                 "Expecting a list of strings.");
      return Value();
    }

    for (const Value& cur : exclusion_value.list_value()) {
      if (!cur.VerifyTypeIs(Value::STRING, err))
        return Value();

      exclusion_set.insert(cur.string_value());
    }
  }

  if (value->type() == Value::STRING) {
    if (value->string_value() == "*") {
      source->MarkAllUsed(exclusion_set);
      return Value();
    }
  } else if (value->type() == Value::LIST) {
    if (exclusion_value.type() != Value::NONE) {
      *err = Err(exclusion_value, "Not supported with a variable list.",
                 "Exclusion list can only be used with the string \"*\".");
      return Value();
    }
    for (const Value& cur : value->list_value()) {
      if (!cur.VerifyTypeIs(Value::STRING, err))
        return Value();
      // We don't need the return value, we invoke scope::GetValue only to mark
      // the value as used. Note that we cannot use Scope::MarkUsed because we
      // want to also search in the parent scope.
      (void)source->GetValue(cur.string_value(), true);
    }
    return Value();
  }

  // Not the right type of argument.
  *err = Err(*value, "Not a valid list of variables.",
             "Expecting either the string \"*\" or a list of strings.");
  return Value();
}

// pool ------------------------------------------------------------------------

const char kPool[] = "pool";
const char kPool_HelpShort[] = "pool: Defines a pool object.";
const char kPool_Help[] =
    R"*(pool: Defines a pool object.

  Pool objects can be applied to a tool to limit the parallelism of the
  build. This object has a single property "depth" corresponding to
  the number of tasks that may run simultaneously.

  As the file containing the pool definition may be executed in the
  context of more than one toolchain it is recommended to specify an
  explicit toolchain when defining and referencing a pool.

  A pool named "console" defined in the root build file represents Ninja's
  console pool. Targets using this pool will have access to the console's
  stdin and stdout, and output will not be buffered. This special pool must
  have a depth of 1. Pools not defined in the root must not be named "console".
  The console pool can only be defined for the default toolchain.
  Refer to the Ninja documentation on the console pool for more info.

  A pool is referenced by its label just like a target.

Variables

  depth*
  * = required

Example

  if (current_toolchain == default_toolchain) {
    pool("link_pool") {
      depth = 1
    }
  }

  toolchain("toolchain") {
    tool("link") {
      command = "..."
      pool = ":link_pool($default_toolchain)"
    }
  }
)*";

const char kDepth[] = "depth";

Value RunPool(const FunctionCallNode* function,
              const std::vector<Value>& args,
              Scope* scope,
              Err* err) {
  NonNestableBlock non_nestable(scope, function, "pool");
  if (!non_nestable.Enter(err))
    return Value();

  if (!EnsureSingleStringArg(function, args, err) ||
      !EnsureNotProcessingImport(function, scope, err))
    return Value();

  Label label(MakeLabelForScope(scope, function, args[0].string_value()));

  if (g_scheduler->verbose_logging())
    g_scheduler->Log("Defining pool", label.GetUserVisibleName(true));

  // Get the pool depth. It is an error to define a pool without a depth,
  // so check first for the presence of the value.
  const Value* depth = scope->GetValue(kDepth, true);
  if (!depth) {
    *err = Err(function, "Can't define a pool without depth.");
    return Value();
  }

  if (!depth->VerifyTypeIs(Value::INTEGER, err))
    return Value();

  if (depth->int_value() < 0) {
    *err = Err(*depth, "depth must be positive or 0.");
    return Value();
  }

  // Create the new pool.
  std::unique_ptr<Pool> pool = std::make_unique<Pool>(
      scope->settings(), label, scope->CollectBuildDependencyFiles());

  if (label.name() == "console") {
    const Settings* settings = scope->settings();
    if (!settings->is_default()) {
      *err = Err(
          function,
          "\"console\" pool must be defined only in the default toolchain.");
      return Value();
    }
    if (label.dir() != settings->build_settings()->root_target_label().dir()) {
      *err = Err(function, "\"console\" pool must be defined in the root //.");
      return Value();
    }
    if (depth->int_value() != 1) {
      *err = Err(*depth, "\"console\" pool must have depth 1.");
      return Value();
    }
  }
  pool->set_depth(depth->int_value());

  // Save the generated item.
  Scope::ItemVector* collector = scope->GetItemCollector();
  if (!collector) {
    *err = Err(function, "Can't define a pool in this context.");
    return Value();
  }
  collector->push_back(std::move(pool));

  return Value();
}

// print -----------------------------------------------------------------------

const char kPrint[] = "print";
const char kPrint_HelpShort[] = "print: Prints to the console.";
const char kPrint_Help[] =
    R"(print: Prints to the console.

  Prints all arguments to the console separated by spaces. A newline is
  automatically appended to the end. In quiet mode (-q command-line parameter)
  the output will be buffered and only printed if there is a fatal error.

  This function is intended for debugging. Note that build files are run in
  parallel so you may get interleaved prints. A buildfile may also be executed
  more than once in parallel in the context of different toolchains so the
  prints from one file may be duplicated or
  interleaved with itself.

Examples

  print("Hello world")

  print(sources, deps)
)";

Value RunPrint(Scope* scope,
               const FunctionCallNode* function,
               const std::vector<Value>& args,
               Err* err) {
  std::string output;
  for (size_t i = 0; i < args.size(); i++) {
    if (i != 0)
      output.push_back(' ');
    output.append(args[i].ToString(false));
  }
  output.push_back('\n');

  const BuildSettings::PrintCallback& cb =
      scope->settings()->build_settings()->print_callback();
  if (cb) {
    cb(output);
  } else {
    OutputLogString(output);
  }

  return Value();
}

// print_stack_trace -----------------------------------------------------------

const char kPrintStackTrace[] = "print_stack_trace";
const char kPrintStackTrace_HelpShort[] =
    "print_stack_trace: Prints a stack trace.";
const char kPrintStackTrace_Help[] =
    R"(print_stack_trace: Prints a stack trace.

  Prints the current file location, and all template invocations that led up to
  this location, to the console.

Examples

  template("foo"){
    print_stack_trace()
  }
  template("bar"){
    foo(target_name + ".foo") {
      baz = invoker.baz
    }
  }
  bar("lala") {
    baz = 42
  }

  will print out the following:

  print_stack_trace() initiated at  //build.gn:2
    bar("lala")  //BUILD.gn:9
    foo("lala.foo")  //BUILD.gn:5
    print_stack_trace()  //BUILD.gn:2

)";

Value RunPrintStackTrace(Scope* scope,
                         const FunctionCallNode* function,
                         const std::vector<Value>& args,
                         Err* err) {
  std::string location_str = function->GetRange().begin().Describe(false);
  std::string toolchain =
      scope->settings()->toolchain_label().GetUserVisibleName(false);
  std::string output = "print_stack_trace() initiated at:  " + location_str +
                       "  using: " + toolchain;
  output.push_back('\n');

  for (const auto& entry : scope->GetTemplateInvocationEntries()) {
    output.append("  " + entry.Describe() + "\n");
  }
  output.append("  print_stack_trace()  " + location_str + "\n");

  const BuildSettings::PrintCallback& cb =
      scope->settings()->build_settings()->print_callback();
  if (cb) {
    cb(output);
  } else {
    OutputLogString(output);
  }

  return Value();
}

// split_list ------------------------------------------------------------------

const char kSplitList[] = "split_list";
const char kSplitList_HelpShort[] =
    "split_list: Splits a list into N different sub-lists.";
const char kSplitList_Help[] =
    R"(split_list: Splits a list into N different sub-lists.

  result = split_list(input, n)

  Given a list and a number N, splits the list into N sub-lists of
  approximately equal size. The return value is a list of the sub-lists. The
  result will always be a list of size N. If N is greater than the number of
  elements in the input, it will be padded with empty lists.

  The expected use is to divide source files into smaller uniform chunks.

Example

  The code:
    mylist = [1, 2, 3, 4, 5, 6]
    print(split_list(mylist, 3))

  Will print:
    [[1, 2], [3, 4], [5, 6]
)";
Value RunSplitList(Scope* scope,
                   const FunctionCallNode* function,
                   const ListNode* args_list,
                   Err* err) {
  const auto& args_vector = args_list->contents();
  if (args_vector.size() != 2) {
    *err = Err(function, "Wrong number of arguments to split_list().",
               "Expecting exactly two.");
    return Value();
  }

  ParseNodeValueAdapter list_adapter;
  if (!list_adapter.InitForType(scope, args_vector[0].get(), Value::LIST, err))
    return Value();
  const std::vector<Value>& input = list_adapter.get().list_value();

  ParseNodeValueAdapter count_adapter;
  if (!count_adapter.InitForType(scope, args_vector[1].get(), Value::INTEGER,
                                 err))
    return Value();
  int64_t count = count_adapter.get().int_value();
  if (count <= 0) {
    *err = Err(function, "Requested result size is not positive.");
    return Value();
  }

  Value result(function, Value::LIST);
  result.list_value().resize(count);

  // Every result list gets at least this many items in it.
  int64_t min_items_per_list = static_cast<int64_t>(input.size()) / count;

  // This many result lists get an extra item which is the remainder from above.
  int64_t extra_items = static_cast<int64_t>(input.size()) % count;

  // Allocate all lists that have a remainder assigned to them (max items).
  int64_t max_items_per_list = min_items_per_list + 1;
  auto last_item_end = input.begin();
  for (int64_t i = 0; i < extra_items; i++) {
    result.list_value()[i] = Value(function, Value::LIST);

    auto begin_add = last_item_end;
    last_item_end += max_items_per_list;
    result.list_value()[i].list_value().assign(begin_add, last_item_end);
  }

  // Allocate all smaller items that don't have a remainder.
  for (int64_t i = extra_items; i < count; i++) {
    result.list_value()[i] = Value(function, Value::LIST);

    auto begin_add = last_item_end;
    last_item_end += min_items_per_list;
    result.list_value()[i].list_value().assign(begin_add, last_item_end);
  }

  return result;
}

// string_hash -----------------------------------------------------------------

const char kStringHash[] = "string_hash";
const char kStringHash_HelpShort[] =
    "string_hash: Calculates a stable hash of the given string.";
const char kStringHash_Help[] =
    R"(string_hash: Calculates a stable hash of the given string.

  hash = string_hash(long_string)

  `string_hash` returns a string that contains a hash of the argument.  The hash
  is computed by first calculating the SHA256 hash of the argument, and then
  returning the first 8 characters of the lowercase-ASCII, hexadecimal encoding
  of the SHA256 hash.

  `string_hash` is intended to be used when it is desirable to translate,
  globally unique strings (such as GN labels) into short filenames that are
  still globally unique.  This is useful when supporting filesystems and build
  systems which impose limits on the length of the supported filenames and/or on
  the total path length.

  Warning: This hash should never be used for cryptographic purposes.
  Unique inputs can be assumed to result in unique hashes if the inputs
  are trustworthy, but malicious inputs may be able to trigger collisions.
  Directories and names of GN labels are usually considered trustworthy.

Examples:

    string_hash("abc")  -->  "ba7816bf"
)";

Value RunStringHash(Scope* scope,
                    const FunctionCallNode* function,
                    const std::vector<Value>& args,
                    Err* err) {
  // Check usage: Number of arguments.
  if (args.size() != 1) {
    *err = Err(function, "Wrong number of arguments to string_hash().",
               "Expecting exactly one. usage: string_hash(string)");
    return Value();
  }

  // Check usage: argument is a string.
  if (!args[0].VerifyTypeIs(Value::STRING, err)) {
    *err = Err(function, "argument of string_hash is not a string",
               "Expecting argument to be a string.");
    return Value();
  }
  const std::string& arg = args[0].string_value();

  // Arguments looks good; do the hash.
  std::array<uint8_t, base::kSha256Length> hash = base::Sha256(arg);

  // Trimming to 32 bits for improved ergonomics.  Probability of collisions
  // should still be sufficiently low (see https://crbug.com/46330294 for more
  // discussion).
  return Value(function, base::ToLowerASCII(base::HexEncode(hash.data(), 4)));
}

// string_join -----------------------------------------------------------------

const char kStringJoin[] = "string_join";
const char kStringJoin_HelpShort[] =
    "string_join: Concatenates a list of strings with a separator.";
const char kStringJoin_Help[] =
    R"(string_join: Concatenates a list of strings with a separator.

  result = string_join(separator, strings)

  Concatenate a list of strings with intervening occurrences of separator.

Examples

    string_join("", ["a", "b", "c"])    --> "abc"
    string_join("|", ["a", "b", "c"])   --> "a|b|c"
    string_join(", ", ["a", "b", "c"])  --> "a, b, c"
    string_join("s", ["", ""])          --> "s"
)";

Value RunStringJoin(Scope* scope,
                    const FunctionCallNode* function,
                    const std::vector<Value>& args,
                    Err* err) {
  // Check usage: Number of arguments.
  if (args.size() != 2) {
    *err = Err(function, "Wrong number of arguments to string_join().",
               "Expecting exactly two. usage: string_join(separator, strings)");
    return Value();
  }

  // Check usage: separator is a string.
  if (!args[0].VerifyTypeIs(Value::STRING, err)) {
    *err = Err(function,
               "separator in string_join(separator, strings) is not "
               "a string",
               "Expecting separator argument to be a string.");
    return Value();
  }
  const std::string separator = args[0].string_value();

  // Check usage: strings is a list.
  if (!args[1].VerifyTypeIs(Value::LIST, err)) {
    *err = Err(function,
               "strings in string_join(separator, strings) "
               "is not a list",
               "Expecting strings argument to be a list.");
    return Value();
  }
  const std::vector<Value> strings = args[1].list_value();

  // Arguments looks good; do the join.
  std::stringstream stream;
  for (size_t i = 0; i < strings.size(); ++i) {
    if (!strings[i].VerifyTypeIs(Value::STRING, err)) {
      return Value();
    }
    if (i != 0) {
      stream << separator;
    }
    stream << strings[i].string_value();
  }
  return Value(function, stream.str());
}

// string_replace --------------------------------------------------------------

const char kStringReplace[] = "string_replace";
const char kStringReplace_HelpShort[] =
    "string_replace: Replaces substring in the given string.";
const char kStringReplace_Help[] =
    R"(string_replace: Replaces substring in the given string.

  result = string_replace(str, old, new[, max])

  Returns a copy of the string str in which the occurrences of old have been
  replaced with new, optionally restricting the number of replacements. The
  replacement is performed sequentially, so if new contains old, it won't be
  replaced.

Example

  The code:
    mystr = "Hello, world!"
    print(string_replace(mystr, "world", "GN"))

  Will print:
    Hello, GN!
)";

Value RunStringReplace(Scope* scope,
                       const FunctionCallNode* function,
                       const std::vector<Value>& args,
                       Err* err) {
  if (args.size() < 3 || args.size() > 4) {
    *err = Err(function, "Wrong number of arguments to string_replace().");
    return Value();
  }

  if (!args[0].VerifyTypeIs(Value::STRING, err))
    return Value();
  const std::string str = args[0].string_value();

  if (!args[1].VerifyTypeIs(Value::STRING, err))
    return Value();
  const std::string& old = args[1].string_value();

  if (!args[2].VerifyTypeIs(Value::STRING, err))
    return Value();
  const std::string& new_ = args[2].string_value();

  int64_t max = INT64_MAX;
  if (args.size() > 3) {
    if (!args[3].VerifyTypeIs(Value::INTEGER, err))
      return Value();
    max = args[3].int_value();
    if (max <= 0) {
      *err = Err(function, "Requested number of replacements is not positive.");
      return Value();
    }
  }

  int64_t n = 0;
  std::string val(str);
  size_t start_pos = 0;
  while ((start_pos = val.find(old, start_pos)) != std::string::npos) {
    val.replace(start_pos, old.length(), new_);
    start_pos += new_.length();
    if (++n >= max)
      break;
  }
  return Value(function, std::move(val));
}

// string_split ----------------------------------------------------------------

const char kStringSplit[] = "string_split";
const char kStringSplit_HelpShort[] =
    "string_split: Split string into a list of strings.";
const char kStringSplit_Help[] =
    R"(string_split: Split string into a list of strings.

  result = string_split(str[, sep])

  Split string into all substrings separated by separator and returns a list
  of the substrings between those separators.

  If the separator argument is omitted, the split is by any whitespace, and
  any leading/trailing whitespace is ignored; similar to Python's str.split().

Examples without a separator (split on whitespace):

  string_split("")          --> []
  string_split("a")         --> ["a"]
  string_split(" aa  bb")   --> ["aa", "bb"]

Examples with a separator (split on separators):

  string_split("", "|")           --> [""]
  string_split("  a b  ", " ")    --> ["", "", "a", "b", "", ""]
  string_split("aa+-bb+-c", "+-") --> ["aa", "bb", "c"]
)";

Value RunStringSplit(Scope* scope,
                     const FunctionCallNode* function,
                     const std::vector<Value>& args,
                     Err* err) {
  // Check usage: argument count.
  if (args.size() != 1 && args.size() != 2) {
    *err = Err(function, "Wrong number of arguments to string_split().",
               "Usage: string_split(str[, sep])");
    return Value();
  }

  // Check usage: str is a string.
  if (!args[0].VerifyTypeIs(Value::STRING, err)) {
    return Value();
  }
  const std::string str = args[0].string_value();

  // Check usage: separator is a non-empty string.
  std::string separator;
  if (args.size() == 2) {
    if (!args[1].VerifyTypeIs(Value::STRING, err)) {
      return Value();
    }
    separator = args[1].string_value();
    if (separator.empty()) {
      *err = Err(function,
                 "Separator argument to string_split() "
                 "cannot be empty string",
                 "Usage: string_split(str[, sep])");
      return Value();
    }
  }

  // Split the string into a std::vector.
  std::vector<std::string> strings;
  if (!separator.empty()) {
    // Case: Explicit separator argument.
    // Note: split_string("", "x") --> [""] like Python.
    size_t pos = 0;
    size_t next_pos = 0;
    while ((next_pos = str.find(separator, pos)) != std::string::npos) {
      strings.push_back(str.substr(pos, next_pos - pos));
      pos = next_pos + separator.length();
    }
    strings.push_back(str.substr(pos, std::string::npos));
  } else {
    // Case: Split on any whitespace and strip ends.
    // Note: split_string("") --> [] like Python.
    std::string::const_iterator pos = str.cbegin();
    while (pos != str.end()) {
      // Advance past spaces. After this, pos is pointing to non-whitespace.
      pos = find_if(pos, str.end(), [](char x) { return !std::isspace(x); });
      if (pos == str.end()) {
        // Tail is all whitespace, so we're done.
        break;
      }
      // Advance past non-whitespace to get next chunk.
      std::string::const_iterator next_whitespace_position =
          find_if(pos, str.end(), [](char x) { return std::isspace(x); });
      strings.push_back(std::string(pos, next_whitespace_position));
      pos = next_whitespace_position;
    }
  }

  // Convert vector of std::strings to list of GN strings.
  Value result(function, Value::LIST);
  result.list_value().resize(strings.size());
  for (size_t i = 0; i < strings.size(); ++i) {
    result.list_value()[i] = Value(function, strings[i]);
  }
  return result;
}

// -----------------------------------------------------------------------------

FunctionInfo::FunctionInfo()
    : self_evaluating_args_runner(nullptr),
      generic_block_runner(nullptr),
      executed_block_runner(nullptr),
      no_block_runner(nullptr),
      help_short(nullptr),
      help(nullptr),
      is_target(false) {}

FunctionInfo::FunctionInfo(SelfEvaluatingArgsFunction seaf,
                           const char* in_help_short,
                           const char* in_help,
                           bool in_is_target)
    : self_evaluating_args_runner(seaf),
      generic_block_runner(nullptr),
      executed_block_runner(nullptr),
      no_block_runner(nullptr),
      help_short(in_help_short),
      help(in_help),
      is_target(in_is_target) {}

FunctionInfo::FunctionInfo(GenericBlockFunction gbf,
                           const char* in_help_short,
                           const char* in_help,
                           bool in_is_target)
    : self_evaluating_args_runner(nullptr),
      generic_block_runner(gbf),
      executed_block_runner(nullptr),
      no_block_runner(nullptr),
      help_short(in_help_short),
      help(in_help),
      is_target(in_is_target) {}

FunctionInfo::FunctionInfo(ExecutedBlockFunction ebf,
                           const char* in_help_short,
                           const char* in_help,
                           bool in_is_target)
    : self_evaluating_args_runner(nullptr),
      generic_block_runner(nullptr),
      executed_block_runner(ebf),
      no_block_runner(nullptr),
      help_short(in_help_short),
      help(in_help),
      is_target(in_is_target) {}

FunctionInfo::FunctionInfo(NoBlockFunction nbf,
                           const char* in_help_short,
                           const char* in_help,
                           bool in_is_target)
    : self_evaluating_args_runner(nullptr),
      generic_block_runner(nullptr),
      executed_block_runner(nullptr),
      no_block_runner(nbf),
      help_short(in_help_short),
      help(in_help),
      is_target(in_is_target) {}

// Setup the function map via a static initializer. We use this because it
// avoids race conditions without having to do some global setup function or
// locking-heavy singleton checks at runtime. In practice, we always need this
// before we can do anything interesting, so it's OK to wait for the
// initializer.
struct FunctionInfoInitializer {
  FunctionInfoMap map;

  FunctionInfoInitializer() {
#define INSERT_FUNCTION(command, is_target)                             \
  map[k##command] = FunctionInfo(&Run##command, k##command##_HelpShort, \
                                 k##command##_Help, is_target);

    INSERT_FUNCTION(Action, true)
    INSERT_FUNCTION(ActionForEach, true)
    INSERT_FUNCTION(BundleData, true)
    INSERT_FUNCTION(CreateBundle, true)
    INSERT_FUNCTION(Copy, true)
    INSERT_FUNCTION(Executable, true)
    INSERT_FUNCTION(Group, true)
    INSERT_FUNCTION(LoadableModule, true)
    INSERT_FUNCTION(SharedLibrary, true)
    INSERT_FUNCTION(SourceSet, true)
    INSERT_FUNCTION(StaticLibrary, true)
    INSERT_FUNCTION(Target, true)
    INSERT_FUNCTION(GeneratedFile, true)
    INSERT_FUNCTION(RustLibrary, true)
    INSERT_FUNCTION(RustProcMacro, true)

    INSERT_FUNCTION(Assert, false)
    INSERT_FUNCTION(Config, false)
    INSERT_FUNCTION(DeclareArgs, false)
    INSERT_FUNCTION(Defined, false)
    INSERT_FUNCTION(ExecScript, false)
    INSERT_FUNCTION(ExpandDirectory, false)
    INSERT_FUNCTION(FilterExclude, false)
    INSERT_FUNCTION(FilterInclude, false)
    INSERT_FUNCTION(FilterLabelsInclude, false)
    INSERT_FUNCTION(FilterLabelsExclude, false)
    INSERT_FUNCTION(ForEach, false)
    INSERT_FUNCTION(ForwardVariablesFrom, false)
    INSERT_FUNCTION(GetEnv, false)
    INSERT_FUNCTION(GetLabelInfo, false)
    INSERT_FUNCTION(GetPathInfo, false)
    INSERT_FUNCTION(GetTargetOutputs, false)
    INSERT_FUNCTION(Import, false)
    INSERT_FUNCTION(LabelMatches, false)
    INSERT_FUNCTION(Len, false)
    INSERT_FUNCTION(NotNeeded, false)
    INSERT_FUNCTION(PathExists, false)
    INSERT_FUNCTION(Pool, false)
    INSERT_FUNCTION(Print, false)
    INSERT_FUNCTION(PrintStackTrace, false)
    INSERT_FUNCTION(ProcessFileTemplate, false)
    INSERT_FUNCTION(ReadFile, false)
    INSERT_FUNCTION(RebasePath, false)
    INSERT_FUNCTION(SetDefaults, false)
    INSERT_FUNCTION(SetDefaultToolchain, false)
    INSERT_FUNCTION(SplitList, false)
    INSERT_FUNCTION(StringHash, false)
    INSERT_FUNCTION(StringJoin, false)
    INSERT_FUNCTION(StringReplace, false)
    INSERT_FUNCTION(StringSplit, false)
    INSERT_FUNCTION(Template, false)
    INSERT_FUNCTION(Tool, false)
    INSERT_FUNCTION(Toolchain, false)
    INSERT_FUNCTION(WriteFile, false)

#undef INSERT_FUNCTION
  }
};
const FunctionInfoInitializer function_info;

const FunctionInfoMap& GetFunctions() {
  return function_info.map;
}

Value RunFunction(Scope* scope,
                  const FunctionCallNode* function,
                  const ListNode* args_list,
                  BlockNode* block,
                  Err* err) {
  const Token& name = function->function();

  std::string template_name(function->function().value());
  const Template* templ = scope->GetTemplate(template_name);
  if (templ) {
    Value args = args_list->Execute(scope, err);
    if (err->has_error())
      return Value();
    return templ->Invoke(scope, function, template_name, args.list_value(),
                         block, err);
  }

  // No template matching this, check for a built-in function.
  const FunctionInfoMap& function_map = GetFunctions();
  FunctionInfoMap::const_iterator found_function =
      function_map.find(name.value());
  if (found_function == function_map.end()) {
    *err = Err(name, "Unknown function.");
    return Value();
  }

  if (found_function->second.self_evaluating_args_runner) {
    // Self evaluating args functions are special weird built-ins like foreach.
    // Rather than force them all to check that they have a block or no block
    // and risk bugs for new additions, check an allowlist here.
    if (found_function->second.self_evaluating_args_runner != &RunForEach) {
      if (!VerifyNoBlockForFunctionCall(function, block, err))
        return Value();
    }
    return found_function->second.self_evaluating_args_runner(scope, function,
                                                              args_list, err);
  }

  // All other function types take a pre-executed set of args.
  Value args = args_list->Execute(scope, err);
  if (err->has_error())
    return Value();

  if (found_function->second.generic_block_runner) {
    if (!block) {
      FillNeedsBlockError(function, err);
      return Value();
    }
    return found_function->second.generic_block_runner(
        scope, function, args.list_value(), block, err);
  }

  if (found_function->second.executed_block_runner) {
    if (!block) {
      FillNeedsBlockError(function, err);
      return Value();
    }

    Scope block_scope(scope);
    block->Execute(&block_scope, err);
    if (err->has_error())
      return Value();

    Value result = found_function->second.executed_block_runner(
        function, args.list_value(), &block_scope, err);
    if (err->has_error())
      return Value();

    if (!block_scope.CheckForUnusedVars(err))
      return Value();
    return result;
  }

  // Otherwise it's a no-block function.
  if (!VerifyNoBlockForFunctionCall(function, block, err))
    return Value();
  return found_function->second.no_block_runner(scope, function,
                                                args.list_value(), err);
}

}  // namespace functions
