// The fish parser. Contains functions for parsing and evaluating code.
#include "config.h"  // IWYU pragma: keep

#include <stdio.h>
#include <wchar.h>

#include <algorithm>
#include <memory>

#include "common.h"
#include "env.h"
#include "event.h"
#include "expand.h"
#include "fallback.h"  // IWYU pragma: keep
#include "function.h"
#include "intern.h"
#include "parse_constants.h"
#include "parse_execution.h"
#include "parse_tree.h"
#include "parse_util.h"
#include "parser.h"
#include "proc.h"
#include "reader.h"
#include "sanity.h"
#include "wutil.h"  // IWYU pragma: keep

class io_chain_t;

/// Error for evaluating in illegal scope.
#define INVALID_SCOPE_ERR_MSG _(L"Tried to evaluate commands using invalid block type '%ls'")

/// While block description.
#define WHILE_BLOCK N_(L"'while' block")

/// For block description.
#define FOR_BLOCK N_(L"'for' block")

/// Breakpoint block.
#define BREAKPOINT_BLOCK N_(L"block created by breakpoint")

/// If block description.
#define IF_BLOCK N_(L"'if' conditional block")

/// Function invocation block description.
#define FUNCTION_CALL_BLOCK N_(L"function invocation block")

/// Function invocation block description.
#define FUNCTION_CALL_NO_SHADOW_BLOCK N_(L"function invocation block with no variable shadowing")

/// Switch block description.
#define SWITCH_BLOCK N_(L"'switch' block")

/// Top block description.
#define TOP_BLOCK N_(L"global root block")

/// Command substitution block description.
#define SUBST_BLOCK N_(L"command substitution block")

/// Begin block description.
#define BEGIN_BLOCK N_(L"'begin' unconditional block")

/// Source block description.
#define SOURCE_BLOCK N_(L"block created by the . builtin")

/// Source block description.
#define EVENT_BLOCK N_(L"event handler block")

/// Unknown block description.
#define UNKNOWN_BLOCK N_(L"unknown/invalid block")

/// Data structure to describe a block type, like while blocks, command substitution blocks, etc.
struct block_lookup_entry {
    // The block type id. The legal values are defined in parser.h.
    block_type_t type;
    // The name of the builtin that creates this type of block, if any.
    const wchar_t *name;
    // A description of this block type.
    const wchar_t *desc;
};

/// List of all legal block types.
static const struct block_lookup_entry block_lookup[] = {
    {WHILE, L"while", WHILE_BLOCK},
    {FOR, L"for", FOR_BLOCK},
    {IF, L"if", IF_BLOCK},
    {FUNCTION_CALL, 0, FUNCTION_CALL_BLOCK},
    {FUNCTION_CALL_NO_SHADOW, 0, FUNCTION_CALL_NO_SHADOW_BLOCK},
    {SWITCH, L"switch", SWITCH_BLOCK},
    {TOP, 0, TOP_BLOCK},
    {SUBST, 0, SUBST_BLOCK},
    {BEGIN, L"begin", BEGIN_BLOCK},
    {SOURCE, L".", SOURCE_BLOCK},
    {EVENT, 0, EVENT_BLOCK},
    {BREAKPOINT, L"breakpoint", BREAKPOINT_BLOCK},
    {(block_type_t)0, 0, 0}};

// Given a file path, return something nicer. Currently we just "unexpand" tildes.
static wcstring user_presentable_path(const wcstring &path) {
    return replace_home_directory_with_tilde(path);
}

parser_t::parser_t() : cancellation_requested(false), is_within_fish_initialization(false) {}

// Out of line destructor to enable forward declaration of parse_execution_context_t
parser_t::~parser_t() {}

static parser_t s_principal_parser;

parser_t &parser_t::principal_parser(void) {
    ASSERT_IS_NOT_FORKED_CHILD();
    ASSERT_IS_MAIN_THREAD();
    return s_principal_parser;
}

void parser_t::set_is_within_fish_initialization(bool flag) {
    is_within_fish_initialization = flag;
}

void parser_t::skip_all_blocks(void) {
    // Tell all blocks to skip.
    // This may be called from a signal handler!
    s_principal_parser.cancellation_requested = true;
}

