supercode_reduce/engine/
stub.rs1use super::{ReductionKind, REDUCTION_SENTINEL};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum Kind {
24 ToolOutput,
26 FileRead,
28 Image,
30 TurnsCleared,
32 ToolInput,
34 OutputNormalized,
36 FileReadDiffed,
38 Duplicate,
40 Superseded,
42}
43
44impl Kind {
45 pub const fn as_str(self) -> &'static str {
50 match self {
51 Kind::ToolOutput => "tool-output",
52 Kind::FileRead => "file-read",
53 Kind::Image => "image",
54 Kind::TurnsCleared => "turns-cleared",
55 Kind::ToolInput => "tool-input",
56 Kind::OutputNormalized => "output-normalized",
57 Kind::FileReadDiffed => "file-read-diffed",
58 Kind::Duplicate => "duplicate",
59 Kind::Superseded => "superseded",
60 }
61 }
62
63 fn from_str(s: &str) -> Option<Self> {
64 Some(match s {
65 "tool-output" => Kind::ToolOutput,
66 "file-read" => Kind::FileRead,
67 "image" => Kind::Image,
68 "turns-cleared" => Kind::TurnsCleared,
69 "tool-input" => Kind::ToolInput,
70 "output-normalized" => Kind::OutputNormalized,
71 "file-read-diffed" => Kind::FileReadDiffed,
72 "duplicate" => Kind::Duplicate,
73 "superseded" => Kind::Superseded,
74 _ => return None,
75 })
76 }
77}
78
79impl From<&ReductionKind> for Kind {
80 fn from(k: &ReductionKind) -> Self {
81 match k {
82 ReductionKind::ToolOutputTruncated { .. } => Kind::ToolOutput,
83 ReductionKind::FileReadElided { .. } => Kind::FileRead,
84 ReductionKind::ImageRedacted { .. } => Kind::Image,
85 ReductionKind::TurnsCleared { .. } => Kind::TurnsCleared,
86 ReductionKind::ToolInputElided { .. } => Kind::ToolInput,
87 ReductionKind::OutputNormalized { .. } => Kind::OutputNormalized,
88 ReductionKind::FileReadDiffed { .. } => Kind::FileReadDiffed,
89 ReductionKind::DuplicateOutput { .. } => Kind::Duplicate,
90 ReductionKind::Superseded { .. } => Kind::Superseded,
91 }
92 }
93}
94
95fn is_valid_id(id: &str) -> bool {
97 let Some(rest) = id.strip_prefix('r') else {
98 return false;
99 };
100 let bytes = rest.as_bytes();
101 if bytes.len() != 9 || bytes[4] != b'-' {
102 return false;
103 }
104 bytes[..4].iter().all(u8::is_ascii_digit)
105 && bytes[5..]
106 .iter()
107 .all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'))
108}
109
110pub fn format(kind: Kind, id: &str, summary: &str) -> String {
117 debug_assert!(is_valid_id(id), "invalid reduction id: {id:?}");
118 debug_assert!(!summary.contains(']'), "summary contains `]`: {summary:?}");
119 debug_assert!(
120 !summary.contains('\n'),
121 "summary is not one line: {summary:?}"
122 );
123 format!("{REDUCTION_SENTINEL} {} {id}: {summary}]", kind.as_str())
124}
125
126pub fn parse(line: &str) -> Option<(Kind, String, String)> {
132 let body = line
133 .strip_prefix(REDUCTION_SENTINEL)?
134 .strip_prefix(' ')?
135 .strip_suffix(']')?;
136 if body.contains(']') {
138 return None;
139 }
140 let (kind_str, rest) = body.split_once(' ')?;
141 let kind = Kind::from_str(kind_str)?;
142 let (id, summary) = rest.split_once(": ")?;
143 if !is_valid_id(id) || summary.is_empty() || summary.contains('\n') {
144 return None;
145 }
146 Some((kind, id.to_string(), summary.to_string()))
147}