pub struct Input { /* private fields */ }Expand description
Read-only original input and its byte-preserving parser view.
Implementations§
Source§impl Input
impl Input
pub fn parse(bytes: Vec<u8>, mode: FileMode) -> Result<Self, InputError>
Sourcepub fn parse_named(
bytes: Vec<u8>,
mode: FileMode,
file: impl Into<String>,
) -> Result<Self, InputError>
pub fn parse_named( bytes: Vec<u8>, mode: FileMode, file: impl Into<String>, ) -> Result<Self, InputError>
Parse original bytes with the file identity used by inspection output.
Examples found in repository?
examples/grow-wedding.rs (line 70)
63fn grow(bytes: Vec<u8>, factor: usize) -> Result<Vec<u8>> {
64 if !(1..=MAX_FACTOR).contains(&factor) {
65 return Err(format!("FACTOR must be in 1..={MAX_FACTOR}"));
66 }
67 if bytes.len() > MAX_INPUT {
68 return Err("input exceeds 1 MiB".into());
69 }
70 let input = Input::parse_named(bytes, FileMode::Data, "wedding.dzn")
71 .map_err(|error| format!("invalid data: {error:?}"))?;
72 if input
73 .syntax()
74 .tokens()
75 .len()
76 .checked_mul(factor)
77 .is_none_or(|n| n > MAX_TOKENS)
78 {
79 return Err("grown input exceeds the 200,000-token allowance".into());
80 }
81 // This example understands these eight assignments, not arbitrary dependencies.
82 let QueryResult::Selection(items) = Query::parse("items", Limits::default())
83 .map_err(|error| error.message)?
84 .evaluate(&input, Limits::default())
85 .map_err(|error| error.message)?
86 else {
87 unreachable!()
88 };
89 let mut names = BTreeSet::new();
90 for item in items.items() {
91 if item.kind != ItemKind::Assignment
92 || !item
93 .name
94 .is_some_and(|name| ASSIGNMENTS.contains(&name) && names.insert(name))
95 {
96 return Err("expected only the eight distinct wedding data assignments".into());
97 }
98 }
99 if names.len() != ASSIGNMENTS.len() {
100 return Err("all eight wedding data assignments are required".into());
101 }
102 // Refuse formatter directives: copying their text could change directive scopes.
103 for (index, token) in input.syntax().tokens().iter().enumerate() {
104 if matches!(token.kind, TokenKind::LineComment | TokenKind::BlockComment)
105 && input
106 .token_bytes(index)
107 .windows(b"zincite-fmt:".len())
108 .any(|w| w == b"zincite-fmt:")
109 {
110 return Err("comments containing zincite directives are unsupported".into());
111 }
112 }
113 let topics = literal(&input, "Topics")?;
114 let guests = literal(&input, "Guests")?;
115 let records = literal(&input, "guests")?;
116 let same = literal(&input, "same_table")?;
117 let different = literal(&input, "different_tables")?;
118 let capacities = literal(&input, "table_capacities")?;
119 let imbalance = literal(&input, "max_gender_imbalance_per_table")?;
120 let topic_members = members(&topics)?;
121 let guest_members = members(&guests)?;
122 if topic_members.is_empty() || guest_members.is_empty() {
123 return Err("Topics and Guests must be nonempty literal member sets".into());
124 }
125 let topic_names: BTreeSet<_> = topic_members.iter().map(|(name, _)| *name).collect();
126 let guest_names: BTreeSet<_> = guest_members.iter().map(|(name, _)| *name).collect();
127 if !topic_names.is_disjoint(&guest_names)
128 || topic_names
129 .iter()
130 .chain(guest_names.iter())
131 .any(|name| ASSIGNMENTS.contains(name) || ["Man", "Woman", "NA", "T"].contains(name))
132 {
133 return Err("enum members collide with the wedding model's identifiers".into());
134 }
135 validate_records(&records, &guest_names, topic_members.len())?;
136 let same_members = group_members(&same, &guest_names)?;
137 let different_members = group_members(&different, &guest_names)?;
138 let table_item = items
139 .items()
140 .iter()
141 .find(|item| item.name == Some("Tables"))
142 .unwrap();
143 let (table_count, table_range) = table_constructor(&input, &table_item.range)?;
144 let capacity_values = positional(&capacities)?;
145 if capacity_values.len() != table_count
146 || capacity_values
147 .iter()
148 .any(|value| !matches!(value.kind, LiteralKind::Integer))
149 || !matches!(imbalance.kind, LiteralKind::Integer)
150 {
151 return Err("table_capacities must contain one literal integer per table; imbalance must be a literal integer".into());
152 }
153 let grown_tables = table_count
154 .checked_mul(factor)
155 .filter(|count| i64::try_from(*count).is_ok())
156 .ok_or("table count overflow")?;
157 if factor == 1 {
158 return Ok(input.source_bytes().to_vec());
159 }
160 let mut occupied: BTreeSet<String> = input
161 .syntax()
162 .tokens()
163 .iter()
164 .enumerate()
165 .filter(|(_, token)| {
166 matches!(
167 token.kind,
168 TokenKind::Identifier | TokenKind::QuotedIdentifier
169 )
170 })
171 .map(|(index, _)| {
172 String::from_utf8_lossy(input.token_bytes(index))
173 .trim_matches('\'')
174 .to_owned()
175 })
176 .collect();
177 let mut mappings = Vec::new();
178 for copy in 1..factor {
179 let mut mapping = BTreeMap::new();
180 for (index, (name, _)) in guest_members.iter().enumerate() {
181 let generated = format!("GrownGuest_{copy}_{}", index + 1);
182 if !occupied.insert(generated.clone()) {
183 return Err(format!("generated guest identifier collides: {generated}"));
184 }
185 mapping.insert(*name, generated);
186 }
187 mappings.push(mapping);
188 }
189 let LiteralKind::Array { entries, .. } = &records.kind else {
190 unreachable!()
191 };
192 let record_keys: Vec<_> = entries
193 .iter()
194 .map(|entry| {
195 let key = entry.key.as_ref().unwrap();
196 let LiteralKind::Member(name) = key.kind else {
197 unreachable!()
198 };
199 (name, key.range.clone())
200 })
201 .collect();
202 let mut edits = Vec::new();
203 for (value, replacements) in [
204 (&guests, guest_members),
205 (&records, record_keys),
206 (&same, same_members),
207 (&different, different_members),
208 (&capacities, Vec::new()),
209 ] {
210 edits.push(copy_collection(&input, value, &replacements, &mappings)?);
211 }
212 edits.push(TextEdit {
213 range: table_range,
214 replacement: vec![EditPart::Text(grown_tables.to_string())],
215 });
216 // Charge the exact final byte count before the public edit API allocates it.
217 let mut output_len = input.source_bytes().len();
218 for edit in &edits {
219 output_len -= edit.range.len();
220 for part in &edit.replacement {
221 let len = match part {
222 EditPart::Text(text) => text.len(),
223 EditPart::Original(range) => range.len(),
224 };
225 output_len = output_len
226 .checked_add(len)
227 .filter(|n| *n <= MAX_OUTPUT)
228 .ok_or("candidate exceeds 16 MiB")?;
229 }
230 }
231 let snapshot = SourceSnapshot::new("wedding.dzn", input.source_bytes());
232 let candidate = prepare_text_edits(&snapshot, input.source_bytes(), &edits)
233 .map_err(|error| error.to_string())?;
234 Input::parse_named(candidate.clone(), FileMode::Data, "grown-wedding.dzn")
235 .map_err(|error| format!("invalid candidate: {error:?}"))?;
236 Ok(candidate)
237}pub fn file(&self) -> &str
Sourcepub fn source_bytes(&self) -> &[u8] ⓘ
pub fn source_bytes(&self) -> &[u8] ⓘ
Examples found in repository?
examples/grow-wedding.rs (line 158)
63fn grow(bytes: Vec<u8>, factor: usize) -> Result<Vec<u8>> {
64 if !(1..=MAX_FACTOR).contains(&factor) {
65 return Err(format!("FACTOR must be in 1..={MAX_FACTOR}"));
66 }
67 if bytes.len() > MAX_INPUT {
68 return Err("input exceeds 1 MiB".into());
69 }
70 let input = Input::parse_named(bytes, FileMode::Data, "wedding.dzn")
71 .map_err(|error| format!("invalid data: {error:?}"))?;
72 if input
73 .syntax()
74 .tokens()
75 .len()
76 .checked_mul(factor)
77 .is_none_or(|n| n > MAX_TOKENS)
78 {
79 return Err("grown input exceeds the 200,000-token allowance".into());
80 }
81 // This example understands these eight assignments, not arbitrary dependencies.
82 let QueryResult::Selection(items) = Query::parse("items", Limits::default())
83 .map_err(|error| error.message)?
84 .evaluate(&input, Limits::default())
85 .map_err(|error| error.message)?
86 else {
87 unreachable!()
88 };
89 let mut names = BTreeSet::new();
90 for item in items.items() {
91 if item.kind != ItemKind::Assignment
92 || !item
93 .name
94 .is_some_and(|name| ASSIGNMENTS.contains(&name) && names.insert(name))
95 {
96 return Err("expected only the eight distinct wedding data assignments".into());
97 }
98 }
99 if names.len() != ASSIGNMENTS.len() {
100 return Err("all eight wedding data assignments are required".into());
101 }
102 // Refuse formatter directives: copying their text could change directive scopes.
103 for (index, token) in input.syntax().tokens().iter().enumerate() {
104 if matches!(token.kind, TokenKind::LineComment | TokenKind::BlockComment)
105 && input
106 .token_bytes(index)
107 .windows(b"zincite-fmt:".len())
108 .any(|w| w == b"zincite-fmt:")
109 {
110 return Err("comments containing zincite directives are unsupported".into());
111 }
112 }
113 let topics = literal(&input, "Topics")?;
114 let guests = literal(&input, "Guests")?;
115 let records = literal(&input, "guests")?;
116 let same = literal(&input, "same_table")?;
117 let different = literal(&input, "different_tables")?;
118 let capacities = literal(&input, "table_capacities")?;
119 let imbalance = literal(&input, "max_gender_imbalance_per_table")?;
120 let topic_members = members(&topics)?;
121 let guest_members = members(&guests)?;
122 if topic_members.is_empty() || guest_members.is_empty() {
123 return Err("Topics and Guests must be nonempty literal member sets".into());
124 }
125 let topic_names: BTreeSet<_> = topic_members.iter().map(|(name, _)| *name).collect();
126 let guest_names: BTreeSet<_> = guest_members.iter().map(|(name, _)| *name).collect();
127 if !topic_names.is_disjoint(&guest_names)
128 || topic_names
129 .iter()
130 .chain(guest_names.iter())
131 .any(|name| ASSIGNMENTS.contains(name) || ["Man", "Woman", "NA", "T"].contains(name))
132 {
133 return Err("enum members collide with the wedding model's identifiers".into());
134 }
135 validate_records(&records, &guest_names, topic_members.len())?;
136 let same_members = group_members(&same, &guest_names)?;
137 let different_members = group_members(&different, &guest_names)?;
138 let table_item = items
139 .items()
140 .iter()
141 .find(|item| item.name == Some("Tables"))
142 .unwrap();
143 let (table_count, table_range) = table_constructor(&input, &table_item.range)?;
144 let capacity_values = positional(&capacities)?;
145 if capacity_values.len() != table_count
146 || capacity_values
147 .iter()
148 .any(|value| !matches!(value.kind, LiteralKind::Integer))
149 || !matches!(imbalance.kind, LiteralKind::Integer)
150 {
151 return Err("table_capacities must contain one literal integer per table; imbalance must be a literal integer".into());
152 }
153 let grown_tables = table_count
154 .checked_mul(factor)
155 .filter(|count| i64::try_from(*count).is_ok())
156 .ok_or("table count overflow")?;
157 if factor == 1 {
158 return Ok(input.source_bytes().to_vec());
159 }
160 let mut occupied: BTreeSet<String> = input
161 .syntax()
162 .tokens()
163 .iter()
164 .enumerate()
165 .filter(|(_, token)| {
166 matches!(
167 token.kind,
168 TokenKind::Identifier | TokenKind::QuotedIdentifier
169 )
170 })
171 .map(|(index, _)| {
172 String::from_utf8_lossy(input.token_bytes(index))
173 .trim_matches('\'')
174 .to_owned()
175 })
176 .collect();
177 let mut mappings = Vec::new();
178 for copy in 1..factor {
179 let mut mapping = BTreeMap::new();
180 for (index, (name, _)) in guest_members.iter().enumerate() {
181 let generated = format!("GrownGuest_{copy}_{}", index + 1);
182 if !occupied.insert(generated.clone()) {
183 return Err(format!("generated guest identifier collides: {generated}"));
184 }
185 mapping.insert(*name, generated);
186 }
187 mappings.push(mapping);
188 }
189 let LiteralKind::Array { entries, .. } = &records.kind else {
190 unreachable!()
191 };
192 let record_keys: Vec<_> = entries
193 .iter()
194 .map(|entry| {
195 let key = entry.key.as_ref().unwrap();
196 let LiteralKind::Member(name) = key.kind else {
197 unreachable!()
198 };
199 (name, key.range.clone())
200 })
201 .collect();
202 let mut edits = Vec::new();
203 for (value, replacements) in [
204 (&guests, guest_members),
205 (&records, record_keys),
206 (&same, same_members),
207 (&different, different_members),
208 (&capacities, Vec::new()),
209 ] {
210 edits.push(copy_collection(&input, value, &replacements, &mappings)?);
211 }
212 edits.push(TextEdit {
213 range: table_range,
214 replacement: vec![EditPart::Text(grown_tables.to_string())],
215 });
216 // Charge the exact final byte count before the public edit API allocates it.
217 let mut output_len = input.source_bytes().len();
218 for edit in &edits {
219 output_len -= edit.range.len();
220 for part in &edit.replacement {
221 let len = match part {
222 EditPart::Text(text) => text.len(),
223 EditPart::Original(range) => range.len(),
224 };
225 output_len = output_len
226 .checked_add(len)
227 .filter(|n| *n <= MAX_OUTPUT)
228 .ok_or("candidate exceeds 16 MiB")?;
229 }
230 }
231 let snapshot = SourceSnapshot::new("wedding.dzn", input.source_bytes());
232 let candidate = prepare_text_edits(&snapshot, input.source_bytes(), &edits)
233 .map_err(|error| error.to_string())?;
234 Input::parse_named(candidate.clone(), FileMode::Data, "grown-wedding.dzn")
235 .map_err(|error| format!("invalid candidate: {error:?}"))?;
236 Ok(candidate)
237}
238
239fn literal<'a>(input: &'a Input, name: &str) -> Result<LiteralValue<'a>> {
240 let query = Query::parse(
241 &format!("filter(name(\"{name}\")) | values"),
242 Limits::default(),
243 )
244 .map_err(|error| error.message)?;
245 let QueryResult::Literals(values) = query
246 .evaluate(input, Limits::default())
247 .map_err(|error| error.message)?
248 else {
249 unreachable!()
250 };
251 match values.values() {
252 [value] => Ok(value.clone()),
253 _ => Err(format!("expected one {name} assignment")),
254 }
255}
256
257fn members<'a>(value: &LiteralValue<'a>) -> Result<Vec<(&'a str, Range<usize>)>> {
258 let LiteralKind::Set(values) = &value.kind else {
259 return Err("expected a literal enum member set".into());
260 };
261 let mut seen = BTreeSet::new();
262 values
263 .iter()
264 .map(|value| match value.kind {
265 LiteralKind::Member(name) if plain_member(value) && seen.insert(name) => {
266 Ok((name, value.range.clone()))
267 }
268 _ => Err("expected distinct literal enum members".into()),
269 })
270 .collect()
271}
272
273// Remapping replaces one identifier token, never parentheses or attached comments.
274fn plain_member(value: &LiteralValue<'_>) -> bool {
275 let LiteralKind::Member(name) = value.kind else {
276 return false;
277 };
278 value.spelling == name.as_bytes()
279 || value
280 .spelling
281 .strip_prefix(b"'")
282 .and_then(|s| s.strip_suffix(b"'"))
283 == Some(name.as_bytes())
284}
285
286fn positional<'a, 'b>(value: &'b LiteralValue<'a>) -> Result<Vec<&'b LiteralValue<'a>>> {
287 match &value.kind {
288 LiteralKind::Array {
289 indexing: ArrayIndexing::Positional,
290 entries,
291 } => Ok(entries.iter().map(|entry| &entry.value).collect()),
292 _ => Err("expected a positional literal array".into()),
293 }
294}
295
296fn validate_records(
297 value: &LiteralValue<'_>,
298 guests: &BTreeSet<&str>,
299 topics: usize,
300) -> Result<()> {
301 let LiteralKind::Array {
302 indexing: ArrayIndexing::Explicit,
303 entries,
304 } = &value.kind
305 else {
306 return Err("guests must be an explicitly guest-keyed record array".into());
307 };
308 let mut keys = BTreeSet::new();
309 for entry in entries {
310 let Some(LiteralValue {
311 kind: LiteralKind::Member(name),
312 ..
313 }) = &entry.key
314 else {
315 return Err("each guest record needs an enum member key".into());
316 };
317 if !plain_member(entry.key.as_ref().unwrap())
318 || !guests.contains(name)
319 || !keys.insert(*name)
320 {
321 return Err("guest keys must cover Guests exactly once".into());
322 }
323 let LiteralKind::Record(fields) = &entry.value.kind else {
324 return Err("expected a guest record".into());
325 };
326 let mut labels = BTreeSet::new();
327 for field in fields {
328 if !labels.insert(field.label.name) {
329 return Err("duplicate guest record field".into());
330 }
331 let valid = match field.label.name {
332 "name" | "surname" => matches!(field.value.kind, LiteralKind::String),
333 "gender" => matches!(
334 field.value.kind,
335 LiteralKind::Member("Man" | "Woman" | "NA")
336 ),
337 "interest" => positional(&field.value).is_ok_and(|values| {
338 values.len() == topics
339 && values
340 .iter()
341 .all(|v| matches!(v.kind, LiteralKind::Integer))
342 }),
343 _ => false,
344 };
345 if !valid {
346 return Err(format!("unsupported guest field: {}", field.label.name));
347 }
348 }
349 if labels != BTreeSet::from(["name", "surname", "gender", "interest"]) {
350 return Err("guest records require name, surname, gender and interest".into());
351 }
352 }
353 if &keys != guests {
354 return Err("guest keys must cover Guests exactly once".into());
355 }
356 Ok(())
357}
358
359fn group_members<'a>(
360 value: &LiteralValue<'a>,
361 guests: &BTreeSet<&str>,
362) -> Result<Vec<(&'a str, Range<usize>)>> {
363 let mut result = Vec::new();
364 for group in positional(value)? {
365 for (name, range) in members(group)? {
366 if !guests.contains(name) {
367 return Err("group contains an unknown guest".into());
368 }
369 result.push((name, range));
370 }
371 }
372 Ok(result)
373}
374
375fn table_constructor(input: &Input, range: &Range<usize>) -> Result<(usize, Range<usize>)> {
376 let tokens: Vec<_> = input
377 .syntax()
378 .tokens()
379 .iter()
380 .enumerate()
381 .filter(|(index, token)| {
382 range.contains(&input.token_range(*index).start)
383 && !matches!(
384 token.kind,
385 TokenKind::Whitespace | TokenKind::LineComment | TokenKind::BlockComment
386 )
387 })
388 .map(|(index, _)| index)
389 .collect();
390 if tokens.len() != 9
391 || tokens.iter().enumerate().any(|(position, index)| {
392 position != 6
393 && input.token_bytes(*index)
394 != [
395 b"Tables".as_slice(),
396 b"=",
397 b"T",
398 b"(",
399 b"1",
400 b"..",
401 b"",
402 b")",
403 b";",
404 ][position]
405 })
406 {
407 return Err("Tables must have the exact constructor shape T(1..N)".into());
408 }
409 let end = input.token_bytes(tokens[6]);
410 if end.is_empty() || !end.iter().all(u8::is_ascii_digit) {
411 return Err("table count must be a positive decimal integer".into());
412 }
413 let count = std::str::from_utf8(end)
414 .unwrap()
415 .parse::<usize>()
416 .map_err(|_| "table count overflow")?;
417 if count == 0 {
418 return Err("table count must be positive".into());
419 }
420 Ok((count, input.token_range(tokens[6])))
421}
422
423fn copy_collection(
424 input: &Input,
425 value: &LiteralValue<'_>,
426 replacements: &[(&str, Range<usize>)],
427 mappings: &[BTreeMap<&str, String>],
428) -> Result<TextEdit> {
429 let bytes = input.source_bytes();
430 let range = value.range.clone();
431 if !matches!(
432 (bytes[range.start], bytes[range.end - 1]),
433 (b'[', b']') | (b'{', b'}')
434 ) {
435 return Err("collection literals must use direct brackets or braces".into());
436 }
437 let inside = range.start + 1..range.end - 1;
438 let last = input
439 .syntax()
440 .tokens()
441 .iter()
442 .enumerate()
443 .rev()
444 .find(|(index, token)| {
445 inside.contains(&input.token_range(*index).start)
446 && !matches!(
447 token.kind,
448 TokenKind::Whitespace | TokenKind::LineComment | TokenKind::BlockComment
449 )
450 });
451 let mut parts = vec![EditPart::Original(range.start..inside.end)];
452 if let Some((_, last)) = last {
453 for mapping in mappings {
454 if last.kind != TokenKind::Comma {
455 parts.push(EditPart::Text(",".into()));
456 }
457 let mut cursor = inside.start;
458 for (name, replaced) in replacements {
459 parts.push(EditPart::Original(cursor..replaced.start));
460 parts.push(EditPart::Text(mapping[*name].clone()));
461 cursor = replaced.end;
462 }
463 parts.push(EditPart::Original(cursor..inside.end));
464 }
465 }
466 parts.push(EditPart::Original(inside.end..range.end));
467 Ok(TextEdit {
468 range,
469 replacement: parts,
470 })
471}Sourcepub fn syntax(&self) -> &ParsedFile
pub fn syntax(&self) -> &ParsedFile
The analysis CST uses offsets after any leading BOM. Use token_range
and selected item ranges for coordinates in the original input.
Examples found in repository?
examples/grow-wedding.rs (line 73)
63fn grow(bytes: Vec<u8>, factor: usize) -> Result<Vec<u8>> {
64 if !(1..=MAX_FACTOR).contains(&factor) {
65 return Err(format!("FACTOR must be in 1..={MAX_FACTOR}"));
66 }
67 if bytes.len() > MAX_INPUT {
68 return Err("input exceeds 1 MiB".into());
69 }
70 let input = Input::parse_named(bytes, FileMode::Data, "wedding.dzn")
71 .map_err(|error| format!("invalid data: {error:?}"))?;
72 if input
73 .syntax()
74 .tokens()
75 .len()
76 .checked_mul(factor)
77 .is_none_or(|n| n > MAX_TOKENS)
78 {
79 return Err("grown input exceeds the 200,000-token allowance".into());
80 }
81 // This example understands these eight assignments, not arbitrary dependencies.
82 let QueryResult::Selection(items) = Query::parse("items", Limits::default())
83 .map_err(|error| error.message)?
84 .evaluate(&input, Limits::default())
85 .map_err(|error| error.message)?
86 else {
87 unreachable!()
88 };
89 let mut names = BTreeSet::new();
90 for item in items.items() {
91 if item.kind != ItemKind::Assignment
92 || !item
93 .name
94 .is_some_and(|name| ASSIGNMENTS.contains(&name) && names.insert(name))
95 {
96 return Err("expected only the eight distinct wedding data assignments".into());
97 }
98 }
99 if names.len() != ASSIGNMENTS.len() {
100 return Err("all eight wedding data assignments are required".into());
101 }
102 // Refuse formatter directives: copying their text could change directive scopes.
103 for (index, token) in input.syntax().tokens().iter().enumerate() {
104 if matches!(token.kind, TokenKind::LineComment | TokenKind::BlockComment)
105 && input
106 .token_bytes(index)
107 .windows(b"zincite-fmt:".len())
108 .any(|w| w == b"zincite-fmt:")
109 {
110 return Err("comments containing zincite directives are unsupported".into());
111 }
112 }
113 let topics = literal(&input, "Topics")?;
114 let guests = literal(&input, "Guests")?;
115 let records = literal(&input, "guests")?;
116 let same = literal(&input, "same_table")?;
117 let different = literal(&input, "different_tables")?;
118 let capacities = literal(&input, "table_capacities")?;
119 let imbalance = literal(&input, "max_gender_imbalance_per_table")?;
120 let topic_members = members(&topics)?;
121 let guest_members = members(&guests)?;
122 if topic_members.is_empty() || guest_members.is_empty() {
123 return Err("Topics and Guests must be nonempty literal member sets".into());
124 }
125 let topic_names: BTreeSet<_> = topic_members.iter().map(|(name, _)| *name).collect();
126 let guest_names: BTreeSet<_> = guest_members.iter().map(|(name, _)| *name).collect();
127 if !topic_names.is_disjoint(&guest_names)
128 || topic_names
129 .iter()
130 .chain(guest_names.iter())
131 .any(|name| ASSIGNMENTS.contains(name) || ["Man", "Woman", "NA", "T"].contains(name))
132 {
133 return Err("enum members collide with the wedding model's identifiers".into());
134 }
135 validate_records(&records, &guest_names, topic_members.len())?;
136 let same_members = group_members(&same, &guest_names)?;
137 let different_members = group_members(&different, &guest_names)?;
138 let table_item = items
139 .items()
140 .iter()
141 .find(|item| item.name == Some("Tables"))
142 .unwrap();
143 let (table_count, table_range) = table_constructor(&input, &table_item.range)?;
144 let capacity_values = positional(&capacities)?;
145 if capacity_values.len() != table_count
146 || capacity_values
147 .iter()
148 .any(|value| !matches!(value.kind, LiteralKind::Integer))
149 || !matches!(imbalance.kind, LiteralKind::Integer)
150 {
151 return Err("table_capacities must contain one literal integer per table; imbalance must be a literal integer".into());
152 }
153 let grown_tables = table_count
154 .checked_mul(factor)
155 .filter(|count| i64::try_from(*count).is_ok())
156 .ok_or("table count overflow")?;
157 if factor == 1 {
158 return Ok(input.source_bytes().to_vec());
159 }
160 let mut occupied: BTreeSet<String> = input
161 .syntax()
162 .tokens()
163 .iter()
164 .enumerate()
165 .filter(|(_, token)| {
166 matches!(
167 token.kind,
168 TokenKind::Identifier | TokenKind::QuotedIdentifier
169 )
170 })
171 .map(|(index, _)| {
172 String::from_utf8_lossy(input.token_bytes(index))
173 .trim_matches('\'')
174 .to_owned()
175 })
176 .collect();
177 let mut mappings = Vec::new();
178 for copy in 1..factor {
179 let mut mapping = BTreeMap::new();
180 for (index, (name, _)) in guest_members.iter().enumerate() {
181 let generated = format!("GrownGuest_{copy}_{}", index + 1);
182 if !occupied.insert(generated.clone()) {
183 return Err(format!("generated guest identifier collides: {generated}"));
184 }
185 mapping.insert(*name, generated);
186 }
187 mappings.push(mapping);
188 }
189 let LiteralKind::Array { entries, .. } = &records.kind else {
190 unreachable!()
191 };
192 let record_keys: Vec<_> = entries
193 .iter()
194 .map(|entry| {
195 let key = entry.key.as_ref().unwrap();
196 let LiteralKind::Member(name) = key.kind else {
197 unreachable!()
198 };
199 (name, key.range.clone())
200 })
201 .collect();
202 let mut edits = Vec::new();
203 for (value, replacements) in [
204 (&guests, guest_members),
205 (&records, record_keys),
206 (&same, same_members),
207 (&different, different_members),
208 (&capacities, Vec::new()),
209 ] {
210 edits.push(copy_collection(&input, value, &replacements, &mappings)?);
211 }
212 edits.push(TextEdit {
213 range: table_range,
214 replacement: vec![EditPart::Text(grown_tables.to_string())],
215 });
216 // Charge the exact final byte count before the public edit API allocates it.
217 let mut output_len = input.source_bytes().len();
218 for edit in &edits {
219 output_len -= edit.range.len();
220 for part in &edit.replacement {
221 let len = match part {
222 EditPart::Text(text) => text.len(),
223 EditPart::Original(range) => range.len(),
224 };
225 output_len = output_len
226 .checked_add(len)
227 .filter(|n| *n <= MAX_OUTPUT)
228 .ok_or("candidate exceeds 16 MiB")?;
229 }
230 }
231 let snapshot = SourceSnapshot::new("wedding.dzn", input.source_bytes());
232 let candidate = prepare_text_edits(&snapshot, input.source_bytes(), &edits)
233 .map_err(|error| error.to_string())?;
234 Input::parse_named(candidate.clone(), FileMode::Data, "grown-wedding.dzn")
235 .map_err(|error| format!("invalid candidate: {error:?}"))?;
236 Ok(candidate)
237}
238
239fn literal<'a>(input: &'a Input, name: &str) -> Result<LiteralValue<'a>> {
240 let query = Query::parse(
241 &format!("filter(name(\"{name}\")) | values"),
242 Limits::default(),
243 )
244 .map_err(|error| error.message)?;
245 let QueryResult::Literals(values) = query
246 .evaluate(input, Limits::default())
247 .map_err(|error| error.message)?
248 else {
249 unreachable!()
250 };
251 match values.values() {
252 [value] => Ok(value.clone()),
253 _ => Err(format!("expected one {name} assignment")),
254 }
255}
256
257fn members<'a>(value: &LiteralValue<'a>) -> Result<Vec<(&'a str, Range<usize>)>> {
258 let LiteralKind::Set(values) = &value.kind else {
259 return Err("expected a literal enum member set".into());
260 };
261 let mut seen = BTreeSet::new();
262 values
263 .iter()
264 .map(|value| match value.kind {
265 LiteralKind::Member(name) if plain_member(value) && seen.insert(name) => {
266 Ok((name, value.range.clone()))
267 }
268 _ => Err("expected distinct literal enum members".into()),
269 })
270 .collect()
271}
272
273// Remapping replaces one identifier token, never parentheses or attached comments.
274fn plain_member(value: &LiteralValue<'_>) -> bool {
275 let LiteralKind::Member(name) = value.kind else {
276 return false;
277 };
278 value.spelling == name.as_bytes()
279 || value
280 .spelling
281 .strip_prefix(b"'")
282 .and_then(|s| s.strip_suffix(b"'"))
283 == Some(name.as_bytes())
284}
285
286fn positional<'a, 'b>(value: &'b LiteralValue<'a>) -> Result<Vec<&'b LiteralValue<'a>>> {
287 match &value.kind {
288 LiteralKind::Array {
289 indexing: ArrayIndexing::Positional,
290 entries,
291 } => Ok(entries.iter().map(|entry| &entry.value).collect()),
292 _ => Err("expected a positional literal array".into()),
293 }
294}
295
296fn validate_records(
297 value: &LiteralValue<'_>,
298 guests: &BTreeSet<&str>,
299 topics: usize,
300) -> Result<()> {
301 let LiteralKind::Array {
302 indexing: ArrayIndexing::Explicit,
303 entries,
304 } = &value.kind
305 else {
306 return Err("guests must be an explicitly guest-keyed record array".into());
307 };
308 let mut keys = BTreeSet::new();
309 for entry in entries {
310 let Some(LiteralValue {
311 kind: LiteralKind::Member(name),
312 ..
313 }) = &entry.key
314 else {
315 return Err("each guest record needs an enum member key".into());
316 };
317 if !plain_member(entry.key.as_ref().unwrap())
318 || !guests.contains(name)
319 || !keys.insert(*name)
320 {
321 return Err("guest keys must cover Guests exactly once".into());
322 }
323 let LiteralKind::Record(fields) = &entry.value.kind else {
324 return Err("expected a guest record".into());
325 };
326 let mut labels = BTreeSet::new();
327 for field in fields {
328 if !labels.insert(field.label.name) {
329 return Err("duplicate guest record field".into());
330 }
331 let valid = match field.label.name {
332 "name" | "surname" => matches!(field.value.kind, LiteralKind::String),
333 "gender" => matches!(
334 field.value.kind,
335 LiteralKind::Member("Man" | "Woman" | "NA")
336 ),
337 "interest" => positional(&field.value).is_ok_and(|values| {
338 values.len() == topics
339 && values
340 .iter()
341 .all(|v| matches!(v.kind, LiteralKind::Integer))
342 }),
343 _ => false,
344 };
345 if !valid {
346 return Err(format!("unsupported guest field: {}", field.label.name));
347 }
348 }
349 if labels != BTreeSet::from(["name", "surname", "gender", "interest"]) {
350 return Err("guest records require name, surname, gender and interest".into());
351 }
352 }
353 if &keys != guests {
354 return Err("guest keys must cover Guests exactly once".into());
355 }
356 Ok(())
357}
358
359fn group_members<'a>(
360 value: &LiteralValue<'a>,
361 guests: &BTreeSet<&str>,
362) -> Result<Vec<(&'a str, Range<usize>)>> {
363 let mut result = Vec::new();
364 for group in positional(value)? {
365 for (name, range) in members(group)? {
366 if !guests.contains(name) {
367 return Err("group contains an unknown guest".into());
368 }
369 result.push((name, range));
370 }
371 }
372 Ok(result)
373}
374
375fn table_constructor(input: &Input, range: &Range<usize>) -> Result<(usize, Range<usize>)> {
376 let tokens: Vec<_> = input
377 .syntax()
378 .tokens()
379 .iter()
380 .enumerate()
381 .filter(|(index, token)| {
382 range.contains(&input.token_range(*index).start)
383 && !matches!(
384 token.kind,
385 TokenKind::Whitespace | TokenKind::LineComment | TokenKind::BlockComment
386 )
387 })
388 .map(|(index, _)| index)
389 .collect();
390 if tokens.len() != 9
391 || tokens.iter().enumerate().any(|(position, index)| {
392 position != 6
393 && input.token_bytes(*index)
394 != [
395 b"Tables".as_slice(),
396 b"=",
397 b"T",
398 b"(",
399 b"1",
400 b"..",
401 b"",
402 b")",
403 b";",
404 ][position]
405 })
406 {
407 return Err("Tables must have the exact constructor shape T(1..N)".into());
408 }
409 let end = input.token_bytes(tokens[6]);
410 if end.is_empty() || !end.iter().all(u8::is_ascii_digit) {
411 return Err("table count must be a positive decimal integer".into());
412 }
413 let count = std::str::from_utf8(end)
414 .unwrap()
415 .parse::<usize>()
416 .map_err(|_| "table count overflow")?;
417 if count == 0 {
418 return Err("table count must be positive".into());
419 }
420 Ok((count, input.token_range(tokens[6])))
421}
422
423fn copy_collection(
424 input: &Input,
425 value: &LiteralValue<'_>,
426 replacements: &[(&str, Range<usize>)],
427 mappings: &[BTreeMap<&str, String>],
428) -> Result<TextEdit> {
429 let bytes = input.source_bytes();
430 let range = value.range.clone();
431 if !matches!(
432 (bytes[range.start], bytes[range.end - 1]),
433 (b'[', b']') | (b'{', b'}')
434 ) {
435 return Err("collection literals must use direct brackets or braces".into());
436 }
437 let inside = range.start + 1..range.end - 1;
438 let last = input
439 .syntax()
440 .tokens()
441 .iter()
442 .enumerate()
443 .rev()
444 .find(|(index, token)| {
445 inside.contains(&input.token_range(*index).start)
446 && !matches!(
447 token.kind,
448 TokenKind::Whitespace | TokenKind::LineComment | TokenKind::BlockComment
449 )
450 });
451 let mut parts = vec![EditPart::Original(range.start..inside.end)];
452 if let Some((_, last)) = last {
453 for mapping in mappings {
454 if last.kind != TokenKind::Comma {
455 parts.push(EditPart::Text(",".into()));
456 }
457 let mut cursor = inside.start;
458 for (name, replaced) in replacements {
459 parts.push(EditPart::Original(cursor..replaced.start));
460 parts.push(EditPart::Text(mapping[*name].clone()));
461 cursor = replaced.end;
462 }
463 parts.push(EditPart::Original(cursor..inside.end));
464 }
465 }
466 parts.push(EditPart::Original(inside.end..range.end));
467 Ok(TextEdit {
468 range,
469 replacement: parts,
470 })
471}pub fn syntax_offset(&self) -> usize
Sourcepub fn token_range(&self, index: usize) -> Range<usize> ⓘ
pub fn token_range(&self, index: usize) -> Range<usize> ⓘ
Examples found in repository?
examples/grow-wedding.rs (line 382)
375fn table_constructor(input: &Input, range: &Range<usize>) -> Result<(usize, Range<usize>)> {
376 let tokens: Vec<_> = input
377 .syntax()
378 .tokens()
379 .iter()
380 .enumerate()
381 .filter(|(index, token)| {
382 range.contains(&input.token_range(*index).start)
383 && !matches!(
384 token.kind,
385 TokenKind::Whitespace | TokenKind::LineComment | TokenKind::BlockComment
386 )
387 })
388 .map(|(index, _)| index)
389 .collect();
390 if tokens.len() != 9
391 || tokens.iter().enumerate().any(|(position, index)| {
392 position != 6
393 && input.token_bytes(*index)
394 != [
395 b"Tables".as_slice(),
396 b"=",
397 b"T",
398 b"(",
399 b"1",
400 b"..",
401 b"",
402 b")",
403 b";",
404 ][position]
405 })
406 {
407 return Err("Tables must have the exact constructor shape T(1..N)".into());
408 }
409 let end = input.token_bytes(tokens[6]);
410 if end.is_empty() || !end.iter().all(u8::is_ascii_digit) {
411 return Err("table count must be a positive decimal integer".into());
412 }
413 let count = std::str::from_utf8(end)
414 .unwrap()
415 .parse::<usize>()
416 .map_err(|_| "table count overflow")?;
417 if count == 0 {
418 return Err("table count must be positive".into());
419 }
420 Ok((count, input.token_range(tokens[6])))
421}
422
423fn copy_collection(
424 input: &Input,
425 value: &LiteralValue<'_>,
426 replacements: &[(&str, Range<usize>)],
427 mappings: &[BTreeMap<&str, String>],
428) -> Result<TextEdit> {
429 let bytes = input.source_bytes();
430 let range = value.range.clone();
431 if !matches!(
432 (bytes[range.start], bytes[range.end - 1]),
433 (b'[', b']') | (b'{', b'}')
434 ) {
435 return Err("collection literals must use direct brackets or braces".into());
436 }
437 let inside = range.start + 1..range.end - 1;
438 let last = input
439 .syntax()
440 .tokens()
441 .iter()
442 .enumerate()
443 .rev()
444 .find(|(index, token)| {
445 inside.contains(&input.token_range(*index).start)
446 && !matches!(
447 token.kind,
448 TokenKind::Whitespace | TokenKind::LineComment | TokenKind::BlockComment
449 )
450 });
451 let mut parts = vec![EditPart::Original(range.start..inside.end)];
452 if let Some((_, last)) = last {
453 for mapping in mappings {
454 if last.kind != TokenKind::Comma {
455 parts.push(EditPart::Text(",".into()));
456 }
457 let mut cursor = inside.start;
458 for (name, replaced) in replacements {
459 parts.push(EditPart::Original(cursor..replaced.start));
460 parts.push(EditPart::Text(mapping[*name].clone()));
461 cursor = replaced.end;
462 }
463 parts.push(EditPart::Original(cursor..inside.end));
464 }
465 }
466 parts.push(EditPart::Original(inside.end..range.end));
467 Ok(TextEdit {
468 range,
469 replacement: parts,
470 })
471}Sourcepub fn token_bytes(&self, index: usize) -> &[u8] ⓘ
pub fn token_bytes(&self, index: usize) -> &[u8] ⓘ
Examples found in repository?
examples/grow-wedding.rs (line 106)
63fn grow(bytes: Vec<u8>, factor: usize) -> Result<Vec<u8>> {
64 if !(1..=MAX_FACTOR).contains(&factor) {
65 return Err(format!("FACTOR must be in 1..={MAX_FACTOR}"));
66 }
67 if bytes.len() > MAX_INPUT {
68 return Err("input exceeds 1 MiB".into());
69 }
70 let input = Input::parse_named(bytes, FileMode::Data, "wedding.dzn")
71 .map_err(|error| format!("invalid data: {error:?}"))?;
72 if input
73 .syntax()
74 .tokens()
75 .len()
76 .checked_mul(factor)
77 .is_none_or(|n| n > MAX_TOKENS)
78 {
79 return Err("grown input exceeds the 200,000-token allowance".into());
80 }
81 // This example understands these eight assignments, not arbitrary dependencies.
82 let QueryResult::Selection(items) = Query::parse("items", Limits::default())
83 .map_err(|error| error.message)?
84 .evaluate(&input, Limits::default())
85 .map_err(|error| error.message)?
86 else {
87 unreachable!()
88 };
89 let mut names = BTreeSet::new();
90 for item in items.items() {
91 if item.kind != ItemKind::Assignment
92 || !item
93 .name
94 .is_some_and(|name| ASSIGNMENTS.contains(&name) && names.insert(name))
95 {
96 return Err("expected only the eight distinct wedding data assignments".into());
97 }
98 }
99 if names.len() != ASSIGNMENTS.len() {
100 return Err("all eight wedding data assignments are required".into());
101 }
102 // Refuse formatter directives: copying their text could change directive scopes.
103 for (index, token) in input.syntax().tokens().iter().enumerate() {
104 if matches!(token.kind, TokenKind::LineComment | TokenKind::BlockComment)
105 && input
106 .token_bytes(index)
107 .windows(b"zincite-fmt:".len())
108 .any(|w| w == b"zincite-fmt:")
109 {
110 return Err("comments containing zincite directives are unsupported".into());
111 }
112 }
113 let topics = literal(&input, "Topics")?;
114 let guests = literal(&input, "Guests")?;
115 let records = literal(&input, "guests")?;
116 let same = literal(&input, "same_table")?;
117 let different = literal(&input, "different_tables")?;
118 let capacities = literal(&input, "table_capacities")?;
119 let imbalance = literal(&input, "max_gender_imbalance_per_table")?;
120 let topic_members = members(&topics)?;
121 let guest_members = members(&guests)?;
122 if topic_members.is_empty() || guest_members.is_empty() {
123 return Err("Topics and Guests must be nonempty literal member sets".into());
124 }
125 let topic_names: BTreeSet<_> = topic_members.iter().map(|(name, _)| *name).collect();
126 let guest_names: BTreeSet<_> = guest_members.iter().map(|(name, _)| *name).collect();
127 if !topic_names.is_disjoint(&guest_names)
128 || topic_names
129 .iter()
130 .chain(guest_names.iter())
131 .any(|name| ASSIGNMENTS.contains(name) || ["Man", "Woman", "NA", "T"].contains(name))
132 {
133 return Err("enum members collide with the wedding model's identifiers".into());
134 }
135 validate_records(&records, &guest_names, topic_members.len())?;
136 let same_members = group_members(&same, &guest_names)?;
137 let different_members = group_members(&different, &guest_names)?;
138 let table_item = items
139 .items()
140 .iter()
141 .find(|item| item.name == Some("Tables"))
142 .unwrap();
143 let (table_count, table_range) = table_constructor(&input, &table_item.range)?;
144 let capacity_values = positional(&capacities)?;
145 if capacity_values.len() != table_count
146 || capacity_values
147 .iter()
148 .any(|value| !matches!(value.kind, LiteralKind::Integer))
149 || !matches!(imbalance.kind, LiteralKind::Integer)
150 {
151 return Err("table_capacities must contain one literal integer per table; imbalance must be a literal integer".into());
152 }
153 let grown_tables = table_count
154 .checked_mul(factor)
155 .filter(|count| i64::try_from(*count).is_ok())
156 .ok_or("table count overflow")?;
157 if factor == 1 {
158 return Ok(input.source_bytes().to_vec());
159 }
160 let mut occupied: BTreeSet<String> = input
161 .syntax()
162 .tokens()
163 .iter()
164 .enumerate()
165 .filter(|(_, token)| {
166 matches!(
167 token.kind,
168 TokenKind::Identifier | TokenKind::QuotedIdentifier
169 )
170 })
171 .map(|(index, _)| {
172 String::from_utf8_lossy(input.token_bytes(index))
173 .trim_matches('\'')
174 .to_owned()
175 })
176 .collect();
177 let mut mappings = Vec::new();
178 for copy in 1..factor {
179 let mut mapping = BTreeMap::new();
180 for (index, (name, _)) in guest_members.iter().enumerate() {
181 let generated = format!("GrownGuest_{copy}_{}", index + 1);
182 if !occupied.insert(generated.clone()) {
183 return Err(format!("generated guest identifier collides: {generated}"));
184 }
185 mapping.insert(*name, generated);
186 }
187 mappings.push(mapping);
188 }
189 let LiteralKind::Array { entries, .. } = &records.kind else {
190 unreachable!()
191 };
192 let record_keys: Vec<_> = entries
193 .iter()
194 .map(|entry| {
195 let key = entry.key.as_ref().unwrap();
196 let LiteralKind::Member(name) = key.kind else {
197 unreachable!()
198 };
199 (name, key.range.clone())
200 })
201 .collect();
202 let mut edits = Vec::new();
203 for (value, replacements) in [
204 (&guests, guest_members),
205 (&records, record_keys),
206 (&same, same_members),
207 (&different, different_members),
208 (&capacities, Vec::new()),
209 ] {
210 edits.push(copy_collection(&input, value, &replacements, &mappings)?);
211 }
212 edits.push(TextEdit {
213 range: table_range,
214 replacement: vec![EditPart::Text(grown_tables.to_string())],
215 });
216 // Charge the exact final byte count before the public edit API allocates it.
217 let mut output_len = input.source_bytes().len();
218 for edit in &edits {
219 output_len -= edit.range.len();
220 for part in &edit.replacement {
221 let len = match part {
222 EditPart::Text(text) => text.len(),
223 EditPart::Original(range) => range.len(),
224 };
225 output_len = output_len
226 .checked_add(len)
227 .filter(|n| *n <= MAX_OUTPUT)
228 .ok_or("candidate exceeds 16 MiB")?;
229 }
230 }
231 let snapshot = SourceSnapshot::new("wedding.dzn", input.source_bytes());
232 let candidate = prepare_text_edits(&snapshot, input.source_bytes(), &edits)
233 .map_err(|error| error.to_string())?;
234 Input::parse_named(candidate.clone(), FileMode::Data, "grown-wedding.dzn")
235 .map_err(|error| format!("invalid candidate: {error:?}"))?;
236 Ok(candidate)
237}
238
239fn literal<'a>(input: &'a Input, name: &str) -> Result<LiteralValue<'a>> {
240 let query = Query::parse(
241 &format!("filter(name(\"{name}\")) | values"),
242 Limits::default(),
243 )
244 .map_err(|error| error.message)?;
245 let QueryResult::Literals(values) = query
246 .evaluate(input, Limits::default())
247 .map_err(|error| error.message)?
248 else {
249 unreachable!()
250 };
251 match values.values() {
252 [value] => Ok(value.clone()),
253 _ => Err(format!("expected one {name} assignment")),
254 }
255}
256
257fn members<'a>(value: &LiteralValue<'a>) -> Result<Vec<(&'a str, Range<usize>)>> {
258 let LiteralKind::Set(values) = &value.kind else {
259 return Err("expected a literal enum member set".into());
260 };
261 let mut seen = BTreeSet::new();
262 values
263 .iter()
264 .map(|value| match value.kind {
265 LiteralKind::Member(name) if plain_member(value) && seen.insert(name) => {
266 Ok((name, value.range.clone()))
267 }
268 _ => Err("expected distinct literal enum members".into()),
269 })
270 .collect()
271}
272
273// Remapping replaces one identifier token, never parentheses or attached comments.
274fn plain_member(value: &LiteralValue<'_>) -> bool {
275 let LiteralKind::Member(name) = value.kind else {
276 return false;
277 };
278 value.spelling == name.as_bytes()
279 || value
280 .spelling
281 .strip_prefix(b"'")
282 .and_then(|s| s.strip_suffix(b"'"))
283 == Some(name.as_bytes())
284}
285
286fn positional<'a, 'b>(value: &'b LiteralValue<'a>) -> Result<Vec<&'b LiteralValue<'a>>> {
287 match &value.kind {
288 LiteralKind::Array {
289 indexing: ArrayIndexing::Positional,
290 entries,
291 } => Ok(entries.iter().map(|entry| &entry.value).collect()),
292 _ => Err("expected a positional literal array".into()),
293 }
294}
295
296fn validate_records(
297 value: &LiteralValue<'_>,
298 guests: &BTreeSet<&str>,
299 topics: usize,
300) -> Result<()> {
301 let LiteralKind::Array {
302 indexing: ArrayIndexing::Explicit,
303 entries,
304 } = &value.kind
305 else {
306 return Err("guests must be an explicitly guest-keyed record array".into());
307 };
308 let mut keys = BTreeSet::new();
309 for entry in entries {
310 let Some(LiteralValue {
311 kind: LiteralKind::Member(name),
312 ..
313 }) = &entry.key
314 else {
315 return Err("each guest record needs an enum member key".into());
316 };
317 if !plain_member(entry.key.as_ref().unwrap())
318 || !guests.contains(name)
319 || !keys.insert(*name)
320 {
321 return Err("guest keys must cover Guests exactly once".into());
322 }
323 let LiteralKind::Record(fields) = &entry.value.kind else {
324 return Err("expected a guest record".into());
325 };
326 let mut labels = BTreeSet::new();
327 for field in fields {
328 if !labels.insert(field.label.name) {
329 return Err("duplicate guest record field".into());
330 }
331 let valid = match field.label.name {
332 "name" | "surname" => matches!(field.value.kind, LiteralKind::String),
333 "gender" => matches!(
334 field.value.kind,
335 LiteralKind::Member("Man" | "Woman" | "NA")
336 ),
337 "interest" => positional(&field.value).is_ok_and(|values| {
338 values.len() == topics
339 && values
340 .iter()
341 .all(|v| matches!(v.kind, LiteralKind::Integer))
342 }),
343 _ => false,
344 };
345 if !valid {
346 return Err(format!("unsupported guest field: {}", field.label.name));
347 }
348 }
349 if labels != BTreeSet::from(["name", "surname", "gender", "interest"]) {
350 return Err("guest records require name, surname, gender and interest".into());
351 }
352 }
353 if &keys != guests {
354 return Err("guest keys must cover Guests exactly once".into());
355 }
356 Ok(())
357}
358
359fn group_members<'a>(
360 value: &LiteralValue<'a>,
361 guests: &BTreeSet<&str>,
362) -> Result<Vec<(&'a str, Range<usize>)>> {
363 let mut result = Vec::new();
364 for group in positional(value)? {
365 for (name, range) in members(group)? {
366 if !guests.contains(name) {
367 return Err("group contains an unknown guest".into());
368 }
369 result.push((name, range));
370 }
371 }
372 Ok(result)
373}
374
375fn table_constructor(input: &Input, range: &Range<usize>) -> Result<(usize, Range<usize>)> {
376 let tokens: Vec<_> = input
377 .syntax()
378 .tokens()
379 .iter()
380 .enumerate()
381 .filter(|(index, token)| {
382 range.contains(&input.token_range(*index).start)
383 && !matches!(
384 token.kind,
385 TokenKind::Whitespace | TokenKind::LineComment | TokenKind::BlockComment
386 )
387 })
388 .map(|(index, _)| index)
389 .collect();
390 if tokens.len() != 9
391 || tokens.iter().enumerate().any(|(position, index)| {
392 position != 6
393 && input.token_bytes(*index)
394 != [
395 b"Tables".as_slice(),
396 b"=",
397 b"T",
398 b"(",
399 b"1",
400 b"..",
401 b"",
402 b")",
403 b";",
404 ][position]
405 })
406 {
407 return Err("Tables must have the exact constructor shape T(1..N)".into());
408 }
409 let end = input.token_bytes(tokens[6]);
410 if end.is_empty() || !end.iter().all(u8::is_ascii_digit) {
411 return Err("table count must be a positive decimal integer".into());
412 }
413 let count = std::str::from_utf8(end)
414 .unwrap()
415 .parse::<usize>()
416 .map_err(|_| "table count overflow")?;
417 if count == 0 {
418 return Err("table count must be positive".into());
419 }
420 Ok((count, input.token_range(tokens[6])))
421}pub fn line_column(&self, offset: usize) -> (usize, usize)
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for Input
impl RefUnwindSafe for Input
impl Send for Input
impl Sync for Input
impl Unpin for Input
impl UnsafeUnpin for Input
impl UnwindSafe for Input
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more