// Given a new-allocated block, push it onto our block stack, acquiring ownership
void parser_t::push_block_int(block_t *new_current) {
    const enum block_type_t type = new_current->type();
    new_current->src_lineno = parser_t::get_lineno();

    const wchar_t *filename = parser_t::current_filename();
    if (filename != NULL) {
        new_current->src_filename = intern(filename);
    }

    // New blocks should be skipped if the outer block is skipped, except TOP and SUBST block, which
    // open up new environments.
    const block_t *old_current = this->current_block();
    new_current->skip = old_current && old_current->skip;

    // Type TOP and SUBST are never skipped.
    if (type == TOP || type == SUBST) {
        new_current->skip = false;
    }

    new_current->job = nullptr;
    new_current->loop_status = LOOP_NORMAL;

    // Push it onto our stack. This acquires ownership because of unique_ptr.
    this->block_stack.emplace_back(new_current);

    // Types TOP and SUBST are not considered blocks for the purposes of `status is-block`.
    if (type != TOP && type != SUBST) {
        is_block = true;
    }

    if (type == BREAKPOINT) {
        is_breakpoint = true;
    }

    if (new_current->type() != TOP) {
        env_push(type == FUNCTION_CALL);
        new_current->wants_pop_env = true;
    }
}

void parser_t::pop_block(const block_t *expected) {
    assert(expected == this->current_block());
    if (block_stack.empty()) {
        debug(1, L"function %s called on empty block stack.", __func__);
        bugreport();
        return;
    }

    // acquire ownership out of the block stack
    // this will trigger deletion when it goes out of scope
    std::unique_ptr<block_t> old = std::move(block_stack.back());
    block_stack.pop_back();

    if (old->wants_pop_env) env_pop();

    // Figure out if `status is-block` should consider us to be in a block now.
    bool new_is_block = false;
    for (const auto &b : block_stack) {
        const enum block_type_t type = b->type();
        if (type != TOP && type != SUBST) {
            new_is_block = true;
            break;
        }
    }
    is_block = new_is_block;

    // Are we still in a breakpoint?
    bool new_is_breakpoint = false;
    for (const auto &b : block_stack) {
        const enum block_type_t type = b->type();
        if (type == BREAKPOINT) {
            new_is_breakpoint = true;
            break;
        }
    }
    is_breakpoint = new_is_breakpoint;
}

const wchar_t *parser_t::get_block_desc(int block) const {
    for (size_t i = 0; block_lookup[i].desc; i++) {
        if (block_lookup[i].type == block) {
            return _(block_lookup[i].desc);
        }
    }
    return _(UNKNOWN_BLOCK);
}

#if 0
// TODO: Lint says this isn't used (which is true). Should this be removed?
wcstring parser_t::block_stack_description() const {
    wcstring result;
    size_t idx = this->block_count();
    size_t spaces = 0;
    while (idx--) {
        if (spaces > 0) {
            result.push_back(L'\n');
        }
        for (size_t j = 0; j < spaces; j++) {
            result.push_back(L' ');
        }
        result.append(this->block_at_index(idx)->description());
        spaces++;
    }
    return result;
}
#endif

const block_t *parser_t::block_at_index(size_t idx) const {
    // Zero corresponds to the last element in our vector.
    size_t count = block_stack.size();
    return idx < count ? block_stack.at(count - idx - 1).get() : NULL;
}

block_t *parser_t::block_at_index(size_t idx) {
    size_t count = block_stack.size();
    return idx < count ? block_stack.at(count - idx - 1).get() : NULL;
}

block_t *parser_t::current_block() { return block_stack.empty() ? NULL : block_stack.back().get(); }

void parser_t::forbid_function(const wcstring &function) { forbidden_function.push_back(function); }

void parser_t::allow_function() { forbidden_function.pop_back(); }

