115 lines
4.3 KiB
C++
115 lines
4.3 KiB
C++
/*
|
|
* SPDX-License-Identifier: LGPL-2.0-or-later
|
|
*
|
|
* SamRegex — a faithful port of plan9 sam's regular-expression engine
|
|
* (plan9port src/cmd/sam/regexp.c) to operate on a QString.
|
|
*
|
|
* Why this exists: sam's regex is a Thompson/Pike NFA simulation. It is
|
|
* - leftmost-longest (POSIX semantics), not Perl leftmost-greedy — so it
|
|
* matches sam exactly, including overlapping alternation (`a|ab` on "ab"
|
|
* matches "ab", the longest), which PCRE does not; and
|
|
* - linear time in the subject length with no backtracking, so it cannot
|
|
* suffer catastrophic blowup.
|
|
*
|
|
* The dialect is sam's regexp(7), deliberately small:
|
|
* . * + ? | ( ) ^ $ [ ] and \ to escape a metacharacter (\n = newline).
|
|
* `^` matches the beginning of a line, `$` the end of a line (per-line, built
|
|
* in). `.` and negated classes do not match newline. There is no \d \w \b,
|
|
* no lookaround, no non-greedy — sam never had them, and their absence keeps
|
|
* the engine linear and the syntax portable back to real sam.
|
|
*
|
|
* The implementation mirrors regexp.c structure: a shunting-yard parser builds
|
|
* an Inst NFA; execute() runs two parallel thread lists (Pike VM) where each
|
|
* thread carries a capture set, and newmatch() keeps the leftmost-longest.
|
|
* Only the forward machine is ported; backward search is done by the caller
|
|
* scanning forward and keeping the last qualifying match.
|
|
*/
|
|
#ifndef KATECUSTOM_SAMREGEX_H
|
|
#define KATECUSTOM_SAMREGEX_H
|
|
|
|
#include <QList>
|
|
#include <QString>
|
|
#include <QVector>
|
|
#include <optional>
|
|
|
|
namespace katecustom
|
|
{
|
|
|
|
/*! A match: half-open character ranges. caps[0] is the whole match; caps[1..9]
|
|
* are parenthesised subexpressions ({-1,-1} if that group did not participate). */
|
|
struct SamMatch {
|
|
struct Range {
|
|
int p1 = -1;
|
|
int p2 = -1;
|
|
};
|
|
QVector<Range> caps; // size NSUBEXP (10)
|
|
int start() const { return caps.isEmpty() ? -1 : caps[0].p1; }
|
|
int end() const { return caps.isEmpty() ? -1 : caps[0].p2; }
|
|
QString captured(int group, const QString &text) const;
|
|
};
|
|
|
|
class SamRegex
|
|
{
|
|
public:
|
|
SamRegex() = default;
|
|
|
|
/*! Compile \a pattern (sam dialect). Returns false and sets error() on a
|
|
* syntax error. */
|
|
bool compile(const QString &pattern);
|
|
|
|
bool isValid() const { return m_valid; }
|
|
QString error() const { return m_error; }
|
|
QString pattern() const { return m_pattern; }
|
|
|
|
/*!
|
|
* Find the leftmost-longest match whose start is at or after \a from and
|
|
* which lies entirely within [0, \a bound). Anchors `^`/`$` are evaluated
|
|
* against the whole \a text (so they fire at any line boundary inside the
|
|
* bound). Returns std::nullopt if there is no match. Linear time.
|
|
*/
|
|
std::optional<SamMatch> match(const QString &text, int from, int bound) const;
|
|
|
|
private:
|
|
// NFA instruction (regexp.c Inst). type < kOperator is a literal rune;
|
|
// otherwise it is one of the action codes below.
|
|
//
|
|
// In the original, `left` and `next` share one union member (l.lleft ==
|
|
// l.lnext): an OR node's "left/continuation" IS its next pointer, so
|
|
// concatenation (which wires `.next`) and OR execution (which reads the
|
|
// continuation) refer to the same slot. We keep that single `next` slot and
|
|
// use `right` for the OR's branch target. `subid`/`classIdx` reuse would be
|
|
// the r-union; they are used disjointly per node type so separate fields are
|
|
// safe.
|
|
struct Inst {
|
|
int type = 0;
|
|
int subid = -1; // LBRA/RBRA capture index
|
|
int classIdx = -1; // CCLASS/NCCLASS index into m_classes
|
|
int next = -1; // continuation (also the OR "left" branch)
|
|
int right = -1; // OR branch target (loop body / first alternative)
|
|
};
|
|
|
|
// A character class: a flat list of items, each either a single rune or a
|
|
// range [lo,hi]. Negated classes also implicitly exclude '\n'.
|
|
struct ClassItem {
|
|
bool range = false;
|
|
int lo = 0;
|
|
int hi = 0;
|
|
};
|
|
|
|
bool classMatch(int classIdx, QChar c, bool negate) const;
|
|
|
|
QString m_pattern;
|
|
bool m_valid = false;
|
|
QString m_error;
|
|
|
|
QVector<Inst> m_prog; // the compiled program
|
|
int m_start = -1; // index of the start instruction
|
|
QVector<QVector<ClassItem>> m_classes;
|
|
|
|
friend class SamRegexCompiler;
|
|
};
|
|
|
|
} // namespace katecustom
|
|
|
|
#endif
|