1use crate::{Bindings, Diagnostic, Document, Node, Position, Report, TokenCounter};
2use std::{collections::BTreeMap, ops::Range};
3
4#[derive(Debug, Clone, PartialEq, Eq)]
7#[cfg_attr(feature = "json", derive(serde::Serialize))]
8pub struct TextMeasurement {
9 pub tokenizer: String,
10 pub tokens: u64,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15#[cfg_attr(feature = "json", derive(serde::Serialize))]
16pub struct SectionOrigin {
17 pub id: Option<String>,
18 pub position: Position,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
25#[cfg_attr(feature = "json", derive(serde::Serialize))]
26pub struct RenderedSpan {
27 pub range: Range<usize>,
28 pub sections: Vec<SectionOrigin>,
29 pub variable: Option<String>,
30}
31
32#[derive(Debug, Clone)]
36pub struct RenderedPrompt {
37 text: String,
38 measurement: TextMeasurement,
39 report: Report,
40 spans: Vec<RenderedSpan>,
41 sections: BTreeMap<String, Range<usize>>,
42}
43impl RenderedPrompt {
44 pub fn text(&self) -> &str {
45 &self.text
46 }
47 pub fn measurement(&self) -> &TextMeasurement {
48 &self.measurement
49 }
50 pub fn report(&self) -> &Report {
51 &self.report
52 }
53 pub fn spans(&self) -> &[RenderedSpan] {
54 &self.spans
55 }
56 pub fn section(&self, id: &str) -> Option<&str> {
59 self.sections.get(id).map(|range| &self.text[range.clone()])
60 }
61 pub fn into_text(self) -> String {
62 self.text
63 }
64}
65
66pub fn render_checked(
69 doc: &Document,
70 bindings: &Bindings,
71 counter: &impl TokenCounter,
72) -> Result<RenderedPrompt, Vec<Diagnostic>> {
73 let (text, report) = crate::check::render_with_report(doc, bindings, counter)?;
74 let mut output = RenderedPrompt {
75 measurement: TextMeasurement {
76 tokenizer: counter.name().into(),
77 tokens: report
78 .measurements
79 .last()
80 .and_then(|m| m.tokens)
81 .expect("render counts root"),
82 },
83 text,
84 report,
85 spans: Vec::new(),
86 sections: BTreeMap::new(),
87 };
88 fn walk(
89 nodes: &[Node],
90 bindings: &Bindings,
91 ancestry: &mut Vec<SectionOrigin>,
92 offset: &mut usize,
93 out: &mut RenderedPrompt,
94 ) {
95 for node in nodes {
96 let (length, variable) = match node {
97 Node::Section(section) => {
98 let start = *offset;
99 ancestry.push(SectionOrigin {
100 id: section.id.clone(),
101 position: section.position,
102 });
103 walk(§ion.children, bindings, ancestry, offset, out);
104 ancestry.pop();
105 if let Some(id) = §ion.id {
106 out.sections.insert(id.clone(), start..*offset);
107 }
108 continue;
109 }
110 Node::Text { value } => (value.len(), None),
111 Node::Variable(variable) => {
112 (bindings[&variable.id].len(), Some(variable.id.clone()))
113 }
114 };
115 if length > 0 {
116 out.spans.push(RenderedSpan {
117 range: *offset..*offset + length,
118 sections: ancestry.clone(),
119 variable,
120 });
121 }
122 *offset += length;
123 }
124 }
125 walk(
126 &doc.children,
127 bindings,
128 &mut Vec::new(),
129 &mut 0,
130 &mut output,
131 );
132 Ok(output)
133}