/* * Copyright (c) 2021, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ #include "Formatter.h" #include "Shell.h" #include #include namespace Shell { ErrorOr> Shell::immediate_length_impl(AST::ImmediateExpression& invoking_node, Vector> const& arguments, bool across) { auto name = across ? "length_across" : "length"; if (arguments.size() < 1 || arguments.size() > 2) { raise_error(ShellError::EvaluatedSyntaxError, ByteString::formatted("Expected one or two arguments to `{}'", name), invoking_node.position()); return nullptr; } enum { Infer, String, List, } mode { Infer }; bool is_inferred = false; const AST::Node* expr_node; if (arguments.size() == 2) { // length string // length list auto& mode_arg = arguments.first(); if (!mode_arg->is_bareword()) { raise_error(ShellError::EvaluatedSyntaxError, ByteString::formatted("Expected a bareword (either 'string' or 'list') in the two-argument form of the `{}' immediate", name), mode_arg->position()); return nullptr; } auto const& mode_name = static_cast(*mode_arg).text(); if (mode_name == "list") { mode = List; } else if (mode_name == "string") { mode = String; } else if (mode_name == "infer") { mode = Infer; } else { raise_error(ShellError::EvaluatedSyntaxError, ByteString::formatted("Expected either 'string' or 'list' (and not {}) in the two-argument form of the `{}' immediate", mode_name, name), mode_arg->position()); return nullptr; } expr_node = arguments[1]; } else { expr_node = arguments[0]; } if (mode == Infer) { is_inferred = true; if (expr_node->is_list()) mode = List; else if (expr_node->is_simple_variable()) // "Look inside" variables mode = TRY(TRY(const_cast(expr_node)->run(this))->resolve_without_cast(this))->is_list_without_resolution() ? List : String; else if (is(expr_node)) mode = List; else mode = String; } auto value_with_number = [&](auto number) -> ErrorOr> { return AST::make_ref_counted(invoking_node.position(), TRY(String::number(number))); }; auto do_across = [&](StringView mode_name, auto& values) -> ErrorOr> { if (is_inferred) mode_name = "infer"sv; // Translate to a list of applications of `length ` Vector> resulting_nodes; resulting_nodes.ensure_capacity(values.size()); for (auto& entry : values) { // ImmediateExpression(length ) resulting_nodes.unchecked_append(AST::make_ref_counted( expr_node->position(), AST::NameWithPosition { "length"_string, invoking_node.function_position() }, Vector> { Vector> { static_cast>(AST::make_ref_counted(expr_node->position(), TRY(String::from_utf8(mode_name)))), AST::make_ref_counted(expr_node->position(), NonnullRefPtr(entry)), } }, expr_node->position())); } return AST::make_ref_counted(invoking_node.position(), move(resulting_nodes)); }; switch (mode) { default: case Infer: VERIFY_NOT_REACHED(); case List: { auto value = TRY(const_cast(expr_node)->run(this)); if (!value) return value_with_number(0); value = TRY(value->resolve_without_cast(this)); if (auto list = dynamic_cast(value.ptr())) { if (across) return do_across("list"sv, list->values()); return value_with_number(list->values().size()); } auto list = TRY(value->resolve_as_list(this)); if (!across) return value_with_number(list.size()); dbgln("List has {} entries", list.size()); auto values = AST::make_ref_counted(move(list)); return do_across("list"sv, values->values()); } case String: { // 'across' will only accept lists, and '!across' will only accept non-lists here. if (expr_node->is_list()) { if (!across) { raise_no_list_allowed:; Formatter formatter { *expr_node }; if (is_inferred) { raise_error(ShellError::EvaluatedSyntaxError, ByteString::formatted("Could not infer expression type, please explicitly use `{0} string' or `{0} list'", name), invoking_node.position()); return nullptr; } auto source = formatter.format(); raise_error(ShellError::EvaluatedSyntaxError, source.is_empty() ? "Invalid application of `length' to a list" : ByteString::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"), expr_node->position()); return nullptr; } } auto value = TRY(const_cast(expr_node)->run(this)); if (!value) return value_with_number(0); value = TRY(value->resolve_without_cast(*this)); if (auto list = dynamic_cast(value.ptr())) { if (!across) goto raise_no_list_allowed; return do_across("string"sv, list->values()); } if (across && !value->is_list()) { Formatter formatter { *expr_node }; auto source = formatter.format(); raise_error(ShellError::EvaluatedSyntaxError, ByteString::formatted("Invalid application of `length_across' to a non-list\nperhaps you meant `{1}length {0}{2}'?", source, "\x1b[32m", "\x1b[0m"), expr_node->position()); return nullptr; } // Evaluate the nodes and substitute with the lengths. auto list = TRY(value->resolve_as_list(this)); if (!expr_node->is_list()) { if (list.size() == 1) { if (across) goto raise_no_list_allowed; // This is the normal case, the expression is a normal non-list expression. return value_with_number(list.first().bytes_as_string_view().length()); } // This can be hit by asking for the length of a command list (e.g. `(>/dev/null)`) // raise an error about misuse of command lists for now. // FIXME: What's the length of `(>/dev/null)` supposed to be? raise_error(ShellError::EvaluatedSyntaxError, "Length of meta value (or command list) requested, this is currently not supported.", expr_node->position()); return nullptr; } auto values = AST::make_ref_counted(move(list)); return do_across("string"sv, values->values()); } } } ErrorOr> Shell::immediate_length(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { return immediate_length_impl(invoking_node, arguments, false); } ErrorOr> Shell::immediate_length_across(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { return immediate_length_impl(invoking_node, arguments, true); } ErrorOr> Shell::immediate_regex_replace(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 3) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 3 arguments to regex_replace", invoking_node.position()); return nullptr; } auto pattern = TRY(const_cast(*arguments[0]).run(this)); auto replacement = TRY(const_cast(*arguments[1]).run(this)); auto value = TRY(TRY(const_cast(*arguments[2]).run(this))->resolve_without_cast(this)); if (!pattern->is_string()) { raise_error(ShellError::EvaluatedSyntaxError, "Expected the regex_replace pattern to be a string", arguments[0]->position()); return nullptr; } if (!replacement->is_string()) { raise_error(ShellError::EvaluatedSyntaxError, "Expected the regex_replace replacement string to be a string", arguments[1]->position()); return nullptr; } if (!value->is_string()) { raise_error(ShellError::EvaluatedSyntaxError, "Expected the regex_replace target value to be a string", arguments[2]->position()); return nullptr; } Regex re { TRY(pattern->resolve_as_list(this)).first().to_byte_string() }; auto result = re.replace( TRY(value->resolve_as_list(this))[0], TRY(replacement->resolve_as_list(this))[0], PosixFlags::Global | PosixFlags::Multiline | PosixFlags::Unicode); return AST::make_ref_counted(invoking_node.position(), TRY(String::from_byte_string(result)), AST::StringLiteral::EnclosureType::None); } ErrorOr> Shell::immediate_remove_suffix(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 2) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to remove_suffix", invoking_node.position()); return nullptr; } auto suffix = TRY(const_cast(*arguments[0]).run(this)); auto value = TRY(TRY(const_cast(*arguments[1]).run(this))->resolve_without_cast(this)); if (!suffix->is_string()) { raise_error(ShellError::EvaluatedSyntaxError, "Expected the remove_suffix suffix string to be a string", arguments[0]->position()); return nullptr; } auto suffix_str = TRY(suffix->resolve_as_list(this))[0]; auto values = TRY(value->resolve_as_list(this)); Vector> nodes; for (auto& value_str : values) { String removed = value_str; if (value_str.bytes_as_string_view().ends_with(suffix_str)) removed = TRY(removed.substring_from_byte_offset(0, value_str.bytes_as_string_view().length() - suffix_str.bytes_as_string_view().length())); nodes.append(AST::make_ref_counted(invoking_node.position(), move(removed), AST::StringLiteral::EnclosureType::None)); } return AST::make_ref_counted(invoking_node.position(), move(nodes)); } ErrorOr> Shell::immediate_remove_prefix(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 2) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to remove_prefix", invoking_node.position()); return nullptr; } auto prefix = TRY(const_cast(*arguments[0]).run(this)); auto value = TRY(TRY(const_cast(*arguments[1]).run(this))->resolve_without_cast(this)); if (!prefix->is_string()) { raise_error(ShellError::EvaluatedSyntaxError, "Expected the remove_prefix prefix string to be a string", arguments[0]->position()); return nullptr; } auto prefix_str = TRY(prefix->resolve_as_list(this))[0]; auto values = TRY(value->resolve_as_list(this)); Vector> nodes; for (auto& value_str : values) { String removed = value_str; if (value_str.bytes_as_string_view().starts_with(prefix_str)) removed = TRY(removed.substring_from_byte_offset(prefix_str.bytes_as_string_view().length())); nodes.append(AST::make_ref_counted(invoking_node.position(), move(removed), AST::StringLiteral::EnclosureType::None)); } return AST::make_ref_counted(invoking_node.position(), move(nodes)); } ErrorOr> Shell::immediate_split(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 2) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to split", invoking_node.position()); return nullptr; } auto delimiter = TRY(const_cast(*arguments[0]).run(this)); auto value = TRY(TRY(const_cast(*arguments[1]).run(this))->resolve_without_cast(this)); if (!delimiter->is_string()) { raise_error(ShellError::EvaluatedSyntaxError, "Expected the split delimiter string to be a string", arguments[0]->position()); return nullptr; } auto delimiter_str = TRY(delimiter->resolve_as_list(this))[0]; auto transform = [&](auto const& values) { // Translate to a list of applications of `split ` Vector> resulting_nodes; resulting_nodes.ensure_capacity(values.size()); for (auto& entry : values) { // ImmediateExpression(split ) resulting_nodes.unchecked_append(AST::make_ref_counted( arguments[1]->position(), invoking_node.function(), Vector> { Vector> { arguments[0], AST::make_ref_counted(arguments[1]->position(), NonnullRefPtr(entry)), } }, arguments[1]->position())); } return AST::make_ref_counted(invoking_node.position(), move(resulting_nodes)); }; if (auto list = dynamic_cast(value.ptr())) { return transform(list->values()); } // Otherwise, just resolve to a list and transform that. auto list = TRY(value->resolve_as_list(this)); if (!value->is_list()) { if (list.is_empty()) return AST::make_ref_counted(invoking_node.position(), Vector> {}); auto& value = list.first(); Vector split_strings; if (delimiter_str.is_empty()) { StringBuilder builder; for (auto code_point : Utf8View { value }) { builder.append_code_point(code_point); split_strings.append(TRY(builder.to_string())); builder.clear(); } } else { auto split = StringView { value }.split_view(delimiter_str, options.inline_exec_keep_empty_segments ? SplitBehavior::KeepEmpty : SplitBehavior::Nothing); split_strings.ensure_capacity(split.size()); for (auto& entry : split) split_strings.append(TRY(String::from_utf8(entry))); } return AST::make_ref_counted(invoking_node.position(), AST::make_ref_counted(move(split_strings))); } return transform(AST::make_ref_counted(list)->values()); } ErrorOr> Shell::immediate_concat_lists(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { Vector> result; for (auto& argument : arguments) { if (auto* list = dynamic_cast(argument.ptr())) { result.extend(list->list()); } else { auto list_of_values = TRY(TRY(const_cast(*argument).run(this))->resolve_without_cast(this)); if (auto* list = dynamic_cast(list_of_values.ptr())) { for (auto& entry : static_cast>&>(list->values())) result.append(AST::make_ref_counted(argument->position(), entry)); } else { auto values = TRY(list_of_values->resolve_as_list(this)); for (auto& entry : values) result.append(AST::make_ref_counted(argument->position(), entry, AST::StringLiteral::EnclosureType::None)); } } } return AST::make_ref_counted(invoking_node.position(), move(result)); } ErrorOr> Shell::immediate_filter_glob(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { // filter_glob string list if (arguments.size() != 2) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly two arguments to filter_glob ( )", invoking_node.position()); return nullptr; } auto glob_list = TRY(TRY(const_cast(*arguments[0]).run(*this))->resolve_as_list(*this)); if (glob_list.size() != 1) { raise_error(ShellError::EvaluatedSyntaxError, "Expected the argument to filter_glob to be a single string", arguments[0]->position()); return nullptr; } auto& glob = glob_list.first(); auto& list_node = arguments[1]; Vector> result; TRY(const_cast(*list_node).for_each_entry(*this, [&](NonnullRefPtr entry) -> ErrorOr { auto value = TRY(entry->resolve_as_list(*this)); if (value.size() == 0) return IterationDecision::Continue; if (value.size() == 1) { if (!value.first().bytes_as_string_view().matches(glob)) return IterationDecision::Continue; result.append(AST::make_ref_counted(arguments[1]->position(), value.first(), AST::StringLiteral::EnclosureType::None)); return IterationDecision::Continue; } for (auto& entry : value) { if (entry.bytes_as_string_view().matches(glob)) { Vector> nodes; for (auto& string : value) nodes.append(AST::make_ref_counted(arguments[1]->position(), string, AST::StringLiteral::EnclosureType::None)); result.append(AST::make_ref_counted(arguments[1]->position(), move(nodes))); return IterationDecision::Continue; } } return IterationDecision::Continue; })); return AST::make_ref_counted(invoking_node.position(), move(result)); } ErrorOr> Shell::immediate_join(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 2) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to join", invoking_node.position()); return nullptr; } auto delimiter = TRY(const_cast(*arguments[0]).run(this)); if (!delimiter->is_string()) { raise_error(ShellError::EvaluatedSyntaxError, "Expected the join delimiter string to be a string", arguments[0]->position()); return nullptr; } auto value = TRY(TRY(const_cast(*arguments[1]).run(this))->resolve_without_cast(this)); if (!value->is_list()) { raise_error(ShellError::EvaluatedSyntaxError, "Expected the joined list to be a list", arguments[1]->position()); return nullptr; } auto delimiter_str = TRY(delimiter->resolve_as_list(this))[0]; StringBuilder builder; builder.join(delimiter_str, TRY(value->resolve_as_list(*this))); return AST::make_ref_counted(invoking_node.position(), TRY(builder.to_string()), AST::StringLiteral::EnclosureType::None); } ErrorOr> Shell::immediate_value_or_default(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 2) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to value_or_default", invoking_node.position()); return nullptr; } auto name = TRY(TRY(const_cast(*arguments.first()).run(*this))->resolve_as_string(*this)); if (!TRY(local_variable_or(name, ""sv)).is_empty()) return make_ref_counted(invoking_node.position(), name); return arguments.last(); } ErrorOr> Shell::immediate_assign_default(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 2) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to assign_default", invoking_node.position()); return nullptr; } auto name = TRY(TRY(const_cast(*arguments.first()).run(*this))->resolve_as_string(*this)); if (!TRY(local_variable_or(name, ""sv)).is_empty()) return make_ref_counted(invoking_node.position(), name); auto value = TRY(TRY(const_cast(*arguments.last()).run(*this))->resolve_without_cast(*this)); set_local_variable(name.to_byte_string(), value); return make_ref_counted(invoking_node.position(), value); } ErrorOr> Shell::immediate_error_if_empty(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 2) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to error_if_empty", invoking_node.position()); return nullptr; } auto name = TRY(TRY(const_cast(*arguments.first()).run(*this))->resolve_as_string(*this)); if (!TRY(local_variable_or(name, ""sv)).is_empty()) return make_ref_counted(invoking_node.position(), name); auto error_value = TRY(TRY(const_cast(*arguments.last()).run(*this))->resolve_as_string(*this)); if (error_value.is_empty()) error_value = TRY(String::formatted("Expected {} to be non-empty", name)); raise_error(ShellError::EvaluatedSyntaxError, error_value.bytes_as_string_view(), invoking_node.position()); return nullptr; } ErrorOr> Shell::immediate_null_or_alternative(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 2) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to null_or_alternative", invoking_node.position()); return nullptr; } auto name = TRY(TRY(const_cast(*arguments.first()).run(*this))->resolve_as_string(*this)); auto frame = find_frame_containing_local_variable(name); if (!frame) return make_ref_counted(invoking_node.position(), ""_string, AST::StringLiteral::EnclosureType::None); auto value = frame->local_variables.get(name.bytes_as_string_view()).value(); if ((value->is_string() && TRY(value->resolve_as_string(*this)).is_empty()) || (value->is_list() && TRY(value->resolve_as_list(*this)).is_empty())) return make_ref_counted(invoking_node.position(), *value); return arguments.last(); } ErrorOr> Shell::immediate_defined_value_or_default(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 2) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to defined_value_or_default", invoking_node.position()); return nullptr; } auto name = TRY(TRY(const_cast(*arguments.first()).run(*this))->resolve_as_string(*this)); if (!find_frame_containing_local_variable(name)) return arguments.last(); return make_ref_counted(invoking_node.position(), name); } ErrorOr> Shell::immediate_assign_defined_default(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 2) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to assign_defined_default", invoking_node.position()); return nullptr; } auto name = TRY(TRY(const_cast(*arguments.first()).run(*this))->resolve_as_string(*this)); if (find_frame_containing_local_variable(name)) return make_ref_counted(invoking_node.position(), name); auto value = TRY(TRY(const_cast(*arguments.last()).run(*this))->resolve_without_cast(*this)); set_local_variable(name.to_byte_string(), value); return make_ref_counted(invoking_node.position(), value); } ErrorOr> Shell::immediate_error_if_unset(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 2) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to error_if_unset", invoking_node.position()); return nullptr; } auto name = TRY(TRY(const_cast(*arguments.first()).run(*this))->resolve_as_string(*this)); if (find_frame_containing_local_variable(name)) return make_ref_counted(invoking_node.position(), name); auto error_value = TRY(TRY(const_cast(*arguments.last()).run(*this))->resolve_as_string(*this)); if (error_value.is_empty()) error_value = TRY(String::formatted("Expected {} to be set", name)); raise_error(ShellError::EvaluatedSyntaxError, error_value.bytes_as_string_view(), invoking_node.position()); return nullptr; } ErrorOr> Shell::immediate_null_if_unset_or_alternative(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 2) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 2 arguments to null_if_unset_or_alternative", invoking_node.position()); return nullptr; } auto name = TRY(TRY(const_cast(*arguments.first()).run(*this))->resolve_as_string(*this)); if (!find_frame_containing_local_variable(name)) return arguments.last(); return make_ref_counted(invoking_node.position(), name); } ErrorOr> Shell::immediate_reexpand(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 1) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 1 argument to reexpand", invoking_node.position()); return nullptr; } auto values = TRY(TRY(const_cast(*arguments.first()).run(*this))->resolve_as_list(*this)); auto result = Vector> {}; for (auto& value : values) { if (auto node = parse(value, m_is_interactive, false)) result.append(node.release_nonnull()); } if (values.size() == 1) return result.take_first(); return AST::make_ref_counted(invoking_node.position(), move(result)); } ErrorOr> Shell::immediate_length_of_variable(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 1) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 1 argument to length_of_variable", invoking_node.position()); return nullptr; } auto name = TRY(TRY(const_cast(*arguments.first()).run(*this))->resolve_as_string(*this)); auto variable = make_ref_counted(invoking_node.position(), name); return immediate_length_impl( invoking_node, { move(variable) }, false); } namespace Arithmetic { struct BinaryOperationNode; struct UnaryOperationNode; struct TernaryOperationNode; struct ErrorNode; struct Node { Variant, NonnullOwnPtr, NonnullOwnPtr, NonnullOwnPtr> value; }; struct ErrorNode { String error; }; enum class Operator { Add, // + Subtract, // - Multiply, // * Quotient, // / Remainder, // % Power, // ** Equal, // == GreaterThan, // > LessThan, // < NotEqual, // != GreaterThanOrEqual, // >= LessThanOrEqual, // <= BitwiseAnd, // & BitwiseOr, // | BitwiseXor, // ^ ShiftLeft, // << ShiftRight, // >> ArithmeticAnd, // && ArithmeticOr, // || Comma, // , Negate, // ! BitwiseNegate, // ~ TernaryQuestion, // ? TernaryColon, // : Assignment, // = PlusAssignment, // += MinusAssignment, // -= MultiplyAssignment, // *= DivideAssignment, // /= ModuloAssignment, // %= AndAssignment, // &= OrAssignment, // |= XorAssignment, // ^= LeftShiftAssignment, // <<= RightShiftAssignment, // >>= OpenParen, // ( CloseParen, // ) }; static Operator assignment_operation_of(Operator op) { switch (op) { case Operator::PlusAssignment: return Operator::Add; case Operator::MinusAssignment: return Operator::Subtract; case Operator::MultiplyAssignment: return Operator::Multiply; case Operator::DivideAssignment: return Operator::Quotient; case Operator::ModuloAssignment: return Operator::Remainder; case Operator::AndAssignment: return Operator::BitwiseAnd; case Operator::OrAssignment: return Operator::BitwiseOr; case Operator::XorAssignment: return Operator::BitwiseXor; case Operator::LeftShiftAssignment: return Operator::ShiftLeft; case Operator::RightShiftAssignment: return Operator::ShiftRight; default: VERIFY_NOT_REACHED(); } } static bool is_assignment_operator(Operator op) { switch (op) { case Operator::Assignment: case Operator::PlusAssignment: case Operator::MinusAssignment: case Operator::MultiplyAssignment: case Operator::DivideAssignment: case Operator::ModuloAssignment: case Operator::AndAssignment: case Operator::OrAssignment: case Operator::XorAssignment: case Operator::LeftShiftAssignment: case Operator::RightShiftAssignment: return true; default: return false; } } using Token = Variant; struct BinaryOperationNode { BinaryOperationNode(Operator op, Node lhs, Node rhs) : op(op) , lhs(move(lhs)) , rhs(move(rhs)) { } Operator op; Node lhs; Node rhs; }; struct UnaryOperationNode { UnaryOperationNode(Operator op, Node rhs) : op(op) , rhs(move(rhs)) { } Operator op; Node rhs; }; struct TernaryOperationNode { TernaryOperationNode(Node condition, Node true_value, Node false_value) : condition(move(condition)) , true_value(move(true_value)) , false_value(move(false_value)) { } Node condition; Node true_value; Node false_value; }; static ErrorOr parse_expression(Span); static ErrorOr parse_assignment_expression(Span&); static ErrorOr parse_comma_expression(Span&); static ErrorOr parse_ternary_expression(Span&); static ErrorOr parse_logical_or_expression(Span&); static ErrorOr parse_logical_and_expression(Span&); static ErrorOr parse_bitwise_or_expression(Span&); static ErrorOr parse_bitwise_xor_expression(Span&); static ErrorOr parse_bitwise_and_expression(Span&); static ErrorOr parse_equality_expression(Span&); static ErrorOr parse_comparison_expression(Span&); static ErrorOr parse_shift_expression(Span&); static ErrorOr parse_additive_expression(Span&); static ErrorOr parse_multiplicative_expression(Span&); static ErrorOr parse_exponential_expression(Span&); static ErrorOr parse_unary_expression(Span&); static ErrorOr parse_primary_expression(Span&); template static ErrorOr parse_binary_expression_using_operators(Span&, Array, Function(Span&)> const& parse_rhs); static ErrorOr parse_binary_expression_using_operator(Span& tokens, Operator op, Function(Span&)> const& parse_rhs) { return parse_binary_expression_using_operators(tokens, Array { op }, parse_rhs); } static bool next_token_is_operator(Span& tokens, Operator op) { if (tokens.is_empty()) return false; return tokens.first().has() && tokens.first().get() == op; } ErrorOr parse_expression(Span tokens) { return parse_comma_expression(tokens); } ErrorOr parse_comma_expression(Span& tokens) { return parse_binary_expression_using_operator(tokens, Operator::Comma, &parse_assignment_expression); } ErrorOr parse_assignment_expression(Span& tokens) { auto lhs = TRY(parse_ternary_expression(tokens)); if (tokens.is_empty()) return lhs; auto is_assignment_operator = [](Operator op) { return op == Operator::Assignment || op == Operator::PlusAssignment || op == Operator::MinusAssignment || op == Operator::MultiplyAssignment || op == Operator::DivideAssignment || op == Operator::ModuloAssignment || op == Operator::AndAssignment || op == Operator::OrAssignment || op == Operator::XorAssignment || op == Operator::LeftShiftAssignment || op == Operator::RightShiftAssignment; }; auto& token = tokens.first(); if (auto op = token.get_pointer(); op && is_assignment_operator(*op)) { if (!lhs.value.has()) { return Node { make("Left-hand side of assignment must be a variable"_string) }; } tokens = tokens.slice(1); auto rhs = TRY(parse_assignment_expression(tokens)); return Node { make(*op, move(lhs), move(rhs)) }; } return lhs; } ErrorOr parse_ternary_expression(Span& tokens) { auto condition = TRY(parse_logical_or_expression(tokens)); if (!next_token_is_operator(tokens, Operator::TernaryQuestion)) return condition; tokens = tokens.slice(1); auto true_value = TRY(parse_comma_expression(tokens)); if (!next_token_is_operator(tokens, Operator::TernaryColon)) { return Node { make("Expected ':' after true value in ternary expression"_string) }; } tokens = tokens.slice(1); auto false_value = TRY(parse_ternary_expression(tokens)); return Node { make(move(condition), move(true_value), move(false_value)) }; } ErrorOr parse_logical_or_expression(Span& tokens) { return parse_binary_expression_using_operator(tokens, Operator::ArithmeticOr, &parse_logical_and_expression); } ErrorOr parse_logical_and_expression(Span& tokens) { return parse_binary_expression_using_operator(tokens, Operator::ArithmeticAnd, &parse_bitwise_or_expression); } ErrorOr parse_bitwise_or_expression(Span& tokens) { return parse_binary_expression_using_operator(tokens, Operator::BitwiseOr, &parse_bitwise_xor_expression); } ErrorOr parse_bitwise_xor_expression(Span& tokens) { return parse_binary_expression_using_operator(tokens, Operator::BitwiseXor, &parse_bitwise_and_expression); } ErrorOr parse_bitwise_and_expression(Span& tokens) { return parse_binary_expression_using_operator(tokens, Operator::BitwiseAnd, &parse_equality_expression); } ErrorOr parse_equality_expression(Span& tokens) { return parse_binary_expression_using_operators(tokens, Array { Operator::Equal, Operator::NotEqual }, &parse_comparison_expression); } ErrorOr parse_comparison_expression(Span& tokens) { return parse_binary_expression_using_operators(tokens, Array { Operator::LessThan, Operator::GreaterThan, Operator::LessThanOrEqual, Operator::GreaterThanOrEqual }, &parse_shift_expression); } ErrorOr parse_shift_expression(Span& tokens) { return parse_binary_expression_using_operators(tokens, Array { Operator::ShiftLeft, Operator::ShiftRight }, &parse_additive_expression); } ErrorOr parse_additive_expression(Span& tokens) { return parse_binary_expression_using_operators(tokens, Array { Operator::Add, Operator::Subtract }, &parse_multiplicative_expression); } ErrorOr parse_multiplicative_expression(Span& tokens) { return parse_binary_expression_using_operators(tokens, Array { Operator::Multiply, Operator::Quotient, Operator::Remainder }, &parse_exponential_expression); } ErrorOr parse_exponential_expression(Span& tokens) { auto lhs = TRY(parse_unary_expression(tokens)); if (!next_token_is_operator(tokens, Operator::Power)) return lhs; tokens = tokens.slice(1); auto rhs = TRY(parse_exponential_expression(tokens)); return Node { make(Operator::Power, move(lhs), move(rhs)) }; } ErrorOr parse_unary_expression(Span& tokens) { if (tokens.is_empty()) { return Node { make("Expected expression, got end of input"_string) }; } auto& token = tokens.first(); if (auto op = token.get_pointer()) { if (*op == Operator::Add || *op == Operator::Subtract || *op == Operator::Negate || *op == Operator::BitwiseNegate) { tokens = tokens.slice(1); auto rhs = TRY(parse_unary_expression(tokens)); return Node { make(*op, move(rhs)) }; } } return parse_primary_expression(tokens); } ErrorOr parse_primary_expression(Span& tokens) { if (tokens.is_empty()) return Node { make("Expected expression, got end of input"_string) }; auto& token = tokens.first(); return token.visit( [&](String const& var) -> ErrorOr { tokens = tokens.slice(1); return Node { var }; }, [&](i64 value) -> ErrorOr { tokens = tokens.slice(1); return Node { value }; }, [&](Operator op) -> ErrorOr { switch (op) { case Operator::OpenParen: { tokens = tokens.slice(1); auto value = TRY(parse_expression(tokens)); if (!next_token_is_operator(tokens, Operator::CloseParen)) { return Node { make("Expected ')' after expression in parentheses"_string) }; } tokens = tokens.slice(1); return value; } default: return Node { make("Expected expression, got operator"_string) }; } }); } template ErrorOr parse_binary_expression_using_operators(Span& tokens, Array operators, Function(Span&)> const& parse_rhs) { auto lhs = TRY(parse_rhs(tokens)); for (;;) { Optional op; for (auto candidate : operators) { if (next_token_is_operator(tokens, candidate)) { op = candidate; break; } } if (!op.has_value()) return lhs; tokens = tokens.slice(1); auto rhs = TRY(parse_rhs(tokens)); lhs = Node { make(*op, move(lhs), move(rhs)) }; } } } ErrorOr> Shell::immediate_math(AST::ImmediateExpression& invoking_node, Vector> const& arguments) { if (arguments.size() != 1) { raise_error(ShellError::EvaluatedSyntaxError, "Expected exactly 1 argument to math", invoking_node.position()); return nullptr; } auto expression_parts = TRY(TRY(const_cast(*arguments.first()).run(*this))->resolve_as_list(*this)); auto expression = TRY(String::join(' ', expression_parts)); using Arithmetic::Operator; using Arithmetic::Token; Vector tokens; auto view = expression.code_points(); Optional integer_or_word_start_offset; for (auto it = view.begin(); it != view.end(); ++it) { auto code_point = *it; if (is_ascii_alphanumeric(code_point) || code_point == U'_') { if (!integer_or_word_start_offset.has_value()) integer_or_word_start_offset = view.byte_offset_of(it); continue; } if (integer_or_word_start_offset.has_value()) { auto integer_or_word = view.substring_view( *integer_or_word_start_offset, view.byte_offset_of(it) - *integer_or_word_start_offset); if (all_of(integer_or_word, is_ascii_digit)) tokens.append(*integer_or_word.as_string().to_number()); else tokens.append(TRY(expression.substring_from_byte_offset_with_shared_superstring(*integer_or_word_start_offset, integer_or_word.length()))); integer_or_word_start_offset.clear(); } switch (code_point) { case U'!': if (it.peek(1) == U'=') { ++it; tokens.append(Operator::NotEqual); } else { tokens.append(Operator::Negate); } break; case U'=': if (it.peek(1) == U'=') { ++it; tokens.append(Operator::Equal); } else { tokens.append(Operator::Assignment); } break; case U'~': tokens.append(Operator::BitwiseNegate); break; case U'(': tokens.append(Operator::OpenParen); break; case U')': tokens.append(Operator::CloseParen); break; case U'&': switch (it.peek(1).value_or(0)) { case U'&': ++it; tokens.append(Operator::ArithmeticAnd); break; case U'=': ++it; tokens.append(Operator::AndAssignment); break; default: tokens.append(Operator::BitwiseAnd); break; } break; case U'|': switch (it.peek(1).value_or(0)) { case U'|': ++it; tokens.append(Operator::ArithmeticOr); break; case U'=': ++it; tokens.append(Operator::OrAssignment); break; default: tokens.append(Operator::BitwiseOr); break; } break; case U'^': if (it.peek(1) == U'=') { ++it; tokens.append(Operator::XorAssignment); } else { tokens.append(Operator::BitwiseXor); } break; case U',': tokens.append(Operator::Comma); break; case U'?': tokens.append(Operator::TernaryQuestion); break; case U':': tokens.append(Operator::TernaryColon); break; case U'+': switch (it.peek(1).value_or(0)) { case U'=': ++it; tokens.append(Operator::PlusAssignment); break; default: tokens.append(Operator::Add); break; } break; case U'-': switch (it.peek(1).value_or(0)) { case U'=': ++it; tokens.append(Operator::MinusAssignment); break; default: tokens.append(Operator::Subtract); break; } break; case U'*': switch (it.peek(1).value_or(0)) { case U'=': ++it; tokens.append(Operator::MultiplyAssignment); break; case U'*': ++it; tokens.append(Operator::Power); break; default: tokens.append(Operator::Multiply); break; } break; case U'/': if (it.peek(1) == U'=') { ++it; tokens.append(Operator::DivideAssignment); } else { tokens.append(Operator::Quotient); } break; case U'%': if (it.peek(1) == U'=') { ++it; tokens.append(Operator::ModuloAssignment); } else { tokens.append(Operator::Remainder); } break; case U'<': switch (it.peek(1).value_or(0)) { case U'<': ++it; if (it.peek(1) == U'=') { ++it; tokens.append(Operator::LeftShiftAssignment); } else { tokens.append(Operator::ShiftLeft); } break; case U'=': ++it; tokens.append(Operator::LessThanOrEqual); break; default: tokens.append(Operator::LessThan); break; } break; case U'>': switch (it.peek(1).value_or(0)) { case U'>': ++it; if (it.peek(1) == U'=') { ++it; tokens.append(Operator::RightShiftAssignment); } else { tokens.append(Operator::ShiftRight); } break; case U'=': ++it; tokens.append(Operator::GreaterThanOrEqual); break; default: tokens.append(Operator::GreaterThan); break; } break; case U' ': case U'\t': case U'\n': case U'\r': break; default: raise_error(ShellError::EvaluatedSyntaxError, ByteString::formatted("Unexpected character '{:c}' in math expression", code_point), arguments.first()->position()); return nullptr; } } if (integer_or_word_start_offset.has_value()) { auto integer_or_word = view.substring_view(*integer_or_word_start_offset); if (all_of(integer_or_word, is_ascii_digit)) tokens.append(*integer_or_word.as_string().to_number()); else tokens.append(TRY(expression.substring_from_byte_offset_with_shared_superstring(*integer_or_word_start_offset, integer_or_word.length()))); integer_or_word_start_offset.clear(); } auto ast = TRY(Arithmetic::parse_expression(tokens)); // Now interpret that. Function(Arithmetic::Node const&)> interpret = [&](Arithmetic::Node const& node) -> ErrorOr { return node.value.visit( [&](String const& name) -> ErrorOr { size_t resolution_attempts_remaining = 100; for (auto resolved_name = name; resolution_attempts_remaining > 0; --resolution_attempts_remaining) { auto value = TRY(look_up_local_variable(resolved_name.bytes_as_string_view())); if (!value) break; StringBuilder builder; builder.join(' ', TRY(const_cast(*value).resolve_as_list(const_cast(*this)))); resolved_name = TRY(builder.to_string()); auto integer = resolved_name.to_number(); if (integer.has_value()) return *integer; } if (resolution_attempts_remaining == 0) raise_error(ShellError::EvaluatedSyntaxError, ByteString::formatted("Too many indirections when resolving variable '{}'", name), arguments.first()->position()); return 0; }, [&](i64 value) -> ErrorOr { return value; }, [&](NonnullOwnPtr const& node) -> ErrorOr { if (Arithmetic::is_assignment_operator(node->op)) { // lhs must be a variable name. auto name = node->lhs.value.get_pointer(); if (!name) { raise_error(ShellError::EvaluatedSyntaxError, "Invalid left-hand side of assignment", arguments.first()->position()); return 0; } auto rhs = TRY(interpret(node->rhs)); if (node->op != Arithmetic::Operator::Assignment) { // Evaluate the new value rhs = TRY(interpret(Arithmetic::Node { .value = make( Arithmetic::assignment_operation_of(node->op), Arithmetic::Node { *name }, Arithmetic::Node { rhs }), })); } set_local_variable(name->to_byte_string(), make_ref_counted(TRY(String::number(rhs)))); return rhs; } auto lhs = TRY(interpret(node->lhs)); auto rhs = TRY(interpret(node->rhs)); using Arithmetic::Operator; switch (node->op) { case Operator::Add: return lhs + rhs; case Operator::Subtract: return lhs - rhs; case Operator::Multiply: return lhs * rhs; case Operator::Quotient: return lhs / rhs; case Operator::Remainder: return lhs % rhs; case Operator::ShiftLeft: return lhs << rhs; case Operator::ShiftRight: return lhs >> rhs; case Operator::BitwiseAnd: return lhs & rhs; case Operator::BitwiseOr: return lhs | rhs; case Operator::BitwiseXor: return lhs ^ rhs; case Operator::ArithmeticAnd: return lhs != 0 && rhs != 0; case Operator::ArithmeticOr: return lhs != 0 || rhs != 0; case Operator::LessThan: return lhs < rhs; case Operator::LessThanOrEqual: return lhs <= rhs; case Operator::GreaterThan: return lhs > rhs; case Operator::GreaterThanOrEqual: return lhs >= rhs; case Operator::Equal: return lhs == rhs; case Operator::NotEqual: return lhs != rhs; case Operator::Power: return trunc(pow(static_cast(lhs), static_cast(rhs))); case Operator::Comma: return rhs; default: VERIFY_NOT_REACHED(); } }, [&](NonnullOwnPtr const& node) -> ErrorOr { auto value = TRY(interpret(node->rhs)); switch (node->op) { case Arithmetic::Operator::Negate: return value == 0; case Arithmetic::Operator::BitwiseNegate: return ~value; case Arithmetic::Operator::Add: return value; case Arithmetic::Operator::Subtract: return -value; default: VERIFY_NOT_REACHED(); } }, [&](NonnullOwnPtr const& node) -> ErrorOr { auto condition = TRY(interpret(node->condition)); if (condition != 0) return TRY(interpret(node->true_value)); return TRY(interpret(node->false_value)); }, [&](NonnullOwnPtr const& node) -> ErrorOr { raise_error(ShellError::EvaluatedSyntaxError, node->error.to_byte_string(), arguments.first()->position()); return 0; }); }; auto result = TRY(interpret(ast)); return make_ref_counted(arguments.first()->position(), TRY(String::number(result)), AST::StringLiteral::EnclosureType::None); } ErrorOr> Shell::run_immediate_function(StringView str, AST::ImmediateExpression& invoking_node, Vector> const& arguments) { #define __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(name) \ if (str == #name) \ return immediate_##name(invoking_node, arguments); ENUMERATE_SHELL_IMMEDIATE_FUNCTIONS() #undef __ENUMERATE_SHELL_IMMEDIATE_FUNCTION raise_error(ShellError::EvaluatedSyntaxError, ByteString::formatted("Unknown immediate function {}", str), invoking_node.position()); return nullptr; } bool Shell::has_immediate_function(StringView str) { #define __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(name) \ if (str == #name) \ return true; ENUMERATE_SHELL_IMMEDIATE_FUNCTIONS() #undef __ENUMERATE_SHELL_IMMEDIATE_FUNCTION return false; } }