/* * Copyright (c) 2020, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include static void print_help_and_exit() { outln(R"( Usage: expr EXPRESSION expr [--help] Print the value of EXPRESSION to standard output.)"); exit(0); } template [[noreturn]] void fail(Fmt&& fmt, Args&&... args) { warn("ERROR: \e[31m"); warnln(StringView { fmt }, args...); warn("\e[0m"); exit(2); } class Expression { public: enum Precedence { Or, And, Comp, ArithS, ArithM, StringO, Paren, }; static NonnullOwnPtr parse(Queue& args, Precedence prec = Or); enum class Type { Integer, String, }; virtual bool truth() const = 0; virtual int integer() const = 0; virtual String string() const = 0; virtual Type type() const = 0; virtual ~Expression() { } }; class ValueExpression : public Expression { public: ValueExpression(int v) : as_integer(v) , m_type(Type::Integer) { } ValueExpression(String&& v) : as_string(move(v)) , m_type(Type::String) { } virtual ~ValueExpression() { } private: virtual bool truth() const override { if (m_type == Type::String) return !as_string.is_empty(); return integer() != 0; } virtual int integer() const override { switch (m_type) { case Type::Integer: return as_integer; case Type::String: if (auto converted = as_string.to_int(); converted.has_value()) return converted.value(); fail("Not an integer: '{}'", as_string); } VERIFY_NOT_REACHED(); } virtual String string() const override { switch (m_type) { case Type::Integer: return String::formatted("{}", as_integer); case Type::String: return as_string; } VERIFY_NOT_REACHED(); } virtual Type type() const override { return m_type; } union { int as_integer; String as_string; }; Type m_type { Type::String }; }; class BooleanExpression : public Expression { public: enum class BooleanOperator { And, Or, }; static BooleanOperator op_from(StringView sv) { if (sv == "&") return BooleanOperator::And; return BooleanOperator::Or; } BooleanExpression(BooleanOperator op, NonnullOwnPtr&& left, NonnullOwnPtr&& right) : m_op(op) , m_left(move(left)) , m_right(move(right)) { if (m_op == BooleanOperator::Or) m_left_truth = m_left->truth(); else m_right_truth = m_right->truth(); } private: virtual bool truth() const override { if (m_op == BooleanOperator::Or) return m_left_truth ? true : m_right->truth(); return m_right_truth ? m_left->truth() : false; } virtual int integer() const override { switch (m_op) { case BooleanOperator::And: if (m_right_truth) return m_left->integer(); return 0; case BooleanOperator::Or: if (m_left_truth) return m_left->integer(); return m_right->integer(); } VERIFY_NOT_REACHED(); } virtual String string() const override { switch (m_op) { case BooleanOperator::And: if (m_right_truth) return m_left->string(); return "0"; case BooleanOperator::Or: if (m_left_truth) return m_left->string(); return m_right->string(); } VERIFY_NOT_REACHED(); } virtual Type type() const override { switch (m_op) { case BooleanOperator::And: if (m_right_truth) return m_left->type(); return m_right->type(); case BooleanOperator::Or: if (m_left_truth) return m_left->type(); return m_right->type(); } VERIFY_NOT_REACHED(); } BooleanOperator m_op { BooleanOperator::And }; NonnullOwnPtr m_left, m_right; bool m_left_truth { false }, m_right_truth { false }; }; class ComparisonExpression : public Expression { public: enum class ComparisonOperation { Less, LessEq, Eq, Neq, GreaterEq, Greater, }; static ComparisonOperation op_from(StringView sv) { if (sv == "<"sv) return ComparisonOperation::Less; if (sv == "<="sv) return ComparisonOperation::LessEq; if (sv == "="sv) return ComparisonOperation::Eq; if (sv == "!="sv) return ComparisonOperation::Neq; if (sv == ">="sv) return ComparisonOperation::GreaterEq; return ComparisonOperation::Greater; } ComparisonExpression(ComparisonOperation op, NonnullOwnPtr&& left, NonnullOwnPtr&& right) : m_op(op) , m_left(move(left)) , m_right(move(right)) { } private: template bool compare(const T& left, const T& right) const { switch (m_op) { case ComparisonOperation::Less: return left < right; case ComparisonOperation::LessEq: return left == right || left < right; case ComparisonOperation::Eq: return left == right; case ComparisonOperation::Neq: return left != right; case ComparisonOperation::GreaterEq: return !(left < right); case ComparisonOperation::Greater: return left != right && !(left < right); } VERIFY_NOT_REACHED(); } virtual bool truth() const override { switch (m_left->type()) { case Type::Integer: return compare(m_left->integer(), m_right->integer()); case Type::String: return compare(m_left->string(), m_right->string()); } VERIFY_NOT_REACHED(); } virtual int integer() const override { return truth(); } virtual String string() const override { return truth() ? "1" : "0"; } virtual Type type() const override { return Type::Integer; } ComparisonOperation m_op { ComparisonOperation::Less }; NonnullOwnPtr m_left, m_right; }; class ArithmeticExpression : public Expression { public: enum class ArithmeticOperation { Sum, Difference, Product, Quotient, Remainder, }; static ArithmeticOperation op_from(StringView sv) { if (sv == "+"sv) return ArithmeticOperation::Sum; if (sv == "-"sv) return ArithmeticOperation::Difference; if (sv == "*"sv) return ArithmeticOperation::Product; if (sv == "/"sv) return ArithmeticOperation::Quotient; return ArithmeticOperation::Remainder; } ArithmeticExpression(ArithmeticOperation op, NonnullOwnPtr&& left, NonnullOwnPtr&& right) : m_op(op) , m_left(move(left)) , m_right(move(right)) { } private: virtual bool truth() const override { switch (m_op) { case ArithmeticOperation::Sum: return m_left->truth() || m_right->truth(); default: return integer() != 0; } } virtual int integer() const override { auto right = m_right->integer(); if (right == 0) { if (m_op == ArithmeticOperation::Product) return 0; if (m_op == ArithmeticOperation::Quotient || m_op == ArithmeticOperation::Remainder) fail("Division by zero"); } auto left = m_left->integer(); switch (m_op) { case ArithmeticOperation::Product: return right * left; case ArithmeticOperation::Sum: return right + left; case ArithmeticOperation::Difference: return left - right; case ArithmeticOperation::Quotient: return left / right; case ArithmeticOperation::Remainder: return left % right; } VERIFY_NOT_REACHED(); } virtual String string() const override { return String::formatted("{}", integer()); } virtual Type type() const override { return Type::Integer; } ArithmeticOperation m_op { ArithmeticOperation::Sum }; NonnullOwnPtr m_left, m_right; }; class StringExpression : public Expression { public: enum class StringOperation { Substring, Index, Length, Match, }; StringExpression(StringOperation op, NonnullOwnPtr string, OwnPtr pos_or_chars = {}, OwnPtr length = {}) : m_op(op) , m_str(move(string)) , m_pos_or_chars(move(pos_or_chars)) , m_length(move(length)) { } private: virtual bool truth() const override { if (type() == Expression::Type::String) return !string().is_empty(); return integer() != 0; } virtual int integer() const override { if (m_op == StringOperation::Substring || m_op == StringOperation::Match) { auto substr = string(); if (auto integer = substr.to_int(); integer.has_value()) return integer.value(); else fail("Not an integer: '{}'", substr); } if (m_op == StringOperation::Index) { if (auto idx = m_str->string().find(m_pos_or_chars->string()); idx.has_value()) return idx.value() + 1; return 0; } if (m_op == StringOperation::Length) return m_str->string().length(); VERIFY_NOT_REACHED(); } static auto safe_substring(const String& str, int start, int length) { if (start < 1 || (size_t)start > str.length()) fail("Index out of range"); --start; if (str.length() - start < (size_t)length) fail("Index out of range"); return str.substring(start, length); } virtual String string() const override { if (m_op == StringOperation::Substring) return safe_substring(m_str->string(), m_pos_or_chars->integer(), m_length->integer()); if (m_op == StringOperation::Match) { auto match = m_compiled_regex->match(m_str->string(), PosixFlags::Global); if (m_compiled_regex->parser_result.capture_groups_count == 0) { if (!match.success) return "0"; size_t count = 0; for (auto& m : match.matches) count += m.view.length(); return String::number(count); } else { if (!match.success) return ""; StringBuilder result; for (auto& e : match.capture_group_matches[0]) result.append(e.view.string_view()); return result.build(); } } return String::number(integer()); } virtual Type type() const override { if (m_op == StringOperation::Substring) return Type::String; if (m_op == StringOperation::Match) { if (!m_pos_or_chars) fail("'match' expects a string pattern"); ensure_regex(); if (m_compiled_regex->parser_result.capture_groups_count == 0) return Type::Integer; return Type::String; } return Type::Integer; } void ensure_regex() const { if (!m_compiled_regex) { m_compiled_regex = make>(m_pos_or_chars->string()); if (m_compiled_regex->parser_result.error != regex::Error::NoError) fail("Regex error: {}", regex::get_error_string(m_compiled_regex->parser_result.error)); } } StringOperation m_op { StringOperation::Substring }; NonnullOwnPtr m_str; OwnPtr m_pos_or_chars, m_length; mutable OwnPtr> m_compiled_regex; }; NonnullOwnPtr Expression::parse(Queue& args, Precedence prec) { switch (prec) { case Or: { auto left = parse(args, And); while (!args.is_empty() && args.head() == "|") { args.dequeue(); auto right = parse(args, And); left = make(BooleanExpression::BooleanOperator::Or, move(left), move(right)); } return left; } case And: { auto left = parse(args, Comp); while (!args.is_empty() && args.head() == "&"sv) { args.dequeue(); auto right = parse(args, Comp); left = make(BooleanExpression::BooleanOperator::And, move(left), move(right)); } return left; } case Comp: { auto left = parse(args, ArithS); while (!args.is_empty() && args.head().is_one_of("<"sv, "<="sv, "="sv, "!="sv, "=>"sv, ">"sv)) { auto op = args.dequeue(); auto right = parse(args, ArithM); left = make(ComparisonExpression::op_from(op), move(left), move(right)); } return left; } case ArithS: { auto left = parse(args, ArithM); while (!args.is_empty() && args.head().is_one_of("+"sv, "-"sv)) { auto op = args.dequeue(); auto right = parse(args, ArithM); left = make(ArithmeticExpression::op_from(op), move(left), move(right)); } return left; } case ArithM: { auto left = parse(args, StringO); while (!args.is_empty() && args.head().is_one_of("*"sv, "/"sv, "%"sv)) { auto op = args.dequeue(); auto right = parse(args, StringO); left = make(ArithmeticExpression::op_from(op), move(left), move(right)); } return left; } case StringO: { if (args.is_empty()) fail("Expected a term"); OwnPtr left; while (!args.is_empty()) { auto& op = args.head(); if (op == "+"sv) { args.dequeue(); left = make(args.dequeue()); } else if (op == "substr"sv) { args.dequeue(); auto str = parse(args, Paren); auto pos = parse(args, Paren); auto len = parse(args, Paren); left = make(StringExpression::StringOperation::Substring, move(str), move(pos), move(len)); } else if (op == "index"sv) { args.dequeue(); auto str = parse(args, Paren); auto chars = parse(args, Paren); left = make(StringExpression::StringOperation::Index, move(str), move(chars)); } else if (op == "match"sv) { args.dequeue(); auto str = parse(args, Paren); auto pattern = parse(args, Paren); left = make(StringExpression::StringOperation::Match, move(str), move(pattern)); } else if (op == "length"sv) { args.dequeue(); auto str = parse(args, Paren); left = make(StringExpression::StringOperation::Length, move(str)); } else if (!left) { left = parse(args, Paren); } if (!args.is_empty() && args.head() == ":"sv) { args.dequeue(); auto right = parse(args, Paren); left = make(StringExpression::StringOperation::Match, left.release_nonnull(), move(right)); } else { return left.release_nonnull(); } } return left.release_nonnull(); } case Paren: { if (args.is_empty()) fail("Expected a term"); if (args.head() == "("sv) { args.dequeue(); auto expr = parse(args); if (args.head() != ")") fail("Expected a close paren"); args.dequeue(); return expr; } return make(args.dequeue()); } } fail("Invalid expression"); } ErrorOr serenity_main(Main::Arguments arguments) { TRY(Core::System::pledge("stdio"sv)); TRY(Core::System::unveil(nullptr, nullptr)); if ((arguments.strings.size() == 2 && "--help"sv == arguments.strings[1]) || arguments.strings.size() == 1) print_help_and_exit(); Queue args; for (size_t i = 1; i < arguments.strings.size(); ++i) args.enqueue(arguments.strings[i]); auto expression = Expression::parse(args); if (!args.is_empty()) fail("Extra tokens at the end of the expression"); switch (expression->type()) { case Expression::Type::Integer: outln("{}", expression->integer()); break; case Expression::Type::String: outln("{}", expression->string()); break; } return expression->truth() ? 0 : 1; }