Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Tests/Utilities/TestSed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,10 @@ TEST_CASE(complex)
{
run_sed({ "h; x; s/./*/gp; x; h; p; x; s/./*/gp", "-n" }, "hello serenity"sv, "**************\nhello serenity\n**************\n"sv);
}

TEST_CASE(strict_posix)
{
run_sed({ "--posix", "s/(.)/b/" }, "foo\n"sv, "foo\n"sv);
run_sed({ "--posix", "s/\\(.\\)/b/" }, "foo\n"sv, "boo\n"sv);
run_sed({ "--posix", "s/\\(.\\)\\(.*\\)/\\1\\2\\1/" }, "foo\n"sv, "foof\n"sv);
}
43 changes: 30 additions & 13 deletions Userland/Utilities/sed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,13 @@ struct RArguments : FilepathArgument<RArguments> {
};

struct SArguments {
Regex<PosixExtended> regex;
Variant<Regex<PosixExtended>, Regex<PosixBasic>> regex;
StringView replacement;
PosixOptions options;
bool print;
Optional<StringView> output_filepath;

static SedErrorOr<SArguments> parse(GenericLexer& lexer)
static SedErrorOr<SArguments> parse(GenericLexer& lexer, bool strict_posix)
{
auto generic_error_message = "Incomplete substitution command"sv;

Expand Down Expand Up @@ -307,7 +307,11 @@ struct SArguments {
}
}

return SArguments { Regex<PosixExtended> { pattern }, replacement, options, print, output_filepath };
if (strict_posix) {
return SArguments { Regex<PosixBasic> { pattern }, replacement, options, print, output_filepath };
} else {
return SArguments { Regex<PosixExtended> { pattern }, replacement, options, print, output_filepath };
}
}

private:
Expand Down Expand Up @@ -503,7 +507,7 @@ static SedErrorOr<void> verify_number_of_addresses(Command const& command)
return {};
}

static SedErrorOr<Command> parse_command(GenericLexer& lexer)
static SedErrorOr<Command> parse_command(GenericLexer& lexer, bool strict_posix)
{
lexer.consume_while(is_ascii_blank);

Expand Down Expand Up @@ -551,7 +555,7 @@ static SedErrorOr<Command> parse_command(GenericLexer& lexer)
command.arguments = TRY(RArguments::parse(lexer));
break;
case 's':
command.arguments = TRY(SArguments::parse(lexer));
command.arguments = TRY(SArguments::parse(lexer, strict_posix));
break;
case 't':
command.arguments = TRY(TArguments::parse(lexer));
Expand Down Expand Up @@ -586,15 +590,15 @@ static SedErrorOr<Command> parse_command(GenericLexer& lexer)

class Script {
public:
[[nodiscard]] bool add_script_part(StringView data)
[[nodiscard]] bool add_script_part(StringView data, bool strict_posix)
{
auto last_pos = m_script.length();
m_script.append(data);
auto lexer = GenericLexer(m_script.string_view().substring_view(last_pos));
while (!lexer.is_eof()) {
if (lexer.is_eof())
break;
auto maybe_command = parse_command(lexer);
auto maybe_command = parse_command(lexer, strict_posix);
if (maybe_command.is_error()) {
warnln("Problem while parsing script part: {}", maybe_command.release_error().message());
return false;
Expand Down Expand Up @@ -866,7 +870,7 @@ static ErrorOr<CycleDecision> apply(Command const& command, StringBuilder& patte
case 's': {
auto pattern_space_sv = pattern_space.string_view();
auto const& s_args = command.arguments->get<SArguments>();
auto result = s_args.regex.replace(pattern_space_sv, s_args.replacement, s_args.options);
auto result = s_args.regex.visit([&](auto& re) { return re.replace(pattern_space_sv, s_args.replacement, s_args.options); });
auto replacement_made = result != pattern_space_sv;
pattern_space.clear();
pattern_space.append(result);
Expand Down Expand Up @@ -1001,17 +1005,19 @@ ErrorOr<int> serenity_main(Main::Arguments args)

bool suppress_default_output = false;
bool edit_in_place = false;
bool strict_posix = false;
Core::ArgsParser arg_parser;
Script script;
Vector<StringView> pos_args;
Vector<StringView> script_parts;
arg_parser.set_general_help("The Stream EDitor");
arg_parser.add_option(suppress_default_output, "suppress default output", nullptr, 'n');
arg_parser.add_option(Core::ArgsParser::Option {
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
.help_string = "A file containing script commands",
.short_name = 'f',
.value_name = "script-file",
.accept_value = [&script](StringView script_file) {
.accept_value = [&script_parts](StringView script_file) {
auto maybe_file = Core::File::open(script_file, Core::File::OpenMode::Read);
if (maybe_file.is_error()) {
warnln("Failed to open script file: {}", maybe_file.release_error());
Expand All @@ -1022,22 +1028,33 @@ ErrorOr<int> serenity_main(Main::Arguments args)
warnln("Failed to read contents of script file {}: {}", script_file, maybe_file_contents.release_error());
return false;
}
return script.add_script_part(StringView { maybe_file_contents.release_value().bytes() });
script_parts.append(StringView { maybe_file_contents.release_value().bytes() });
return true;
},
});
arg_parser.add_option(Core::ArgsParser::Option {
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
.help_string = "A script of commands",
.short_name = 'e',
.value_name = "script",
.accept_value = [&script](StringView script_argument) {
return script.add_script_part(script_argument);
.accept_value = [&script_parts](StringView script_argument) {
script_parts.append(script_argument);
return true;
},
});
arg_parser.add_ignored("regexp-extended", 'E');
arg_parser.add_option(strict_posix, "Use POSIX regular expressions", "posix", 'p');
arg_parser.add_option(edit_in_place, "Edit file in place, implies -n", "in-place", 'i');
arg_parser.add_positional_argument(pos_args, "script and/or file", "...", Core::ArgsParser::Required::No);
arg_parser.parse(args);

for (StringView part : script_parts) {
if (!script.add_script_part(part, strict_posix)) {
// Warning has already been emitted so just exit.
return 1;
}
}

// When editing in-place, there's also no default output.
suppress_default_output |= edit_in_place;

Expand All @@ -1050,7 +1067,7 @@ ErrorOr<int> serenity_main(Main::Arguments args)
warnln("No script specified, aborting");
return 1;
}
if (!script.add_script_part(pos_args[0])) {
if (!script.add_script_part(pos_args[0], strict_posix)) {
return 1;
}
pos_args.remove(0);
Expand Down
Loading