/// Print profiling information to the specified stream.
static void print_profile(const std::vector<std::unique_ptr<profile_item_t>> &items, FILE *out) {
    for (size_t pos = 0; pos < items.size(); pos++) {
        const profile_item_t *me, *prev;
        size_t i;
        int my_time;

        me = items.at(pos).get();
        if (me->skipped) {
            continue;
        }

        my_time = me->parse + me->exec;
        for (i = pos + 1; i < items.size(); i++) {
            prev = items.at(i).get();
            if (prev->skipped) {
                continue;
            }
            if (prev->level <= me->level) {
                break;
            }
            if (prev->level > me->level + 1) {
                continue;
            }

            my_time -= prev->parse + prev->exec;
        }

        if (me->cmd.size() == 0) {
            continue;
        }

        if (fwprintf(out, L"%d\t%d\t", my_time, me->parse + me->exec) < 0) {
            wperror(L"fwprintf");
            return;
        }

        for (i = 0; i < me->level; i++) {
            if (fwprintf(out, L"-") < 0) {
                wperror(L"fwprintf");
                return;
            }
        }

        if (fwprintf(out, L"> %ls\n", me->cmd.c_str()) < 0) {
            wperror(L"fwprintf");
            return;
        }
    }
}

void parser_t::emit_profiling(const char *path) const {
    // Save profiling information. OK to not use CLO_EXEC here because this is called while fish is
    // dying (and hence will not fork).
    FILE *f = fopen(path, "w");
    if (!f) {
        debug(1, _(L"Could not write profiling information to file '%s'"), path);
    } else {
        if (fwprintf(f, _(L"Time\tSum\tCommand\n"), profile_items.size()) < 0) {
            wperror(L"fwprintf");
        } else {
            print_profile(profile_items, f);
        }

        if (fclose(f)) {
            wperror(L"fclose");
        }
    }
}

void parser_t::expand_argument_list(const wcstring &arg_list_src, expand_flags_t eflags,
                                    std::vector<completion_t> *output_arg_list) {
    assert(output_arg_list != NULL);

    // Parse the string as an argument list.
    parse_node_tree_t tree;
    if (!parse_tree_from_string(arg_list_src, parse_flag_none, &tree, NULL /* errors */,
                                symbol_freestanding_argument_list)) {
        // Failed to parse. Here we expect to have reported any errors in test_args.
        return;
    }

    // Get the root argument list.
    assert(!tree.empty());  //!OCLINT(multiple unary operator)
    const parse_node_t *arg_list = &tree.at(0);
    assert(arg_list->type == symbol_freestanding_argument_list);

    // Extract arguments from it.
    while (arg_list != NULL) {
        const parse_node_t *arg_node =
            tree.next_node_in_node_list(*arg_list, symbol_argument, &arg_list);
        if (arg_node != NULL) {
            const wcstring arg_src = arg_node->get_source(arg_list_src);
            if (expand_string(arg_src, output_arg_list, eflags, NULL) == EXPAND_ERROR) {
                break;  // failed to expand a string
            }
        }
    }
}

wcstring parser_t::stack_trace() const {
    wcstring trace;
    this->stack_trace_internal(0, &trace);
    return trace;
}

