Files
CoreUtils/Parser.h
T
Brad ArantandClaude Sonnet 5 a033cc6718 Add StreamReader and rebuild Parser/LineParser/IMFHeader/IMFBoundary on it
Introduces StreamReader to accumulate socket data delivered in arbitrary
chunks into one buffer, so a Parser can build an object (a line, a header
block, a MIME boundary) that spans any number of packets without losing
its place. Parser is redesigned around this: no longer inherits MString
(was redundant with its own buffer member), takes a StreamReader&, and
drives a resumable COMPLETE/INCOMPLETE/DATA_AVAIL state-machine protocol
via doParse(). LineParser, IMFHeader, and IMFBoundary are rewritten as
real, working resumable parsers against the shared buffer instead of the
previous non-compiling stubs (mismatched constructors/return types, a
syntax error, checkParse() never implemented).

IMFFormData is left as the stub it already was, just restored to
compiling against the new IMFHeader signature - fully wiring it up needs
IMFMultipart/IMFText migrated to StreamReader too, out of scope here.

Verified with real runtime tests (not just compilation) covering data
split mid-line, mid-CRLF, mid-header-fold, and mid-boundary-marker across
separate feed() calls, plus StreamReader::compact(). Along the way fixed
a header-value bug (space after ':' wasn't skipped) and a double
cursor-offset subtraction bug in two places caused by forgetting that
ZString::getLength() is already cursor-relative, not the raw buffer size.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 14:25:58 -07:00

83 lines
2.7 KiB
C++

#ifndef __Parser_h__
#define __Parser_h__
#include "StreamReader.h"
namespace coreutils {
///
/// Parser is the base class for building a protocol/object parser that reads
/// from a shared StreamReader's buffer. A subclass implements parse() as a
/// resumable state machine (typically a switch on an internal state member)
/// that consumes as much of the buffer as it currently can and reports how
/// the parse should proceed via Status:
///
/// - COMPLETE: the object this Parser builds is fully parsed and ready.
/// - INCOMPLETE: not enough data is currently buffered to make more
/// progress; the caller should wait for more data
/// (StreamReader::feed()) before calling doParse() again.
/// The subclass's state member must reflect exactly where
/// parsing should resume - and must NOT have consumed any
/// cursor position it can't yet commit to (see individual
/// Parser subclasses for how they guarantee this).
/// - DATA_AVAIL: this call made progress and there is more buffered data
/// this Parser can still work through right now, without
/// waiting for new data to arrive.
///
/// Call doParse() (not parse() directly) to drive a Parser - it loops on
/// DATA_AVAIL automatically and returns once the result is COMPLETE or
/// INCOMPLETE.
///
class Parser {
public:
Parser(StreamReader &reader);
enum class Status {
COMPLETE,
INCOMPLETE,
DATA_AVAIL
};
virtual ~Parser();
///
/// doParse is the entry and re-entry point method to continue the
/// parsing process. Safe to call repeatedly (e.g. once per
/// StreamReader::feed()) - each call resumes exactly where the
/// previous one left off.
///
Status doParse();
///
/// Inside of parse() you can call another parser object with the
/// checkParse method to attempt parsing with a candidate sub-parser
/// sharing the same StreamReader. Returns true only once that
/// sub-parser fully completes - retain the same sub-parser instance
/// across re-entries into your own parse() (e.g. as a member) so it
/// can resume, rather than constructing a new one on every call.
///
bool checkParse(Parser &parser);
protected:
///
/// parse() performs one resumable step of parsing against reader's
/// buffer. Must be implemented by every concrete Parser to build its
/// specific object.
///
virtual Status parse() = 0;
StreamReader &reader;
};
}
#endif