Skip to main content

zincite_query/
structured.rs

1//! Literal views and explicit collection edits over original CST ranges.
2use std::cmp::Ordering;
3use std::collections::HashSet;
4use std::ops::Range;
5
6use serde_json::{Value, json};
7use zincite_lint::{EditPart, TextEdit};
8use zincite_syntax::{FileMode, NodeKind, SyntaxElement, SyntaxNode, TokenKind};
9
10use crate::inspection::{NodeSelection, SelectedNode, check_collection, into_nodes, original_text};
11use crate::language::{Stage, StageKind};
12use crate::pipeline;
13use crate::{Input, Limits, QueryError, QueryResult, Work, assignment_rhs, identifier_identity};
14
15/// Written indexing form; a starting key does not establish bare-tail identities.
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum ArrayIndexing {
18    Positional,
19    Explicit,
20    StartingKey,
21}
22
23/// One literal with original source spelling and byte coordinates.
24#[derive(Clone, Debug)]
25pub struct LiteralValue<'a> {
26    pub file: &'a str,
27    pub range: Range<usize>,
28    pub spelling: &'a [u8],
29    pub kind: LiteralKind<'a>,
30}
31
32/// Recursive inspection data, without evaluating computed expressions.
33#[derive(Clone, Debug)]
34pub enum LiteralKind<'a> {
35    Absent,
36    Integer,
37    Float,
38    Boolean(bool),
39    String,
40    Member(&'a str),
41    Set(Vec<LiteralValue<'a>>),
42    Tuple(Vec<LiteralValue<'a>>),
43    Record(Vec<LiteralField<'a>>),
44    Array {
45        indexing: ArrayIndexing,
46        entries: Vec<LiteralEntry<'a>>,
47    },
48    Matrix {
49        column_keys: Vec<LiteralValue<'a>>,
50        rows: Vec<LiteralRow<'a>>,
51    },
52}
53
54/// A field label is distinct from an enum member or string value.
55#[derive(Clone, Debug)]
56pub struct LiteralLabel<'a> {
57    pub name: &'a str,
58    pub range: Range<usize>,
59    pub spelling: &'a [u8],
60}
61
62#[derive(Clone, Debug)]
63pub struct LiteralField<'a> {
64    pub label: LiteralLabel<'a>,
65    pub value: LiteralValue<'a>,
66}
67
68#[derive(Clone, Debug)]
69pub struct LiteralEntry<'a> {
70    pub key: Option<LiteralValue<'a>>,
71    pub value: LiteralValue<'a>,
72}
73
74#[derive(Clone, Debug)]
75pub struct LiteralRow<'a> {
76    pub key: Option<LiteralValue<'a>>,
77    pub cells: Vec<LiteralValue<'a>>,
78}
79
80#[derive(Debug)]
81pub struct LiteralSelection<'a> {
82    pub(crate) input: &'a Input,
83    pub(crate) values: Vec<LiteralValue<'a>>,
84}
85
86impl<'a> LiteralSelection<'a> {
87    pub fn input(&self) -> &'a Input {
88        self.input
89    }
90    pub fn values(&self) -> &[LiteralValue<'a>] {
91        &self.values
92    }
93}
94
95fn rhs<'a>(node: &'a SyntaxNode, input: &Input) -> Result<&'a SyntaxNode, QueryError> {
96    if node.kind() == NodeKind::Assignment {
97        assignment_rhs(node).ok_or_else(|| {
98            QueryError::input(
99                input.original_range(node.range()),
100                "assignment has no single right-hand side",
101            )
102        })
103    } else if node.kind() == NodeKind::RecordLiteralField {
104        node.child_nodes().next().ok_or_else(|| {
105            QueryError::input(
106                input.original_range(node.range()),
107                "record field has no value",
108            )
109        })
110    } else {
111        Ok(node)
112    }
113}
114
115fn collection_node<'a>(
116    node: &'a SyntaxNode,
117    input: &Input,
118    work: &mut Work,
119    range: &Range<usize>,
120    limits: Limits,
121) -> Result<&'a SyntaxNode, QueryError> {
122    let mut node = rhs(node, input)?;
123    let mut depth = 0;
124    while node.kind() == NodeKind::ParenthesizedExpression {
125        work.visit(range)?;
126        if depth >= limits.nesting.min(64) {
127            return Err(QueryError::query(
128                range.clone(),
129                "literal nesting limit exceeded",
130            ));
131        }
132        depth += 1;
133        node = node.child_nodes().next().unwrap();
134    }
135    Ok(node)
136}
137
138pub(crate) fn unwrapped(mut node: &SyntaxNode) -> &SyntaxNode {
139    while node.kind() == NodeKind::ParenthesizedExpression {
140        let Some(child) = node.child_nodes().next() else {
141            break;
142        };
143        node = child;
144    }
145    node
146}
147
148pub(crate) fn direct_token(input: &Input, node: &SyntaxNode) -> Option<usize> {
149    syntax_token(input.syntax(), node)
150}
151
152pub(crate) fn syntax_token(
153    parsed: &zincite_syntax::ParsedFile,
154    node: &SyntaxNode,
155) -> Option<usize> {
156    node.children().iter().find_map(|child| match child {
157        SyntaxElement::Token(index)
158            if !matches!(
159                parsed.tokens()[*index].kind,
160                TokenKind::Whitespace | TokenKind::LineComment | TokenKind::BlockComment
161            ) =>
162        {
163            Some(*index)
164        }
165        _ => None,
166    })
167}
168
169pub(crate) fn has_colon(input: &Input, node: &SyntaxNode) -> bool {
170    node.children().iter().any(|child| matches!(child, SyntaxElement::Token(index) if input.syntax().tokens()[*index].kind == TokenKind::Colon))
171}
172
173pub(crate) fn array_indexing(node: &SyntaxNode) -> ArrayIndexing {
174    let mut explicit = false;
175    let mut bare = false;
176    for entry in node.child_nodes() {
177        explicit |= entry.kind() == NodeKind::IndexedArrayEntry;
178        bare |= entry.kind() != NodeKind::IndexedArrayEntry;
179    }
180    match (explicit, bare) {
181        (false, _) => ArrayIndexing::Positional,
182        (true, false) => ArrayIndexing::Explicit,
183        (true, true) => ArrayIndexing::StartingKey,
184    }
185}
186
187pub(crate) fn project<'a>(
188    result: QueryResult<'a>,
189    stage: &Stage,
190    work: &mut Work,
191    limits: Limits,
192) -> Result<QueryResult<'a>, QueryError> {
193    let selection = into_nodes(result, &stage.range, work)?;
194    let input = selection.input;
195    let mut nodes = Vec::new();
196    for selected in selection.nodes {
197        work.visit(&stage.range)?;
198        let node = collection_node(selected.node, input, work, &stage.range, limits)?;
199        work.spend(node.children().len(), &stage.range)?;
200        let mut push = |node: &'a SyntaxNode, work: &mut Work| -> Result<(), QueryError> {
201            check_collection(nodes.len(), limits.collection, &stage.range)?;
202            work.produce(1, &stage.range)?;
203            nodes.push(SelectedNode {
204                node,
205                file: input.file(),
206                range: input.original_range(node.range()),
207            });
208            Ok(())
209        };
210        match stage.kind {
211            StageKind::Fields if node.kind() == NodeKind::RecordLiteral => {
212                for field in node.child_nodes() {
213                    push(field, work)?;
214                }
215            }
216            StageKind::Elements => match node.kind() {
217                NodeKind::SetLiteral | NodeKind::TupleLiteral => {
218                    for child in node.child_nodes() {
219                        push(child, work)?;
220                    }
221                }
222                NodeKind::ArrayLiteral => {
223                    for entry in node.child_nodes() {
224                        push(
225                            if entry.kind() == NodeKind::IndexedArrayEntry {
226                                entry.child_nodes().nth(1).unwrap()
227                            } else {
228                                entry
229                            },
230                            work,
231                        )?;
232                    }
233                }
234                NodeKind::MatrixLiteral => {
235                    for row in node
236                        .child_nodes()
237                        .filter(|row| row.kind() == NodeKind::MatrixRow)
238                    {
239                        work.spend(row.children().len(), &stage.range)?;
240                        for cell in row.child_nodes().skip(usize::from(has_colon(input, row))) {
241                            push(cell, work)?;
242                        }
243                    }
244                }
245                _ => {
246                    return Err(QueryError::input(
247                        input.original_range(node.range()),
248                        "elements requires a literal set, tuple, array or matrix",
249                    ));
250                }
251            },
252            StageKind::Keys => match node.kind() {
253                NodeKind::ArrayLiteral => {
254                    for entry in node
255                        .child_nodes()
256                        .filter(|entry| entry.kind() == NodeKind::IndexedArrayEntry)
257                    {
258                        push(entry.child_nodes().next().unwrap(), work)?;
259                    }
260                }
261                NodeKind::MatrixLiteral => {
262                    for row in node.child_nodes() {
263                        work.spend(row.children().len(), &stage.range)?;
264                        if row.kind() == NodeKind::MatrixColumnIndices {
265                            for key in row.child_nodes() {
266                                push(key, work)?;
267                            }
268                        } else if has_colon(input, row) {
269                            push(row.child_nodes().next().unwrap(), work)?;
270                        }
271                    }
272                }
273                _ => {
274                    return Err(QueryError::input(
275                        input.original_range(node.range()),
276                        "keys requires a literal array or matrix",
277                    ));
278                }
279            },
280            _ => {
281                return Err(QueryError::input(
282                    input.original_range(node.range()),
283                    "fields requires a literal record",
284                ));
285            }
286        }
287    }
288    Ok(QueryResult::Nodes(NodeSelection { input, nodes }))
289}
290
291fn literal_slots<T>(
292    work: &mut Work,
293    len: usize,
294    range: &Range<usize>,
295) -> Result<Vec<T>, QueryError> {
296    if len
297        .checked_mul(std::mem::size_of::<T>())
298        .is_none_or(|n| n > isize::MAX as usize)
299    {
300        return Err(QueryError::query(
301            range.clone(),
302            "literal allocation size overflow",
303        ));
304    }
305    work.spend(len, range)?;
306    work.produce(len, range)?;
307    Ok(Vec::with_capacity(len))
308}
309
310struct LiteralReader<'a, 'w, 'b> {
311    input: &'a Input,
312    work: &'w mut Work<'b>,
313    range: &'w Range<usize>,
314    limits: Limits,
315    entries: usize,
316}
317
318impl<'a> LiteralReader<'a, '_, '_> {
319    fn read(&mut self, node: &'a SyntaxNode, depth: usize) -> Result<LiteralValue<'a>, QueryError> {
320        if depth >= self.limits.nesting.min(64) {
321            return Err(QueryError::query(
322                self.range.clone(),
323                "literal nesting limit exceeded",
324            ));
325        }
326        check_collection(self.entries, self.limits.collection, self.range)?;
327        self.work.produce(1, self.range)?;
328        self.entries += 1;
329        self.work.visit(self.range)?;
330        self.work.spend(node.children().len(), self.range)?;
331        let range = self.input.original_range(node.range());
332        let kind = match node.kind() {
333            NodeKind::ParenthesizedExpression => {
334                self.read(node.child_nodes().next().unwrap(), depth + 1)?
335                    .kind
336            }
337            NodeKind::UnaryExpression => {
338                let index = direct_token(self.input, node).unwrap();
339                if !matches!(
340                    self.input.syntax().tokens()[index].kind,
341                    TokenKind::Plus | TokenKind::Minus
342                ) {
343                    return Err(QueryError::input(
344                        range,
345                        "only numeric signs may wrap literal values",
346                    ));
347                }
348                let inner = self.read(node.child_nodes().next().unwrap(), depth + 1)?;
349                match inner.kind {
350                    LiteralKind::Integer => LiteralKind::Integer,
351                    LiteralKind::Float => LiteralKind::Float,
352                    _ => {
353                        return Err(QueryError::input(
354                            range,
355                            "numeric sign requires a numeric literal",
356                        ));
357                    }
358                }
359            }
360            NodeKind::Expression => {
361                let index = direct_token(self.input, node).ok_or_else(|| {
362                    QueryError::input(range.clone(), "unsupported computed value")
363                })?;
364                match self.input.syntax().tokens()[index].kind {
365                    TokenKind::Absent => LiteralKind::Absent,
366                    TokenKind::IntegerLiteral => LiteralKind::Integer,
367                    TokenKind::FloatLiteral => LiteralKind::Float,
368                    TokenKind::StringLiteral => LiteralKind::String,
369                    TokenKind::True => LiteralKind::Boolean(true),
370                    TokenKind::False => LiteralKind::Boolean(false),
371                    TokenKind::Identifier | TokenKind::QuotedIdentifier => {
372                        let text = std::str::from_utf8(self.input.token_bytes(index)).unwrap();
373                        LiteralKind::Member(identifier_identity(text))
374                    }
375                    _ => {
376                        return Err(QueryError::input(
377                            range,
378                            "unsupported computed value; expected a literal scalar or enum member",
379                        ));
380                    }
381                }
382            }
383            NodeKind::SetLiteral | NodeKind::TupleLiteral | NodeKind::IndexTuple => {
384                let mut values = literal_slots(self.work, node.child_nodes().count(), self.range)?;
385                for child in node.child_nodes() {
386                    values.push(self.read(child, depth + 1)?);
387                }
388                if node.kind() == NodeKind::SetLiteral {
389                    LiteralKind::Set(values)
390                } else {
391                    LiteralKind::Tuple(values)
392                }
393            }
394            NodeKind::RecordLiteral => {
395                let mut fields = literal_slots(self.work, node.child_nodes().count(), self.range)?;
396                for field in node.child_nodes() {
397                    self.work.spend(field.children().len(), self.range)?;
398                    let index = direct_token(self.input, field).unwrap();
399                    let label_range = self.input.token_range(index);
400                    let spelling = self.input.token_bytes(index);
401                    fields.push(LiteralField {
402                        label: LiteralLabel {
403                            name: identifier_identity(std::str::from_utf8(spelling).unwrap()),
404                            range: label_range,
405                            spelling,
406                        },
407                        value: self.read(field.child_nodes().next().unwrap(), depth + 1)?,
408                    });
409                }
410                LiteralKind::Record(fields)
411            }
412            NodeKind::ArrayLiteral => {
413                let mut entries = literal_slots(self.work, node.child_nodes().count(), self.range)?;
414                for entry in node.child_nodes() {
415                    let mut children = entry.child_nodes();
416                    let key = if entry.kind() == NodeKind::IndexedArrayEntry {
417                        Some(self.read(children.next().unwrap(), depth + 1)?)
418                    } else {
419                        None
420                    };
421                    let value = if key.is_some() {
422                        children.next().unwrap()
423                    } else {
424                        entry
425                    };
426                    entries.push(LiteralEntry {
427                        key,
428                        value: self.read(value, depth + 1)?,
429                    });
430                }
431                LiteralKind::Array {
432                    indexing: array_indexing(node),
433                    entries,
434                }
435            }
436            NodeKind::MatrixLiteral => {
437                let header_keys = node
438                    .child_nodes()
439                    .find(|row| row.kind() == NodeKind::MatrixColumnIndices)
440                    .map_or(0, |row| row.child_nodes().count());
441                let mut column_keys = literal_slots(self.work, header_keys, self.range)?;
442                let mut rows = literal_slots(
443                    self.work,
444                    node.child_nodes()
445                        .filter(|row| row.kind() == NodeKind::MatrixRow)
446                        .count(),
447                    self.range,
448                )?;
449                for row in node.child_nodes() {
450                    self.work.spend(row.children().len(), self.range)?;
451                    if row.kind() == NodeKind::MatrixColumnIndices {
452                        for key in row.child_nodes() {
453                            column_keys.push(self.read(key, depth + 1)?);
454                        }
455                    } else {
456                        check_collection(self.entries, self.limits.collection, self.range)?;
457                        self.entries += 1;
458                        let mut cells = row.child_nodes();
459                        let key = if has_colon(self.input, row) {
460                            Some(self.read(cells.next().unwrap(), depth + 1)?)
461                        } else {
462                            None
463                        };
464                        let mut values = literal_slots(
465                            self.work,
466                            row.child_nodes().count() - usize::from(key.is_some()),
467                            self.range,
468                        )?;
469                        for cell in cells {
470                            values.push(self.read(cell, depth + 1)?);
471                        }
472                        rows.push(LiteralRow { key, cells: values });
473                    }
474                }
475                LiteralKind::Matrix { column_keys, rows }
476            }
477            _ => {
478                return Err(QueryError::input(
479                    range,
480                    "unsupported computed value; literal inspection does not evaluate expressions",
481                ));
482            }
483        };
484        Ok(LiteralValue {
485            file: self.input.file(),
486            spelling: &self.input.bytes[range.clone()],
487            range,
488            kind,
489        })
490    }
491}
492
493/// Read an original RHS with the same literal reader used by `values`.
494pub(crate) fn read_literal<'a>(
495    input: &'a Input,
496    node: &'a SyntaxNode,
497    work: &mut Work,
498    range: &Range<usize>,
499    limits: Limits,
500) -> Result<LiteralValue<'a>, QueryError> {
501    LiteralReader {
502        input,
503        work,
504        range,
505        limits,
506        entries: 0,
507    }
508    .read(node, 0)
509}
510
511pub(crate) fn values<'a>(
512    result: QueryResult<'a>,
513    stage: &Stage,
514    work: &mut Work,
515    limits: Limits,
516) -> Result<QueryResult<'a>, QueryError> {
517    let selection = into_nodes(result, &stage.range, work)?;
518    let mut reader = LiteralReader {
519        input: selection.input,
520        work,
521        range: &stage.range,
522        limits,
523        entries: 0,
524    };
525    let mut values = Vec::new();
526    for selected in selection.nodes {
527        values.push(reader.read(rhs(selected.node, selection.input)?, 0)?);
528    }
529    Ok(QueryResult::Literals(LiteralSelection {
530        input: selection.input,
531        values,
532    }))
533}
534
535fn children<'a>(value: &'a LiteralValue<'a>) -> Vec<&'a LiteralValue<'a>> {
536    match &value.kind {
537        LiteralKind::Set(values) | LiteralKind::Tuple(values) => values.iter().collect(),
538        LiteralKind::Record(fields) => fields.iter().map(|field| &field.value).collect(),
539        LiteralKind::Array { entries, .. } => entries
540            .iter()
541            .flat_map(|entry| entry.key.iter().chain(std::iter::once(&entry.value)))
542            .collect(),
543        LiteralKind::Matrix { column_keys, rows } => column_keys
544            .iter()
545            .chain(
546                rows.iter()
547                    .flat_map(|row| row.key.iter().chain(row.cells.iter())),
548            )
549            .collect(),
550        _ => Vec::new(),
551    }
552}
553
554pub(crate) fn validate_json(
555    selection: &LiteralSelection<'_>,
556    work: &mut Work,
557    range: &Range<usize>,
558) -> Result<(), QueryError> {
559    let mut pending: Vec<_> = selection.values.iter().collect();
560    while let Some(value) = pending.pop() {
561        work.spend(value.range.len().max(1) + value.file.len(), range)?;
562        original_text(selection.input, &value.range)?;
563        pending.extend(children(value));
564    }
565    Ok(())
566}
567
568fn value_json(value: &LiteralValue<'_>) -> Value {
569    let mut result = json!({"file":value.file, "range":{"start":value.range.start,"end":value.range.end}, "text":std::str::from_utf8(value.spelling).unwrap()});
570    let (kind, content) = match &value.kind {
571        LiteralKind::Absent => ("absent", Value::Null),
572        LiteralKind::Integer => ("integer", Value::Null),
573        LiteralKind::Float => ("float", Value::Null),
574        LiteralKind::Boolean(value) => ("boolean", json!({"value":value})),
575        LiteralKind::String => ("string", Value::Null),
576        LiteralKind::Member(name) => ("member", json!({"name":name})),
577        LiteralKind::Set(values) => (
578            "set",
579            json!({"elements":values.iter().map(value_json).collect::<Vec<_>>()}),
580        ),
581        LiteralKind::Tuple(values) => (
582            "tuple",
583            json!({"elements":values.iter().map(value_json).collect::<Vec<_>>()}),
584        ),
585        LiteralKind::Record(fields) => (
586            "record",
587            json!({"fields":fields.iter().map(|field| json!({"label":{"name":field.label.name,"range":{"start":field.label.range.start,"end":field.label.range.end},"text":std::str::from_utf8(field.label.spelling).unwrap()},"value":value_json(&field.value)})).collect::<Vec<_>>()}),
588        ),
589        LiteralKind::Array { indexing, entries } => (
590            "array",
591            json!({"indexing": match indexing {ArrayIndexing::Positional=>"positional",ArrayIndexing::Explicit=>"explicit",ArrayIndexing::StartingKey=>"starting_key"}, "entries":entries.iter().map(|entry| json!({"key":entry.key.as_ref().map(value_json),"value":value_json(&entry.value)})).collect::<Vec<_>>()}),
592        ),
593        LiteralKind::Matrix { column_keys, rows } => (
594            "matrix",
595            json!({"column_keys":column_keys.iter().map(value_json).collect::<Vec<_>>(),"rows":rows.iter().map(|row| json!({"key":row.key.as_ref().map(value_json),"cells":row.cells.iter().map(value_json).collect::<Vec<_>>()})).collect::<Vec<_>>()}),
596        ),
597    };
598    result["kind"] = json!(kind);
599    if let Value::Object(content) = content {
600        result.as_object_mut().unwrap().extend(content);
601    }
602    result
603}
604
605pub(crate) fn json_value(selection: &LiteralSelection<'_>) -> Result<Value, QueryError> {
606    Ok(Value::Array(
607        selection.values.iter().map(value_json).collect(),
608    ))
609}
610
611#[derive(Clone, Debug)]
612pub(crate) enum Scalar {
613    Integer(i64),
614    Float(f64),
615    Boolean(bool),
616    String(String),
617    Member(String),
618}
619
620#[derive(Clone, Copy, Debug, PartialEq, Eq)]
621pub(crate) enum ComparisonKind {
622    Eq,
623    Lt,
624    Le,
625    Gt,
626    Ge,
627}
628
629#[derive(Clone, Debug)]
630pub(crate) struct Comparison {
631    pub kind: ComparisonKind,
632    pub value: Scalar,
633    pub predicate: Option<std::rc::Rc<zirql::ValueExpression>>,
634    pub bindings: std::rc::Rc<zirql::Bindings<zirql::ConstructedValue>>,
635    pub read_work: usize,
636}
637
638pub(crate) fn parse_number(text: &str) -> Result<Scalar, &'static str> {
639    let (negative, unsigned) = if let Some(text) = text.strip_prefix('-') {
640        (true, text)
641    } else {
642        (false, text.strip_prefix('+').unwrap_or(text))
643    };
644    if unsigned.starts_with("0x") || unsigned.starts_with("0X") || unsigned.starts_with("0o") {
645        if unsigned.contains(['p', 'P', '.']) {
646            return Err("hexadecimal-float comparisons are unsupported");
647        }
648        let radix = if unsigned.starts_with("0o") { 8 } else { 16 };
649        let magnitude = u64::from_str_radix(&unsigned[2..], radix)
650            .map_err(|_| "integer comparison operand exceeds the signed 64-bit range")?;
651        return signed_integer(magnitude, negative);
652    }
653    if unsigned.contains(['.', 'e', 'E']) {
654        // Reject non-numeric Rust spellings (NaN, inf) and partial decimal forms.
655        if unsigned.is_empty()
656            || !unsigned.bytes().all(|byte| {
657                byte.is_ascii_digit() || matches!(byte, b'.' | b'e' | b'E' | b'+' | b'-')
658            })
659        {
660            return Err("expected a numeric literal");
661        }
662        if !decimal_float(unsigned) {
663            return Err("expected a decimal numeric literal");
664        }
665        let value: f64 = text
666            .parse()
667            .map_err(|_| "expected a decimal numeric literal")?;
668        if !value.is_finite() {
669            return Err("float comparison operand overflows the finite range");
670        }
671        let nonzero = unsigned
672            .split(['e', 'E'])
673            .next()
674            .unwrap()
675            .bytes()
676            .any(|byte| matches!(byte, b'1'..=b'9'));
677        if value == 0.0 && nonzero {
678            return Err("float comparison operand underflows the finite range");
679        }
680        Ok(Scalar::Float(value))
681    } else {
682        if unsigned.is_empty() || !unsigned.bytes().all(|byte| byte.is_ascii_digit()) {
683            return Err("expected a numeric literal");
684        }
685        signed_integer(
686            unsigned
687                .parse()
688                .map_err(|_| "integer comparison operand exceeds the signed 64-bit range")?,
689            negative,
690        )
691    }
692}
693
694fn decimal_float(text: &str) -> bool {
695    let bytes = text.as_bytes();
696    let mut position = 0;
697    while bytes.get(position).is_some_and(u8::is_ascii_digit) {
698        position += 1;
699    }
700    if position == 0 {
701        return false;
702    }
703    if bytes.get(position) == Some(&b'.') {
704        position += 1;
705        let start = position;
706        while bytes.get(position).is_some_and(u8::is_ascii_digit) {
707            position += 1;
708        }
709        if start == position {
710            return false;
711        }
712    }
713    if bytes
714        .get(position)
715        .is_some_and(|byte| matches!(byte, b'e' | b'E'))
716    {
717        position += 1;
718        if bytes
719            .get(position)
720            .is_some_and(|byte| matches!(byte, b'+' | b'-'))
721        {
722            position += 1;
723        }
724        let start = position;
725        while bytes.get(position).is_some_and(u8::is_ascii_digit) {
726            position += 1;
727        }
728        if start == position {
729            return false;
730        }
731    }
732    position == bytes.len()
733}
734
735fn signed_integer(magnitude: u64, negative: bool) -> Result<Scalar, &'static str> {
736    let value = if negative && magnitude == 1_u64 << 63 {
737        i64::MIN
738    } else {
739        let value = i64::try_from(magnitude)
740            .map_err(|_| "integer comparison operand exceeds the signed 64-bit range")?;
741        if negative { -value } else { value }
742    };
743    Ok(Scalar::Integer(value))
744}
745
746fn numeric_text(
747    input: &Input,
748    node: &SyntaxNode,
749    work: &mut Work,
750    range: &Range<usize>,
751) -> Result<String, QueryError> {
752    let source_range = input.original_range(node.range());
753    work.spend(source_range.len(), range)?;
754    let mut text = String::new();
755    let mut pending = vec![node];
756    let mut negative = false;
757    while let Some(node) = pending.pop() {
758        work.visit(range)?;
759        match node.kind() {
760            NodeKind::ParenthesizedExpression => pending.push(node.child_nodes().next().unwrap()),
761            NodeKind::UnaryExpression => {
762                let token = direct_token(input, node).unwrap();
763                match input.syntax().tokens()[token].kind {
764                    TokenKind::Minus => negative = !negative,
765                    TokenKind::Plus => {}
766                    _ => {
767                        return Err(QueryError::input(
768                            source_range,
769                            "unsupported computed comparison value",
770                        ));
771                    }
772                }
773                pending.push(node.child_nodes().next().unwrap());
774            }
775            NodeKind::Expression => {
776                let index = direct_token(input, node).unwrap();
777                text.push_str(std::str::from_utf8(input.token_bytes(index)).unwrap());
778            }
779            _ => {
780                return Err(QueryError::input(
781                    source_range,
782                    "unsupported computed comparison value",
783                ));
784            }
785        }
786    }
787    if negative {
788        text.insert(0, '-');
789    }
790    Ok(text)
791}
792
793fn scalar(
794    input: &Input,
795    node: &SyntaxNode,
796    value: &LiteralValue<'_>,
797    work: &mut Work,
798    range: &Range<usize>,
799) -> Result<Scalar, QueryError> {
800    match &value.kind {
801        LiteralKind::Integer | LiteralKind::Float => {
802            parse_number(&numeric_text(input, node, work, range)?)
803                .map_err(|message| QueryError::input(value.range.clone(), message))
804        }
805        LiteralKind::Boolean(value) => Ok(Scalar::Boolean(*value)),
806        LiteralKind::Member(name) => {
807            work.spend(name.len(), range)?;
808            Ok(Scalar::Member((*name).to_owned()))
809        }
810        LiteralKind::String => {
811            let node = unwrapped(node);
812            let index = direct_token(input, node).unwrap();
813            let text = std::str::from_utf8(input.token_bytes(index)).unwrap();
814            work.spend(text.len(), range)?;
815            Ok(Scalar::String(decode_string(text).map_err(|message| {
816                QueryError::input(value.range.clone(), message)
817            })?))
818        }
819        _ => Err(QueryError::input(
820            value.range.clone(),
821            "element comparison requires scalar values; collections and records are incompatible",
822        )),
823    }
824}
825
826pub(crate) fn decode_string(text: &str) -> Result<String, &'static str> {
827    let mut result = String::new();
828    let mut characters = text[1..text.len() - 1].chars();
829    while let Some(character) = characters.next() {
830        result.push(if character == '\\' {
831            match characters.next() {
832                Some('"') => '"',
833                Some('\\') => '\\',
834                Some('n') => '\n',
835                Some('r') => '\r',
836                Some('t') => '\t',
837                _ => {
838                    return Err(
839                        "string comparison supports only escaped quote, backslash, n, r and t",
840                    );
841                }
842            }
843        } else {
844            character
845        });
846    }
847    Ok(result)
848}
849
850fn exact_float(integer: i64) -> Option<f64> {
851    let magnitude = integer.unsigned_abs();
852    let significant_bits = 64 - magnitude.leading_zeros();
853    // Binary64 has 53 significant bits. Lower bits must be zero when the
854    // integer is wider; a float-to-i64 round trip would saturate at i64::MAX.
855    if significant_bits <= 53 || magnitude.trailing_zeros() >= significant_bits - 53 {
856        Some(integer as f64)
857    } else {
858        None
859    }
860}
861
862fn compare(
863    left: &Scalar,
864    comparison: &Comparison,
865    range: &Range<usize>,
866) -> Result<bool, QueryError> {
867    let ordering = match (left, &comparison.value) {
868        (Scalar::Integer(a), Scalar::Integer(b)) => a.cmp(b),
869        (Scalar::Float(a), Scalar::Float(b)) => a.partial_cmp(b).unwrap(),
870        (Scalar::Integer(a), Scalar::Float(b)) => exact_float(*a)
871            .ok_or_else(|| {
872                QueryError::input(
873                    range.clone(),
874                    "mixed numeric comparison would lose integer precision",
875                )
876            })?
877            .partial_cmp(b)
878            .unwrap(),
879        (Scalar::Float(a), Scalar::Integer(b)) => a
880            .partial_cmp(&exact_float(*b).ok_or_else(|| {
881                QueryError::input(
882                    range.clone(),
883                    "mixed numeric comparison would lose integer precision",
884                )
885            })?)
886            .unwrap(),
887        (Scalar::Boolean(a), Scalar::Boolean(b)) if comparison.kind == ComparisonKind::Eq => {
888            a.cmp(b)
889        }
890        (Scalar::String(a), Scalar::String(b)) | (Scalar::Member(a), Scalar::Member(b))
891            if comparison.kind == ComparisonKind::Eq =>
892        {
893            a.cmp(b)
894        }
895        _ => {
896            return Err(QueryError::input(
897                range.clone(),
898                "element and comparison operand have incompatible types",
899            ));
900        }
901    };
902    Ok(match comparison.kind {
903        ComparisonKind::Eq => ordering == Ordering::Equal,
904        ComparisonKind::Lt => ordering == Ordering::Less,
905        ComparisonKind::Le => ordering != Ordering::Greater,
906        ComparisonKind::Gt => ordering == Ordering::Greater,
907        ComparisonKind::Ge => ordering != Ordering::Less,
908    })
909}
910
911#[derive(Clone, Debug, PartialEq, Eq, Hash)]
912pub(crate) enum Key {
913    Integer(i64),
914    Member(String),
915}
916
917pub(crate) fn key_parts(
918    value: &LiteralValue<'_>,
919    node: &SyntaxNode,
920    input: &Input,
921    work: &mut Work,
922    range: &Range<usize>,
923) -> Result<Vec<Key>, QueryError> {
924    match &value.kind {
925        LiteralKind::Tuple(values) => {
926            let mut parts = Vec::new();
927            for (value, child) in values.iter().zip(unwrapped(node).child_nodes()) {
928                parts.push(key_part(value, child, input, work, range)?);
929            }
930            Ok(parts)
931        }
932        _ => Ok(vec![key_part(value, node, input, work, range)?]),
933    }
934}
935
936fn key_part(
937    value: &LiteralValue<'_>,
938    node: &SyntaxNode,
939    input: &Input,
940    work: &mut Work,
941    range: &Range<usize>,
942) -> Result<Key, QueryError> {
943    match scalar(input, node, value, work, range)? {
944        Scalar::Integer(value) => Ok(Key::Integer(value)),
945        Scalar::Member(name) => Ok(Key::Member(name)),
946        _ => Err(QueryError::input(
947            value.range.clone(),
948            "array keys require integer or enum member identities",
949        )),
950    }
951}
952
953pub(crate) fn validate_keys(
954    keys: &[Vec<Key>],
955    source_range: &Range<usize>,
956    work: &mut Work,
957    query_range: &Range<usize>,
958) -> Result<(), QueryError> {
959    let Some(first) = keys.first() else {
960        return Ok(());
961    };
962    let arity = first.len();
963    let mut axes: Vec<HashSet<&Key>> = (0..arity).map(|_| HashSet::new()).collect();
964    let mut seen = HashSet::new();
965    for key in keys {
966        work.spend(key.len().max(1), query_range)?;
967        if key.len() != arity || arity == 0 {
968            return Err(QueryError::input(
969                source_range.clone(),
970                "array key arity is inconsistent",
971            ));
972        }
973        if !seen.insert(key) {
974            return Err(QueryError::input(
975                source_range.clone(),
976                "duplicate array key",
977            ));
978        }
979        for (axis, part) in axes.iter_mut().zip(key) {
980            axis.insert(part);
981        }
982    }
983    let mut coverage = 1usize;
984    for axis in axes {
985        coverage = coverage.checked_mul(axis.len()).ok_or_else(|| {
986            QueryError::input(
987                source_range.clone(),
988                "array axis coverage exceeds the supported range",
989            )
990        })?;
991        let mut integers = Vec::new();
992        for part in &axis {
993            if let Key::Integer(value) = part {
994                integers.push(*value);
995            }
996        }
997        if !integers.is_empty() {
998            if integers.len() != axis.len() {
999                return Err(QueryError::input(
1000                    source_range.clone(),
1001                    "array axis mixes integer and enum keys",
1002                ));
1003            }
1004            let min = *integers.iter().min().unwrap();
1005            let max = *integers.iter().max().unwrap();
1006            if i128::from(max) - i128::from(min) + 1 != integers.len() as i128 {
1007                return Err(QueryError::input(
1008                    source_range.clone(),
1009                    "integer array keys would leave a nonrectangular axis",
1010                ));
1011            }
1012        }
1013    }
1014    if coverage != keys.len() {
1015        return Err(QueryError::input(
1016            source_range.clone(),
1017            "tuple array keys do not cover a rectangular set of axes",
1018        ));
1019    }
1020    Ok(())
1021}
1022
1023fn compare_elements(
1024    input: &Input,
1025    nodes: &[&SyntaxNode],
1026    values: &[&LiteralValue<'_>],
1027    comparison: &Comparison,
1028    work: &mut Work,
1029    range: &Range<usize>,
1030) -> Result<Vec<bool>, QueryError> {
1031    for value in values {
1032        let ty = match value.kind {
1033            LiteralKind::Integer => zirql::ValueType::Integer,
1034            LiteralKind::Float => zirql::ValueType::Float,
1035            LiteralKind::Boolean(_) => zirql::ValueType::Boolean,
1036            LiteralKind::String => zirql::ValueType::String,
1037            _ => continue,
1038        };
1039        comparison
1040            .prepare(ty, work.budget, range)
1041            .map_err(|mut error| {
1042                error.location = crate::ErrorLocation::Input;
1043                error.range = value.range.clone();
1044                error
1045            })?;
1046    }
1047    let mut retained = Vec::new();
1048    for (node, value) in nodes.iter().zip(values) {
1049        work.visit(range)?;
1050        let scalar = scalar(input, node, value, work, range)?;
1051        retained.push(if comparison.predicate.is_some() {
1052            comparison.test(&scalar, work.budget, &value.range)?
1053        } else {
1054            compare(&scalar, comparison, &value.range)?
1055        });
1056    }
1057    Ok(retained)
1058}
1059
1060/// Resolve original targets, read bounded entries, compare every element, check
1061/// shape, build source copies, then validate the complete candidate atomically.
1062pub(crate) fn transform_elements(
1063    input: &Input,
1064    result: QueryResult<'_>,
1065    comparison: &Comparison,
1066    range: &Range<usize>,
1067    work: &mut Work,
1068    limits: Limits,
1069) -> Result<Vec<u8>, QueryError> {
1070    if input.mode != FileMode::Data {
1071        return Err(QueryError::query(
1072            range.clone(),
1073            "collection transformations require data-file mode (.dzn)",
1074        ));
1075    }
1076    work.spend(comparison.read_work, range)?;
1077    comparison.prepare(zirql::ValueType::ScalarKind, work.budget, range)?;
1078    let targets = into_nodes(result, range, work)?;
1079    let mut reader = LiteralReader {
1080        input,
1081        work,
1082        range,
1083        limits,
1084        entries: 0,
1085    };
1086    let mut edits = Vec::new();
1087    for selected in targets.nodes {
1088        reader.work.visit(range)?;
1089        let target = collection_node(selected.node, input, reader.work, range, limits)?;
1090        if !matches!(
1091            target.kind(),
1092            NodeKind::SetLiteral | NodeKind::ArrayLiteral | NodeKind::MatrixLiteral
1093        ) {
1094            return Err(QueryError::input(
1095                input.original_range(target.range()),
1096                "filter_elements requires a literal set, array or matrix; tuples and records are inspection values",
1097            ));
1098        }
1099        let literal = reader.read(target, 0)?;
1100        let edit = match &literal.kind {
1101            LiteralKind::Set(values) => {
1102                let nodes: Vec<_> = target.child_nodes().collect();
1103                let values: Vec<_> = values.iter().collect();
1104                let retained =
1105                    compare_elements(input, &nodes, &values, comparison, reader.work, range)?;
1106                collection_edit(
1107                    input,
1108                    target,
1109                    &nodes,
1110                    &retained,
1111                    TokenKind::Comma,
1112                    reader.work,
1113                    range,
1114                )?
1115            }
1116            LiteralKind::Array { indexing, entries } => {
1117                let nodes: Vec<_> = target.child_nodes().collect();
1118                let values: Vec<_> = entries.iter().map(|entry| &entry.value).collect();
1119                let value_nodes: Vec<_> = nodes
1120                    .iter()
1121                    .map(|node| {
1122                        if node.kind() == NodeKind::IndexedArrayEntry {
1123                            node.child_nodes().nth(1).unwrap()
1124                        } else {
1125                            node
1126                        }
1127                    })
1128                    .collect();
1129                let retained =
1130                    compare_elements(input, &value_nodes, &values, comparison, reader.work, range)?;
1131                if *indexing == ArrayIndexing::StartingKey {
1132                    let key = entries[0].key.as_ref().unwrap();
1133                    let key_node = nodes[0].child_nodes().next().unwrap();
1134                    let parts = key_parts(key, key_node, input, reader.work, range)?;
1135                    validate_keys(&[parts], &literal.range, reader.work, range)?;
1136                    if retained.iter().any(|retain| !retain) {
1137                        return Err(QueryError::input(
1138                            literal.range,
1139                            "cannot filter a starting-key array with a bare tail without model facts establishing surviving key identities",
1140                        ));
1141                    }
1142                }
1143                if *indexing == ArrayIndexing::Explicit {
1144                    let mut keys = Vec::new();
1145                    for (entry, node) in entries.iter().zip(&nodes) {
1146                        keys.push(key_parts(
1147                            entry.key.as_ref().unwrap(),
1148                            node.child_nodes().next().unwrap(),
1149                            input,
1150                            reader.work,
1151                            range,
1152                        )?);
1153                    }
1154                    validate_keys(&keys, &literal.range, reader.work, range)?;
1155                    let kept: Vec<_> = keys
1156                        .iter()
1157                        .zip(&retained)
1158                        .filter(|(_, retain)| **retain)
1159                        .map(|(key, _)| key.clone())
1160                        .collect();
1161                    validate_keys(&kept, &literal.range, reader.work, range)?;
1162                    if kept.is_empty() && keys.first().is_some_and(|key| key.len() > 1) {
1163                        return Err(QueryError::input(
1164                            literal.range,
1165                            "empty tuple-key array cannot retain its written axis arity",
1166                        ));
1167                    }
1168                }
1169                collection_edit(
1170                    input,
1171                    target,
1172                    &nodes,
1173                    &retained,
1174                    TokenKind::Comma,
1175                    reader.work,
1176                    range,
1177                )?
1178            }
1179            LiteralKind::Matrix { column_keys, rows } => matrix_edit(
1180                input,
1181                target,
1182                column_keys,
1183                rows,
1184                comparison,
1185                reader.work,
1186                range,
1187            )?,
1188            _ => unreachable!(),
1189        };
1190        edits.push(edit);
1191    }
1192    let ancestors = enclosing_shapes(input, &edits, reader.work, range)?;
1193    let candidate = crate::finish_candidate(input, &edits, &[], reader.work, range)?;
1194    for (ancestor, kind) in ancestors {
1195        let start = shifted_boundary(ancestor.start, &edits);
1196        let end = shifted_boundary(ancestor.end, &edits);
1197        let mut pending = vec![candidate.syntax().tree()];
1198        let mut found = false;
1199        while let Some(node) = pending.pop() {
1200            reader.work.visit(range)?;
1201            let node_range = candidate.original_range(node.range());
1202            if node_range.start == start && node_range.end == end && node.kind() == kind {
1203                validate_enclosing_shape(&candidate, node, reader.work, range, limits)
1204                    .map_err(|error| QueryError::input(ancestor.clone(), error.message))?;
1205                found = true;
1206                break;
1207            }
1208            reader.work.spend(node.children().len(), range)?;
1209            pending.extend(node.child_nodes().filter(|child| {
1210                let child_range = candidate.original_range(child.range());
1211                child_range.start <= start && child_range.end >= end
1212            }));
1213        }
1214        if !found {
1215            return Err(QueryError::query(
1216                range.clone(),
1217                "data edit candidate changed an enclosing collection unexpectedly",
1218            ));
1219        }
1220    }
1221    Ok(candidate.bytes)
1222}
1223
1224fn enclosing_shapes(
1225    input: &Input,
1226    edits: &[TextEdit],
1227    work: &mut Work,
1228    range: &Range<usize>,
1229) -> Result<Vec<(Range<usize>, NodeKind)>, QueryError> {
1230    let mut ancestors = Vec::new();
1231    let mut seen = HashSet::new();
1232    for edit in edits {
1233        let mut pending = vec![input.syntax().tree()];
1234        while let Some(node) = pending.pop() {
1235            work.visit(range)?;
1236            let node_range = input.original_range(node.range());
1237            work.spend(node.children().len(), range)?;
1238            if node_range != edit.range
1239                && (node.kind() == NodeKind::MatrixLiteral
1240                    || (node.kind() == NodeKind::ArrayLiteral
1241                        && array_indexing(node) != ArrayIndexing::Positional))
1242                && seen.insert(node_range.start)
1243            {
1244                ancestors.push((node_range, node.kind()));
1245            }
1246            pending.extend(node.child_nodes().filter(|child| {
1247                let child_range = input.original_range(child.range());
1248                child_range.start <= edit.range.start && child_range.end >= edit.range.end
1249            }));
1250        }
1251    }
1252    Ok(ancestors)
1253}
1254
1255fn shifted_boundary(position: usize, edits: &[TextEdit]) -> usize {
1256    let mut shifted = position;
1257    for edit in edits.iter().filter(|edit| edit.range.end <= position) {
1258        let length: usize = edit
1259            .replacement
1260            .iter()
1261            .map(|part| match part {
1262                EditPart::Original(range) => range.len(),
1263                EditPart::Text(text) => text.len(),
1264            })
1265            .sum();
1266        shifted = shifted - edit.range.len() + length;
1267    }
1268    shifted
1269}
1270
1271fn validate_enclosing_shape(
1272    input: &Input,
1273    node: &SyntaxNode,
1274    work: &mut Work,
1275    range: &Range<usize>,
1276    limits: Limits,
1277) -> Result<(), QueryError> {
1278    // Filtering a nested value preserves its enclosing entry/cell count. Check
1279    // enclosing keys and axes without evaluating unrelated sibling values.
1280    let mut reader = LiteralReader {
1281        input,
1282        work,
1283        range,
1284        limits,
1285        entries: 0,
1286    };
1287    let source_range = input.original_range(node.range());
1288    reader.work.spend(node.children().len(), range)?;
1289    let mut axes = Vec::new();
1290    if node.kind() == NodeKind::ArrayLiteral {
1291        for entry in node
1292            .child_nodes()
1293            .filter(|entry| entry.kind() == NodeKind::IndexedArrayEntry)
1294        {
1295            let key_node = entry.child_nodes().next().unwrap();
1296            let key = reader.read(key_node, 0)?;
1297            axes.push(key_parts(&key, key_node, input, reader.work, range)?);
1298        }
1299        validate_keys(&axes, &source_range, reader.work, range)?;
1300    } else {
1301        let mut width = None;
1302        let mut column_width = None;
1303        let mut row_keyed = None;
1304        let mut row_keys = Vec::new();
1305        for row in node.child_nodes() {
1306            reader.work.spend(row.children().len(), range)?;
1307            if row.kind() == NodeKind::MatrixColumnIndices {
1308                column_width = Some(row.child_nodes().count());
1309                for key_node in row.child_nodes() {
1310                    let key = reader.read(key_node, 0)?;
1311                    axes.push(vec![key_part(&key, key_node, input, reader.work, range)?]);
1312                }
1313            } else {
1314                let keyed = has_colon(input, row);
1315                if row_keyed.is_some_and(|previous| previous != keyed) {
1316                    return Err(QueryError::input(
1317                        source_range,
1318                        "matrix rows mix explicit and positional keys",
1319                    ));
1320                }
1321                row_keyed = Some(keyed);
1322                let cells = row.child_nodes().count() - usize::from(keyed);
1323                if width.is_some_and(|previous| previous != cells) {
1324                    return Err(QueryError::input(
1325                        source_range,
1326                        "enclosing matrix must remain rectangular",
1327                    ));
1328                }
1329                width = Some(cells);
1330                if keyed {
1331                    let key_node = row.child_nodes().next().unwrap();
1332                    let key = reader.read(key_node, 0)?;
1333                    row_keys.push(vec![key_part(&key, key_node, input, reader.work, range)?]);
1334                }
1335            }
1336        }
1337        if column_width.is_some_and(|columns| width != Some(columns)) {
1338            return Err(QueryError::input(
1339                source_range,
1340                "enclosing matrix cells and column keys must remain rectangular",
1341            ));
1342        }
1343        validate_keys(&axes, &source_range, reader.work, range)?;
1344        validate_keys(&row_keys, &source_range, reader.work, range)?;
1345    }
1346    Ok(())
1347}
1348
1349fn matrix_edit(
1350    input: &Input,
1351    target: &SyntaxNode,
1352    columns: &[LiteralValue<'_>],
1353    rows: &[LiteralRow<'_>],
1354    comparison: &Comparison,
1355    work: &mut Work,
1356    range: &Range<usize>,
1357) -> Result<TextEdit, QueryError> {
1358    let source_range = input.original_range(target.range());
1359    let written: Vec<_> = target.child_nodes().collect();
1360    let row_nodes: Vec<_> = written
1361        .iter()
1362        .copied()
1363        .filter(|node| node.kind() == NodeKind::MatrixRow)
1364        .collect();
1365    let width = rows.first().map_or(0, |row| row.cells.len());
1366    if rows.iter().any(|row| row.cells.len() != width)
1367        || (!columns.is_empty() && columns.len() != width)
1368    {
1369        return Err(QueryError::input(
1370            source_range,
1371            "matrix cells and column keys must form a rectangular shape",
1372        ));
1373    }
1374    let row_keyed = rows.first().is_some_and(|row| row.key.is_some());
1375    if rows.iter().any(|row| row.key.is_some() != row_keyed) {
1376        return Err(QueryError::input(
1377            source_range,
1378            "matrix rows mix explicit and positional keys",
1379        ));
1380    }
1381    for (keys, nodes) in [
1382        (
1383            columns.iter().collect::<Vec<_>>(),
1384            written
1385                .first()
1386                .filter(|node| node.kind() == NodeKind::MatrixColumnIndices)
1387                .map_or(Vec::new(), |node| node.child_nodes().collect()),
1388        ),
1389        (
1390            rows.iter().filter_map(|row| row.key.as_ref()).collect(),
1391            row_nodes
1392                .iter()
1393                .filter(|node| has_colon(input, node))
1394                .map(|node| node.child_nodes().next().unwrap())
1395                .collect(),
1396        ),
1397    ] {
1398        let mut parts = Vec::new();
1399        for (key, node) in keys.iter().zip(nodes) {
1400            parts.push(vec![key_part(key, node, input, work, range)?]);
1401        }
1402        validate_keys(&parts, &source_range, work, range)?;
1403    }
1404    let mut common = None;
1405    let mut parts = Vec::new();
1406    for (row, node) in rows.iter().zip(row_nodes) {
1407        let nodes: Vec<_> = node
1408            .child_nodes()
1409            .skip(usize::from(row.key.is_some()))
1410            .collect();
1411        let values: Vec<_> = row.cells.iter().collect();
1412        let retained = compare_elements(input, &nodes, &values, comparison, work, range)?;
1413        if let Some(common) = &common {
1414            if common != &retained {
1415                return Err(QueryError::input(
1416                    source_range,
1417                    "matrix filtering requires the same surviving columns in every row",
1418                ));
1419            }
1420        } else {
1421            common = Some(retained.clone());
1422        }
1423        parts.push(collection_edit(
1424            input,
1425            node,
1426            &nodes,
1427            &retained,
1428            TokenKind::Comma,
1429            work,
1430            range,
1431        )?);
1432    }
1433    if let Some(retained) = common {
1434        if !retained.iter().any(|retain| *retain) {
1435            return Err(QueryError::input(
1436                source_range,
1437                "matrix filtering cannot represent an empty surviving column axis",
1438            ));
1439        }
1440        if !columns.is_empty() {
1441            let header = written[0];
1442            let nodes: Vec<_> = header.child_nodes().collect();
1443            let mut kept = Vec::new();
1444            for ((key, node), retain) in columns.iter().zip(&nodes).zip(&retained) {
1445                if *retain {
1446                    kept.push(vec![key_part(key, node, input, work, range)?]);
1447                }
1448            }
1449            validate_keys(&kept, &source_range, work, range)?;
1450            parts.push(collection_edit(
1451                input,
1452                header,
1453                &nodes,
1454                &retained,
1455                TokenKind::Colon,
1456                work,
1457                range,
1458            )?);
1459        }
1460    }
1461    parts.sort_by_key(|part| part.range.start);
1462    let mut replacement = Vec::new();
1463    let mut cursor = source_range.start;
1464    for part in parts {
1465        if cursor < part.range.start {
1466            replacement.push(EditPart::Original(cursor..part.range.start));
1467        }
1468        replacement.extend(part.replacement);
1469        cursor = part.range.end;
1470    }
1471    if cursor < source_range.end {
1472        replacement.push(EditPart::Original(cursor..source_range.end));
1473    }
1474    Ok(TextEdit {
1475        range: source_range,
1476        replacement,
1477    })
1478}
1479
1480pub(crate) fn collection_edit(
1481    input: &Input,
1482    target: &SyntaxNode,
1483    entries: &[&SyntaxNode],
1484    retained: &[bool],
1485    separator: TokenKind,
1486    work: &mut Work,
1487    range: &Range<usize>,
1488) -> Result<TextEdit, QueryError> {
1489    let source_range = input.original_range(target.range());
1490    let trailing_separator = separator == TokenKind::Colon;
1491    if retained.iter().all(|retain| *retain) {
1492        return Ok(TextEdit {
1493            range: source_range.clone(),
1494            replacement: vec![EditPart::Original(source_range)],
1495        });
1496    }
1497    let entry_ranges: Vec<_> = entries
1498        .iter()
1499        .map(|entry| input.original_range(entry.range()))
1500        .collect();
1501    let mut owned = entry_ranges.clone();
1502    let separators: Vec<_> = target
1503        .children()
1504        .iter()
1505        .filter_map(|child| match child {
1506            SyntaxElement::Token(index) if input.syntax().tokens()[*index].kind == separator => {
1507                Some(input.token_range(*index))
1508            }
1509            _ => None,
1510        })
1511        .filter(|token| {
1512            entry_ranges
1513                .first()
1514                .is_some_and(|first| token.start >= first.start)
1515        })
1516        .collect();
1517    attach_entry_comments(input, &entry_ranges, &mut owned, &source_range, work, range)?;
1518    let mut deleted = separators;
1519    for (entry, retain) in owned.into_iter().zip(retained) {
1520        if !retain {
1521            deleted.push(entry);
1522        }
1523    }
1524    deleted.sort_by_key(|range| (range.start, range.end));
1525    let kept: Vec<_> = entry_ranges
1526        .iter()
1527        .zip(retained)
1528        .filter(|(_, retain)| **retain)
1529        .map(|(range, _)| range)
1530        .collect();
1531    let punctuation = match separator {
1532        TokenKind::Colon => ":",
1533        TokenKind::Pipe => "|",
1534        _ => ",",
1535    };
1536    let insertions: Vec<_> = kept
1537        .iter()
1538        .enumerate()
1539        .filter(|(index, _)| trailing_separator || index + 1 < kept.len())
1540        .map(|(_, entry)| entry.end)
1541        .collect();
1542    // Cut only removed entries, their attached comments and old separators.
1543    // Copy every other byte, inserting separators before retained line comments.
1544    let mut replacement = Vec::new();
1545    let mut cursor = source_range.start;
1546    let mut insertion = 0;
1547    for deleted in deleted
1548        .iter()
1549        .chain(std::iter::once(&(source_range.end..source_range.end)))
1550    {
1551        while insertions
1552            .get(insertion)
1553            .is_some_and(|position| *position <= deleted.start)
1554        {
1555            let position = insertions[insertion];
1556            if cursor < position {
1557                replacement.push(EditPart::Original(cursor..position));
1558            }
1559            replacement.push(EditPart::Text(punctuation.to_owned()));
1560            cursor = cursor.max(position);
1561            insertion += 1;
1562        }
1563        if cursor < deleted.start {
1564            replacement.push(EditPart::Original(cursor..deleted.start));
1565        }
1566        cursor = cursor.max(deleted.end);
1567    }
1568    Ok(TextEdit {
1569        range: source_range,
1570        replacement,
1571    })
1572}
1573
1574fn attach_entry_comments(
1575    input: &Input,
1576    entries: &[Range<usize>],
1577    owned: &mut [Range<usize>],
1578    scope: &Range<usize>,
1579    work: &mut Work,
1580    query_range: &Range<usize>,
1581) -> Result<(), QueryError> {
1582    // Delimiters bound the first/last gaps. Matrix rows have no delimiters;
1583    // their prefix (an optional row key) stays outside the first attachment.
1584    let tokens = input.syntax().tokens();
1585    let first_token =
1586        tokens.partition_point(|token| token.range.end + input.syntax_offset <= scope.start);
1587    let last_token =
1588        tokens.partition_point(|token| token.range.start + input.syntax_offset < scope.end);
1589    let entry_start = entries.first().map_or(scope.start, |entry| entry.start);
1590    let prefix_end =
1591        tokens.partition_point(|token| token.range.end + input.syntax_offset <= entry_start);
1592    work.spend(prefix_end.saturating_sub(first_token), query_range)?;
1593    let prefix = &tokens[first_token..prefix_end];
1594    let start = tokens
1595        .get(first_token)
1596        .filter(|token| matches!(token.kind, TokenKind::LeftBracket | TokenKind::LeftBrace))
1597        .map_or_else(
1598            || {
1599                prefix
1600                    .iter()
1601                    .rev()
1602                    .find(|token| token.kind == TokenKind::Colon)
1603                    .map_or(entry_start, |token| token.range.end + input.syntax_offset)
1604            },
1605            |token| token.range.end + input.syntax_offset,
1606        );
1607    let end = tokens
1608        .get(last_token.saturating_sub(1))
1609        .filter(|token| matches!(token.kind, TokenKind::RightBracket | TokenKind::RightBrace))
1610        .map_or(scope.end, |token| token.range.start + input.syntax_offset);
1611    for next in 0..=entries.len() {
1612        let gap_start = if next == 0 {
1613            start
1614        } else {
1615            entries[next - 1].end
1616        };
1617        let gap_end = entries.get(next).map_or(end, |entry| entry.start);
1618        let first =
1619            tokens.partition_point(|token| token.range.end + input.syntax_offset <= gap_start);
1620        let last =
1621            tokens.partition_point(|token| token.range.start + input.syntax_offset < gap_end);
1622        let mut boundary = gap_start;
1623        if next > 0 {
1624            let mut trailing = None;
1625            for (index, token) in tokens.iter().enumerate().take(last).skip(first) {
1626                work.visit(query_range)?;
1627                let token_range = input.token_range(index);
1628                let bytes = input.token_bytes(index);
1629                if token.kind == TokenKind::Whitespace
1630                    && let Some(length) = crate::first_line_end(bytes)
1631                {
1632                    if trailing.is_some() {
1633                        owned[next - 1].end = token_range.start + length;
1634                    }
1635                    boundary = token_range.start + length;
1636                    break;
1637                }
1638                if matches!(token.kind, TokenKind::LineComment | TokenKind::BlockComment) {
1639                    trailing = Some(token_range.end);
1640                    owned[next - 1].end = token_range.end;
1641                }
1642            }
1643            boundary = boundary.max(owned[next - 1].end);
1644        }
1645        if next < entries.len() {
1646            let mut attachment = entries[next].start;
1647            let mut comment = false;
1648            for index in (first..last).rev() {
1649                work.visit(query_range)?;
1650                let token_range = input.token_range(index);
1651                if token_range.end <= boundary {
1652                    break;
1653                }
1654                let token = &tokens[index];
1655                if token.kind == TokenKind::Whitespace {
1656                    if crate::line_breaks(input.token_bytes(index)) >= 2 {
1657                        break;
1658                    }
1659                    attachment = crate::line_indent_start(&input.bytes, attachment, boundary);
1660                } else if matches!(token.kind, TokenKind::Comma | TokenKind::Colon) {
1661                    break;
1662                } else if matches!(token.kind, TokenKind::LineComment | TokenKind::BlockComment)
1663                    && crate::standalone(&input.bytes, token_range.start, boundary)
1664                {
1665                    comment = true;
1666                    attachment =
1667                        crate::line_indent_start(&input.bytes, token_range.start, boundary);
1668                } else {
1669                    break;
1670                }
1671            }
1672            if comment {
1673                owned[next].start = attachment.max(boundary);
1674            }
1675        }
1676    }
1677    Ok(())
1678}
1679
1680/// Decode only an index, leaving computed descendants in the value untouched.
1681pub(crate) fn read_key(
1682    input: &Input,
1683    node: &SyntaxNode,
1684    work: &mut Work,
1685    range: &Range<usize>,
1686    limits: Limits,
1687) -> Result<Vec<Key>, QueryError> {
1688    let mut reader = LiteralReader {
1689        input,
1690        work,
1691        range,
1692        limits,
1693        entries: 0,
1694    };
1695    let value = reader.read(node, 0)?;
1696    key_parts(&value, node, input, reader.work, range)
1697}
1698
1699pub(crate) fn admit_json(
1700    selection: &LiteralSelection<'_>,
1701    work: &mut Work,
1702    range: &Range<usize>,
1703) -> Result<(), QueryError> {
1704    fn admit_value(
1705        value: &LiteralValue<'_>,
1706        work: &mut Work,
1707        range: &Range<usize>,
1708    ) -> Result<(), QueryError> {
1709        // Source metadata, kind, content containers and their field storage.
1710        work.produce(24, range)?;
1711        work.spend(128 + value.file.len() + value.spelling.len(), range)?;
1712        match &value.kind {
1713            LiteralKind::Member(name) => work.spend(name.len(), range)?,
1714            LiteralKind::Set(values) | LiteralKind::Tuple(values) => {
1715                for child in values {
1716                    admit_value(child, work, range)?;
1717                }
1718            }
1719            LiteralKind::Record(fields) => {
1720                for field in fields {
1721                    work.produce(14, range)?;
1722                    work.spend(
1723                        48 + field.label.name.len() + field.label.spelling.len(),
1724                        range,
1725                    )?;
1726                    admit_value(&field.value, work, range)?;
1727                }
1728            }
1729            LiteralKind::Array { entries, .. } => {
1730                for entry in entries {
1731                    work.produce(5, range)?;
1732                    if let Some(key) = &entry.key {
1733                        admit_value(key, work, range)?;
1734                    }
1735                    admit_value(&entry.value, work, range)?;
1736                }
1737            }
1738            LiteralKind::Matrix { column_keys, rows } => {
1739                for key in column_keys {
1740                    admit_value(key, work, range)?;
1741                }
1742                for row in rows {
1743                    work.produce(5, range)?;
1744                    if let Some(key) = &row.key {
1745                        admit_value(key, work, range)?;
1746                    }
1747                    for cell in &row.cells {
1748                        admit_value(cell, work, range)?;
1749                    }
1750                }
1751            }
1752            _ => {}
1753        }
1754        Ok(())
1755    }
1756    for v in &selection.values {
1757        admit_value(v, work, range)?;
1758    }
1759    Ok(())
1760}
1761
1762impl Comparison {
1763    pub(crate) fn shared(predicate: zirql::ValueExpression, read_work: usize) -> Self {
1764        Self {
1765            kind: ComparisonKind::Eq,
1766            value: Scalar::Boolean(true),
1767            predicate: Some(std::rc::Rc::new(predicate)),
1768            bindings: std::rc::Rc::new(zirql::Bindings::new(Vec::new()).expect("empty bindings")),
1769            read_work,
1770        }
1771    }
1772    fn prepare(
1773        &self,
1774        subject: zirql::ValueType,
1775        budget: &mut zirql::Budget,
1776        range: &Range<usize>,
1777    ) -> Result<(), QueryError> {
1778        let Some(predicate) = &self.predicate else {
1779            return Ok(());
1780        };
1781        let mut types = std::collections::BTreeMap::new();
1782        for (name, value) in self.bindings.iter() {
1783            types.insert(
1784                name.to_owned(),
1785                zirql::constructed_value_type(value, budget, zirql::Location::Query(range.clone()))
1786                    .map_err(pipeline::core_error)?,
1787            );
1788        }
1789        types.insert("\0scalar".into(), subject);
1790        zirql::value_expression_types(predicate, &types, budget).map_err(pipeline::core_error)?;
1791        Ok(())
1792    }
1793    fn test(
1794        &self,
1795        scalar: &Scalar,
1796        budget: &mut zirql::Budget,
1797        range: &Range<usize>,
1798    ) -> Result<bool, QueryError> {
1799        let value = match scalar {
1800            Scalar::Integer(value) => zirql::ConstructedValue::Integer(*value),
1801            Scalar::Float(value) => zirql::ConstructedValue::Float(*value),
1802            Scalar::Boolean(value) => zirql::ConstructedValue::Boolean(*value),
1803            Scalar::String(value) => zirql::ConstructedValue::String(value.clone()),
1804            Scalar::Member(_) => {
1805                return Err(QueryError::input(
1806                    range.clone(),
1807                    "scalar element predicate needs established nominal facts; use a direct member comparison",
1808                ));
1809            }
1810        };
1811        let mut values = Vec::new();
1812        for (name, value) in self.bindings.iter() {
1813            values.push((
1814                zirql::Located::new(name.to_owned(), range.clone()),
1815                zirql::copy_constructed_value(value, budget, zirql::Location::Query(range.clone()))
1816                    .map_err(pipeline::core_error)?,
1817            ));
1818        }
1819        values.push((zirql::Located::new("\0scalar".into(), range.clone()), value));
1820        let bindings = zirql::Bindings::new(values).map_err(pipeline::core_error)?;
1821        let result = zirql::evaluate_value(
1822            self.predicate.as_ref().expect("shared predicate"),
1823            &bindings,
1824            budget,
1825        )
1826        .map_err(|error| {
1827            let mut error = pipeline::core_error(error);
1828            error.location = crate::ErrorLocation::Input;
1829            error.range = range.clone();
1830            error
1831        })?;
1832        match result.data() {
1833            zirql::ConstructedValue::Boolean(keep) => Ok(*keep),
1834            _ => Err(QueryError::input(
1835                range.clone(),
1836                "scalar predicate requires a Boolean",
1837            )),
1838        }
1839    }
1840}