Skip to main content

Input

Struct Input 

Source
pub struct Input { /* private fields */ }
Expand description

Read-only original input and its byte-preserving parser view.

Implementations§

Source§

impl Input

Source

pub fn parse(bytes: Vec<u8>, mode: FileMode) -> Result<Self, InputError>

Source

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}
Source

pub fn file(&self) -> &str

Source

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}
Source

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}
Source

pub fn syntax_offset(&self) -> usize

Source

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}
Source

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}
Source

pub fn line_column(&self, offset: usize) -> (usize, usize)

Trait Implementations§

Source§

impl Debug for Input

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.