Files
2026-06-09 00:22:22 -05:00

89 lines
2.5 KiB
C++

#pragma once
#include <cstdint>
#include <optional>
#include <regex.h>
#include <string>
#include <vector>
namespace sedpp {
// Address kinds mirror GNU sed's address grammar. Step, Plus, and Modulo are
// GNU extensions: FIRST~STEP as a standalone address, and +N/~N only as the
// second half of a range.
enum class AddressKind {
None,
Line,
Last,
Regex,
Step,
Plus,
Modulo,
};
// A parsed address keeps both the original text and numeric forms. The text is
// still needed for special cases such as line address 0 and for empty regexes,
// which mean "reuse the previous regular expression" at runtime.
struct Address {
AddressKind kind = AddressKind::None;
std::string text;
std::uint64_t first = 0;
std::uint64_t second = 0;
};
// Parsed state for the s command. flagOrder preserves p/e ordering because GNU
// sed changes observable output depending on whether printing happens before or
// after shell execution.
struct Substitute {
std::string pattern;
std::string replacement;
bool global = false;
bool print = false;
bool execute = false;
bool ignoreCase = false;
bool multiline = false;
std::uint64_t occurrence = 0;
std::string flagOrder;
std::string writeFile;
};
// y transliteration operates on decoded character strings, not regex syntax.
struct Translate {
std::string from;
std::string to;
};
// A compiled sed command. The opcode selects which optional payload is valid:
// text for a/i/c/r/R/w/W/e, label for :/b/t/T, substitute for s, translate for y.
struct Command {
std::optional<Address> firstAddress;
std::optional<Address> secondAddress;
bool negate = false;
char opcode = '\0';
// These fields are intentionally plain. sed commands are compact and varied;
// a tiny tagged structure is easier to audit than a deep inheritance tree.
std::string text;
std::string label;
std::optional<Substitute> substitute;
std::optional<Translate> translate;
int exitCode = 0;
int lineLength = 0;
// Runtime range state. GNU sed keeps range activation per compiled command,
// not globally, so the state lives with the command object.
bool rangeActive = false;
bool rangeJustStarted = false;
std::uint64_t rangeCountdown = 0;
bool zeroRangeStarted = false;
};
// The compiler flattens the sed script into one vector. Braced blocks remain
// explicit { and } opcodes so the runner can skip inactive blocks without a
// second tree-shaped representation.
struct Program {
std::vector<Command> commands;
};
} // namespace sedpp