1use 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum ArrayIndexing {
18 Positional,
19 Explicit,
20 StartingKey,
21}
22
23#[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#[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#[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
493pub(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 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 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
1060pub(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 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 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 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
1680pub(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 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}