mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +00:00
de395a3df2
We had two functions for doing mostly the same thing. Combine both of them into String::find() and use that everywhere. Also add some tests to cover basic behavior.
600 lines
17 KiB
C++
600 lines
17 KiB
C++
/*
|
|
* Copyright (c) 2020, the SerenityOS developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Assertions.h>
|
|
#include <AK/GenericLexer.h>
|
|
#include <AK/NonnullOwnPtr.h>
|
|
#include <AK/OwnPtr.h>
|
|
#include <AK/Queue.h>
|
|
#include <AK/String.h>
|
|
#include <AK/StringView.h>
|
|
#include <LibRegex/Regex.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
|
|
static void print_help_and_exit()
|
|
{
|
|
outln(R"(
|
|
Usage: expr EXPRESSION
|
|
expr [--help]
|
|
|
|
Print the value of EXPRESSION to standard output.)");
|
|
exit(0);
|
|
}
|
|
|
|
template<typename Fmt, typename... Args>
|
|
[[noreturn]] void fail(Fmt&& fmt, Args&&... args)
|
|
{
|
|
warn("ERROR: \e[31m");
|
|
warnln(StringView { fmt }, args...);
|
|
warn("\e[0m");
|
|
exit(1);
|
|
}
|
|
|
|
class Expression {
|
|
public:
|
|
enum Precedence {
|
|
Or,
|
|
And,
|
|
Comp,
|
|
ArithS,
|
|
ArithM,
|
|
StringO,
|
|
Paren,
|
|
};
|
|
static NonnullOwnPtr<Expression> parse(Queue<StringView>& 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(const StringView& sv)
|
|
{
|
|
if (sv == "&")
|
|
return BooleanOperator::And;
|
|
return BooleanOperator::Or;
|
|
}
|
|
BooleanExpression(BooleanOperator op, NonnullOwnPtr<Expression>&& left, NonnullOwnPtr<Expression>&& 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<Expression> 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(const StringView& sv)
|
|
{
|
|
if (sv == "<")
|
|
return ComparisonOperation::Less;
|
|
if (sv == "<=")
|
|
return ComparisonOperation::LessEq;
|
|
if (sv == "=")
|
|
return ComparisonOperation::Eq;
|
|
if (sv == "!=")
|
|
return ComparisonOperation::Neq;
|
|
if (sv == ">=")
|
|
return ComparisonOperation::GreaterEq;
|
|
return ComparisonOperation::Greater;
|
|
}
|
|
|
|
ComparisonExpression(ComparisonOperation op, NonnullOwnPtr<Expression>&& left, NonnullOwnPtr<Expression>&& right)
|
|
: m_op(op)
|
|
, m_left(move(left))
|
|
, m_right(move(right))
|
|
{
|
|
}
|
|
|
|
private:
|
|
template<typename T>
|
|
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<Expression> m_left, m_right;
|
|
};
|
|
|
|
class ArithmeticExpression : public Expression {
|
|
public:
|
|
enum class ArithmeticOperation {
|
|
Sum,
|
|
Difference,
|
|
Product,
|
|
Quotient,
|
|
Remainder,
|
|
};
|
|
static ArithmeticOperation op_from(const StringView& sv)
|
|
{
|
|
if (sv == "+")
|
|
return ArithmeticOperation::Sum;
|
|
if (sv == "-")
|
|
return ArithmeticOperation::Difference;
|
|
if (sv == "*")
|
|
return ArithmeticOperation::Product;
|
|
if (sv == "/")
|
|
return ArithmeticOperation::Quotient;
|
|
return ArithmeticOperation::Remainder;
|
|
}
|
|
ArithmeticExpression(ArithmeticOperation op, NonnullOwnPtr<Expression>&& left, NonnullOwnPtr<Expression>&& 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<Expression> m_left, m_right;
|
|
};
|
|
|
|
class StringExpression : public Expression {
|
|
public:
|
|
enum class StringOperation {
|
|
Substring,
|
|
Index,
|
|
Length,
|
|
Match,
|
|
};
|
|
|
|
StringExpression(StringOperation op, NonnullOwnPtr<Expression> string, OwnPtr<Expression> pos_or_chars = {}, OwnPtr<Expression> 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 { 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& m : match.capture_group_matches) {
|
|
for (auto& e : m)
|
|
result.append(e.view.to_string());
|
|
}
|
|
|
|
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<regex::Regex<PosixExtended>>(m_pos_or_chars->string());
|
|
}
|
|
|
|
StringOperation m_op { StringOperation::Substring };
|
|
NonnullOwnPtr<Expression> m_str;
|
|
OwnPtr<Expression> m_pos_or_chars, m_length;
|
|
mutable OwnPtr<regex::Regex<PosixExtended>> m_compiled_regex;
|
|
};
|
|
|
|
NonnullOwnPtr<Expression> Expression::parse(Queue<StringView>& 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>(BooleanExpression::BooleanOperator::Or, move(left), move(right));
|
|
}
|
|
return left;
|
|
}
|
|
case And: {
|
|
auto left = parse(args, Comp);
|
|
while (!args.is_empty() && args.head() == "&") {
|
|
args.dequeue();
|
|
auto right = parse(args, Comp);
|
|
left = make<BooleanExpression>(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("<", "<=", "=", "!=", "=>", ">")) {
|
|
auto op = args.dequeue();
|
|
auto right = parse(args, ArithM);
|
|
left = make<ComparisonExpression>(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("+", "-")) {
|
|
auto op = args.dequeue();
|
|
auto right = parse(args, ArithM);
|
|
left = make<ArithmeticExpression>(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("*", "/", "%")) {
|
|
auto op = args.dequeue();
|
|
auto right = parse(args, StringO);
|
|
left = make<ArithmeticExpression>(ArithmeticExpression::op_from(op), move(left), move(right));
|
|
}
|
|
return left;
|
|
}
|
|
case StringO: {
|
|
if (args.is_empty())
|
|
fail("Expected a term");
|
|
|
|
OwnPtr<Expression> left;
|
|
|
|
while (!args.is_empty()) {
|
|
auto& op = args.head();
|
|
if (op == "+") {
|
|
args.dequeue();
|
|
left = make<ValueExpression>(args.dequeue());
|
|
} else if (op == "substr") {
|
|
args.dequeue();
|
|
auto str = parse(args, Paren);
|
|
auto pos = parse(args, Paren);
|
|
auto len = parse(args, Paren);
|
|
left = make<StringExpression>(StringExpression::StringOperation::Substring, move(str), move(pos), move(len));
|
|
} else if (op == "index") {
|
|
args.dequeue();
|
|
auto str = parse(args, Paren);
|
|
auto chars = parse(args, Paren);
|
|
left = make<StringExpression>(StringExpression::StringOperation::Index, move(str), move(chars));
|
|
} else if (op == "match") {
|
|
args.dequeue();
|
|
auto str = parse(args, Paren);
|
|
auto pattern = parse(args, Paren);
|
|
left = make<StringExpression>(StringExpression::StringOperation::Match, move(str), move(pattern));
|
|
} else if (op == "length") {
|
|
args.dequeue();
|
|
auto str = parse(args, Paren);
|
|
left = make<StringExpression>(StringExpression::StringOperation::Length, move(str));
|
|
} else if (!left) {
|
|
left = parse(args, Paren);
|
|
}
|
|
|
|
if (!args.is_empty() && args.head() == ":") {
|
|
args.dequeue();
|
|
auto right = parse(args, Paren);
|
|
left = make<StringExpression>(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() == "(") {
|
|
args.dequeue();
|
|
auto expr = parse(args);
|
|
if (args.head() != ")")
|
|
fail("Expected a close paren");
|
|
args.dequeue();
|
|
return expr;
|
|
}
|
|
|
|
return make<ValueExpression>(args.dequeue());
|
|
}
|
|
}
|
|
|
|
fail("Invalid expression");
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
if (pledge("stdio", nullptr) < 0) {
|
|
perror("pledge");
|
|
return 1;
|
|
}
|
|
|
|
if (unveil(nullptr, nullptr) < 0) {
|
|
perror("unveil");
|
|
return 1;
|
|
}
|
|
|
|
if ((argc == 2 && StringView { "--help" } == argv[1]) || argc == 1)
|
|
print_help_and_exit();
|
|
|
|
Queue<StringView> args;
|
|
for (int i = 1; i < argc; ++i)
|
|
args.enqueue(argv[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 0;
|
|
}
|