void parser_t::stack_trace_internal(size_t block_idx, wcstring *buff) const {
    // Check if we should end the recursion.
    if (block_idx >= this->block_count()) return;

    const block_t *b = this->block_at_index(block_idx);

    if (b->type() == EVENT) {
        // This is an event handler.
        const event_block_t *eb = static_cast<const event_block_t *>(b);
        wcstring description = event_get_desc(eb->event);
        append_format(*buff, _(L"in event handler: %ls\n"), description.c_str());
        buff->append(L"\n");

        // Stop recursing at event handler. No reason to believe that any other code is relevant.
        //
        // It might make sense in the future to continue printing the stack trace of the code that
        // invoked the event, if this is a programmatic event, but we can't currently detect that.
        return;
    }

    if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW || b->type() == SOURCE ||
        b->type() == SUBST) {
        // These types of blocks should be printed.
        int i;

        switch (b->type()) {
            case SOURCE: {
                const source_block_t *sb = static_cast<const source_block_t *>(b);
                const wchar_t *source_dest = sb->source_file;
                append_format(*buff, _(L"from sourcing file %ls\n"),
                              user_presentable_path(source_dest).c_str());
                break;
            }
            case FUNCTION_CALL:
            case FUNCTION_CALL_NO_SHADOW: {
                const function_block_t *fb = static_cast<const function_block_t *>(b);
                append_format(*buff, _(L"in function '%ls'\n"), fb->name.c_str());
                break;
            }
            case SUBST: {
                append_format(*buff, _(L"in command substitution\n"));
                break;
            }
            default: {
                break;  // can't get here
            }
        }

        const wchar_t *file = b->src_filename;

        if (file) {
            append_format(*buff, _(L"\tcalled on line %d of file %ls\n"), b->src_lineno,
                          user_presentable_path(file).c_str());
        } else if (is_within_fish_initialization) {
            append_format(*buff, _(L"\tcalled during startup\n"));
        } else {
            append_format(*buff, _(L"\tcalled on standard input\n"));
        }

        if (b->type() == FUNCTION_CALL) {
            const function_block_t *fb = static_cast<const function_block_t *>(b);
            const process_t *const process = fb->process;
            if (process->argv(1)) {
                wcstring tmp;

                for (i = 1; process->argv(i); i++) {
                    if (i > 1) tmp.push_back(L' ');
                    tmp.append(process->argv(i));
                }
                append_format(*buff, _(L"\twith parameter list '%ls'\n"), tmp.c_str());
            }
        }

        append_format(*buff, L"\n");
    }

    // Recursively print the next block.
    parser_t::stack_trace_internal(block_idx + 1, buff);
}

/// Returns the name of the currently evaluated function if we are currently evaluating a function,
/// NULL otherwise. This is tested by moving down the block-scope-stack, checking every block if it
/// is of type FUNCTION_CALL. If the caller doesn't specify a starting position in the stack we
/// begin with the current block.
const wchar_t *parser_t::is_function(size_t idx) const {
    // PCA: Have to make this a string somehow.
    ASSERT_IS_MAIN_THREAD();

    const wchar_t *result = NULL;
    for (size_t block_idx = idx; block_idx < this->block_count(); block_idx++) {
        const block_t *b = this->block_at_index(block_idx);
        if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW) {
            const function_block_t *fb = static_cast<const function_block_t *>(b);
            result = fb->name.c_str();
            break;
        } else if (b->type() == SOURCE) {
            // If a function sources a file, obviously that function's offset doesn't contribute.
            break;
        }
    }
    return result;
}

/// Return the function name for the specified stack frame. Default is zero (current frame).
/// The special value zero means the function frame immediately above the closest breakpoint frame.
const wchar_t *parser_t::get_function_name(int level) {
    if (level == 0) {
        // Return the function name for the level preceding the most recent breakpoint. If there
        // isn't one return the function name for the current level.
        int idx = 0;
        for (const auto &b : block_stack) {
            const enum block_type_t type = b->type();
            if (type == BREAKPOINT) {
                return this->is_function(idx);
            }
            idx++;
        }
        return NULL;  // couldn't find a breakpoint frame
    } else if (level == 1) {
        // Return the function name for the current level.
        return this->is_function();
    }

    // Return the function name for the specific function stack frame.
    int idx = 0;
    for (const auto &b : block_stack) {
        const enum block_type_t type = b->type();
        if (type == FUNCTION_CALL || type == FUNCTION_CALL_NO_SHADOW) {
            if (--level == 0) return this->is_function(idx);
        }
        idx++;
    }
    return NULL;  // couldn't find that function level
}

int parser_t::get_lineno() const {
    int lineno = -1;
    if (!execution_contexts.empty()) {
        lineno = execution_contexts.back()->get_current_line_number();

        // If we are executing a function, we have to add in its offset.
        const wchar_t *function_name = is_function();
        if (function_name != NULL) {
            lineno += function_get_definition_offset(function_name);
        }
    }
    return lineno;
}

const wchar_t *parser_t::current_filename() const {
    ASSERT_IS_MAIN_THREAD();

    for (size_t i = 0; i < this->block_count(); i++) {
        const block_t *b = this->block_at_index(i);
        if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW) {
            const function_block_t *fb = static_cast<const function_block_t *>(b);
            return function_get_definition_file(fb->name);
        } else if (b->type() == SOURCE) {
            const source_block_t *sb = static_cast<const source_block_t *>(b);
            return sb->source_file;
        }
    }

    // We query a global array for the current file name, but only do that if we are the principal
    // parser.
    if (this == &principal_parser()) {
        return reader_current_filename();
    }
    return NULL;
}

