Serenity Operating System
1/*
2 * Copyright (c) 2021, the SerenityOS developers.
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "Formatter.h"
8#include "Shell.h"
9#include <LibRegex/Regex.h>
10
11namespace Shell {
12
13ErrorOr<RefPtr<AST::Node>> Shell::immediate_length_impl(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments, bool across)
14{
15 auto name = across ? "length_across" : "length";
16 if (arguments.size() < 1 || arguments.size() > 2) {
17 raise_error(ShellError::EvaluatedSyntaxError, DeprecatedString::formatted("Expected one or two arguments to `{}'", name), invoking_node.position());
18 return nullptr;
19 }
20
21 enum {
22 Infer,
23 String,
24 List,
25 } mode { Infer };
26
27 bool is_inferred = false;
28
29 const AST::Node* expr_node;
30 if (arguments.size() == 2) {
31 // length string <expr>
32 // length list <expr>
33
34 auto& mode_arg = arguments.first();
35 if (!mode_arg->is_bareword()) {
36 raise_error(ShellError::EvaluatedSyntaxError, DeprecatedString::formatted("Expected a bareword (either 'string' or 'list') in the two-argument form of the `{}' immediate", name), mode_arg->position());
37 return nullptr;
38 }
39
40 auto const& mode_name = static_cast<const AST::BarewordLiteral&>(*mode_arg).text();
41 if (mode_name == "list") {
42 mode = List;
43 } else if (mode_name == "string") {
44 mode = String;
45 } else if (mode_name == "infer") {
46 mode = Infer;
47 } else {
48 raise_error(ShellError::EvaluatedSyntaxError, DeprecatedString::formatted("Expected either 'string' or 'list' (and not {}) in the two-argument form of the `{}' immediate", mode_name, name), mode_arg->position());
49 return nullptr;
50 }
51
52 expr_node = arguments[1];
53 } else {
54 expr_node = arguments[0];
55 }
56
57 if (mode == Infer) {
58 is_inferred = true;
59 if (expr_node->is_list())
60 mode = List;
61 else if (expr_node->is_simple_variable()) // "Look inside" variables
62 mode = TRY(TRY(const_cast<AST::Node*>(expr_node)->run(this))->resolve_without_cast(this))->is_list_without_resolution() ? List : String;
63 else if (is<AST::ImmediateExpression>(expr_node))
64 mode = List;
65 else
66 mode = String;
67 }
68
69 auto value_with_number = [&](auto number) -> ErrorOr<NonnullRefPtr<AST::Node>> {
70 return AST::make_ref_counted<AST::BarewordLiteral>(invoking_node.position(), TRY(String::number(number)));
71 };
72
73 auto do_across = [&](StringView mode_name, auto& values) -> ErrorOr<RefPtr<AST::Node>> {
74 if (is_inferred)
75 mode_name = "infer"sv;
76 // Translate to a list of applications of `length <mode_name>`
77 Vector<NonnullRefPtr<AST::Node>> resulting_nodes;
78 resulting_nodes.ensure_capacity(values.size());
79 for (auto& entry : values) {
80 // ImmediateExpression(length <mode_name> <entry>)
81 resulting_nodes.unchecked_append(AST::make_ref_counted<AST::ImmediateExpression>(
82 expr_node->position(),
83 AST::NameWithPosition { TRY("length"_string), invoking_node.function_position() },
84 Vector<NonnullRefPtr<AST::Node>> { Vector<NonnullRefPtr<AST::Node>> {
85 static_cast<NonnullRefPtr<AST::Node>>(AST::make_ref_counted<AST::BarewordLiteral>(expr_node->position(), TRY(String::from_utf8(mode_name)))),
86 AST::make_ref_counted<AST::SyntheticNode>(expr_node->position(), NonnullRefPtr<AST::Value>(entry)),
87 } },
88 expr_node->position()));
89 }
90
91 return AST::make_ref_counted<AST::ListConcatenate>(invoking_node.position(), move(resulting_nodes));
92 };
93
94 switch (mode) {
95 default:
96 case Infer:
97 VERIFY_NOT_REACHED();
98 case List: {
99 auto value = TRY(const_cast<AST::Node*>(expr_node)->run(this));
100 if (!value)
101 return value_with_number(0);
102
103 value = TRY(value->resolve_without_cast(this));
104
105 if (auto list = dynamic_cast<AST::ListValue*>(value.ptr())) {
106 if (across)
107 return do_across("list"sv, list->values());
108
109 return value_with_number(list->values().size());
110 }
111
112 auto list = TRY(value->resolve_as_list(this));
113 if (!across)
114 return value_with_number(list.size());
115
116 dbgln("List has {} entries", list.size());
117 auto values = AST::make_ref_counted<AST::ListValue>(move(list));
118 return do_across("list"sv, values->values());
119 }
120 case String: {
121 // 'across' will only accept lists, and '!across' will only accept non-lists here.
122 if (expr_node->is_list()) {
123 if (!across) {
124 raise_no_list_allowed:;
125 Formatter formatter { *expr_node };
126
127 if (is_inferred) {
128 raise_error(ShellError::EvaluatedSyntaxError,
129 DeprecatedString::formatted("Could not infer expression type, please explicitly use `{0} string' or `{0} list'", name),
130 invoking_node.position());
131 return nullptr;
132 }
133
134 auto source = formatter.format();
135 raise_error(ShellError::EvaluatedSyntaxError,
136 source.is_empty()
137 ? "Invalid application of `length' to a list"
138 : DeprecatedString::formatted("Invalid application of `length' to a list\nperhaps you meant `{1}length \"{0}\"{2}' or `{1}length_across {0}{2}'?", source, "\x1b[32m", "\x1b[0m"),
139 expr_node->position());
140 return nullptr;
141 }
142 }
143
144 auto value = TRY(const_cast<AST::Node*>(expr_node)->run(this));
145 if (!value)
146 return value_with_number(0);
147
148 value = TRY(value->resolve_without_cast(*this));
149
150 if (auto list = dynamic_cast<AST::ListValue*>(value.ptr())) {
151 if (!across)
152 goto raise_no_list_allowed;
153
154 return do_across("string"sv, list->values());
155 }
156
157 if (across && !value->is_list()) {
158 Formatter formatter { *expr_node };
159
160 auto source = formatter.format();
161 raise_error(ShellError::EvaluatedSyntaxError,
162 DeprecatedString::formatted("Invalid application of `length_across' to a non-list\nperhaps you meant `{1}length {0}{2}'?", source, "\x1b[32m", "\x1b[0m"),
163 expr_node->position());
164 return nullptr;
165 }
166
167 // Evaluate the nodes and substitute with the lengths.
168 auto list = TRY(value->resolve_as_list(this));
169
170 if (!expr_node->is_list()) {
171 if (list.size() == 1) {
172 if (across)
173 goto raise_no_list_allowed;
174
175 // This is the normal case, the expression is a normal non-list expression.
176 return value_with_number(list.first().bytes_as_string_view().length());
177 }
178
179 // This can be hit by asking for the length of a command list (e.g. `(>/dev/null)`)
180 // raise an error about misuse of command lists for now.
181 // FIXME: What's the length of `(>/dev/null)` supposed to be?
182 raise_error(ShellError::EvaluatedSyntaxError, "Length of meta value (or command list) requested, this is currently not supported.", expr_node->position());
183 return nullptr;
184 }
185
186 auto values = AST::make_ref_counted<AST::ListValue>(move(list));
187 return do_across("string"sv, values->values());
188 }
189 }
190}
191
192ErrorOr<RefPtr<AST::Node>> Shell::immediate_length(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
193{
194 return immediate_length_impl(invoking_node, arguments, false);
195}
196
197ErrorOr<RefPtr<AST::Node>> Shell::immediate_length_across(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
198{
199 return immediate_length_impl(invoking_node, arguments, true);
200}
201
202ErrorOr<RefPtr<AST::Node>> Shell::immediate_regex_replace(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
203{
204 if (arguments.size() != 3) {
205 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 3 arguments to regex_replace", invoking_node.position());
206 return nullptr;
207 }
208
209 auto pattern = TRY(const_cast<AST::Node&>(*arguments[0]).run(this));
210 auto replacement = TRY(const_cast<AST::Node&>(*arguments[1]).run(this));
211 auto value = TRY(TRY(const_cast<AST::Node&>(*arguments[2]).run(this))->resolve_without_cast(this));
212
213 if (!pattern->is_string()) {
214 raise_error(ShellError::EvaluatedSyntaxError, "Expected the regex_replace pattern to be a string", arguments[0]->position());
215 return nullptr;
216 }
217
218 if (!replacement->is_string()) {
219 raise_error(ShellError::EvaluatedSyntaxError, "Expected the regex_replace replacement string to be a string", arguments[1]->position());
220 return nullptr;
221 }
222
223 if (!value->is_string()) {
224 raise_error(ShellError::EvaluatedSyntaxError, "Expected the regex_replace target value to be a string", arguments[2]->position());
225 return nullptr;
226 }
227
228 Regex<PosixExtendedParser> re { TRY(pattern->resolve_as_list(this)).first().to_deprecated_string() };
229 auto result = re.replace(
230 TRY(value->resolve_as_list(this))[0],
231 TRY(replacement->resolve_as_list(this))[0],
232 PosixFlags::Global | PosixFlags::Multiline | PosixFlags::Unicode);
233
234 return AST::make_ref_counted<AST::StringLiteral>(invoking_node.position(), TRY(String::from_utf8(result)), AST::StringLiteral::EnclosureType::None);
235}
236
237ErrorOr<RefPtr<AST::Node>> Shell::immediate_remove_suffix(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
238{
239 if (arguments.size() != 2) {
240 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to remove_suffix", invoking_node.position());
241 return nullptr;
242 }
243
244 auto suffix = TRY(const_cast<AST::Node&>(*arguments[0]).run(this));
245 auto value = TRY(TRY(const_cast<AST::Node&>(*arguments[1]).run(this))->resolve_without_cast(this));
246
247 if (!suffix->is_string()) {
248 raise_error(ShellError::EvaluatedSyntaxError, "Expected the remove_suffix suffix string to be a string", arguments[0]->position());
249 return nullptr;
250 }
251
252 auto suffix_str = TRY(suffix->resolve_as_list(this))[0];
253 auto values = TRY(value->resolve_as_list(this));
254
255 Vector<NonnullRefPtr<AST::Node>> nodes;
256
257 for (auto& value_str : values) {
258 String removed = TRY(String::from_utf8(value_str));
259
260 if (value_str.bytes_as_string_view().ends_with(suffix_str))
261 removed = TRY(removed.substring_from_byte_offset(0, value_str.bytes_as_string_view().length() - suffix_str.bytes_as_string_view().length()));
262
263 nodes.append(AST::make_ref_counted<AST::StringLiteral>(invoking_node.position(), move(removed), AST::StringLiteral::EnclosureType::None));
264 }
265
266 return AST::make_ref_counted<AST::ListConcatenate>(invoking_node.position(), move(nodes));
267}
268
269ErrorOr<RefPtr<AST::Node>> Shell::immediate_remove_prefix(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
270{
271 if (arguments.size() != 2) {
272 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to remove_prefix", invoking_node.position());
273 return nullptr;
274 }
275
276 auto prefix = TRY(const_cast<AST::Node&>(*arguments[0]).run(this));
277 auto value = TRY(TRY(const_cast<AST::Node&>(*arguments[1]).run(this))->resolve_without_cast(this));
278
279 if (!prefix->is_string()) {
280 raise_error(ShellError::EvaluatedSyntaxError, "Expected the remove_prefix prefix string to be a string", arguments[0]->position());
281 return nullptr;
282 }
283
284 auto prefix_str = TRY(prefix->resolve_as_list(this))[0];
285 auto values = TRY(value->resolve_as_list(this));
286
287 Vector<NonnullRefPtr<AST::Node>> nodes;
288
289 for (auto& value_str : values) {
290 String removed = TRY(String::from_utf8(value_str));
291
292 if (value_str.bytes_as_string_view().starts_with(prefix_str))
293 removed = TRY(removed.substring_from_byte_offset(prefix_str.bytes_as_string_view().length()));
294 nodes.append(AST::make_ref_counted<AST::StringLiteral>(invoking_node.position(), move(removed), AST::StringLiteral::EnclosureType::None));
295 }
296
297 return AST::make_ref_counted<AST::ListConcatenate>(invoking_node.position(), move(nodes));
298}
299
300ErrorOr<RefPtr<AST::Node>> Shell::immediate_split(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
301{
302 if (arguments.size() != 2) {
303 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to split", invoking_node.position());
304 return nullptr;
305 }
306
307 auto delimiter = TRY(const_cast<AST::Node&>(*arguments[0]).run(this));
308 auto value = TRY(TRY(const_cast<AST::Node&>(*arguments[1]).run(this))->resolve_without_cast(this));
309
310 if (!delimiter->is_string()) {
311 raise_error(ShellError::EvaluatedSyntaxError, "Expected the split delimiter string to be a string", arguments[0]->position());
312 return nullptr;
313 }
314
315 auto delimiter_str = TRY(delimiter->resolve_as_list(this))[0];
316
317 auto transform = [&](auto const& values) {
318 // Translate to a list of applications of `split <delimiter>`
319 Vector<NonnullRefPtr<AST::Node>> resulting_nodes;
320 resulting_nodes.ensure_capacity(values.size());
321 for (auto& entry : values) {
322 // ImmediateExpression(split <delimiter> <entry>)
323 resulting_nodes.unchecked_append(AST::make_ref_counted<AST::ImmediateExpression>(
324 arguments[1]->position(),
325 invoking_node.function(),
326 Vector<NonnullRefPtr<AST::Node>> { Vector<NonnullRefPtr<AST::Node>> {
327 arguments[0],
328 AST::make_ref_counted<AST::SyntheticNode>(arguments[1]->position(), NonnullRefPtr<AST::Value>(entry)),
329 } },
330 arguments[1]->position()));
331 }
332
333 return AST::make_ref_counted<AST::ListConcatenate>(invoking_node.position(), move(resulting_nodes));
334 };
335
336 if (auto list = dynamic_cast<AST::ListValue*>(value.ptr())) {
337 return transform(list->values());
338 }
339
340 // Otherwise, just resolve to a list and transform that.
341 auto list = TRY(value->resolve_as_list(this));
342 if (!value->is_list()) {
343 if (list.is_empty())
344 return AST::make_ref_counted<AST::ListConcatenate>(invoking_node.position(), Vector<NonnullRefPtr<AST::Node>> {});
345
346 auto& value = list.first();
347 Vector<String> split_strings;
348 if (delimiter_str.is_empty()) {
349 StringBuilder builder;
350 for (auto code_point : Utf8View { value }) {
351 builder.append_code_point(code_point);
352 split_strings.append(TRY(builder.to_string()));
353 builder.clear();
354 }
355 } else {
356 auto split = StringView { value }.split_view(delimiter_str, options.inline_exec_keep_empty_segments ? SplitBehavior::KeepEmpty : SplitBehavior::Nothing);
357 split_strings.ensure_capacity(split.size());
358 for (auto& entry : split)
359 split_strings.append(TRY(String::from_utf8(entry)));
360 }
361 return AST::make_ref_counted<AST::SyntheticNode>(invoking_node.position(), AST::make_ref_counted<AST::ListValue>(move(split_strings)));
362 }
363
364 return transform(AST::make_ref_counted<AST::ListValue>(list)->values());
365}
366
367ErrorOr<RefPtr<AST::Node>> Shell::immediate_concat_lists(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
368{
369 Vector<NonnullRefPtr<AST::Node>> result;
370
371 for (auto& argument : arguments) {
372 if (auto* list = dynamic_cast<AST::ListConcatenate const*>(argument.ptr())) {
373 result.extend(list->list());
374 } else {
375 auto list_of_values = TRY(TRY(const_cast<AST::Node&>(*argument).run(this))->resolve_without_cast(this));
376 if (auto* list = dynamic_cast<AST::ListValue*>(list_of_values.ptr())) {
377 for (auto& entry : static_cast<Vector<NonnullRefPtr<AST::Value>>&>(list->values()))
378 result.append(AST::make_ref_counted<AST::SyntheticNode>(argument->position(), entry));
379 } else {
380 auto values = TRY(list_of_values->resolve_as_list(this));
381 for (auto& entry : values)
382 result.append(AST::make_ref_counted<AST::StringLiteral>(argument->position(), entry, AST::StringLiteral::EnclosureType::None));
383 }
384 }
385 }
386
387 return AST::make_ref_counted<AST::ListConcatenate>(invoking_node.position(), move(result));
388}
389
390ErrorOr<RefPtr<AST::Node>> Shell::immediate_filter_glob(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
391{
392 // filter_glob string list
393 if (arguments.size() != 2) {
394 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly two arguments to filter_glob (<glob> <list>)", invoking_node.position());
395 return nullptr;
396 }
397
398 auto glob_list = TRY(TRY(const_cast<AST::Node&>(*arguments[0]).run(*this))->resolve_as_list(*this));
399 if (glob_list.size() != 1) {
400 raise_error(ShellError::EvaluatedSyntaxError, "Expected the <glob> argument to filter_glob to be a single string", arguments[0]->position());
401 return nullptr;
402 }
403 auto& glob = glob_list.first();
404 auto& list_node = arguments[1];
405
406 Vector<NonnullRefPtr<AST::Node>> result;
407
408 TRY(const_cast<AST::Node&>(*list_node).for_each_entry(*this, [&](NonnullRefPtr<AST::Value> entry) -> ErrorOr<IterationDecision> {
409 auto value = TRY(entry->resolve_as_list(*this));
410 if (value.size() == 0)
411 return IterationDecision::Continue;
412 if (value.size() == 1) {
413 if (!value.first().bytes_as_string_view().matches(glob))
414 return IterationDecision::Continue;
415 result.append(AST::make_ref_counted<AST::StringLiteral>(arguments[1]->position(), value.first(), AST::StringLiteral::EnclosureType::None));
416 return IterationDecision::Continue;
417 }
418
419 for (auto& entry : value) {
420 if (entry.bytes_as_string_view().matches(glob)) {
421 Vector<NonnullRefPtr<AST::Node>> nodes;
422 for (auto& string : value)
423 nodes.append(AST::make_ref_counted<AST::StringLiteral>(arguments[1]->position(), string, AST::StringLiteral::EnclosureType::None));
424 result.append(AST::make_ref_counted<AST::ListConcatenate>(arguments[1]->position(), move(nodes)));
425 return IterationDecision::Continue;
426 }
427 }
428 return IterationDecision::Continue;
429 }));
430
431 return AST::make_ref_counted<AST::ListConcatenate>(invoking_node.position(), move(result));
432}
433
434ErrorOr<RefPtr<AST::Node>> Shell::immediate_join(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
435{
436 if (arguments.size() != 2) {
437 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to join", invoking_node.position());
438 return nullptr;
439 }
440
441 auto delimiter = TRY(const_cast<AST::Node&>(*arguments[0]).run(this));
442 if (!delimiter->is_string()) {
443 raise_error(ShellError::EvaluatedSyntaxError, "Expected the join delimiter string to be a string", arguments[0]->position());
444 return nullptr;
445 }
446
447 auto value = TRY(TRY(const_cast<AST::Node&>(*arguments[1]).run(this))->resolve_without_cast(this));
448 if (!value->is_list()) {
449 raise_error(ShellError::EvaluatedSyntaxError, "Expected the joined list to be a list", arguments[1]->position());
450 return nullptr;
451 }
452
453 auto delimiter_str = TRY(delimiter->resolve_as_list(this))[0];
454 StringBuilder builder;
455 builder.join(delimiter_str, TRY(value->resolve_as_list(*this)));
456
457 return AST::make_ref_counted<AST::StringLiteral>(invoking_node.position(), TRY(builder.to_string()), AST::StringLiteral::EnclosureType::None);
458}
459
460ErrorOr<RefPtr<AST::Node>> Shell::immediate_value_or_default(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
461{
462 if (arguments.size() != 2) {
463 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to value_or_default", invoking_node.position());
464 return nullptr;
465 }
466
467 auto name = TRY(TRY(const_cast<AST::Node&>(*arguments.first()).run(*this))->resolve_as_string(*this));
468 if (!TRY(local_variable_or(name, ""sv)).is_empty())
469 return make_ref_counted<AST::SimpleVariable>(invoking_node.position(), name);
470
471 return arguments.last();
472}
473
474ErrorOr<RefPtr<AST::Node>> Shell::immediate_assign_default(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
475{
476 if (arguments.size() != 2) {
477 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to assign_default", invoking_node.position());
478 return nullptr;
479 }
480
481 auto name = TRY(TRY(const_cast<AST::Node&>(*arguments.first()).run(*this))->resolve_as_string(*this));
482 if (!TRY(local_variable_or(name, ""sv)).is_empty())
483 return make_ref_counted<AST::SimpleVariable>(invoking_node.position(), name);
484
485 auto value = TRY(TRY(const_cast<AST::Node&>(*arguments.last()).run(*this))->resolve_without_cast(*this));
486 set_local_variable(name.to_deprecated_string(), value);
487
488 return make_ref_counted<AST::SyntheticNode>(invoking_node.position(), value);
489}
490
491ErrorOr<RefPtr<AST::Node>> Shell::immediate_error_if_empty(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
492{
493 if (arguments.size() != 2) {
494 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to error_if_empty", invoking_node.position());
495 return nullptr;
496 }
497
498 auto name = TRY(TRY(const_cast<AST::Node&>(*arguments.first()).run(*this))->resolve_as_string(*this));
499 if (!TRY(local_variable_or(name, ""sv)).is_empty())
500 return make_ref_counted<AST::SimpleVariable>(invoking_node.position(), name);
501
502 auto error_value = TRY(TRY(const_cast<AST::Node&>(*arguments.last()).run(*this))->resolve_as_string(*this));
503 if (error_value.is_empty())
504 error_value = TRY(String::formatted("Expected {} to be non-empty", name));
505
506 raise_error(ShellError::EvaluatedSyntaxError, error_value.bytes_as_string_view(), invoking_node.position());
507 return nullptr;
508}
509
510ErrorOr<RefPtr<AST::Node>> Shell::immediate_null_or_alternative(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
511{
512 if (arguments.size() != 2) {
513 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to null_or_alternative", invoking_node.position());
514 return nullptr;
515 }
516
517 auto value = TRY(TRY(const_cast<AST::Node&>(*arguments.first()).run(*this))->resolve_without_cast(*this));
518 if ((value->is_string() && TRY(value->resolve_as_string(*this)).is_empty()) || (value->is_list() && TRY(value->resolve_as_list(*this)).is_empty()))
519 return make_ref_counted<AST::SyntheticNode>(invoking_node.position(), value);
520
521 return arguments.last();
522}
523
524ErrorOr<RefPtr<AST::Node>> Shell::immediate_defined_value_or_default(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
525{
526 if (arguments.size() != 2) {
527 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to defined_value_or_default", invoking_node.position());
528 return nullptr;
529 }
530
531 auto name = TRY(TRY(const_cast<AST::Node&>(*arguments.first()).run(*this))->resolve_as_string(*this));
532 if (!find_frame_containing_local_variable(name))
533 return arguments.last();
534
535 return make_ref_counted<AST::SimpleVariable>(invoking_node.position(), name);
536}
537
538ErrorOr<RefPtr<AST::Node>> Shell::immediate_assign_defined_default(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
539{
540 if (arguments.size() != 2) {
541 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to assign_defined_default", invoking_node.position());
542 return nullptr;
543 }
544
545 auto name = TRY(TRY(const_cast<AST::Node&>(*arguments.first()).run(*this))->resolve_as_string(*this));
546 if (find_frame_containing_local_variable(name))
547 return make_ref_counted<AST::SimpleVariable>(invoking_node.position(), name);
548
549 auto value = TRY(TRY(const_cast<AST::Node&>(*arguments.last()).run(*this))->resolve_without_cast(*this));
550 set_local_variable(name.to_deprecated_string(), value);
551
552 return make_ref_counted<AST::SyntheticNode>(invoking_node.position(), value);
553}
554
555ErrorOr<RefPtr<AST::Node>> Shell::immediate_error_if_unset(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
556{
557 if (arguments.size() != 2) {
558 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to error_if_unset", invoking_node.position());
559 return nullptr;
560 }
561
562 auto name = TRY(TRY(const_cast<AST::Node&>(*arguments.first()).run(*this))->resolve_as_string(*this));
563 if (find_frame_containing_local_variable(name))
564 return make_ref_counted<AST::SimpleVariable>(invoking_node.position(), name);
565
566 auto error_value = TRY(TRY(const_cast<AST::Node&>(*arguments.last()).run(*this))->resolve_as_string(*this));
567 if (error_value.is_empty())
568 error_value = TRY(String::formatted("Expected {} to be set", name));
569
570 raise_error(ShellError::EvaluatedSyntaxError, error_value.bytes_as_string_view(), invoking_node.position());
571 return nullptr;
572}
573
574ErrorOr<RefPtr<AST::Node>> Shell::immediate_null_if_unset_or_alternative(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
575{
576 if (arguments.size() != 2) {
577 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to null_if_unset_or_alternative", invoking_node.position());
578 return nullptr;
579 }
580
581 auto name = TRY(TRY(const_cast<AST::Node&>(*arguments.first()).run(*this))->resolve_as_string(*this));
582 if (!find_frame_containing_local_variable(name))
583 return arguments.last();
584
585 return make_ref_counted<AST::SimpleVariable>(invoking_node.position(), name);
586}
587
588ErrorOr<RefPtr<AST::Node>> Shell::immediate_reexpand(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
589{
590 if (arguments.size() != 1) {
591 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 1 argument to reexpand", invoking_node.position());
592 return nullptr;
593 }
594
595 auto value = TRY(TRY(const_cast<AST::Node&>(*arguments.first()).run(*this))->resolve_as_string(*this));
596 return parse(value, m_is_interactive, false);
597}
598
599ErrorOr<RefPtr<AST::Node>> Shell::immediate_length_of_variable(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
600{
601 if (arguments.size() != 1) {
602 raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 1 argument to length_of_variable", invoking_node.position());
603 return nullptr;
604 }
605
606 auto name = TRY(TRY(const_cast<AST::Node&>(*arguments.first()).run(*this))->resolve_as_string(*this));
607 auto variable = make_ref_counted<AST::SimpleVariable>(invoking_node.position(), name);
608
609 return immediate_length_impl(
610 invoking_node,
611 { move(variable) },
612 false);
613}
614
615ErrorOr<RefPtr<AST::Node>> Shell::run_immediate_function(StringView str, AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const& arguments)
616{
617#define __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(name) \
618 if (str == #name) \
619 return immediate_##name(invoking_node, arguments);
620
621 ENUMERATE_SHELL_IMMEDIATE_FUNCTIONS()
622
623#undef __ENUMERATE_SHELL_IMMEDIATE_FUNCTION
624 raise_error(ShellError::EvaluatedSyntaxError, DeprecatedString::formatted("Unknown immediate function {}", str), invoking_node.position());
625 return nullptr;
626}
627
628bool Shell::has_immediate_function(StringView str)
629{
630#define __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(name) \
631 if (str == #name) \
632 return true;
633
634 ENUMERATE_SHELL_IMMEDIATE_FUNCTIONS()
635
636#undef __ENUMERATE_SHELL_IMMEDIATE_FUNCTION
637
638 return false;
639}
640}