1use alloc::{
8 collections::BTreeMap,
9 string::{String, ToString},
10 vec,
11 vec::Vec,
12};
13use core::{fmt, ops::Range};
14
15use crate::{
16 AssignmentState, BareKeys, Document, Entry, Reference, ReferenceKind, ReferenceOperator,
17 ReferenceRules,
18};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22#[non_exhaustive]
23pub enum ExpandScope {
24 Sequential,
26 WholeFile,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32#[non_exhaustive]
33pub enum LookupOrder {
34 CallerFirst,
36 FileFirst,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43#[non_exhaustive]
44pub enum Unresolved {
45 Keep,
47 Empty,
49 Error,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55#[non_exhaustive]
56pub struct ExpandOptions {
57 pub rules: ReferenceRules,
59 pub scope: ExpandScope,
61 pub lookup_order: LookupOrder,
63 pub unresolved: Unresolved,
65 pub max_depth: usize,
67 pub max_value_bytes: usize,
69}
70
71impl ExpandOptions {
72 #[must_use]
74 pub const fn with_rules(mut self, value: ReferenceRules) -> Self {
75 self.rules = value;
76 self
77 }
78 #[must_use]
80 pub const fn with_scope(mut self, value: ExpandScope) -> Self {
81 self.scope = value;
82 self
83 }
84 #[must_use]
86 pub const fn with_lookup_order(mut self, value: LookupOrder) -> Self {
87 self.lookup_order = value;
88 self
89 }
90 #[must_use]
92 pub const fn with_unresolved(mut self, value: Unresolved) -> Self {
93 self.unresolved = value;
94 self
95 }
96 #[must_use]
98 pub const fn with_max_depth(mut self, value: usize) -> Self {
99 self.max_depth = value;
100 self
101 }
102 #[must_use]
104 pub const fn with_max_value_bytes(mut self, value: usize) -> Self {
105 self.max_value_bytes = value;
106 self
107 }
108}
109impl ExpandOptions {
110 #[must_use]
112 pub const fn portable() -> Self {
113 Self {
114 rules: ReferenceRules::PORTABLE,
115 scope: ExpandScope::Sequential,
116 lookup_order: LookupOrder::FileFirst,
117 unresolved: Unresolved::Keep,
118 max_depth: 64,
119 max_value_bytes: 1_048_576,
120 }
121 }
122 #[must_use]
124 pub const fn compose() -> Self {
125 Self {
126 rules: ReferenceRules::COMPOSE,
127 ..Self::portable()
128 }
129 }
130 #[must_use]
132 pub const fn dotenv_expand() -> Self {
133 Self {
134 rules: ReferenceRules::DOTENV_EXPAND,
135 ..Self::portable()
136 }
137 }
138 #[must_use]
140 pub const fn none() -> Self {
141 Self {
142 rules: ReferenceRules::NONE,
143 ..Self::portable()
144 }
145 }
146}
147
148impl Default for ExpandOptions {
149 fn default() -> Self {
150 Self::portable()
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
156#[non_exhaustive]
157pub struct CommandContext<'a> {
158 pub command: &'a str,
160 pub reference: Range<usize>,
162 pub payload: Range<usize>,
164 pub source: Option<&'a str>,
166}
167
168#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct CommandError {
171 message: String,
172}
173
174impl CommandError {
175 #[must_use]
177 pub fn new(message: impl Into<String>) -> Self {
178 Self {
179 message: message.into(),
180 }
181 }
182 #[must_use]
184 pub fn message(&self) -> &str {
185 &self.message
186 }
187}
188
189impl fmt::Display for CommandError {
190 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
191 formatter.write_str(&self.message)
192 }
193}
194
195impl core::error::Error for CommandError {}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
199#[non_exhaustive]
200pub enum CommandOutcome {
201 Output(String),
203 Error(String),
205}
206
207#[derive(Debug, Clone, PartialEq, Eq)]
209#[non_exhaustive]
210pub struct CommandCall {
211 pub reference: Range<usize>,
213 pub payload: Range<usize>,
215 pub source: Option<String>,
217 pub outcome: CommandOutcome,
219}
220
221#[derive(Debug, Clone, PartialEq, Eq)]
223#[non_exhaustive]
224pub struct ResolveFailure {
225 pub error: ResolveError,
227 pub commands: Vec<CommandCall>,
229}
230
231impl fmt::Display for ResolveFailure {
232 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
233 self.error.fmt(formatter)
234 }
235}
236
237impl core::error::Error for ResolveFailure {}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
241#[non_exhaustive]
242pub enum ValueOrigin {
243 File {
245 entry: usize,
247 key: String,
249 span: Range<usize>,
251 },
252 Lookup,
254}
255
256#[derive(Debug, Clone, PartialEq, Eq)]
258#[non_exhaustive]
259pub struct ResolvedSegment {
260 pub value: String,
262 pub kind: ResolvedSegmentKind,
264}
265
266#[derive(Debug, Clone, PartialEq, Eq)]
268#[non_exhaustive]
269pub enum ResolvedSegmentKind {
270 Literal {
272 span: Range<usize>,
274 },
275 Variable {
277 name: String,
279 reference: Range<usize>,
281 operator: Option<ReferenceOperator>,
283 origin: Option<ValueOrigin>,
285 parts: Vec<ResolvedSegment>,
287 },
288 Unresolved {
290 name: String,
292 reference: Range<usize>,
294 },
295 Command {
297 reference: Range<usize>,
299 payload: Range<usize>,
301 source: Option<String>,
303 },
304}
305
306#[derive(Debug, Clone, PartialEq, Eq)]
308#[non_exhaustive]
309pub struct ResolvedEntry {
310 pub entry: usize,
312 pub key: String,
314 pub state: AssignmentState,
316 pub value: Option<String>,
318 pub effective_value: Option<String>,
320 pub segments: Vec<ResolvedSegment>,
322}
323
324#[derive(Debug, Clone, PartialEq, Eq)]
326pub struct ResolvedDocument {
327 entries: Vec<ResolvedEntry>,
328 commands: Vec<CommandCall>,
329}
330
331impl ResolvedDocument {
332 #[must_use]
334 pub fn entries(&self) -> &[ResolvedEntry] {
335 &self.entries
336 }
337 #[must_use]
339 pub fn commands(&self) -> &[CommandCall] {
340 &self.commands
341 }
342 #[must_use]
344 pub fn len(&self) -> usize {
345 self.entries.len()
346 }
347 #[must_use]
349 pub fn is_empty(&self) -> bool {
350 self.entries.is_empty()
351 }
352}
353
354#[derive(Debug, Clone, PartialEq, Eq)]
356#[non_exhaustive]
357pub enum ResolveError {
358 Unresolved {
360 key: String,
362 name: String,
364 span: Range<usize>,
366 },
367 Required {
369 key: String,
371 name: String,
373 message: String,
375 span: Range<usize>,
377 },
378 Cycle {
380 key: String,
382 chain: Vec<String>,
384 },
385 DepthExceeded {
387 key: String,
389 limit: usize,
391 span: Range<usize>,
393 },
394 ValueTooLarge {
396 key: String,
398 limit: usize,
400 span: Range<usize>,
402 },
403 Command {
405 key: String,
407 reference: Range<usize>,
409 message: String,
411 },
412}
413
414impl fmt::Display for ResolveError {
415 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
416 match self {
417 Self::Unresolved { key, name, .. } => {
418 write!(
419 formatter,
420 "unresolved variable {name} while resolving {key}"
421 )
422 }
423 Self::Required {
424 key, name, message, ..
425 } => write!(
426 formatter,
427 "required variable {name} is unset or empty while resolving {key}: {message}"
428 ),
429 Self::Cycle { key, chain } => {
430 write!(formatter, "reference cycle while resolving {key}: ")?;
431 for (index, name) in chain.iter().enumerate() {
432 if index != 0 {
433 formatter.write_str(" -> ")?;
434 }
435 formatter.write_str(name)?;
436 }
437 Ok(())
438 }
439 Self::DepthExceeded { key, limit, .. } => {
440 write!(
441 formatter,
442 "reference depth limit {limit} exceeded while resolving {key}"
443 )
444 }
445 Self::ValueTooLarge { key, limit, .. } => {
446 write!(formatter, "resolved value for {key} exceeds {limit} bytes")
447 }
448 Self::Command { key, message, .. } => {
449 write!(
450 formatter,
451 "command callback failed while resolving {key}: {message}"
452 )
453 }
454 }
455 }
456}
457
458impl core::error::Error for ResolveError {}
459
460impl Document<'_> {
461 pub fn resolve<F>(
485 &self,
486 lookup: F,
487 options: &ExpandOptions,
488 ) -> Result<ResolvedDocument, ResolveError>
489 where
490 F: Fn(&str) -> Option<String>,
491 {
492 let mut resolver = Resolver {
493 document: self,
494 lookup,
495 options: *options,
496 bindings: BTreeMap::new(),
497 cache: BTreeMap::new(),
498 stack: Vec::new(),
499 commands: None,
500 source_identity: None,
501 command_calls: Vec::new(),
502 };
503 resolver.resolve_document()
504 }
505
506 pub fn resolve_with_commands<F, C>(
518 &self,
519 lookup: F,
520 mut commands: C,
521 source_identity: Option<&str>,
522 options: &ExpandOptions,
523 ) -> Result<ResolvedDocument, ResolveFailure>
524 where
525 F: Fn(&str) -> Option<String>,
526 C: FnMut(CommandContext<'_>) -> Result<String, CommandError>,
527 {
528 let mut resolver = Resolver {
529 document: self,
530 lookup,
531 options: *options,
532 bindings: BTreeMap::new(),
533 cache: BTreeMap::new(),
534 stack: Vec::new(),
535 commands: Some(&mut commands),
536 source_identity: source_identity.map(str::to_string),
537 command_calls: Vec::new(),
538 };
539 match resolver.resolve_document() {
540 Ok(mut document) => {
541 document.commands = core::mem::take(&mut resolver.command_calls);
542 Ok(document)
543 }
544 Err(error) => Err(ResolveFailure {
545 error,
546 commands: core::mem::take(&mut resolver.command_calls),
547 }),
548 }
549 }
550}
551
552type CommandCallback<'a> = dyn FnMut(CommandContext<'_>) -> Result<String, CommandError> + 'a;
553
554struct Resolver<'a, 'src, F> {
555 document: &'a Document<'src>,
556 lookup: F,
557 options: ExpandOptions,
558 bindings: BTreeMap<&'a str, usize>,
559 cache: BTreeMap<usize, ResolvedEntry>,
560 stack: Vec<usize>,
561 commands: Option<&'a mut CommandCallback<'a>>,
562 source_identity: Option<String>,
563 command_calls: Vec<CommandCall>,
564}
565
566enum Bound {
567 Unset,
568 Value {
569 text: String,
570 parts: Vec<ResolvedSegment>,
571 origin: ValueOrigin,
572 },
573}
574
575impl<'a, F> Resolver<'a, '_, F>
576where
577 F: Fn(&str) -> Option<String>,
578{
579 fn resolve_document(&mut self) -> Result<ResolvedDocument, ResolveError> {
580 let entries = match self.options.scope {
581 ExpandScope::Sequential => {
582 let mut entries = Vec::new();
583 for entry in self.document.entries() {
584 let entry_result = self.resolve_entry(entry.id())?;
585 if entry.participates_in_lookup() {
586 self.bindings.insert(entry.key(), entry.id());
587 }
588 entries.push(entry_result);
589 }
590 entries
591 }
592 ExpandScope::WholeFile => {
593 for entry in self.document.entries() {
594 if entry.participates_in_lookup() {
595 self.bindings.insert(entry.key(), entry.id());
596 }
597 }
598 let mut entries = Vec::new();
599 for entry in self.document.entries() {
600 entries.push(self.resolve_entry(entry.id())?);
601 }
602 entries
603 }
604 };
605 Ok(ResolvedDocument {
606 entries,
607 commands: Vec::new(),
608 })
609 }
610
611 fn resolve_entry(&mut self, id: usize) -> Result<ResolvedEntry, ResolveError> {
612 if let Some(cached) = self.cache.get(&id) {
613 return Ok(cached.clone());
614 }
615 let entry = self
616 .document
617 .entry(id)
618 .expect("resolved entry id belongs to the document");
619 if let Some(position) = self.stack.iter().position(|current| *current == id) {
620 let mut chain = self.stack[position..]
621 .iter()
622 .map(|current| {
623 self.document
624 .entry(*current)
625 .expect("resolved stack id belongs to the document")
626 .key()
627 .to_string()
628 })
629 .collect::<Vec<_>>();
630 chain.push(entry.key().to_string());
631 return Err(ResolveError::Cycle {
632 key: entry.key().to_string(),
633 chain,
634 });
635 }
636 if self.stack.len() > self.options.max_depth {
637 return Err(ResolveError::DepthExceeded {
638 key: entry.key().to_string(),
639 limit: self.options.max_depth,
640 span: entry.line_span(),
641 });
642 }
643 self.stack.push(id);
644 let result = self.resolve_entry_value(entry);
645 self.stack.pop();
646 let resolved = result?;
647 self.cache.insert(id, resolved.clone());
648 Ok(resolved)
649 }
650
651 fn resolve_entry_value(&mut self, entry: Entry<'a>) -> Result<ResolvedEntry, ResolveError> {
652 let key = entry.key().to_string();
653 let state = entry.state();
654 let Some(value) = entry.value() else {
655 return Ok(ResolvedEntry {
656 entry: entry.id(),
657 key,
658 state,
659 value: None,
660 effective_value: effective_binding(entry, None),
661 segments: Vec::new(),
662 });
663 };
664 let references = entry.references();
665 let segments = self.resolve_segments(entry, value, 0..value.len(), None, &references)?;
666 let resolved = segments
667 .iter()
668 .map(|segment| segment.value.as_str())
669 .collect::<String>();
670 if resolved.len() > self.options.max_value_bytes {
671 return Err(ResolveError::ValueTooLarge {
672 key,
673 limit: self.options.max_value_bytes,
674 span: entry.value_span().unwrap_or_else(|| entry.line_span()),
675 });
676 }
677 Ok(ResolvedEntry {
678 entry: entry.id(),
679 effective_value: effective_binding(entry, Some(&resolved)),
680 key,
681 state,
682 value: Some(resolved),
683 segments,
684 })
685 }
686
687 fn resolve_segments(
688 &mut self,
689 entry: Entry<'a>,
690 decoded: &str,
691 range: Range<usize>,
692 parent: Option<&Reference>,
693 references: &[Reference],
694 ) -> Result<Vec<ResolvedSegment>, ResolveError> {
695 let mut segments = Vec::new();
696 let mut length = 0usize;
697 let mut cursor = range.start;
698 for reference in references {
699 if let Some(parent) = parent {
700 if reference.depth != parent.depth.saturating_add(1)
701 || !reference.span_within(parent.argument.as_ref())
702 {
703 continue;
704 }
705 } else if !reference.containers.is_empty() {
706 continue;
707 }
708 if !reference.is_eligible(self.options.rules) {
709 continue;
710 }
711 if reference.kind == ReferenceKind::Command && self.commands.is_none() {
712 continue;
713 }
714 if reference.depth > self.options.max_depth {
715 return Err(ResolveError::DepthExceeded {
716 key: entry.key().to_string(),
717 limit: self.options.max_depth,
718 span: reference.span.clone(),
719 });
720 }
721 let Some(start) = decoded_start_for_raw(entry, reference.span.start) else {
722 continue;
723 };
724 let Some(end) = decoded_end_for_raw(entry, reference.span.end) else {
725 continue;
726 };
727 if start < range.start || end > range.end || start < cursor || start > end {
728 continue;
729 }
730 if start > cursor {
731 let literal = literal_segment(entry, decoded, cursor..start);
732 account(&mut length, &literal, self.options.max_value_bytes, entry)?;
733 segments.push(literal);
734 }
735 let replacement = match reference.kind {
736 ReferenceKind::Variable => {
737 self.resolve_reference(entry, reference, decoded, start..end, references)?
738 }
739 ReferenceKind::Command => self.run_command(entry, reference)?,
740 };
741 for segment in replacement {
742 account(&mut length, &segment, self.options.max_value_bytes, entry)?;
743 segments.push(segment);
744 }
745 cursor = end;
746 }
747 if cursor < range.end {
748 let literal = literal_segment(entry, decoded, cursor..range.end);
749 account(&mut length, &literal, self.options.max_value_bytes, entry)?;
750 segments.push(literal);
751 }
752 Ok(segments)
753 }
754
755 fn run_command(
756 &mut self,
757 entry: Entry<'a>,
758 reference: &Reference,
759 ) -> Result<Vec<ResolvedSegment>, ResolveError> {
760 let payload = reference
761 .command
762 .clone()
763 .expect("eligible command has a payload");
764 let command = self.document.source()[payload.clone()].to_string();
765 let reference_span = reference.span.clone();
766 let source = self.source_identity.clone();
767 let result = {
768 let context = CommandContext {
769 command: &command,
770 reference: reference_span.clone(),
771 payload: payload.clone(),
772 source: source.as_deref(),
773 };
774 let callback = self
775 .commands
776 .as_mut()
777 .expect("command callback was checked before invocation");
778 callback(context)
779 };
780 match result {
781 Ok(output) => {
782 self.command_calls.push(CommandCall {
783 reference: reference_span.clone(),
784 payload: payload.clone(),
785 source: source.clone(),
786 outcome: CommandOutcome::Output(output.clone()),
787 });
788 Ok(vec![ResolvedSegment {
789 value: output,
790 kind: ResolvedSegmentKind::Command {
791 reference: reference_span,
792 payload,
793 source,
794 },
795 }])
796 }
797 Err(error) => {
798 let message = error.message().to_string();
799 self.command_calls.push(CommandCall {
800 reference: reference_span.clone(),
801 payload,
802 source,
803 outcome: CommandOutcome::Error(message.clone()),
804 });
805 Err(ResolveError::Command {
806 key: entry.key().to_string(),
807 reference: reference_span,
808 message,
809 })
810 }
811 }
812 }
813
814 fn resolve_reference(
815 &mut self,
816 entry: Entry<'a>,
817 reference: &Reference,
818 decoded: &str,
819 decoded_span: Range<usize>,
820 references: &[Reference],
821 ) -> Result<Vec<ResolvedSegment>, ResolveError> {
822 let name_span = reference
823 .name
824 .as_ref()
825 .expect("eligible variable has a name");
826 let name = self.document.source()[name_span.clone()].to_string();
827 let bound = self.lookup_variable(&name)?;
828 let (set, empty, text, parts, origin) = match bound {
829 Some(Bound::Value {
830 text,
831 parts,
832 origin,
833 }) => (true, text.is_empty(), text, parts, Some(origin)),
834 _ => (false, true, String::new(), Vec::new(), None),
835 };
836 let variable = |text: String, parts: Vec<ResolvedSegment>, origin: Option<ValueOrigin>| {
837 ResolvedSegment {
838 value: text,
839 kind: ResolvedSegmentKind::Variable {
840 name: name.clone(),
841 reference: reference.span.clone(),
842 operator: reference.operator,
843 origin,
844 parts,
845 },
846 }
847 };
848 match reference.operator {
849 None => {
850 if set {
851 Ok(vec![variable(text, parts, origin)])
852 } else {
853 self.unresolved(reference, decoded, decoded_span, &name)
854 }
855 }
856 Some(ReferenceOperator::DefaultIfUnset) => {
857 if set {
858 Ok(vec![variable(text, parts, origin)])
859 } else {
860 self.argument(entry, reference, decoded, references)
861 }
862 }
863 Some(ReferenceOperator::DefaultIfUnsetOrEmpty) => {
864 if set && !empty {
865 Ok(vec![variable(text, parts, origin)])
866 } else {
867 self.argument(entry, reference, decoded, references)
868 }
869 }
870 Some(ReferenceOperator::AlternativeIfSet) => {
871 if set {
872 self.argument(entry, reference, decoded, references)
873 } else {
874 Ok(Vec::new())
875 }
876 }
877 Some(ReferenceOperator::AlternativeIfSetAndNonEmpty) => {
878 if set && !empty {
879 self.argument(entry, reference, decoded, references)
880 } else {
881 Ok(Vec::new())
882 }
883 }
884 Some(ReferenceOperator::ErrorIfUnset) => {
885 if set {
886 Ok(vec![variable(text, parts, origin)])
887 } else {
888 Err(self.required(entry, reference, decoded, name))
889 }
890 }
891 Some(ReferenceOperator::ErrorIfUnsetOrEmpty) => {
892 if set && !empty {
893 Ok(vec![variable(text, parts, origin)])
894 } else {
895 Err(self.required(entry, reference, decoded, name))
896 }
897 }
898 }
899 }
900
901 fn unresolved(
902 &self,
903 reference: &Reference,
904 decoded: &str,
905 decoded_span: Range<usize>,
906 name: &str,
907 ) -> Result<Vec<ResolvedSegment>, ResolveError> {
908 Ok(match self.options.unresolved {
909 Unresolved::Keep => vec![ResolvedSegment {
910 value: decoded[decoded_span].to_string(),
911 kind: ResolvedSegmentKind::Unresolved {
912 name: name.to_string(),
913 reference: reference.span.clone(),
914 },
915 }],
916 Unresolved::Empty => Vec::new(),
917 Unresolved::Error => {
918 return Err(ResolveError::Unresolved {
919 key: self.current_key(),
920 name: name.to_string(),
921 span: reference.span.clone(),
922 });
923 }
924 })
925 }
926
927 fn required(
928 &self,
929 entry: Entry<'a>,
930 reference: &Reference,
931 decoded: &str,
932 name: String,
933 ) -> ResolveError {
934 let message = reference
935 .argument
936 .as_ref()
937 .map_or_else(String::new, |argument| {
938 decoded_start_for_raw(entry, argument.start)
939 .zip(decoded_end_for_raw(entry, argument.end))
940 .map_or_else(
941 || self.document.source()[argument.clone()].to_string(),
942 |(start, end)| decoded[start..end].to_string(),
943 )
944 });
945 ResolveError::Required {
946 key: entry.key().to_string(),
947 name,
948 message,
949 span: reference.span.clone(),
950 }
951 }
952
953 fn argument(
954 &mut self,
955 entry: Entry<'a>,
956 reference: &Reference,
957 decoded: &str,
958 references: &[Reference],
959 ) -> Result<Vec<ResolvedSegment>, ResolveError> {
960 let Some(argument) = reference.argument.as_ref() else {
961 return Ok(Vec::new());
962 };
963 let Some(start) = decoded_start_for_raw(entry, argument.start) else {
964 return Ok(vec![ResolvedSegment {
965 value: self.document.source()[argument.clone()].to_string(),
966 kind: ResolvedSegmentKind::Literal {
967 span: argument.clone(),
968 },
969 }]);
970 };
971 let Some(end) = decoded_end_for_raw(entry, argument.end) else {
972 return Ok(vec![ResolvedSegment {
973 value: self.document.source()[argument.clone()].to_string(),
974 kind: ResolvedSegmentKind::Literal {
975 span: argument.clone(),
976 },
977 }]);
978 };
979 self.resolve_segments(entry, decoded, start..end, Some(reference), references)
980 }
981
982 fn lookup_variable(&mut self, name: &str) -> Result<Option<Bound>, ResolveError> {
983 match self.options.lookup_order {
984 LookupOrder::CallerFirst => {
985 if let Some(value) = (self.lookup)(name) {
986 return Ok(Some(Bound::Value {
987 text: value,
988 parts: Vec::new(),
989 origin: ValueOrigin::Lookup,
990 }));
991 }
992 self.file_binding(name)
993 }
994 LookupOrder::FileFirst => {
995 let file = self.file_binding(name)?;
996 if file.is_some() {
997 return Ok(file);
998 }
999 Ok((self.lookup)(name).map(|value| Bound::Value {
1000 text: value,
1001 parts: Vec::new(),
1002 origin: ValueOrigin::Lookup,
1003 }))
1004 }
1005 }
1006 }
1007
1008 fn file_binding(&mut self, name: &str) -> Result<Option<Bound>, ResolveError> {
1009 let Some(id) = self.bindings.get(name).copied() else {
1010 return Ok(None);
1011 };
1012 let entry = self
1013 .document
1014 .entry(id)
1015 .expect("binding id belongs to the document");
1016 if entry.value().is_none() {
1017 if entry.state() == AssignmentState::Bare
1018 && self.document.dialect().bare_keys == BareKeys::Empty
1019 {
1020 return Ok(Some(Bound::Value {
1021 text: String::new(),
1022 parts: Vec::new(),
1023 origin: file_origin(entry),
1024 }));
1025 }
1026 return Ok(Some(Bound::Unset));
1027 }
1028 let resolved = self.resolve_entry(id)?;
1029 Ok(Some(Bound::Value {
1030 text: resolved.value.unwrap_or_default(),
1031 parts: resolved.segments,
1032 origin: file_origin(entry),
1033 }))
1034 }
1035
1036 fn current_key(&self) -> String {
1037 self.stack
1038 .last()
1039 .and_then(|id| self.document.entry(*id))
1040 .map_or_else(String::new, |entry| entry.key().to_string())
1041 }
1042}
1043
1044impl Reference {
1045 fn span_within(&self, parent: Option<&Range<usize>>) -> bool {
1046 parent.is_some_and(|parent| parent.start <= self.span.start && self.span.end <= parent.end)
1047 }
1048}
1049
1050fn effective_binding(entry: Entry<'_>, value: Option<&str>) -> Option<String> {
1051 if !entry.participates_in_lookup() {
1052 return None;
1053 }
1054 match value {
1055 Some(value) => Some(value.to_string()),
1056 None if entry.state() == AssignmentState::Bare
1057 && entry.document.dialect().bare_keys == BareKeys::Empty =>
1058 {
1059 Some(String::new())
1060 }
1061 None => None,
1062 }
1063}
1064
1065fn file_origin(entry: Entry<'_>) -> ValueOrigin {
1066 ValueOrigin::File {
1067 entry: entry.id(),
1068 key: entry.key().to_string(),
1069 span: entry.value_span().unwrap_or_else(|| entry.line_span()),
1070 }
1071}
1072
1073fn literal_segment(entry: Entry<'_>, decoded: &str, range: Range<usize>) -> ResolvedSegment {
1074 let span = entry
1075 .decoded_offset_to_raw(range.start)
1076 .ok()
1077 .zip(entry.decoded_offset_to_raw(range.end).ok())
1078 .map_or_else(
1079 || entry.value_span().unwrap_or_else(|| entry.line_span()),
1080 |(start, end)| start..end,
1081 );
1082 ResolvedSegment {
1083 value: decoded[range].to_string(),
1084 kind: ResolvedSegmentKind::Literal { span },
1085 }
1086}
1087
1088fn account(
1089 length: &mut usize,
1090 segment: &ResolvedSegment,
1091 limit: usize,
1092 entry: Entry<'_>,
1093) -> Result<(), ResolveError> {
1094 *length = length.saturating_add(segment.value.len());
1095 if *length > limit {
1096 return Err(ResolveError::ValueTooLarge {
1097 key: entry.key().to_string(),
1098 limit,
1099 span: entry.line_span(),
1100 });
1101 }
1102 Ok(())
1103}
1104
1105fn decoded_start_for_raw(entry: Entry<'_>, raw: usize) -> Option<usize> {
1106 let value = entry.value()?;
1107 let mut start = None;
1108 for (offset, _) in value.char_indices() {
1109 let mapped = entry.decoded_offset_to_raw(offset).ok()?;
1110 if mapped <= raw {
1111 start = Some(offset);
1112 } else {
1113 break;
1114 }
1115 }
1116 if entry.decoded_offset_to_raw(value.len()).ok()? <= raw {
1117 start = Some(value.len());
1118 }
1119 start
1120}
1121
1122fn decoded_end_for_raw(entry: Entry<'_>, raw: usize) -> Option<usize> {
1123 let value = entry.value()?;
1124 for (offset, _) in value.char_indices() {
1125 if entry.decoded_offset_to_raw(offset).ok()? >= raw {
1126 return Some(offset);
1127 }
1128 }
1129 Some(value.len())
1130}