wcstring parser_t::current_line() {
    if (execution_contexts.empty()) {
        return wcstring();
    }
    const parse_execution_context_t *context = execution_contexts.back().get();
    assert(context != NULL);

    int source_offset = context->get_current_source_offset();
    if (source_offset < 0) {
        return wcstring();
    }

    const int lineno = this->get_lineno();
    const wchar_t *file = this->current_filename();

    wcstring prefix;

    // If we are not going to print a stack trace, at least print the line number and filename.
    if (!shell_is_interactive() || is_function()) {
        if (file) {
            append_format(prefix, _(L"%ls (line %d): "), user_presentable_path(file).c_str(),
                          lineno);
        } else if (is_within_fish_initialization) {
            append_format(prefix, L"%ls (line %d): ", _(L"Startup"), lineno);
        } else {
            append_format(prefix, L"%ls (line %d): ", _(L"Standard input"), lineno);
        }
    }

    bool is_interactive = shell_is_interactive();
    bool skip_caret = is_interactive && !is_function();

    // Use an error with empty text.
    assert(source_offset >= 0);
    parse_error_t empty_error = {};
    empty_error.source_start = source_offset;

    wcstring line_info =
        empty_error.describe_with_prefix(context->get_source(), prefix, is_interactive, skip_caret);
    if (!line_info.empty()) {
        line_info.push_back(L'\n');
    }

    line_info.append(this->stack_trace());
    return line_info;
}

void parser_t::job_add(shared_ptr<job_t> job) {
    assert(job != NULL);
    assert(!job->processes.empty());
    this->my_job_list.push_front(std::move(job));
}

bool parser_t::job_remove(job_t *job) {
    for (auto iter = my_job_list.begin(); iter != my_job_list.end(); ++iter) {
        if (iter->get() == job) {
            my_job_list.erase(iter);
            return true;
        }
    }

    debug(1, _(L"Job inconsistency"));
    sanity_lose();
    return false;
}

void parser_t::job_promote(job_t *job) {
    job_list_t::iterator loc;
    for (loc = my_job_list.begin(); loc != my_job_list.end(); ++loc) {
        if (loc->get() == job) {
            break;
        }
    }
    assert(loc != my_job_list.end());

    // Move the job to the beginning.
    my_job_list.splice(my_job_list.begin(), my_job_list, loc);
}

job_t *parser_t::job_get(job_id_t id) {
    for (const auto &job : my_job_list) {
        if (id <= 0 || job->job_id == id) return job.get();
    }
    return NULL;
}

job_t *parser_t::job_get_from_pid(pid_t pid) {
    job_iterator_t jobs;
    job_t *job;

    pid_t pgid = getpgid(pid);

    if (pgid == -1) {
        return 0;
    }

    while ((job = jobs.next())) {
        if (job->pgid == pgid) {
            for (const process_ptr_t &p : job->processes) {
                if (p->pid == pid) {
                    return job;
                }
            }
        }
    }
    return 0;
}

profile_item_t *parser_t::create_profile_item() {
    profile_item_t *result = nullptr;
    if (g_profiling_active) {
        profile_items.push_back(make_unique<profile_item_t>());
        result = profile_items.back().get();
    }
    return result;
}

int parser_t::eval(const wcstring &cmd, const io_chain_t &io, enum block_type_t block_type) {
    // Parse the source into a tree, if we can.
    parse_node_tree_t tree;
    parse_error_list_t error_list;
    if (!parse_tree_from_string(cmd, parse_flag_none, &tree, &error_list)) {
        // Get a backtrace. This includes the message.
        wcstring backtrace_and_desc;
        this->get_backtrace(cmd, error_list, backtrace_and_desc);

        // Print it.
        fwprintf(stderr, L"%ls\n", backtrace_and_desc.c_str());
        return 1;
    }
    return this->eval(cmd, io, block_type, std::move(tree));
}

