aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-25 12:58:57 +0900
committernsfisis <nsfisis@gmail.com>2026-06-25 23:47:47 +0900
commiteb082eab51982f97b6dcf5412a52880d4a01ceb1 (patch)
tree941fb3527a2296487b0d1032b00ff3b86335e792 /crates
parentaebe252f47fa5c9b2963fe2f42516e1b54b5c79f (diff)
downloadphp-shirabe-eb082eab51982f97b6dcf5412a52880d4a01ceb1.tar.gz
php-shirabe-eb082eab51982f97b6dcf5412a52880d4a01ceb1.tar.zst
php-shirabe-eb082eab51982f97b6dcf5412a52880d4a01ceb1.zip
feat(json): port remove_list_item to hand-written scanner
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates')
-rw-r--r--crates/shirabe/src/json/json_manipulator.rs97
1 files changed, 50 insertions, 47 deletions
diff --git a/crates/shirabe/src/json/json_manipulator.rs b/crates/shirabe/src/json/json_manipulator.rs
index fa31fff..bc27a75 100644
--- a/crates/shirabe/src/json/json_manipulator.rs
+++ b/crates/shirabe/src/json/json_manipulator.rs
@@ -1246,17 +1246,17 @@ impl JsonManipulator {
}
// no node content match-able
- let node_regex = format!(
- "{{{}^(?P<start> \\s* \\{{ \\s* (?: (?&string) \\s* : (?&json) \\s* , \\s* )*?{}\\s*:\\s*)(?P<content>(?&array))(?P<end>.*)}}sx",
- Self::DEFINES,
- preg_quote(&JsonFile::encode(main_node), None)
- );
- let mut match_map: IndexMap<String, String> = IndexMap::new();
- if !Preg::is_match_named(&node_regex, &self.contents, &mut match_map) {
- return Ok(false);
- }
-
- let children = match_map.get("content").cloned().unwrap_or_default();
+ let node = match json_grammar::find_top_level_key(
+ self.contents.as_bytes(),
+ JsonFile::encode(main_node).as_bytes(),
+ ValueKind::Array,
+ ) {
+ Some(node) => node,
+ None => return Ok(false),
+ };
+ let node_start = self.contents[..node.value_pos].to_string();
+ let node_end = self.contents[node.value_end..].to_string();
+ let children = self.contents[node.value_pos..node.value_end].to_string();
// invalid match due to un-regexable content, abort
if json_decode(&children, true)?.as_bool() == Some(false) {
@@ -1272,45 +1272,48 @@ impl JsonManipulator {
return Ok(true);
}
- let mut content_regex = "(?&json)".to_string();
- let start_regex: String;
- let end_regex: String;
-
- if node_index > 1 {
- start_regex = format!("(?&json)\\s*+(?:,(?&json)\\s*+){{{}}}", node_index - 1);
- // remove leading array separator in case we might remove the last
- content_regex = format!("\\s*+,?\\s*+{}", content_regex);
- end_regex = "(?:(\\s*+,\\s*+(?&json))*(?:\\s*+(?&json))?)\\s*+".to_string();
- } else if node_index > 0 {
- start_regex = "(?&json)\\s*+".to_string();
- // remove leading array separator in case we might remove the last
- content_regex = format!("\\s*+,?\\s*+{}", content_regex);
- end_regex = "(?:(\\s*+,\\s*+(?&json))*(?:\\s*+(?&json))?)\\s*+".to_string();
- } else {
- start_regex = "\\s*+".to_string();
- // remove trailing array separator when we delete first
- content_regex = format!("{}\\s*+,?\\s*+", content_regex);
- end_regex = "(?:((?&json)\\s*+,\\s*+)*(?:\\s*+(?&json))?)\\s*+".to_string();
- }
+ // Locate the byte range `[a..b]` to drop: the item at `node_index` together with one
+ // array separator (the trailing comma for the first item, otherwise the leading comma).
+ let cb = children.as_bytes();
+ let removal: Option<(usize, usize)> = (|| {
+ let mut i = json_grammar::skip_ws(cb, 1); // '[' then \s*
+ if node_index == 0 {
+ let a = i;
+ let mut e = json_grammar::scan_value(cb, a)?;
+ e = json_grammar::skip_ws(cb, e);
+ if cb.get(e) == Some(&b',') {
+ e += 1;
+ }
+ e = json_grammar::skip_ws(cb, e);
+ Some((a, e))
+ } else {
+ i = json_grammar::scan_value(cb, i)?;
+ i = json_grammar::skip_ws(cb, i);
+ for _ in 0..(node_index - 1) {
+ if cb.get(i) != Some(&b',') {
+ return None;
+ }
+ i = json_grammar::scan_value(cb, i + 1)?;
+ i = json_grammar::skip_ws(cb, i);
+ }
+ let a = i;
+ let mut b = json_grammar::skip_ws(cb, a);
+ if cb.get(b) == Some(&b',') {
+ b += 1;
+ }
+ b = json_grammar::skip_ws(cb, b);
+ b = json_grammar::scan_value(cb, b)?;
+ Some((a, b))
+ }
+ })();
- let mut child_match: IndexMap<String, String> = IndexMap::new();
- if Preg::is_match_named(
- &format!(
- "{{{}(?P<start>\\[{})(?P<content>{})(?P<end>{}\\])}}sx",
- Self::DEFINES,
- start_regex,
- content_regex,
- end_regex
- ),
- &children,
- &mut child_match,
- ) {
+ if let Some((a, b)) = removal {
self.contents = format!(
"{}{}{}{}",
- match_map.get("start").cloned().unwrap_or_default(),
- child_match.get("start").cloned().unwrap_or_default(),
- child_match.get("end").cloned().unwrap_or_default(),
- match_map.get("end").cloned().unwrap_or_default()
+ node_start,
+ &children[..a],
+ &children[b..],
+ node_end
);
return Ok(true);