2016-05-03 00:22:44 +00:00
|
|
|
// The fish parser. Contains functions for parsing and evaluating code.
|
2016-05-18 22:30:21 +00:00
|
|
|
#include "config.h" // IWYU pragma: keep
|
|
|
|
|
2005-09-20 13:26:39 +00:00
|
|
|
#include <stdio.h>
|
2019-03-12 21:06:01 +00:00
|
|
|
#include <cwchar>
|
2017-02-14 04:37:27 +00:00
|
|
|
|
2012-02-28 15:50:09 +00:00
|
|
|
#include <algorithm>
|
2016-04-21 06:00:54 +00:00
|
|
|
#include <memory>
|
2018-02-19 02:39:03 +00:00
|
|
|
#include <utility>
|
2005-09-20 13:26:39 +00:00
|
|
|
|
|
|
|
#include "common.h"
|
|
|
|
#include "env.h"
|
2005-10-05 22:37:08 +00:00
|
|
|
#include "event.h"
|
2016-05-03 00:22:44 +00:00
|
|
|
#include "expand.h"
|
|
|
|
#include "fallback.h" // IWYU pragma: keep
|
|
|
|
#include "function.h"
|
2006-02-02 15:23:56 +00:00
|
|
|
#include "intern.h"
|
2016-04-21 06:00:54 +00:00
|
|
|
#include "parse_constants.h"
|
2016-05-03 00:22:44 +00:00
|
|
|
#include "parse_execution.h"
|
|
|
|
#include "parse_util.h"
|
|
|
|
#include "parser.h"
|
|
|
|
#include "proc.h"
|
|
|
|
#include "reader.h"
|
|
|
|
#include "sanity.h"
|
2018-01-20 19:58:57 +00:00
|
|
|
#include "tnode.h"
|
2016-05-03 00:22:44 +00:00
|
|
|
#include "wutil.h" // IWYU pragma: keep
|
2016-04-21 06:00:54 +00:00
|
|
|
|
|
|
|
class io_chain_t;
|
2005-09-20 13:26:39 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
/// Error for evaluating in illegal scope.
|
|
|
|
#define INVALID_SCOPE_ERR_MSG _(L"Tried to evaluate commands using invalid block type '%ls'")
|
2012-11-19 00:30:30 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
/// While block description.
|
|
|
|
#define WHILE_BLOCK N_(L"'while' block")
|
|
|
|
|
|
|
|
/// For block description.
|
|
|
|
#define FOR_BLOCK N_(L"'for' block")
|
|
|
|
|
|
|
|
/// Breakpoint block.
|
2016-06-15 02:55:30 +00:00
|
|
|
#define BREAKPOINT_BLOCK N_(L"block created by breakpoint")
|
2012-11-19 00:30:30 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
/// 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.
|
2016-06-15 02:55:30 +00:00
|
|
|
#define SOURCE_BLOCK N_(L"block created by the . builtin")
|
2016-05-03 00:22:44 +00:00
|
|
|
|
|
|
|
/// Source block description.
|
|
|
|
#define EVENT_BLOCK N_(L"event handler block")
|
|
|
|
|
|
|
|
/// Unknown block description.
|
|
|
|
#define UNKNOWN_BLOCK N_(L"unknown/invalid block")
|
|
|
|
|
2016-05-29 05:28:26 +00:00
|
|
|
/// Data structure to describe a block type, like while blocks, command substitution blocks, etc.
|
2016-05-03 00:22:44 +00:00
|
|
|
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.
|
2012-11-19 00:30:30 +00:00
|
|
|
const wchar_t *desc;
|
2012-01-23 05:57:30 +00:00
|
|
|
};
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
/// 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},
|
2017-08-08 01:29:33 +00:00
|
|
|
{SOURCE, L"source", SOURCE_BLOCK},
|
2016-05-03 00:22:44 +00:00
|
|
|
{EVENT, 0, EVENT_BLOCK},
|
|
|
|
{BREAKPOINT, L"breakpoint", BREAKPOINT_BLOCK},
|
|
|
|
{(block_type_t)0, 0, 0}};
|
|
|
|
|
2013-12-16 23:33:20 +00:00
|
|
|
// Given a file path, return something nicer. Currently we just "unexpand" tildes.
|
2018-09-22 04:52:47 +00:00
|
|
|
wcstring parser_t::user_presentable_path(const wcstring &path) const {
|
|
|
|
return replace_home_directory_with_tilde(path, vars());
|
2013-12-16 23:33:20 +00:00
|
|
|
}
|
|
|
|
|
2019-05-20 16:27:46 +00:00
|
|
|
parser_t::parser_t(std::shared_ptr<env_stack_t> vars) : variables(std::move(vars)) {
|
|
|
|
assert(variables.get() && "Null variables in parser initializer");
|
|
|
|
}
|
|
|
|
|
|
|
|
parser_t::parser_t() : parser_t(env_stack_t::principal_ref()) {}
|
2013-12-16 23:33:20 +00:00
|
|
|
|
2017-01-21 22:53:10 +00:00
|
|
|
// Out of line destructor to enable forward declaration of parse_execution_context_t
|
2018-02-19 02:44:58 +00:00
|
|
|
parser_t::~parser_t() = default;
|
2017-01-21 22:15:03 +00:00
|
|
|
|
2019-02-24 20:12:24 +00:00
|
|
|
std::shared_ptr<parser_t> parser_t::principal{new parser_t()};
|
2012-06-04 21:20:01 +00:00
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
parser_t &parser_t::principal_parser() {
|
2012-01-23 05:40:08 +00:00
|
|
|
ASSERT_IS_MAIN_THREAD();
|
2019-02-24 20:12:24 +00:00
|
|
|
return *principal;
|
2014-02-20 18:57:13 +00:00
|
|
|
}
|
|
|
|
|
2018-02-19 02:33:04 +00:00
|
|
|
void parser_t::skip_all_blocks() {
|
2016-05-03 00:22:44 +00:00
|
|
|
// Tell all blocks to skip.
|
2017-01-21 22:15:03 +00:00
|
|
|
// This may be called from a signal handler!
|
2019-02-24 20:12:24 +00:00
|
|
|
principal->cancellation_requested = true;
|
2012-06-04 21:20:01 +00:00
|
|
|
}
|
|
|
|
|
2017-01-21 23:35:35 +00:00
|
|
|
// Given a new-allocated block, push it onto our block stack, acquiring ownership
|
2019-05-19 21:40:06 +00:00
|
|
|
block_t *parser_t::push_block(block_t &&block) {
|
2019-05-20 16:42:18 +00:00
|
|
|
block_t new_current{std::move(block)};
|
|
|
|
const enum block_type_t type = new_current.type();
|
|
|
|
new_current.src_lineno = parser_t::get_lineno();
|
2014-03-16 23:45:00 +00:00
|
|
|
|
|
|
|
const wchar_t *filename = parser_t::current_filename();
|
2016-05-03 00:22:44 +00:00
|
|
|
if (filename != NULL) {
|
2019-05-20 16:42:18 +00:00
|
|
|
new_current.src_filename = intern(filename);
|
2014-03-16 23:45:00 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2017-06-20 04:05:34 +00:00
|
|
|
// Types TOP and SUBST are not considered blocks for the purposes of `status is-block`.
|
2016-05-03 00:22:44 +00:00
|
|
|
if (type != TOP && type != SUBST) {
|
2019-05-13 01:02:57 +00:00
|
|
|
libdata().is_block = true;
|
2017-06-20 04:05:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (type == BREAKPOINT) {
|
2019-05-13 01:02:57 +00:00
|
|
|
libdata().is_breakpoint = true;
|
2014-10-01 03:58:45 +00:00
|
|
|
}
|
|
|
|
|
2019-05-20 16:42:18 +00:00
|
|
|
if (new_current.type() != TOP) {
|
2018-09-11 01:59:57 +00:00
|
|
|
vars().push(type == FUNCTION_CALL);
|
2019-05-20 16:42:18 +00:00
|
|
|
new_current.wants_pop_env = true;
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2019-05-19 21:40:06 +00:00
|
|
|
|
2019-05-20 16:42:18 +00:00
|
|
|
// Push it onto our stack and return a pointer to it.
|
|
|
|
// Note that deques do not move their contents so this is safe.
|
2019-05-19 21:40:06 +00:00
|
|
|
this->block_stack.push_back(std::move(new_current));
|
2019-05-20 16:42:18 +00:00
|
|
|
return &this->block_stack.back();
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
|
|
|
|
2017-01-21 23:35:35 +00:00
|
|
|
void parser_t::pop_block(const block_t *expected) {
|
|
|
|
assert(expected == this->current_block());
|
2016-05-03 00:22:44 +00:00
|
|
|
if (block_stack.empty()) {
|
|
|
|
debug(1, L"function %s called on empty block stack.", __func__);
|
2012-11-19 00:30:30 +00:00
|
|
|
bugreport();
|
|
|
|
return;
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2019-05-20 16:42:18 +00:00
|
|
|
// Acquire ownership out of the block stack.
|
|
|
|
block_t old = std::move(block_stack.back());
|
2013-12-21 01:41:21 +00:00
|
|
|
block_stack.pop_back();
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2019-05-20 16:42:18 +00:00
|
|
|
if (old.wants_pop_env) vars().pop();
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2017-06-20 04:05:34 +00:00
|
|
|
// Figure out if `status is-block` should consider us to be in a block now.
|
|
|
|
bool new_is_block = false;
|
2017-01-21 23:35:35 +00:00
|
|
|
for (const auto &b : block_stack) {
|
2019-05-20 16:42:18 +00:00
|
|
|
const enum block_type_t type = b.type();
|
2016-05-03 00:22:44 +00:00
|
|
|
if (type != TOP && type != SUBST) {
|
2017-06-20 04:05:34 +00:00
|
|
|
new_is_block = true;
|
2014-10-01 03:58:45 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-05-13 01:02:57 +00:00
|
|
|
libdata().is_block = new_is_block;
|
2017-06-20 04:05:34 +00:00
|
|
|
|
|
|
|
// Are we still in a breakpoint?
|
|
|
|
bool new_is_breakpoint = false;
|
|
|
|
for (const auto &b : block_stack) {
|
2019-05-20 16:42:18 +00:00
|
|
|
const enum block_type_t type = b.type();
|
2017-06-20 04:05:34 +00:00
|
|
|
if (type == BREAKPOINT) {
|
|
|
|
new_is_breakpoint = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-05-13 01:02:57 +00:00
|
|
|
libdata().is_breakpoint = new_is_breakpoint;
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
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) {
|
2012-11-19 00:30:30 +00:00
|
|
|
return _(block_lookup[i].desc);
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
return _(UNKNOWN_BLOCK);
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
|
|
|
|
2016-05-29 05:28:26 +00:00
|
|
|
#if 0
|
|
|
|
// TODO: Lint says this isn't used (which is true). Should this be removed?
|
2016-05-03 00:22:44 +00:00
|
|
|
wcstring parser_t::block_stack_description() const {
|
2014-03-16 23:45:00 +00:00
|
|
|
wcstring result;
|
|
|
|
size_t idx = this->block_count();
|
|
|
|
size_t spaces = 0;
|
2016-05-03 00:22:44 +00:00
|
|
|
while (idx--) {
|
|
|
|
if (spaces > 0) {
|
2014-03-16 23:45:00 +00:00
|
|
|
result.push_back(L'\n');
|
|
|
|
}
|
2016-05-03 00:22:44 +00:00
|
|
|
for (size_t j = 0; j < spaces; j++) {
|
2014-03-16 23:45:00 +00:00
|
|
|
result.push_back(L' ');
|
|
|
|
}
|
|
|
|
result.append(this->block_at_index(idx)->description());
|
|
|
|
spaces++;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2016-05-29 05:28:26 +00:00
|
|
|
#endif
|
2014-03-16 23:45:00 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
const block_t *parser_t::block_at_index(size_t idx) const {
|
|
|
|
// Zero corresponds to the last element in our vector.
|
2013-12-21 01:41:21 +00:00
|
|
|
size_t count = block_stack.size();
|
2019-05-20 16:42:18 +00:00
|
|
|
return idx < count ? &block_stack.at(count - idx - 1) : NULL;
|
2013-12-21 01:41:21 +00:00
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
block_t *parser_t::block_at_index(size_t idx) {
|
2013-12-21 01:41:21 +00:00
|
|
|
size_t count = block_stack.size();
|
2019-05-20 16:42:18 +00:00
|
|
|
return idx < count ? &block_stack.at(count - idx - 1) : NULL;
|
2013-12-21 01:41:21 +00:00
|
|
|
}
|
|
|
|
|
2019-05-20 16:42:18 +00:00
|
|
|
block_t *parser_t::current_block() { return block_stack.empty() ? NULL : &block_stack.back(); }
|
2013-12-21 01:41:21 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
void parser_t::forbid_function(const wcstring &function) { forbidden_function.push_back(function); }
|
2005-09-20 13:26:39 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
void parser_t::allow_function() { forbidden_function.pop_back(); }
|
2005-09-20 13:26:39 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
/// Print profiling information to the specified stream.
|
2017-01-21 22:33:17 +00:00
|
|
|
static void print_profile(const std::vector<std::unique_ptr<profile_item_t>> &items, FILE *out) {
|
2016-05-03 00:22:44 +00:00
|
|
|
for (size_t pos = 0; pos < items.size(); pos++) {
|
2013-01-13 23:49:32 +00:00
|
|
|
const profile_item_t *me, *prev;
|
|
|
|
size_t i;
|
|
|
|
int my_time;
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2017-01-21 22:33:17 +00:00
|
|
|
me = items.at(pos).get();
|
2016-10-30 21:49:22 +00:00
|
|
|
if (me->skipped) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
my_time = me->parse + me->exec;
|
|
|
|
for (i = pos + 1; i < items.size(); i++) {
|
2017-01-21 22:33:17 +00:00
|
|
|
prev = items.at(i).get();
|
2016-10-30 21:49:22 +00:00
|
|
|
if (prev->skipped) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (prev->level <= me->level) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (prev->level > me->level + 1) {
|
|
|
|
continue;
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-10-30 21:49:22 +00:00
|
|
|
my_time -= prev->parse + prev->exec;
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-10-30 21:49:22 +00:00
|
|
|
if (me->cmd.size() == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2019-03-12 21:06:01 +00:00
|
|
|
if (std::fwprintf(out, L"%d\t%d\t", my_time, me->parse + me->exec) < 0) {
|
2016-10-30 21:49:22 +00:00
|
|
|
wperror(L"fwprintf");
|
|
|
|
return;
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-10-30 21:49:22 +00:00
|
|
|
for (i = 0; i < me->level; i++) {
|
2019-03-12 21:06:01 +00:00
|
|
|
if (std::fwprintf(out, L"-") < 0) {
|
2016-10-30 21:49:22 +00:00
|
|
|
wperror(L"fwprintf");
|
|
|
|
return;
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2016-10-30 21:49:22 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2019-03-12 21:06:01 +00:00
|
|
|
if (std::fwprintf(out, L"> %ls\n", me->cmd.c_str()) < 0) {
|
2016-10-30 21:49:22 +00:00
|
|
|
wperror(L"fwprintf");
|
|
|
|
return;
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
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).
|
2014-02-09 22:04:43 +00:00
|
|
|
FILE *f = fopen(path, "w");
|
2016-05-03 00:22:44 +00:00
|
|
|
if (!f) {
|
|
|
|
debug(1, _(L"Could not write profiling information to file '%s'"), path);
|
|
|
|
} else {
|
2019-03-12 21:06:01 +00:00
|
|
|
if (std::fwprintf(f, _(L"Time\tSum\tCommand\n"), profile_items.size()) < 0) {
|
2014-02-09 22:04:43 +00:00
|
|
|
wperror(L"fwprintf");
|
2016-05-03 00:22:44 +00:00
|
|
|
} else {
|
2014-02-09 22:04:43 +00:00
|
|
|
print_profile(profile_items, f);
|
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
if (fclose(f)) {
|
2014-02-09 22:04:43 +00:00
|
|
|
wperror(L"fclose");
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
|
|
|
|
2019-05-05 02:16:26 +00:00
|
|
|
std::vector<completion_t> parser_t::expand_argument_list(const wcstring &arg_list_src,
|
|
|
|
expand_flags_t eflags,
|
|
|
|
const environment_t &vars,
|
|
|
|
const std::shared_ptr<parser_t> &parser) {
|
2016-05-03 00:22:44 +00:00
|
|
|
// Parse the string as an argument list.
|
2014-03-17 15:45:25 +00:00
|
|
|
parse_node_tree_t tree;
|
2016-05-03 00:22:44 +00:00
|
|
|
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.
|
2019-05-05 02:16:26 +00:00
|
|
|
return {};
|
2014-03-17 15:45:25 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2018-01-12 19:36:45 +00:00
|
|
|
// Get the root argument list and extract arguments from it.
|
2019-05-05 02:16:26 +00:00
|
|
|
std::vector<completion_t> result;
|
|
|
|
assert(!tree.empty());
|
2018-01-12 19:36:45 +00:00
|
|
|
tnode_t<grammar::freestanding_argument_list> arg_list(&tree, &tree.at(0));
|
|
|
|
while (auto arg = arg_list.next_in_list<grammar::argument>()) {
|
|
|
|
const wcstring arg_src = arg.get_source(arg_list_src);
|
2019-05-05 02:16:26 +00:00
|
|
|
if (expand_string(arg_src, &result, eflags, vars, parser, NULL /* errors */) ==
|
2019-04-22 22:06:52 +00:00
|
|
|
expand_result_t::error) {
|
2018-01-12 19:36:45 +00:00
|
|
|
break; // failed to expand a string
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
2019-05-05 02:16:26 +00:00
|
|
|
return result;
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
wcstring parser_t::stack_trace() const {
|
2015-09-21 18:24:49 +00:00
|
|
|
wcstring trace;
|
|
|
|
this->stack_trace_internal(0, &trace);
|
|
|
|
return trace;
|
|
|
|
}
|
|
|
|
|
2019-05-05 01:17:18 +00:00
|
|
|
std::shared_ptr<parser_t> parser_t::shared() { return shared_from_this(); }
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
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;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2013-12-21 01:41:21 +00:00
|
|
|
const block_t *b = this->block_at_index(block_idx);
|
2012-11-19 00:30:30 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
if (b->type() == EVENT) {
|
|
|
|
// This is an event handler.
|
2019-05-19 21:44:17 +00:00
|
|
|
assert(b->event && "Should have an event");
|
|
|
|
wcstring description = event_get_desc(*b->event);
|
2015-09-21 18:24:49 +00:00
|
|
|
append_format(*buff, _(L"in event handler: %ls\n"), description.c_str());
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
// 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.
|
2012-11-19 00:30:30 +00:00
|
|
|
return;
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW || b->type() == SOURCE ||
|
|
|
|
b->type() == SUBST) {
|
|
|
|
// These types of blocks should be printed.
|
|
|
|
switch (b->type()) {
|
|
|
|
case SOURCE: {
|
2019-05-19 21:44:17 +00:00
|
|
|
const wchar_t *source_dest = b->sourced_file;
|
2016-05-03 00:22:44 +00:00
|
|
|
append_format(*buff, _(L"from sourcing file %ls\n"),
|
|
|
|
user_presentable_path(source_dest).c_str());
|
2012-11-19 08:31:03 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case FUNCTION_CALL:
|
2016-05-03 00:22:44 +00:00
|
|
|
case FUNCTION_CALL_NO_SHADOW: {
|
2019-05-19 21:44:17 +00:00
|
|
|
append_format(*buff, _(L"in function '%ls'"), b->function_name.c_str());
|
2019-03-26 16:15:51 +00:00
|
|
|
// Print arguments on the same line.
|
2019-05-19 03:58:45 +00:00
|
|
|
wcstring args_str;
|
2019-05-19 21:44:17 +00:00
|
|
|
for (const wcstring &arg : b->function_args) {
|
2019-05-19 03:58:45 +00:00
|
|
|
if (!args_str.empty()) args_str.push_back(L' ');
|
|
|
|
// We can't quote the arguments because we print this in quotes.
|
|
|
|
// As a special-case, add the empty argument as "".
|
|
|
|
if (!arg.empty()) {
|
|
|
|
args_str.append(escape_string(arg, ESCAPE_ALL | ESCAPE_NO_QUOTED));
|
|
|
|
} else {
|
|
|
|
args_str.append(L"\"\"");
|
2019-03-26 16:15:51 +00:00
|
|
|
}
|
2019-05-19 03:58:45 +00:00
|
|
|
}
|
|
|
|
if (!args_str.empty()) {
|
2019-03-26 16:15:51 +00:00
|
|
|
// TODO: Escape these.
|
2019-05-19 03:58:45 +00:00
|
|
|
append_format(*buff, _(L" with arguments '%ls'"), args_str.c_str());
|
2019-03-26 16:15:51 +00:00
|
|
|
}
|
2019-05-19 03:58:45 +00:00
|
|
|
buff->push_back('\n');
|
2012-11-19 08:31:03 +00:00
|
|
|
break;
|
|
|
|
}
|
2016-05-03 00:22:44 +00:00
|
|
|
case SUBST: {
|
2015-09-21 18:24:49 +00:00
|
|
|
append_format(*buff, _(L"in command substitution\n"));
|
2012-11-19 08:31:03 +00:00
|
|
|
break;
|
|
|
|
}
|
2016-05-03 00:22:44 +00:00
|
|
|
default: {
|
|
|
|
break; // can't get here
|
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2019-03-26 16:10:56 +00:00
|
|
|
// Print where the function is called.
|
2012-11-19 00:30:30 +00:00
|
|
|
const wchar_t *file = b->src_filename;
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
if (file) {
|
|
|
|
append_format(*buff, _(L"\tcalled on line %d of file %ls\n"), b->src_lineno,
|
2013-12-16 23:33:20 +00:00
|
|
|
user_presentable_path(file).c_str());
|
2018-09-09 08:36:21 +00:00
|
|
|
} else if (is_within_fish_initialization()) {
|
2015-09-21 18:24:49 +00:00
|
|
|
append_format(*buff, _(L"\tcalled during startup\n"));
|
2016-05-03 00:22:44 +00:00
|
|
|
} else {
|
2019-03-26 16:10:56 +00:00
|
|
|
// This one is way too noisy
|
|
|
|
// append_format(*buff, _(L"\tcalled on standard input\n"));
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
// Recursively print the next block.
|
2015-09-21 18:24:49 +00:00
|
|
|
parser_t::stack_trace_internal(block_idx + 1, buff);
|
2006-01-26 14:48:10 +00:00
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
/// Returns the name of the currently evaluated function if we are currently evaluating a function,
|
2017-06-24 05:14:21 +00:00
|
|
|
/// 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 {
|
2016-05-03 00:22:44 +00:00
|
|
|
// PCA: Have to make this a string somehow.
|
2012-02-08 01:06:45 +00:00
|
|
|
ASSERT_IS_MAIN_THREAD();
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2013-12-21 01:41:21 +00:00
|
|
|
const wchar_t *result = NULL;
|
2017-06-24 05:14:21 +00:00
|
|
|
for (size_t block_idx = idx; block_idx < this->block_count(); block_idx++) {
|
2013-12-21 01:41:21 +00:00
|
|
|
const block_t *b = this->block_at_index(block_idx);
|
2016-05-03 00:22:44 +00:00
|
|
|
if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW) {
|
2019-05-19 21:44:17 +00:00
|
|
|
result = b->function_name.c_str();
|
2013-12-21 01:41:21 +00:00
|
|
|
break;
|
2016-05-03 00:22:44 +00:00
|
|
|
} else if (b->type() == SOURCE) {
|
|
|
|
// If a function sources a file, obviously that function's offset doesn't contribute.
|
2014-03-16 23:45:00 +00:00
|
|
|
break;
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
2013-12-21 01:41:21 +00:00
|
|
|
return result;
|
2006-01-26 14:48:10 +00:00
|
|
|
}
|
|
|
|
|
2017-06-24 05:14:21 +00:00
|
|
|
/// 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) {
|
2019-05-20 16:42:18 +00:00
|
|
|
const enum block_type_t type = b.type();
|
2017-06-24 05:14:21 +00:00
|
|
|
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) {
|
2019-05-20 16:42:18 +00:00
|
|
|
const enum block_type_t type = b.type();
|
2017-06-24 05:14:21 +00:00
|
|
|
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
|
|
|
|
}
|
2017-04-12 22:34:25 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
int parser_t::get_lineno() const {
|
2014-03-02 21:11:17 +00:00
|
|
|
int lineno = -1;
|
2018-02-12 06:00:17 +00:00
|
|
|
if (execution_context) {
|
|
|
|
lineno = execution_context->get_current_line_number();
|
2014-03-16 23:45:00 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
// If we are executing a function, we have to add in its offset.
|
2014-03-16 23:45:00 +00:00
|
|
|
const wchar_t *function_name = is_function();
|
2016-05-03 00:22:44 +00:00
|
|
|
if (function_name != NULL) {
|
2018-02-10 05:53:06 +00:00
|
|
|
lineno += function_get_definition_lineno(function_name);
|
2014-03-16 23:45:00 +00:00
|
|
|
}
|
2014-03-02 00:04:13 +00:00
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
return lineno;
|
2006-01-26 14:48:10 +00:00
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
const wchar_t *parser_t::current_filename() const {
|
2012-02-02 23:05:08 +00:00
|
|
|
ASSERT_IS_MAIN_THREAD();
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
for (size_t i = 0; i < this->block_count(); i++) {
|
2013-12-21 01:41:21 +00:00
|
|
|
const block_t *b = this->block_at_index(i);
|
2016-05-03 00:22:44 +00:00
|
|
|
if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW) {
|
2019-05-19 21:44:17 +00:00
|
|
|
return function_get_definition_file(b->function_name);
|
2016-05-03 00:22:44 +00:00
|
|
|
} else if (b->type() == SOURCE) {
|
2019-05-19 21:44:17 +00:00
|
|
|
return b->sourced_file;
|
2014-03-16 23:45:00 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-05-22 20:34:03 +00:00
|
|
|
// Fall back to the file being sourced.
|
|
|
|
return libdata().current_filename;
|
2006-01-26 14:48:10 +00:00
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
wcstring parser_t::current_line() {
|
2018-02-12 06:00:17 +00:00
|
|
|
if (!execution_context) {
|
2014-03-17 05:06:32 +00:00
|
|
|
return wcstring();
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
2018-02-12 06:00:17 +00:00
|
|
|
int source_offset = execution_context->get_current_source_offset();
|
2016-05-03 00:22:44 +00:00
|
|
|
if (source_offset < 0) {
|
2014-03-17 05:06:32 +00:00
|
|
|
return wcstring();
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2014-03-17 05:06:32 +00:00
|
|
|
const int lineno = this->get_lineno();
|
|
|
|
const wchar_t *file = this->current_filename();
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2014-03-17 05:06:32 +00:00
|
|
|
wcstring prefix;
|
2006-01-30 19:53:10 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
// If we are not going to print a stack trace, at least print the line number and filename.
|
2016-05-15 03:35:54 +00:00
|
|
|
if (!shell_is_interactive() || is_function()) {
|
2016-05-03 00:22:44 +00:00
|
|
|
if (file) {
|
|
|
|
append_format(prefix, _(L"%ls (line %d): "), user_presentable_path(file).c_str(),
|
|
|
|
lineno);
|
2018-09-09 08:36:21 +00:00
|
|
|
} else if (is_within_fish_initialization()) {
|
2017-04-25 04:05:51 +00:00
|
|
|
append_format(prefix, L"%ls (line %d): ", _(L"Startup"), lineno);
|
2016-05-03 00:22:44 +00:00
|
|
|
} else {
|
2017-04-25 04:05:51 +00:00
|
|
|
append_format(prefix, L"%ls (line %d): ", _(L"Standard input"), lineno);
|
2014-02-20 18:57:13 +00:00
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
|
|
|
|
2016-05-15 03:35:54 +00:00
|
|
|
bool is_interactive = shell_is_interactive();
|
2016-05-03 00:22:44 +00:00
|
|
|
bool skip_caret = is_interactive && !is_function();
|
2014-03-17 05:06:32 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
// Use an error with empty text.
|
2014-03-17 05:06:32 +00:00
|
|
|
assert(source_offset >= 0);
|
|
|
|
parse_error_t empty_error = {};
|
|
|
|
empty_error.source_start = source_offset;
|
|
|
|
|
2018-02-12 06:00:17 +00:00
|
|
|
wcstring line_info = empty_error.describe_with_prefix(execution_context->get_source(), prefix,
|
|
|
|
is_interactive, skip_caret);
|
2016-05-03 00:22:44 +00:00
|
|
|
if (!line_info.empty()) {
|
2014-03-17 05:06:32 +00:00
|
|
|
line_info.push_back(L'\n');
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
|
|
|
|
2015-09-21 18:24:49 +00:00
|
|
|
line_info.append(this->stack_trace());
|
2014-03-17 05:06:32 +00:00
|
|
|
return line_info;
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
|
|
|
|
2017-01-26 22:47:32 +00:00
|
|
|
void parser_t::job_add(shared_ptr<job_t> job) {
|
2013-12-27 09:38:43 +00:00
|
|
|
assert(job != NULL);
|
2017-01-27 04:00:43 +00:00
|
|
|
assert(!job->processes.empty());
|
2019-05-05 05:12:31 +00:00
|
|
|
job_list.push_front(std::move(job));
|
2013-12-27 09:38:43 +00:00
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
void parser_t::job_promote(job_t *job) {
|
2017-01-26 22:47:32 +00:00
|
|
|
job_list_t::iterator loc;
|
2019-05-05 05:12:31 +00:00
|
|
|
for (loc = job_list.begin(); loc != job_list.end(); ++loc) {
|
2017-01-26 22:47:32 +00:00
|
|
|
if (loc->get() == job) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-05-05 05:12:31 +00:00
|
|
|
assert(loc != job_list.end());
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
// Move the job to the beginning.
|
2019-05-05 05:12:31 +00:00
|
|
|
std::rotate(job_list.begin(), loc, job_list.end());
|
2012-02-28 02:43:24 +00:00
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
job_t *parser_t::job_get(job_id_t id) {
|
2019-05-05 05:12:31 +00:00
|
|
|
for (const auto &job : job_list) {
|
2017-01-26 22:47:32 +00:00
|
|
|
if (id <= 0 || job->job_id == id) return job.get();
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
|
|
|
return NULL;
|
2012-02-28 02:43:24 +00:00
|
|
|
}
|
|
|
|
|
2018-08-09 23:46:11 +00:00
|
|
|
job_t *parser_t::job_get_from_pid(pid_t pid) const {
|
2017-04-23 14:04:37 +00:00
|
|
|
pid_t pgid = getpgid(pid);
|
|
|
|
|
|
|
|
if (pgid == -1) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-03-21 03:37:26 +00:00
|
|
|
for (const auto &job : jobs()) {
|
2017-04-23 14:04:37 +00:00
|
|
|
if (job->pgid == pgid) {
|
|
|
|
for (const process_ptr_t &p : job->processes) {
|
|
|
|
if (p->pid == pid) {
|
2018-12-31 03:25:16 +00:00
|
|
|
return job.get();
|
2017-04-23 14:04:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
|
|
|
return 0;
|
2012-02-28 02:43:24 +00:00
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
profile_item_t *parser_t::create_profile_item() {
|
2017-01-21 22:33:17 +00:00
|
|
|
profile_item_t *result = nullptr;
|
2016-05-03 00:22:44 +00:00
|
|
|
if (g_profiling_active) {
|
2017-01-21 23:02:41 +00:00
|
|
|
profile_items.push_back(make_unique<profile_item_t>());
|
2017-01-21 22:33:17 +00:00
|
|
|
result = profile_items.back().get();
|
2014-02-09 22:04:43 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-04-02 03:19:28 +00:00
|
|
|
int parser_t::eval(wcstring cmd, const io_chain_t &io, enum block_type_t block_type) {
|
2016-05-03 00:22:44 +00:00
|
|
|
// Parse the source into a tree, if we can.
|
2014-10-13 03:09:45 +00:00
|
|
|
parse_error_list_t error_list;
|
2017-12-22 22:40:15 +00:00
|
|
|
parsed_source_ref_t ps = parse_source(cmd, parse_flag_none, &error_list);
|
|
|
|
if (!ps) {
|
2016-05-03 00:22:44 +00:00
|
|
|
// Get a backtrace. This includes the message.
|
2016-02-28 02:37:59 +00:00
|
|
|
wcstring backtrace_and_desc;
|
2017-06-18 05:36:56 +00:00
|
|
|
this->get_backtrace(cmd, error_list, backtrace_and_desc);
|
2016-05-03 00:22:44 +00:00
|
|
|
|
|
|
|
// Print it.
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr, L"%ls\n", backtrace_and_desc.c_str());
|
2014-03-01 01:54:05 +00:00
|
|
|
return 1;
|
|
|
|
}
|
2019-04-13 21:57:00 +00:00
|
|
|
return this->eval(ps, io, block_type);
|
2016-02-28 08:33:11 +00:00
|
|
|
}
|
2012-11-19 08:31:03 +00:00
|
|
|
|
2019-04-13 21:57:00 +00:00
|
|
|
int parser_t::eval(parsed_source_ref_t ps, const io_chain_t &io, enum block_type_t block_type) {
|
2016-02-28 08:33:11 +00:00
|
|
|
assert(block_type == TOP || block_type == SUBST);
|
2018-02-12 07:13:06 +00:00
|
|
|
if (!ps->tree.empty()) {
|
|
|
|
// Execute the first node.
|
|
|
|
tnode_t<grammar::job_list> start{&ps->tree, &ps->tree.front()};
|
2019-04-13 21:57:00 +00:00
|
|
|
return this->eval_node(ps, start, io, block_type, nullptr /* parent */);
|
2016-02-28 08:33:11 +00:00
|
|
|
}
|
2019-04-13 21:57:00 +00:00
|
|
|
return 0;
|
2014-03-01 01:54:05 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2018-02-11 03:16:35 +00:00
|
|
|
template <typename T>
|
2018-02-12 03:34:12 +00:00
|
|
|
int parser_t::eval_node(parsed_source_ref_t ps, tnode_t<T> node, const io_chain_t &io,
|
2018-11-04 07:58:44 +00:00
|
|
|
block_type_t block_type, std::shared_ptr<job_t> parent_job) {
|
2018-02-11 03:16:35 +00:00
|
|
|
static_assert(
|
|
|
|
std::is_same<T, grammar::statement>::value || std::is_same<T, grammar::job_list>::value,
|
|
|
|
"Unexpected node type");
|
2016-05-03 00:22:44 +00:00
|
|
|
// 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()) {
|
2014-03-01 01:54:05 +00:00
|
|
|
return 1;
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2016-05-04 22:19:47 +00:00
|
|
|
this->cancellation_requested = false;
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
// 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));
|
2014-03-01 01:54:05 +00:00
|
|
|
bugreport();
|
|
|
|
return 1;
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2019-04-30 03:58:58 +00:00
|
|
|
job_reap(*this, false); // not sure why we reap jobs here
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2017-01-21 22:53:10 +00:00
|
|
|
// Start it up
|
2019-05-19 21:40:06 +00:00
|
|
|
block_t *scope_block = this->push_block(block_t::scope_block(block_type));
|
2018-02-12 03:34:12 +00:00
|
|
|
|
2018-02-12 06:00:17 +00:00
|
|
|
// Create and set a new execution context.
|
|
|
|
using exc_ctx_ref_t = std::unique_ptr<parse_execution_context_t>;
|
|
|
|
scoped_push<exc_ctx_ref_t> exc(&execution_context,
|
2018-11-04 07:58:44 +00:00
|
|
|
make_unique<parse_execution_context_t>(ps, this, parent_job));
|
2018-02-12 06:00:17 +00:00
|
|
|
int result = execution_context->eval_node(node, scope_block, io);
|
|
|
|
exc.restore();
|
2017-01-21 23:35:35 +00:00
|
|
|
this->pop_block(scope_block);
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2019-04-30 03:58:58 +00:00
|
|
|
job_reap(*this, false); // reap again
|
2014-03-01 01:54:05 +00:00
|
|
|
return result;
|
2006-05-21 19:25:24 +00:00
|
|
|
}
|
|
|
|
|
2018-02-11 03:16:35 +00:00
|
|
|
// Explicit instantiations. TODO: use overloads instead?
|
2018-02-12 03:34:12 +00:00
|
|
|
template int parser_t::eval_node(parsed_source_ref_t, tnode_t<grammar::statement>,
|
2018-11-04 07:58:44 +00:00
|
|
|
const io_chain_t &, enum block_type_t,
|
|
|
|
std::shared_ptr<job_t> parent_job);
|
2018-02-12 03:34:12 +00:00
|
|
|
template int parser_t::eval_node(parsed_source_ref_t, tnode_t<grammar::job_list>,
|
2018-11-04 07:58:44 +00:00
|
|
|
const io_chain_t &, enum block_type_t,
|
|
|
|
std::shared_ptr<job_t> parent_job);
|
2018-02-11 03:16:35 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
bool parser_t::detect_errors_in_argument_list(const wcstring &arg_list_src, wcstring *out,
|
2018-08-09 23:46:11 +00:00
|
|
|
const wchar_t *prefix) const {
|
2014-03-18 21:14:32 +00:00
|
|
|
bool errored = false;
|
|
|
|
parse_error_list_t errors;
|
2013-02-22 14:34:30 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
// Use empty string for the prefix if it's NULL.
|
2016-10-21 01:53:31 +00:00
|
|
|
if (!prefix) prefix = L""; //!OCLINT(parameter reassignment)
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
// Parse the string as an argument list.
|
2014-03-18 21:14:32 +00:00
|
|
|
parse_node_tree_t tree;
|
2016-05-03 00:22:44 +00:00
|
|
|
if (!parse_tree_from_string(arg_list_src, parse_flag_none, &tree, &errors,
|
|
|
|
symbol_freestanding_argument_list)) {
|
|
|
|
// Failed to parse.
|
2014-03-18 21:14:32 +00:00
|
|
|
errored = true;
|
|
|
|
}
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
if (!errored) {
|
2018-01-12 19:36:45 +00:00
|
|
|
// Get the root argument list and extract arguments from it.
|
2016-10-23 20:58:12 +00:00
|
|
|
assert(!tree.empty()); //!OCLINT(multiple unary operator)
|
2018-01-12 19:36:45 +00:00
|
|
|
tnode_t<grammar::freestanding_argument_list> arg_list(&tree, &tree.at(0));
|
|
|
|
while (auto arg = arg_list.next_in_list<grammar::argument>()) {
|
|
|
|
const wcstring arg_src = arg.get_source(arg_list_src);
|
|
|
|
if (parse_util_detect_errors_in_argument(arg, arg_src, &errors)) {
|
|
|
|
errored = true;
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
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 */));
|
2014-03-18 21:14:32 +00:00
|
|
|
}
|
|
|
|
return errored;
|
2006-05-21 19:25:24 +00:00
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
void parser_t::get_backtrace(const wcstring &src, const parse_error_list_t &errors,
|
2017-06-18 05:36:56 +00:00
|
|
|
wcstring &output) const {
|
2016-05-03 00:22:44 +00:00
|
|
|
if (!errors.empty()) {
|
2013-12-27 09:38:43 +00:00
|
|
|
const parse_error_t &err = errors.at(0);
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2016-05-15 03:35:54 +00:00
|
|
|
const bool is_interactive = shell_is_interactive();
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
// 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.
|
2014-03-22 00:13:33 +00:00
|
|
|
size_t which_line = 0;
|
|
|
|
bool skip_caret = true;
|
2016-05-03 00:22:44 +00:00
|
|
|
if (err.source_start != SOURCE_LOCATION_UNKNOWN && err.source_start <= src.size()) {
|
|
|
|
// Determine which line we're on.
|
2014-03-22 00:13:33 +00:00
|
|
|
which_line = 1 + std::count(src.begin(), src.begin() + err.source_start, L'\n');
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
// 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.
|
2014-03-22 00:13:33 +00:00
|
|
|
skip_caret = (is_interactive && which_line == 1 && err.source_start == 0);
|
|
|
|
}
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2014-03-22 00:13:33 +00:00
|
|
|
wcstring prefix;
|
2013-12-16 00:05:37 +00:00
|
|
|
const wchar_t *filename = this->current_filename();
|
2016-05-03 00:22:44 +00:00
|
|
|
if (filename) {
|
|
|
|
if (which_line > 0) {
|
|
|
|
prefix = format_string(_(L"%ls (line %lu): "),
|
|
|
|
user_presentable_path(filename).c_str(), which_line);
|
|
|
|
} else {
|
2014-03-22 00:13:33 +00:00
|
|
|
prefix = format_string(_(L"%ls: "), user_presentable_path(filename).c_str());
|
|
|
|
}
|
2016-05-03 00:22:44 +00:00
|
|
|
} else {
|
2018-11-28 14:08:24 +00:00
|
|
|
prefix = L"fish: ";
|
2013-12-16 00:05:37 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
const wcstring description =
|
|
|
|
err.describe_with_prefix(src, prefix, is_interactive, skip_caret);
|
|
|
|
if (!description.empty()) {
|
2017-06-18 05:36:56 +00:00
|
|
|
output.append(description);
|
|
|
|
output.push_back(L'\n');
|
2014-03-15 20:07:19 +00:00
|
|
|
}
|
2017-06-18 05:36:56 +00:00
|
|
|
output.append(this->stack_trace());
|
2013-12-12 02:34:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-12 04:10:57 +00:00
|
|
|
block_t::block_t(block_type_t t) : block_type(t) {}
|
2012-08-27 05:42:29 +00:00
|
|
|
|
2018-02-19 02:44:58 +00:00
|
|
|
block_t::~block_t() = default;
|
2012-08-27 06:16:20 +00:00
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
wcstring block_t::description() const {
|
2014-03-16 23:45:00 +00:00
|
|
|
wcstring result;
|
2016-05-03 00:22:44 +00:00
|
|
|
switch (this->type()) {
|
|
|
|
case WHILE: {
|
2014-03-16 23:45:00 +00:00
|
|
|
result.append(L"while");
|
|
|
|
break;
|
2016-05-03 00:22:44 +00:00
|
|
|
}
|
|
|
|
case FOR: {
|
2014-03-16 23:45:00 +00:00
|
|
|
result.append(L"for");
|
|
|
|
break;
|
2016-05-03 00:22:44 +00:00
|
|
|
}
|
|
|
|
case IF: {
|
2014-03-16 23:45:00 +00:00
|
|
|
result.append(L"if");
|
|
|
|
break;
|
2016-05-03 00:22:44 +00:00
|
|
|
}
|
|
|
|
case FUNCTION_CALL: {
|
2014-03-16 23:45:00 +00:00
|
|
|
result.append(L"function_call");
|
|
|
|
break;
|
2016-05-03 00:22:44 +00:00
|
|
|
}
|
|
|
|
case FUNCTION_CALL_NO_SHADOW: {
|
2014-03-16 23:45:00 +00:00
|
|
|
result.append(L"function_call_no_shadow");
|
|
|
|
break;
|
2016-05-03 00:22:44 +00:00
|
|
|
}
|
|
|
|
case SWITCH: {
|
2014-03-16 23:45:00 +00:00
|
|
|
result.append(L"switch");
|
|
|
|
break;
|
2016-05-03 00:22:44 +00:00
|
|
|
}
|
|
|
|
case SUBST: {
|
2014-03-16 23:45:00 +00:00
|
|
|
result.append(L"substitution");
|
|
|
|
break;
|
2016-05-03 00:22:44 +00:00
|
|
|
}
|
|
|
|
case TOP: {
|
2014-03-16 23:45:00 +00:00
|
|
|
result.append(L"top");
|
|
|
|
break;
|
2016-05-03 00:22:44 +00:00
|
|
|
}
|
|
|
|
case BEGIN: {
|
2014-03-16 23:45:00 +00:00
|
|
|
result.append(L"begin");
|
|
|
|
break;
|
2016-05-03 00:22:44 +00:00
|
|
|
}
|
|
|
|
case SOURCE: {
|
2014-03-16 23:45:00 +00:00
|
|
|
result.append(L"source");
|
|
|
|
break;
|
2016-05-03 00:22:44 +00:00
|
|
|
}
|
|
|
|
case EVENT: {
|
2014-03-16 23:45:00 +00:00
|
|
|
result.append(L"event");
|
|
|
|
break;
|
2016-05-03 00:22:44 +00:00
|
|
|
}
|
|
|
|
case BREAKPOINT: {
|
2014-03-16 23:45:00 +00:00
|
|
|
result.append(L"breakpoint");
|
|
|
|
break;
|
2016-05-03 00:22:44 +00:00
|
|
|
}
|
2014-03-16 23:45:00 +00:00
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
if (this->src_lineno >= 0) {
|
2014-03-16 23:45:00 +00:00
|
|
|
append_format(result, L" (line %d)", this->src_lineno);
|
|
|
|
}
|
2016-05-03 00:22:44 +00:00
|
|
|
if (this->src_filename != NULL) {
|
2014-03-16 23:45:00 +00:00
|
|
|
append_format(result, L" (file %ls)", this->src_filename);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-05-03 00:22:44 +00:00
|
|
|
// Various block constructors.
|
2012-08-27 06:16:20 +00:00
|
|
|
|
2019-05-19 21:40:06 +00:00
|
|
|
block_t block_t::if_block() { return block_t(IF); }
|
|
|
|
|
|
|
|
block_t block_t::event_block(event_t evt) {
|
|
|
|
block_t b{EVENT};
|
|
|
|
b.event = std::move(evt);
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
block_t block_t::function_block(wcstring name, wcstring_list_t args, bool shadows) {
|
|
|
|
block_t b{shadows ? FUNCTION_CALL : FUNCTION_CALL_NO_SHADOW};
|
|
|
|
b.function_name = std::move(name);
|
|
|
|
b.function_args = std::move(args);
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
block_t block_t::source_block(const wchar_t *src) {
|
|
|
|
block_t b{SOURCE};
|
|
|
|
b.sourced_file = src;
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
block_t block_t::for_block() { return block_t{FOR}; }
|
|
|
|
block_t block_t::while_block() { return block_t{WHILE}; }
|
|
|
|
block_t block_t::switch_block() { return block_t{SWITCH}; }
|
|
|
|
block_t block_t::scope_block(block_type_t type) {
|
|
|
|
assert((type == BEGIN || type == TOP || type == SUBST) && "Invalid scope type");
|
|
|
|
return block_t(type);
|
|
|
|
}
|
|
|
|
block_t block_t::breakpoint_block() { return block_t(BREAKPOINT); }
|