int parser_t::eval(const wcstring &cmd, const io_chain_t &io, enum block_type_t block_type,
                   parse_node_tree_t tree) {
    CHECK_BLOCK(1);
    assert(block_type == TOP || block_type == SUBST);

    if (tree.empty()) {
        return 0;
    }

    // Determine the initial eval level. If this is the first context, it's -1; otherwise it's the
    // eval level of the top context. This is sort of wonky because we're stitching together a
    // global notion of eval level from these separate objects. A better approach would be some
    // profile object that all contexts share, and that tracks the eval levels on its own.
    int exec_eval_level =
        (execution_contexts.empty() ? -1 : execution_contexts.back()->current_eval_level());

    // Append to the execution context stack.
    execution_contexts.push_back(
        make_unique<parse_execution_context_t>(std::move(tree), cmd, this, exec_eval_level));
    const parse_execution_context_t *ctx = execution_contexts.back().get();

    // Execute the first node.
    this->eval_block_node(0, io, block_type);

    // Clean up the execution context stack.
    assert(!execution_contexts.empty() && execution_contexts.back().get() == ctx);
    execution_contexts.pop_back();

    return 0;
}

int parser_t::eval_block_node(node_offset_t node_idx, const io_chain_t &io,
                              enum block_type_t block_type) {
    // Paranoia. It's a little frightening that we're given only a node_idx and we interpret this in
    // the topmost execution context's tree. What happens if two trees were to be interleaved?
    // Fortunately that cannot happen (yet); in the future we probably want some sort of reference
    // counted trees.
    parse_execution_context_t *ctx = execution_contexts.back().get();
    assert(ctx != NULL);

    CHECK_BLOCK(1);

    // Handle cancellation requests. If our block stack is currently empty, then we already did
    // successfully cancel (or there was nothing to cancel); clear the flag. If our block stack is
    // not empty, we are still in the process of cancelling; refuse to evaluate anything.
    if (this->cancellation_requested) {
        if (!block_stack.empty()) {
            return 1;
        }
        this->cancellation_requested = false;
    }

    // Only certain blocks are allowed.
    if ((block_type != TOP) && (block_type != SUBST)) {
        debug(1, INVALID_SCOPE_ERR_MSG, parser_t::get_block_desc(block_type));
        bugreport();
        return 1;
    }

    job_reap(0);  // not sure why we reap jobs here

    // Start it up
    scope_block_t *scope_block = this->push_block<scope_block_t>(block_type);
    int result = ctx->eval_node_at_offset(node_idx, scope_block, io);
    this->pop_block(scope_block);

    job_reap(0);  // reap again

    return result;
}

bool parser_t::detect_errors_in_argument_list(const wcstring &arg_list_src, wcstring *out,
                                              const wchar_t *prefix) {
    bool errored = false;
    parse_error_list_t errors;

    // Use empty string for the prefix if it's NULL.
    if (!prefix) prefix = L"";  //!OCLINT(parameter reassignment)

    // Parse the string as an argument list.
    parse_node_tree_t tree;
    if (!parse_tree_from_string(arg_list_src, parse_flag_none, &tree, &errors,
                                symbol_freestanding_argument_list)) {
        // Failed to parse.
        errored = true;
    }

    if (!errored) {
        // Get the root argument list.
        assert(!tree.empty());  //!OCLINT(multiple unary operator)
        const parse_node_t *arg_list = &tree.at(0);
        assert(arg_list->type == symbol_freestanding_argument_list);

        // Extract arguments from it.
        while (arg_list != NULL && !errored) {
            const parse_node_t *arg_node =
                tree.next_node_in_node_list(*arg_list, symbol_argument, &arg_list);
            if (arg_node != NULL) {
                const wcstring arg_src = arg_node->get_source(arg_list_src);
                if (parse_util_detect_errors_in_argument(*arg_node, arg_src, &errors)) {
                    errored = true;
                }
            }
        }
    }

    if (!errors.empty() && out != NULL) {
        out->assign(errors.at(0).describe_with_prefix(
            arg_list_src, prefix, false /* not interactive */, false /* don't skip caret */));
    }
    return errored;
}

