// M1 api — the public facade: `repair`, `parse` and `PartialParser`.
//
// The facade is the only place that turns engine state into user visible
// results, and the only place that owns the public result types.
///|
/// How complete a value is.
pub(all) enum Completeness {
Empty
Partial
Complete
}
///|
/// Explicit `Eq` implementation for `Completeness`.
pub impl Eq for Completeness with fn equal(
self : Completeness,
other : Completeness,
) -> Bool {
(self is Empty && other is Empty) ||
(self is Partial && other is Partial) ||
(self is Complete && other is Complete)
}
///|
/// Explicit `Eq` extension for `Completeness`.
pub extend Completeness with Eq::{not_equal, equal}
///|
/// One step of `Snapshot::open_path`.
pub(all) enum PathSegment {
Key(String)
Index(Int32)
}
///|
/// Explicit `Eq` implementation for `PathSegment`.
pub impl Eq for PathSegment with fn equal(
self : PathSegment,
other : PathSegment,
) -> Bool {
match (self, other) {
(Key(a), Key(b)) => a == b
(Index(a), Index(b)) => a == b
_ => false
}
}
///|
/// Explicit `Eq` extension for `PathSegment`.
pub extend PathSegment with Eq::{not_equal, equal}
///|
/// Result of `repair`.
pub(all) struct RepairResult {
text : String
repairs : Array[Repair]
completeness : Completeness
repairs_truncated : Bool
exactness : Exactness
/// Whether any recorded repair admits a second reading (FR-27).
ambiguous : Bool
}
///|
/// Result of `parse`.
pub(all) struct ParseResult {
value : Json
repairs : Array[Repair]
completeness : Completeness
repairs_truncated : Bool
exactness : Exactness
/// Whether any recorded repair admits a second reading (FR-27).
ambiguous : Bool
}
///|
/// A non-destructive view of the value parsed so far.
pub(all) struct Snapshot {
value : Json?
completeness : Completeness
open_path : Array[PathSegment]
/// Exactness of everything consumed so far. A snapshot is a display channel,
/// so this is informational; the execution channel is `finish`.
exactness : Exactness
/// Whether anything consumed so far was repaired ambiguously (FR-27).
ambiguous : Bool
}
///|
/// Result of `PartialParser::finish`.
pub(all) struct FinalResult {
text : String
value : Json
repairs : Array[Repair]
completeness : Completeness
repairs_truncated : Bool
exactness : Exactness
/// Whether any recorded repair admits a second reading (FR-27).
ambiguous : Bool
}
///|
/// Operation of one `Delta` (FR-18). The names follow JSON Patch (RFC 6902)
/// where an equivalent exists; `AppendText` has no RFC 6902 counterpart.
pub(all) enum DeltaOp {
/// The value at `path` was created or replaced; `value` is its content.
Set
/// `text` was appended to the string at `path`.
AppendText
/// The container at `path` is complete; `value` is its final content. A
/// later `Set` on the same path is still legal: `duplicate_keys KeepLast`
/// overwrites a member after its container was closed.
Close
}
///|
/// One incremental change to the value built so far.
pub(all) struct Delta {
/// JSON Pointer style path (RFC 6901): `Key` segments are object members,
/// `Index` segments are array positions, the root is the empty path.
path : Array[PathSegment]
op : DeltaOp
value : Json?
text : String
}
///|
/// `DeltaOp` as a stable lowercase word, e.g. `append_text`.
pub fn DeltaOp::name(self : DeltaOp) -> String {
match self {
Set => "set"
AppendText => "append_text"
Close => "close"
}
}
///|
/// Explicit `Eq` implementation for `DeltaOp`.
pub impl Eq for DeltaOp with fn equal(self : DeltaOp, other : DeltaOp) -> Bool {
self.name() == other.name()
}
///|
/// Explicit `Eq` extension for `DeltaOp`.
pub extend DeltaOp with Eq::{not_equal, equal}
///|
/// Render a path as an RFC 6901 JSON Pointer, e.g. `/items/0/name`.
pub fn Delta::pointer(self : Delta) -> String {
let sb = StringBuilder()
for seg in self.path {
sb.write_char('/')
match seg {
Key(k) =>
sb.write_string(
k.replace_all(old="~", new="~0").replace_all(old="/", new="~1"),
)
Index(i) => sb.write_string(i.to_string())
}
}
sb.to_string()
}
///|
/// Streaming partial parser. `feed` any number of chunks, then `finish`; the
/// one-shot functions are built on exactly this type.
pub struct PartialParser {
priv engine : Engine
}
///|
/// Create a parser. Invalid options raise `E050`.
pub fn PartialParser::new(options? : Options) -> PartialParser raise MendError {
let opts = match options {
Some(o) => o
None => Options::default()
}
opts.validate()
{ engine: Engine::make(opts), }
}
///|
/// Feed one chunk. A chunk may split any token, including a surrogate pair.
pub fn PartialParser::feed(
self : PartialParser,
chunk : String,
) -> Unit raise MendError {
errdefer {
self.engine.phase = PFailed
}
self.engine.feed_str(chunk)
}
///|
/// Feed raw UTF-8 bytes. Invalid or truncated UTF-8 raises `E040`.
pub fn PartialParser::feed_bytes(
self : PartialParser,
bytes : Bytes,
) -> Unit raise MendError {
errdefer {
self.engine.phase = PFailed
}
self.engine.feed_bytes(bytes)
}
///|
/// Snapshot the value parsed so far. Never raises, never mutates the parser.
///
/// The returned value may share structure with the parser's cache, so it must
/// be treated as immutable; use `snapshot_copy` when the caller needs to
/// mutate it (FR-13).
pub fn PartialParser::snapshot(self : PartialParser) -> Snapshot {
self.engine.snapshot()
}
///|
/// A deep copy of the snapshot: safe to mutate without disturbing the parser
/// or any earlier snapshot (FR-13).
pub fn PartialParser::snapshot_copy(self : PartialParser) -> Snapshot {
let s = self.engine.snapshot()
{
value: match s.value {
Some(v) => Some(copy_json(v))
None => None
},
completeness: s.completeness,
open_path: s.open_path.copy(),
exactness: s.exactness,
ambiguous: s.ambiguous,
}
}
///|
/// Deep copy of a `Json` value.
pub fn copy_json(v : Json) -> Json {
match v {
Json::Array(items) => Json::array(items.map(copy_json))
Json::Object(map) => {
let out : Map[String, Json] = Map([])
for k, item in map {
out[k] = copy_json(item)
}
Json::object(out)
}
_ => v
}
}
///|
/// Take the deltas recorded since the previous call (FR-18).
///
/// Requires `Options::for_llm()` (or any options with `collect_deltas: true`);
/// otherwise this raises `E050`. Applying the returned operations in order to
/// the value obtained from the previous drain yields the current
/// `snapshot().value`.
///|
/// FR-16, the event interface, as a thin wrapper over `drain_deltas`.
///
/// `on_delta` is called once per change, in the order `drain_deltas` would
/// return them, immediately after the change happened. This is deliberately not
/// a separate engine: it is the same log, consumed as it is produced, because
/// the callback is what a caller integrating with an event loop needs and a
/// second implementation would be a second place for the two to diverge.
///
/// The callback runs while the parser is inside `feed`, so it must not feed the
/// parser again; callbacks that need to do so should collect the deltas and act
/// after `feed` returns.
pub fn PartialParser::feed_with(
self : PartialParser,
chunk : String,
on_delta : (Delta) -> Unit,
) -> Unit raise MendError {
self.feed(chunk)
for d in self.drain_deltas() {
on_delta(d)
}
}
///|
/// The same, for byte input (FR-12, FR-16).
pub fn PartialParser::feed_bytes_with(
self : PartialParser,
bytes : Bytes,
on_delta : (Delta) -> Unit,
) -> Unit raise MendError {
self.feed_bytes(bytes)
for d in self.drain_deltas() {
on_delta(d)
}
}
///|
/// `drain_deltas` on the parser that the stream produced, keeping the existing
/// documentation in one place.
pub fn PartialParser::drain_deltas(
self : PartialParser,
) -> Array[Delta] raise MendError {
if !self.engine.opts.collect_deltas {
raise InvalidOption(
"drain_deltas requires collect_deltas: use Options::for_llm()",
)
}
self.engine.drain_deltas()
}
///|
/// Finish parsing: close open containers, produce the repaired text and the
/// value. Calling it twice, or after a fatal error, raises `E030`.
pub fn PartialParser::finish(
self : PartialParser,
) -> FinalResult raise MendError {
errdefer {
self.engine.phase = PFailed
}
self.engine.finish_engine()
}
///|
/// Repair `input` in one shot. Returns the repaired text, the repair records
/// and `Complete`.
pub fn repair(
input : String,
options? : Options,
) -> RepairResult raise MendError {
let opts = match options {
Some(o) => o
None => Options::default()
}
opts.validate()
repair_once(input, opts) catch {
NoJsonFound(off) =>
// See `analyze`: `skip_prose` has no container to find in a scalar-root
// document, so a valid document is refused as prose. Undo the skip when
// the input is valid JSON as written. The offset is re-raised unchanged,
// because `prop_wbtest.mbt` TC-P-03 requires the one-shot and the chunked
// path to agree on it.
if opts.skip_prose && is_valid_json(input) {
repair_once(input, { ..opts, skip_prose: false, })
} else {
raise NoJsonFound(off)
}
e => raise e
}
}
///|
/// One attempt at repairing `input` with `opts`.
fn repair_once(input : String, opts : Options) -> RepairResult raise MendError {
let engine = Engine::make(opts)
engine.feed_str(input)
let fin = engine.finish_engine()
{
text: fin.text,
repairs: fin.repairs,
completeness: fin.completeness,
repairs_truncated: fin.repairs_truncated,
exactness: fin.exactness,
ambiguous: fin.ambiguous,
}
}
///|
/// Repair `input` and return the resulting `Json` value.
pub fn parse(input : String, options? : Options) -> ParseResult raise MendError {
let opts = match options {
Some(o) => o
None => Options::default()
}
opts.validate()
let engine = Engine::make(opts)
engine.feed_str(input)
let fin = engine.finish_engine()
{
value: fin.value,
repairs: fin.repairs,
completeness: fin.completeness,
repairs_truncated: fin.repairs_truncated,
exactness: fin.exactness,
ambiguous: fin.ambiguous,
}
}
///|
/// Whether `text` is valid JSON according to the strict core parser. This is
/// the validation used by the test suite to prove that every repaired output
/// is legal JSON; it performs no repair of its own.
pub fn is_valid_json(text : String) -> Bool {
try {
ignore(@json.parse(text))
true
} catch {
_ => false
}
}