void parser_t::get_backtrace(const wcstring &src, const parse_error_list_t &errors,
                             wcstring &output) const {
    if (!errors.empty()) {
        const parse_error_t &err = errors.at(0);

        const bool is_interactive = shell_is_interactive();

        // Determine if we want to try to print a caret to point at the source error. The
        // err.source_start <= src.size() check is due to the nasty way that slices work, which is
        // by rewriting the source.
        size_t which_line = 0;
        bool skip_caret = true;
        if (err.source_start != SOURCE_LOCATION_UNKNOWN && err.source_start <= src.size()) {
            // Determine which line we're on.
            which_line = 1 + std::count(src.begin(), src.begin() + err.source_start, L'\n');

            // Don't include the caret if we're interactive, this is the first line of text, and our
            // source is at its beginning, because then it's obvious.
            skip_caret = (is_interactive && which_line == 1 && err.source_start == 0);
        }

        wcstring prefix;
        const wchar_t *filename = this->current_filename();
        if (filename) {
            if (which_line > 0) {
                prefix = format_string(_(L"%ls (line %lu): "),
                                       user_presentable_path(filename).c_str(), which_line);
            } else {
                prefix = format_string(_(L"%ls: "), user_presentable_path(filename).c_str());
            }
        } else {
            prefix = L"fish: ";
        }

        const wcstring description =
            err.describe_with_prefix(src, prefix, is_interactive, skip_caret);
        if (!description.empty()) {
            output.append(description);
            output.push_back(L'\n');
        }
        output.append(this->stack_trace());
    }
}

block_t::block_t(block_type_t t)
    : block_type(t),
      skip(false),
      tok_pos(),
      node_offset(NODE_OFFSET_INVALID),
      loop_status(LOOP_NORMAL),
      job(),
      src_filename(),
      src_lineno(),
      wants_pop_env(false),
      event_blocks() {}

block_t::~block_t() {}

wcstring block_t::description() const {
    wcstring result;
    switch (this->type()) {
        case WHILE: {
            result.append(L"while");
            break;
        }
        case FOR: {
            result.append(L"for");
            break;
        }
        case IF: {
            result.append(L"if");
            break;
        }
        case FUNCTION_CALL: {
            result.append(L"function_call");
            break;
        }
        case FUNCTION_CALL_NO_SHADOW: {
            result.append(L"function_call_no_shadow");
            break;
        }
        case SWITCH: {
            result.append(L"switch");
            break;
        }
        case SUBST: {
            result.append(L"substitution");
            break;
        }
        case TOP: {
            result.append(L"top");
            break;
        }
        case BEGIN: {
            result.append(L"begin");
            break;
        }
        case SOURCE: {
            result.append(L"source");
            break;
        }
        case EVENT: {
            result.append(L"event");
            break;
        }
        case BREAKPOINT: {
            result.append(L"breakpoint");
            break;
        }
    }

    if (this->src_lineno >= 0) {
        append_format(result, L" (line %d)", this->src_lineno);
    }
    if (this->src_filename != NULL) {
        append_format(result, L" (file %ls)", this->src_filename);
    }
    return result;
}

// Various block constructors.

if_block_t::if_block_t() : block_t(IF) {}

event_block_t::event_block_t(const event_t &evt) : block_t(EVENT), event(evt) {}

function_block_t::function_block_t(const process_t *p, const wcstring &n, bool shadows)
    : block_t(shadows ? FUNCTION_CALL : FUNCTION_CALL_NO_SHADOW), process(p), name(n) {}

source_block_t::source_block_t(const wchar_t *src) : block_t(SOURCE), source_file(src) {}

for_block_t::for_block_t() : block_t(FOR) {}

while_block_t::while_block_t() : block_t(WHILE) {}

switch_block_t::switch_block_t() : block_t(SWITCH) {}

scope_block_t::scope_block_t(block_type_t type) : block_t(type) {
    assert(type == BEGIN || type == TOP || type == SUBST);
}

breakpoint_block_t::breakpoint_block_t() : block_t(BREAKPOINT) {}
