Skip to main content

brink_format/inkt/
read.rs

1//! Pest-based reader for the `.inkt` textual format.
2
3use pest::Parser;
4use pest_derive::Parser;
5
6use crate::counting::CountingFlags;
7use crate::definition::{
8    AddressDef, AddressPath, ContainerDef, ExternalFnDef, GlobalVarDef, LineEntry, ListDef,
9    ListItemDef, ScopeLineTable, SlotInfo, SourceLocation,
10};
11use crate::id::{DefinitionId, NameId};
12use crate::line::{LineContent, LinePart, PluralCategory, SelectKey};
13use crate::opcode::{ChoiceFlags, Opcode, SequenceKind};
14use crate::story::StoryData;
15use crate::value::{ListValue, Value, ValueType};
16
17#[derive(Parser)]
18#[grammar = "inkt/inkt.pest"]
19struct InktParser;
20
21/// Error returned when parsing `.inkt` text fails.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct InktParseError {
24    pub message: String,
25    pub line: usize,
26    pub col: usize,
27}
28
29impl core::fmt::Display for InktParseError {
30    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
31        write!(f, "{}:{}: {}", self.line, self.col, self.message)
32    }
33}
34
35impl std::error::Error for InktParseError {}
36
37/// Parse `.inkt` text into a [`StoryData`].
38pub fn read_inkt(input: &str) -> Result<StoryData, InktParseError> {
39    let pairs = InktParser::parse(Rule::story, input).map_err(|e| {
40        let (line, col) = match e.line_col {
41            pest::error::LineColLocation::Pos(pos) => pos,
42            pest::error::LineColLocation::Span(start, _) => start,
43        };
44        InktParseError {
45            message: e.to_string(),
46            line,
47            col,
48        }
49    })?;
50
51    let story_pair = pairs.into_iter().next().ok_or_else(|| InktParseError {
52        message: "no story node".into(),
53        line: 1,
54        col: 1,
55    })?;
56
57    parse_story(story_pair)
58}
59
60type P<'a> = pest::iterators::Pair<'a, Rule>;
61
62fn err(pair: &P<'_>, msg: impl Into<String>) -> InktParseError {
63    let (line, col) = pair.line_col();
64    InktParseError {
65        message: msg.into(),
66        line,
67        col,
68    }
69}
70
71fn parse_story(pair: P<'_>) -> Result<StoryData, InktParseError> {
72    let mut name_table = Vec::new();
73    let mut variables = Vec::new();
74    let mut list_defs = Vec::new();
75    let mut list_items = Vec::new();
76    let mut externals = Vec::new();
77    let mut addresses = Vec::new();
78    let mut address_paths = Vec::new();
79    let mut containers = Vec::new();
80    let mut line_tables = Vec::new();
81    let mut list_literals = Vec::new();
82    let mut source_checksum = 0u32;
83
84    for inner in pair.into_inner() {
85        match inner.as_rule() {
86            Rule::story_checksum => {
87                if let Some(hex_pair) = inner.into_inner().next() {
88                    source_checksum = parse_hex_u32(hex_pair.as_str());
89                }
90            }
91            Rule::name_table => name_table = parse_name_table(inner)?,
92            Rule::globals => variables = parse_globals(inner)?,
93            Rule::lists => list_defs = parse_lists(inner)?,
94            Rule::list_items => list_items = parse_list_items(inner)?,
95            Rule::externals => externals = parse_externals(inner)?,
96            Rule::addresses => addresses = parse_addresses(inner)?,
97            Rule::address_paths => address_paths = parse_address_paths(inner)?,
98            Rule::list_literals => list_literals = parse_list_literals(inner)?,
99            Rule::container => {
100                let (container, lt) = parse_container(inner)?;
101                let is_scope_owner = container.scope_id == container.id;
102                containers.push(container);
103                // Only add line tables for scope-owning containers.
104                // Child containers (scope_id != id) have no lines in the text.
105                if is_scope_owner {
106                    line_tables.push(lt);
107                }
108            }
109            _ => {}
110        }
111    }
112
113    // Sort line tables by scope_id for deterministic ordering,
114    // matching the converter's output.
115    line_tables.sort_by_key(|lt| lt.scope_id.to_raw());
116
117    Ok(StoryData {
118        containers,
119        line_tables,
120        variables,
121        list_defs,
122        list_items,
123        externals,
124        addresses,
125        address_paths,
126        name_table,
127        list_literals,
128        source_checksum,
129    })
130}
131
132// ── Name table ──────────────────────────────────────────────────────────────
133
134fn parse_name_table(pair: P<'_>) -> Result<Vec<String>, InktParseError> {
135    let mut names = Vec::new();
136    for entry in pair.into_inner() {
137        if entry.as_rule() == Rule::name_entry {
138            let mut inner = entry.into_inner();
139            let _index = inner.next(); // integer index (implied by position)
140            let s = inner.next().ok_or_else(|| InktParseError {
141                message: "expected string in name_entry".into(),
142                line: 0,
143                col: 0,
144            })?;
145            names.push(unescape_string(s.as_str()));
146        }
147    }
148    Ok(names)
149}
150
151// ── Globals ─────────────────────────────────────────────────────────────────
152
153fn parse_globals(pair: P<'_>) -> Result<Vec<GlobalVarDef>, InktParseError> {
154    let mut vars = Vec::new();
155    for entry in pair.into_inner() {
156        if entry.as_rule() == Rule::global_entry {
157            vars.push(parse_global_entry(entry)?);
158        }
159    }
160    Ok(vars)
161}
162
163fn parse_global_entry(pair: P<'_>) -> Result<GlobalVarDef, InktParseError> {
164    let mut inner = pair.into_inner();
165    let id = parse_def_id(inner.next().ok_or_else(|| InktParseError {
166        message: "expected def_id in global".into(),
167        line: 0,
168        col: 0,
169    })?)?;
170
171    let type_pair = inner.next().ok_or_else(|| InktParseError {
172        message: "expected type_name in global".into(),
173        line: 0,
174        col: 0,
175    })?;
176    let value_type = parse_value_type(type_pair)?;
177
178    let value_pair = inner.next().ok_or_else(|| InktParseError {
179        message: "expected value in global".into(),
180        line: 0,
181        col: 0,
182    })?;
183    let default_value = parse_value(value_pair, Some(value_type))?;
184
185    let mut mutable = false;
186    let mut local = false;
187    let mut name = NameId(0);
188
189    for remaining in inner {
190        match remaining.as_rule() {
191            Rule::mutable_flag => mutable = true,
192            Rule::local_flag => local = true,
193            Rule::integer => {
194                name = NameId(parse_u16(&remaining)?);
195            }
196            _ => {}
197        }
198    }
199
200    Ok(GlobalVarDef {
201        id,
202        name,
203        value_type,
204        default_value,
205        mutable,
206        local,
207    })
208}
209
210#[expect(clippy::needless_pass_by_value)]
211fn parse_value_type(pair: P<'_>) -> Result<ValueType, InktParseError> {
212    let s = pair.as_str();
213    match s {
214        "int" => Ok(ValueType::Int),
215        "float" => Ok(ValueType::Float),
216        "bool" => Ok(ValueType::Bool),
217        "string" => Ok(ValueType::String),
218        "list" => Ok(ValueType::List),
219        "divert_target" => Ok(ValueType::DivertTarget),
220        "var_pointer" => Ok(ValueType::VariablePointer),
221        "temp_pointer" => Ok(ValueType::TempPointer),
222        "fragment_ref" => Ok(ValueType::FragmentRef),
223        "null" => Ok(ValueType::Null),
224        _ => Err(err(&pair, format!("unknown value type: {s}"))),
225    }
226}
227
228fn parse_value(pair: P<'_>, type_hint: Option<ValueType>) -> Result<Value, InktParseError> {
229    let inner = pair.into_inner().next().ok_or_else(|| InktParseError {
230        message: "empty value".into(),
231        line: 0,
232        col: 0,
233    })?;
234
235    match inner.as_rule() {
236        Rule::integer => {
237            // Use the type hint to disambiguate: an integer literal can represent
238            // Int, Float, or Bool depending on the declared value_type.
239            match type_hint {
240                Some(ValueType::Float) => {
241                    let n: f32 = inner
242                        .as_str()
243                        .parse()
244                        .map_err(|_| err(&inner, "invalid float"))?;
245                    Ok(Value::Float(n))
246                }
247                Some(ValueType::Bool) => {
248                    let n: i32 = inner
249                        .as_str()
250                        .parse()
251                        .map_err(|_| err(&inner, "invalid integer"))?;
252                    Ok(Value::Bool(n != 0))
253                }
254                _ => {
255                    let n: i32 = inner
256                        .as_str()
257                        .parse()
258                        .map_err(|_| err(&inner, "invalid integer"))?;
259                    Ok(Value::Int(n))
260                }
261            }
262        }
263        Rule::float => {
264            let n: f32 = inner
265                .as_str()
266                .parse()
267                .map_err(|_| err(&inner, "invalid float"))?;
268            Ok(Value::Float(n))
269        }
270        Rule::bool_value => Ok(Value::Bool(inner.as_str() == "true")),
271        Rule::string => Ok(Value::String(unescape_string(inner.as_str()).into())),
272        Rule::def_id => Ok(Value::DivertTarget(parse_def_id(inner)?)),
273        Rule::null_value => Ok(Value::Null),
274        Rule::list_value => parse_list_value(inner),
275        Rule::var_pointer_value => {
276            let id_pair = inner.into_inner().next().ok_or_else(|| InktParseError {
277                message: "expected def_id in var_pointer".into(),
278                line: 0,
279                col: 0,
280            })?;
281            Ok(Value::VariablePointer(parse_def_id(id_pair)?))
282        }
283        Rule::fragment_ref_value => {
284            let idx_pair = inner.into_inner().next().ok_or_else(|| InktParseError {
285                message: "expected integer in fragment_ref".into(),
286                line: 0,
287                col: 0,
288            })?;
289            let idx: u32 = idx_pair.as_str().parse().map_err(|_| InktParseError {
290                message: "invalid fragment_ref index".into(),
291                line: 0,
292                col: 0,
293            })?;
294            Ok(Value::FragmentRef(idx))
295        }
296        _ => Err(err(
297            &inner,
298            format!("unexpected value rule: {:?}", inner.as_rule()),
299        )),
300    }
301}
302
303fn parse_list_value(pair: P<'_>) -> Result<Value, InktParseError> {
304    let mut items = Vec::new();
305    let mut origins = Vec::new();
306
307    for child in pair.into_inner() {
308        match child.as_rule() {
309            Rule::list_value_items => {
310                for def_pair in child.into_inner() {
311                    if def_pair.as_rule() == Rule::def_id {
312                        items.push(parse_def_id(def_pair)?);
313                    }
314                }
315            }
316            Rule::list_value_origins => {
317                for def_pair in child.into_inner() {
318                    if def_pair.as_rule() == Rule::def_id {
319                        origins.push(parse_def_id(def_pair)?);
320                    }
321                }
322            }
323            _ => {}
324        }
325    }
326
327    Ok(Value::List(ListValue { items, origins }.into()))
328}
329
330// ── Lists ───────────────────────────────────────────────────────────────────
331
332fn parse_lists(pair: P<'_>) -> Result<Vec<ListDef>, InktParseError> {
333    let mut defs = Vec::new();
334    for entry in pair.into_inner() {
335        if entry.as_rule() == Rule::list_entry {
336            defs.push(parse_list_entry(entry)?);
337        }
338    }
339    Ok(defs)
340}
341
342fn parse_list_entry(pair: P<'_>) -> Result<ListDef, InktParseError> {
343    let mut inner = pair.into_inner();
344    let id = parse_def_id(inner.next().ok_or_else(|| InktParseError {
345        message: "expected def_id in list".into(),
346        line: 0,
347        col: 0,
348    })?)?;
349
350    let name_int = inner.next().ok_or_else(|| InktParseError {
351        message: "expected name integer in list".into(),
352        line: 0,
353        col: 0,
354    })?;
355    let name = NameId(parse_u16(&name_int)?);
356
357    let mut items = Vec::new();
358    for remaining in inner {
359        if remaining.as_rule() == Rule::list_item_inline {
360            let mut li_inner = remaining.into_inner();
361            let item_name_id = parse_u16(&li_inner.next().ok_or_else(|| InktParseError {
362                message: "expected name in list item".into(),
363                line: 0,
364                col: 0,
365            })?)?;
366            let ordinal: i32 = li_inner
367                .next()
368                .ok_or_else(|| InktParseError {
369                    message: "expected ordinal in list item".into(),
370                    line: 0,
371                    col: 0,
372                })?
373                .as_str()
374                .parse()
375                .map_err(|_| InktParseError {
376                    message: "invalid ordinal".into(),
377                    line: 0,
378                    col: 0,
379                })?;
380            items.push((NameId(item_name_id), ordinal));
381        }
382    }
383
384    Ok(ListDef { id, name, items })
385}
386
387// ── List items ──────────────────────────────────────────────────────────────
388
389fn parse_list_items(pair: P<'_>) -> Result<Vec<ListItemDef>, InktParseError> {
390    let mut items = Vec::new();
391    for entry in pair.into_inner() {
392        if entry.as_rule() == Rule::list_item_entry {
393            items.push(parse_list_item_entry(entry)?);
394        }
395    }
396    Ok(items)
397}
398
399fn parse_list_item_entry(pair: P<'_>) -> Result<ListItemDef, InktParseError> {
400    let mut inner = pair.into_inner();
401    let id = parse_def_id(next_rule(&mut inner, Rule::def_id, "list_item id")?)?;
402    let origin = parse_def_id(next_rule(&mut inner, Rule::def_id, "list_item origin")?)?;
403    let ordinal: i32 = next_rule(&mut inner, Rule::integer, "list_item ordinal")?
404        .as_str()
405        .parse()
406        .map_err(|_| InktParseError {
407            message: "invalid ordinal".into(),
408            line: 0,
409            col: 0,
410        })?;
411    let name_val =
412        next_rule(&mut inner, Rule::integer, "list_item name").map_or(Ok(0), |p| parse_u16(&p))?;
413    Ok(ListItemDef {
414        id,
415        origin,
416        ordinal,
417        name: NameId(name_val),
418    })
419}
420
421// ── Externals ───────────────────────────────────────────────────────────────
422
423fn parse_externals(pair: P<'_>) -> Result<Vec<ExternalFnDef>, InktParseError> {
424    let mut exts = Vec::new();
425    for entry in pair.into_inner() {
426        if entry.as_rule() == Rule::extern_entry {
427            exts.push(parse_extern_entry(entry)?);
428        }
429    }
430    Ok(exts)
431}
432
433fn parse_extern_entry(pair: P<'_>) -> Result<ExternalFnDef, InktParseError> {
434    let mut inner = pair.into_inner();
435    let id = parse_def_id(inner.next().ok_or_else(|| InktParseError {
436        message: "expected def_id in extern".into(),
437        line: 0,
438        col: 0,
439    })?)?;
440
441    let argc_pair = inner.next().ok_or_else(|| InktParseError {
442        message: "expected argc in extern".into(),
443        line: 0,
444        col: 0,
445    })?;
446    let arg_count: u8 = argc_pair
447        .as_str()
448        .parse()
449        .map_err(|_| err(&argc_pair, "invalid argc"))?;
450
451    let name_int = inner.next().ok_or_else(|| InktParseError {
452        message: "expected name in extern".into(),
453        line: 0,
454        col: 0,
455    })?;
456    let name = NameId(parse_u16(&name_int)?);
457
458    let mut fallback = None;
459    for remaining in inner {
460        if remaining.as_rule() == Rule::fallback {
461            let fb_inner = remaining
462                .into_inner()
463                .next()
464                .ok_or_else(|| InktParseError {
465                    message: "expected def_id in fallback".into(),
466                    line: 0,
467                    col: 0,
468                })?;
469            fallback = Some(parse_def_id(fb_inner)?);
470        }
471    }
472
473    Ok(ExternalFnDef {
474        id,
475        name,
476        arg_count,
477        fallback,
478    })
479}
480
481// ── Addresses ───────────────────────────────────────────────────────────────
482
483fn parse_addresses(pair: P<'_>) -> Result<Vec<AddressDef>, InktParseError> {
484    let mut addresses = Vec::new();
485    for entry in pair.into_inner() {
486        if entry.as_rule() == Rule::address_entry {
487            addresses.push(parse_address_entry(entry)?);
488        }
489    }
490    Ok(addresses)
491}
492
493fn parse_address_entry(pair: P<'_>) -> Result<AddressDef, InktParseError> {
494    let mut inner = pair.into_inner();
495    let id = parse_def_id(inner.next().ok_or_else(|| InktParseError {
496        message: "expected def_id in address".into(),
497        line: 0,
498        col: 0,
499    })?)?;
500    let container_id = parse_def_id(inner.next().ok_or_else(|| InktParseError {
501        message: "expected container_id in address".into(),
502        line: 0,
503        col: 0,
504    })?)?;
505    let offset_pair = inner.next().ok_or_else(|| InktParseError {
506        message: "expected byte_offset in address".into(),
507        line: 0,
508        col: 0,
509    })?;
510    let byte_offset: u32 = offset_pair
511        .as_str()
512        .parse()
513        .map_err(|_| err(&offset_pair, "invalid byte_offset"))?;
514    Ok(AddressDef {
515        id,
516        container_id,
517        byte_offset,
518    })
519}
520
521fn parse_address_paths(pair: P<'_>) -> Result<Vec<AddressPath>, InktParseError> {
522    let mut paths = Vec::new();
523    for entry in pair.into_inner() {
524        if entry.as_rule() == Rule::address_path_entry {
525            paths.push(parse_address_path_entry(entry)?);
526        }
527    }
528    Ok(paths)
529}
530
531fn parse_address_path_entry(pair: P<'_>) -> Result<AddressPath, InktParseError> {
532    let mut inner = pair.into_inner();
533    let path_int = inner.next().ok_or_else(|| InktParseError {
534        message: "expected path index in address_path".into(),
535        line: 0,
536        col: 0,
537    })?;
538    let path = NameId(parse_u16(&path_int)?);
539    let target = parse_def_id(inner.next().ok_or_else(|| InktParseError {
540        message: "expected target def_id in address_path".into(),
541        line: 0,
542        col: 0,
543    })?)?;
544    Ok(AddressPath { path, target })
545}
546
547// ── List literals ────────────────────────────────────────────────────────────
548
549fn parse_list_literals(pair: P<'_>) -> Result<Vec<ListValue>, InktParseError> {
550    let mut literals = Vec::new();
551    for entry in pair.into_inner() {
552        if entry.as_rule() == Rule::list_literal_entry {
553            literals.push(parse_list_literal_entry(entry)?);
554        }
555    }
556    Ok(literals)
557}
558
559fn parse_list_literal_entry(pair: P<'_>) -> Result<ListValue, InktParseError> {
560    let mut items = Vec::new();
561    let mut origins = Vec::new();
562
563    for child in pair.into_inner() {
564        match child.as_rule() {
565            Rule::list_value_items => {
566                for def_pair in child.into_inner() {
567                    if def_pair.as_rule() == Rule::def_id {
568                        items.push(parse_def_id(def_pair)?);
569                    }
570                }
571            }
572            Rule::list_value_origins => {
573                for def_pair in child.into_inner() {
574                    if def_pair.as_rule() == Rule::def_id {
575                        origins.push(parse_def_id(def_pair)?);
576                    }
577                }
578            }
579            _ => {}
580        }
581    }
582
583    Ok(ListValue { items, origins })
584}
585
586// ── Containers ──────────────────────────────────────────────────────────────
587
588fn parse_container(pair: P<'_>) -> Result<(ContainerDef, ScopeLineTable), InktParseError> {
589    let mut inner = pair.into_inner();
590    let id = parse_def_id(inner.next().ok_or_else(|| InktParseError {
591        message: "expected def_id in container".into(),
592        line: 0,
593        col: 0,
594    })?)?;
595
596    let mut counting_flags = CountingFlags::empty();
597    let mut path_hash = 0i32;
598    let mut param_count = 0u8;
599    let mut local = false;
600    let mut lines = Vec::new();
601    let mut bytecode = Vec::new();
602    let mut name: Option<NameId> = None;
603
604    let mut scope_id = id;
605
606    for child in inner {
607        match child.as_rule() {
608            Rule::scope_field => {
609                let scope_pair = child.into_inner().next().ok_or_else(|| InktParseError {
610                    message: "expected def_id in scope".into(),
611                    line: 0,
612                    col: 0,
613                })?;
614                scope_id = parse_def_id(scope_pair)?;
615            }
616            Rule::container_name_field => {
617                let val = child.into_inner().next().ok_or_else(|| InktParseError {
618                    message: "expected integer in container name".into(),
619                    line: 0,
620                    col: 0,
621                })?;
622                name = Some(NameId(parse_u16(&val)?));
623            }
624            Rule::flags_field => {
625                for flag in child.into_inner() {
626                    if flag.as_rule() == Rule::flag_name {
627                        match flag.as_str() {
628                            "visits" => counting_flags |= CountingFlags::VISITS,
629                            "turns" => counting_flags |= CountingFlags::TURNS,
630                            "start_only" => counting_flags |= CountingFlags::COUNT_START_ONLY,
631                            _ => {}
632                        }
633                    }
634                }
635            }
636            Rule::path_hash_field => {
637                let val = child.into_inner().next().ok_or_else(|| InktParseError {
638                    message: "expected integer in path_hash".into(),
639                    line: 0,
640                    col: 0,
641                })?;
642                path_hash = val.as_str().parse().map_err(|_| InktParseError {
643                    message: "invalid path_hash integer".into(),
644                    line: 0,
645                    col: 0,
646                })?;
647            }
648            Rule::params_field => {
649                let val = child.into_inner().next().ok_or_else(|| InktParseError {
650                    message: "expected integer in params".into(),
651                    line: 0,
652                    col: 0,
653                })?;
654                param_count = val.as_str().parse().map_err(|_| InktParseError {
655                    message: "invalid params integer".into(),
656                    line: 0,
657                    col: 0,
658                })?;
659            }
660            Rule::local_flag => local = true,
661            Rule::lines_field => {
662                lines = parse_lines_field(child)?;
663            }
664            Rule::code_field => {
665                bytecode = parse_code_field(child)?;
666            }
667            _ => {}
668        }
669    }
670
671    let container = ContainerDef {
672        id,
673        scope_id,
674        name,
675        bytecode,
676        counting_flags,
677        path_hash,
678        param_count,
679        local,
680    };
681    let line_table = ScopeLineTable { scope_id, lines };
682    Ok((container, line_table))
683}
684
685fn parse_lines_field(pair: P<'_>) -> Result<Vec<LineEntry>, InktParseError> {
686    let mut entries = Vec::new();
687    for entry in pair.into_inner() {
688        if entry.as_rule() == Rule::line_entry {
689            entries.push(parse_line_entry(entry)?);
690        }
691    }
692    Ok(entries)
693}
694
695fn parse_line_entry(pair: P<'_>) -> Result<LineEntry, InktParseError> {
696    let mut inner = pair.into_inner();
697    let _index = inner.next(); // integer index (implied by position)
698    let content_pair = inner.next().ok_or_else(|| InktParseError {
699        message: "expected line content".into(),
700        line: 0,
701        col: 0,
702    })?;
703    let content = parse_line_content(content_pair)?;
704    let hash_pair = inner.next().ok_or_else(|| InktParseError {
705        message: "expected source_hash".into(),
706        line: 0,
707        col: 0,
708    })?;
709    // source_hash is @HHHHHHHHHHHHHHHH
710    let hash_str = hash_pair.as_str();
711    let source_hash = parse_hex_u64(&format!("0x{}", &hash_str[1..]))?;
712
713    let mut audio_ref = None;
714    let mut slot_info = Vec::new();
715    let mut source_location = None;
716
717    for remaining in inner {
718        match remaining.as_rule() {
719            Rule::audio_field => {
720                let s = remaining
721                    .into_inner()
722                    .next()
723                    .ok_or_else(|| InktParseError {
724                        message: "expected audio string".into(),
725                        line: 0,
726                        col: 0,
727                    })?;
728                audio_ref = Some(unescape_string(s.as_str()));
729            }
730            Rule::slots_field => {
731                for slot_entry in remaining.into_inner() {
732                    if slot_entry.as_rule() == Rule::slot_entry {
733                        let mut parts = slot_entry.into_inner();
734                        let idx_str = parts.next().map_or("0", |p| p.as_str());
735                        let idx: u8 = idx_str.parse().unwrap_or(0);
736                        let name_str = parts
737                            .next()
738                            .map_or_else(String::new, |p| unescape_string(p.as_str()));
739                        slot_info.push(SlotInfo {
740                            index: idx,
741                            name: name_str,
742                        });
743                    }
744                }
745            }
746            Rule::source_field => {
747                let mut parts = remaining.into_inner();
748                let file = parts
749                    .next()
750                    .map_or_else(String::new, |p| unescape_string(p.as_str()));
751                let start: u32 = parts
752                    .next()
753                    .and_then(|p| p.as_str().parse().ok())
754                    .unwrap_or(0);
755                let end: u32 = parts
756                    .next()
757                    .and_then(|p| p.as_str().parse().ok())
758                    .unwrap_or(0);
759                source_location = Some(SourceLocation {
760                    file,
761                    range_start: start,
762                    range_end: end,
763                });
764            }
765            _ => {}
766        }
767    }
768
769    let flags = crate::LineFlags::from_content(&content);
770    Ok(LineEntry {
771        content,
772        flags,
773        source_hash,
774        audio_ref,
775        slot_info,
776        source_location,
777    })
778}
779
780fn parse_line_content(pair: P<'_>) -> Result<LineContent, InktParseError> {
781    let inner = pair.into_inner().next().ok_or_else(|| InktParseError {
782        message: "empty line content".into(),
783        line: 0,
784        col: 0,
785    })?;
786    match inner.as_rule() {
787        Rule::string => Ok(LineContent::Plain(unescape_string(inner.as_str()))),
788        Rule::template => parse_template(inner),
789        _ => Err(err(
790            &inner,
791            format!("unexpected line content: {:?}", inner.as_rule()),
792        )),
793    }
794}
795
796fn parse_template(pair: P<'_>) -> Result<LineContent, InktParseError> {
797    let mut parts = Vec::new();
798    for child in pair.into_inner() {
799        if child.as_rule() == Rule::template_part {
800            parts.push(parse_template_part(child)?);
801        }
802    }
803    Ok(LineContent::Template(parts))
804}
805
806fn parse_template_part(pair: P<'_>) -> Result<LinePart, InktParseError> {
807    let inner = pair.into_inner().next().ok_or_else(|| InktParseError {
808        message: "empty template part".into(),
809        line: 0,
810        col: 0,
811    })?;
812    match inner.as_rule() {
813        Rule::literal_part => {
814            let s = inner.into_inner().next().ok_or_else(|| InktParseError {
815                message: "expected string in literal".into(),
816                line: 0,
817                col: 0,
818            })?;
819            Ok(LinePart::Literal(unescape_string(s.as_str())))
820        }
821        Rule::slot_part => {
822            let idx = inner.into_inner().next().ok_or_else(|| InktParseError {
823                message: "expected integer in slot".into(),
824                line: 0,
825                col: 0,
826            })?;
827            let n: u8 = idx
828                .as_str()
829                .parse()
830                .map_err(|_| err(&idx, "invalid slot index"))?;
831            Ok(LinePart::Slot(n))
832        }
833        Rule::select_part => parse_select_part(inner),
834        _ => Err(err(
835            &inner,
836            format!("unexpected template part: {:?}", inner.as_rule()),
837        )),
838    }
839}
840
841fn parse_select_part(pair: P<'_>) -> Result<LinePart, InktParseError> {
842    let mut inner = pair.into_inner();
843    let slot_pair = inner.next().ok_or_else(|| InktParseError {
844        message: "expected slot in select".into(),
845        line: 0,
846        col: 0,
847    })?;
848    let slot: u8 = slot_pair
849        .as_str()
850        .parse()
851        .map_err(|_| err(&slot_pair, "invalid slot"))?;
852
853    let mut variants = Vec::new();
854    let mut default = String::new();
855
856    for child in inner {
857        match child.as_rule() {
858            Rule::select_variant => {
859                let mut vi = child.into_inner();
860                let key_pair = vi.next().ok_or_else(|| InktParseError {
861                    message: "expected key in variant".into(),
862                    line: 0,
863                    col: 0,
864                })?;
865                let key = parse_select_key(key_pair)?;
866                let text = vi.next().ok_or_else(|| InktParseError {
867                    message: "expected text in variant".into(),
868                    line: 0,
869                    col: 0,
870                })?;
871                variants.push((key, unescape_string(text.as_str())));
872            }
873            Rule::select_default => {
874                let s = child.into_inner().next().ok_or_else(|| InktParseError {
875                    message: "expected string in default".into(),
876                    line: 0,
877                    col: 0,
878                })?;
879                default = unescape_string(s.as_str());
880            }
881            _ => {}
882        }
883    }
884
885    Ok(LinePart::Select {
886        slot,
887        variants,
888        default,
889    })
890}
891
892fn parse_select_key(pair: P<'_>) -> Result<SelectKey, InktParseError> {
893    let inner = pair.into_inner().next().ok_or_else(|| InktParseError {
894        message: "empty select key".into(),
895        line: 0,
896        col: 0,
897    })?;
898    match inner.as_rule() {
899        Rule::cardinal_key => {
900            let cat = inner.into_inner().next().ok_or_else(|| InktParseError {
901                message: "expected plural_cat".into(),
902                line: 0,
903                col: 0,
904            })?;
905            Ok(SelectKey::Cardinal(parse_plural_cat(cat)?))
906        }
907        Rule::ordinal_key => {
908            let cat = inner.into_inner().next().ok_or_else(|| InktParseError {
909                message: "expected plural_cat".into(),
910                line: 0,
911                col: 0,
912            })?;
913            Ok(SelectKey::Ordinal(parse_plural_cat(cat)?))
914        }
915        Rule::exact_key => {
916            let n = inner.into_inner().next().ok_or_else(|| InktParseError {
917                message: "expected integer".into(),
918                line: 0,
919                col: 0,
920            })?;
921            let v: i32 = n
922                .as_str()
923                .parse()
924                .map_err(|_| err(&n, "invalid exact key"))?;
925            Ok(SelectKey::Exact(v))
926        }
927        Rule::keyword_key => {
928            let ident = inner.into_inner().next().ok_or_else(|| InktParseError {
929                message: "expected ident".into(),
930                line: 0,
931                col: 0,
932            })?;
933            Ok(SelectKey::Keyword(ident.as_str().to_owned()))
934        }
935        _ => Err(err(
936            &inner,
937            format!("unexpected select key: {:?}", inner.as_rule()),
938        )),
939    }
940}
941
942#[expect(clippy::needless_pass_by_value)]
943fn parse_plural_cat(pair: P<'_>) -> Result<PluralCategory, InktParseError> {
944    match pair.as_str() {
945        "Zero" => Ok(PluralCategory::Zero),
946        "One" => Ok(PluralCategory::One),
947        "Two" => Ok(PluralCategory::Two),
948        "Few" => Ok(PluralCategory::Few),
949        "Many" => Ok(PluralCategory::Many),
950        "Other" => Ok(PluralCategory::Other),
951        _ => Err(err(
952            &pair,
953            format!("unknown plural category: {}", pair.as_str()),
954        )),
955    }
956}
957
958// ── Code field ──────────────────────────────────────────────────────────────
959
960fn parse_code_field(pair: P<'_>) -> Result<Vec<u8>, InktParseError> {
961    let mut bytecode = Vec::new();
962    for child in pair.into_inner() {
963        if child.as_rule() == Rule::instruction {
964            let op = parse_instruction(child)?;
965            op.encode(&mut bytecode);
966        }
967    }
968    Ok(bytecode)
969}
970
971#[expect(clippy::too_many_lines)]
972fn parse_instruction(pair: P<'_>) -> Result<Opcode, InktParseError> {
973    let mut inner = pair.into_inner();
974    let mnemonic_pair = inner.next().ok_or_else(|| InktParseError {
975        message: "expected opcode mnemonic".into(),
976        line: 0,
977        col: 0,
978    })?;
979    let mnemonic = mnemonic_pair.as_str();
980
981    let operands: Vec<P<'_>> = inner.collect();
982
983    match mnemonic {
984        // Stack & literals
985        "push_int" => Ok(Opcode::PushInt(parse_operand_i32(&operands, 0, mnemonic)?)),
986        "push_float" => Ok(Opcode::PushFloat(parse_operand_f32(
987            &operands, 0, mnemonic,
988        )?)),
989        "push_bool" => {
990            let s = operand_str(&operands, 0, mnemonic)?;
991            Ok(Opcode::PushBool(s == "true"))
992        }
993        "push_string" => Ok(Opcode::PushString(parse_operand_u16(
994            &operands, 0, mnemonic,
995        )?)),
996        "push_list" => Ok(Opcode::PushList(parse_operand_u16(&operands, 0, mnemonic)?)),
997        "push_divert_target" => Ok(Opcode::PushDivertTarget(parse_operand_def_id(
998            &operands, 0, mnemonic,
999        )?)),
1000        "push_null" => Ok(Opcode::PushNull),
1001        "pop" => Ok(Opcode::Pop),
1002        "duplicate" => Ok(Opcode::Duplicate),
1003
1004        // Arithmetic
1005        "add" => Ok(Opcode::Add),
1006        "subtract" => Ok(Opcode::Subtract),
1007        "multiply" => Ok(Opcode::Multiply),
1008        "divide" => Ok(Opcode::Divide),
1009        "modulo" => Ok(Opcode::Modulo),
1010        "negate" => Ok(Opcode::Negate),
1011
1012        // Comparison
1013        "equal" => Ok(Opcode::Equal),
1014        "not_equal" => Ok(Opcode::NotEqual),
1015        "greater" => Ok(Opcode::Greater),
1016        "greater_or_equal" => Ok(Opcode::GreaterOrEqual),
1017        "less" => Ok(Opcode::Less),
1018        "less_or_equal" => Ok(Opcode::LessOrEqual),
1019
1020        // Logic
1021        "not" => Ok(Opcode::Not),
1022        "and" => Ok(Opcode::And),
1023        "or" => Ok(Opcode::Or),
1024
1025        // Global vars
1026        "get_global" => Ok(Opcode::GetGlobal(parse_operand_def_id(
1027            &operands, 0, mnemonic,
1028        )?)),
1029        "set_global" => Ok(Opcode::SetGlobal(parse_operand_def_id(
1030            &operands, 0, mnemonic,
1031        )?)),
1032
1033        // Temp vars
1034        "declare_temp" => Ok(Opcode::DeclareTemp(parse_operand_u16(
1035            &operands, 0, mnemonic,
1036        )?)),
1037        "get_temp" => Ok(Opcode::GetTemp(parse_operand_u16(&operands, 0, mnemonic)?)),
1038        "set_temp" => Ok(Opcode::SetTemp(parse_operand_u16(&operands, 0, mnemonic)?)),
1039        "get_temp_raw" => Ok(Opcode::GetTempRaw(parse_operand_u16(
1040            &operands, 0, mnemonic,
1041        )?)),
1042
1043        // Variable pointers
1044        "push_var_pointer" => Ok(Opcode::PushVarPointer(parse_operand_def_id(
1045            &operands, 0, mnemonic,
1046        )?)),
1047        "push_temp_pointer" => Ok(Opcode::PushTempPointer(parse_operand_u16(
1048            &operands, 0, mnemonic,
1049        )?)),
1050
1051        // Control flow
1052        "jump" => Ok(Opcode::Jump(parse_operand_i32(&operands, 0, mnemonic)?)),
1053        "jump_if_false" => Ok(Opcode::JumpIfFalse(parse_operand_i32(
1054            &operands, 0, mnemonic,
1055        )?)),
1056        "goto" => Ok(Opcode::Goto(parse_operand_def_id(&operands, 0, mnemonic)?)),
1057        "goto_if" => Ok(Opcode::GotoIf(parse_operand_def_id(
1058            &operands, 0, mnemonic,
1059        )?)),
1060        "goto_variable" => Ok(Opcode::GotoVariable),
1061
1062        // Container flow
1063        "enter_container" => Ok(Opcode::EnterContainer(parse_operand_def_id(
1064            &operands, 0, mnemonic,
1065        )?)),
1066        "exit_container" => Ok(Opcode::ExitContainer),
1067
1068        // Functions / tunnels
1069        "call" => Ok(Opcode::Call(parse_operand_def_id(&operands, 0, mnemonic)?)),
1070        "return" => Ok(Opcode::Return),
1071        "tunnel_call" => Ok(Opcode::TunnelCall(parse_operand_def_id(
1072            &operands, 0, mnemonic,
1073        )?)),
1074        "tunnel_return" => Ok(Opcode::TunnelReturn),
1075        "tunnel_call_variable" => Ok(Opcode::TunnelCallVariable),
1076        "call_variable" => Ok(Opcode::CallVariable),
1077
1078        // Threads
1079        "thread_call" => Ok(Opcode::ThreadCall(parse_operand_def_id(
1080            &operands, 0, mnemonic,
1081        )?)),
1082        "thread_start" => Ok(Opcode::ThreadStart),
1083        "thread_done" => Ok(Opcode::ThreadDone),
1084
1085        // Output
1086        "emit_line" => {
1087            let idx = parse_operand_u16(&operands, 0, mnemonic)?;
1088            let slots = parse_operand_u8(&operands, 1, mnemonic)?;
1089            Ok(Opcode::EmitLine(idx, slots))
1090        }
1091        "emit_value" => Ok(Opcode::EmitValue),
1092        "emit_newline" => Ok(Opcode::EmitNewline),
1093        "spring" => Ok(Opcode::Spring),
1094        "glue" => Ok(Opcode::Glue),
1095        "begin_tag" => Ok(Opcode::BeginTag),
1096        "end_tag" => Ok(Opcode::EndTag),
1097        "eval_line" => {
1098            let idx = parse_operand_u16(&operands, 0, mnemonic)?;
1099            let slots = parse_operand_u8(&operands, 1, mnemonic)?;
1100            Ok(Opcode::EvalLine(idx, slots))
1101        }
1102
1103        // Choices
1104        "begin_choice" => {
1105            let flags = parse_choice_flags_operand(&operands, 0, mnemonic)?;
1106            let target = parse_operand_def_id(&operands, 1, mnemonic)?;
1107            Ok(Opcode::BeginChoice(flags, target))
1108        }
1109        "end_choice" => Ok(Opcode::EndChoice),
1110
1111        // Sequences
1112        "sequence" => {
1113            let kind_str = operand_str(&operands, 0, mnemonic)?;
1114            let kind = match kind_str {
1115                "cycle" => SequenceKind::Cycle,
1116                "stopping" => SequenceKind::Stopping,
1117                "once_only" => SequenceKind::OnceOnly,
1118                "shuffle" => SequenceKind::Shuffle,
1119                _ => {
1120                    return Err(InktParseError {
1121                        message: format!("unknown sequence kind: {kind_str}"),
1122                        line: 0,
1123                        col: 0,
1124                    });
1125                }
1126            };
1127            let count: u8 =
1128                operand_str(&operands, 1, mnemonic)?
1129                    .parse()
1130                    .map_err(|_| InktParseError {
1131                        message: "invalid sequence count".into(),
1132                        line: 0,
1133                        col: 0,
1134                    })?;
1135            Ok(Opcode::Sequence(kind, count))
1136        }
1137        "sequence_branch" => Ok(Opcode::SequenceBranch(parse_operand_i32(
1138            &operands, 0, mnemonic,
1139        )?)),
1140
1141        // Intrinsics
1142        "visit_count" => Ok(Opcode::VisitCount),
1143        "current_visit_count" => Ok(Opcode::CurrentVisitCount),
1144        "turns_since" => Ok(Opcode::TurnsSince),
1145        "turn_index" => Ok(Opcode::TurnIndex),
1146        "choice_count" => Ok(Opcode::ChoiceCount),
1147        "random" => Ok(Opcode::Random),
1148        "seed_random" => Ok(Opcode::SeedRandom),
1149
1150        // Casts / math
1151        "cast_to_int" => Ok(Opcode::CastToInt),
1152        "cast_to_float" => Ok(Opcode::CastToFloat),
1153        "floor" => Ok(Opcode::Floor),
1154        "ceiling" => Ok(Opcode::Ceiling),
1155        "pow" => Ok(Opcode::Pow),
1156        "min" => Ok(Opcode::Min),
1157        "max" => Ok(Opcode::Max),
1158
1159        // External fns
1160        "call_external" => {
1161            let id = parse_operand_def_id(&operands, 0, mnemonic)?;
1162            // "argc=N" is parsed as a kv_operand. Extract the value after "=".
1163            let kv_str = operand_str(&operands, 1, mnemonic)?;
1164            let argc_str = kv_str.strip_prefix("argc=").unwrap_or(kv_str);
1165            let argc: u8 = argc_str.parse().map_err(|_| InktParseError {
1166                message: format!("invalid argc in call_external: {kv_str}"),
1167                line: 0,
1168                col: 0,
1169            })?;
1170            Ok(Opcode::CallExternal(id, argc))
1171        }
1172
1173        // List ops
1174        "list_contains" => Ok(Opcode::ListContains),
1175        "list_not_contains" => Ok(Opcode::ListNotContains),
1176        "list_intersect" => Ok(Opcode::ListIntersect),
1177        "list_all" => Ok(Opcode::ListAll),
1178        "list_invert" => Ok(Opcode::ListInvert),
1179        "list_count" => Ok(Opcode::ListCount),
1180        "list_min" => Ok(Opcode::ListMin),
1181        "list_max" => Ok(Opcode::ListMax),
1182        "list_value" => Ok(Opcode::ListValue),
1183        "list_range" => Ok(Opcode::ListRange),
1184        "list_from_int" => Ok(Opcode::ListFromInt),
1185        "list_random" => Ok(Opcode::ListRandom),
1186
1187        // Lifecycle
1188        "done" => Ok(Opcode::Done),
1189        "yield" => Ok(Opcode::Yield),
1190        "end" => Ok(Opcode::End),
1191        "nop" => Ok(Opcode::Nop),
1192
1193        // String eval
1194        "begin_string_eval" => Ok(Opcode::BeginStringEval),
1195        "end_string_eval" => Ok(Opcode::EndStringEval),
1196
1197        // Fragment capture
1198        "begin_fragment" => Ok(Opcode::BeginFragment),
1199        "end_fragment" => Ok(Opcode::EndFragment),
1200
1201        // Debug
1202        "source_location" => {
1203            // Written as "source_location LINE:COL" — parsed as source_loc operand
1204            let s = operand_str(&operands, 0, mnemonic)?;
1205            let parts: Vec<&str> = s.split(':').collect();
1206            if parts.len() != 2 {
1207                return Err(InktParseError {
1208                    message: format!("invalid source_location: {s}"),
1209                    line: 0,
1210                    col: 0,
1211                });
1212            }
1213            let line: u32 = parts[0].parse().map_err(|_| InktParseError {
1214                message: "invalid line".into(),
1215                line: 0,
1216                col: 0,
1217            })?;
1218            let col: u32 = parts[1].parse().map_err(|_| InktParseError {
1219                message: "invalid col".into(),
1220                line: 0,
1221                col: 0,
1222            })?;
1223            Ok(Opcode::SourceLocation(line, col))
1224        }
1225
1226        _ => Err(InktParseError {
1227            message: format!("unknown opcode: {mnemonic}"),
1228            line: mnemonic_pair.line_col().0,
1229            col: mnemonic_pair.line_col().1,
1230        }),
1231    }
1232}
1233
1234fn parse_choice_flags_operand(
1235    operands: &[P<'_>],
1236    idx: usize,
1237    context: &str,
1238) -> Result<ChoiceFlags, InktParseError> {
1239    let s = operand_str(operands, idx, context)?;
1240    let mut flags = ChoiceFlags {
1241        has_condition: false,
1242        has_start_content: false,
1243        has_choice_only_content: false,
1244        once_only: false,
1245        is_invisible_default: false,
1246    };
1247    if s == "none" {
1248        return Ok(flags);
1249    }
1250    for part in s.split('+') {
1251        match part {
1252            "cond" => flags.has_condition = true,
1253            "start" => flags.has_start_content = true,
1254            "choice_only" => flags.has_choice_only_content = true,
1255            "once" => flags.once_only = true,
1256            "invis_default" => flags.is_invisible_default = true,
1257            _ => {
1258                return Err(InktParseError {
1259                    message: format!("unknown choice flag: {part}"),
1260                    line: 0,
1261                    col: 0,
1262                });
1263            }
1264        }
1265    }
1266    Ok(flags)
1267}
1268
1269// ── Operand helpers ─────────────────────────────────────────────────────────
1270
1271fn operand_str<'a>(
1272    operands: &'a [P<'_>],
1273    idx: usize,
1274    context: &str,
1275) -> Result<&'a str, InktParseError> {
1276    let op = operands.get(idx).ok_or_else(|| InktParseError {
1277        message: format!("missing operand {idx} for {context}"),
1278        line: 0,
1279        col: 0,
1280    })?;
1281    // The operand rule wraps the actual value. Get the inner pair.
1282    let inner = op.clone().into_inner().next();
1283    match inner {
1284        Some(p) => Ok(p.as_str()),
1285        None => Ok(op.as_str()),
1286    }
1287}
1288
1289fn parse_operand_i32(operands: &[P<'_>], idx: usize, context: &str) -> Result<i32, InktParseError> {
1290    let s = operand_str(operands, idx, context)?;
1291    s.parse().map_err(|_| InktParseError {
1292        message: format!("invalid i32 operand for {context}: {s}"),
1293        line: 0,
1294        col: 0,
1295    })
1296}
1297
1298fn parse_operand_f32(operands: &[P<'_>], idx: usize, context: &str) -> Result<f32, InktParseError> {
1299    let s = operand_str(operands, idx, context)?;
1300    s.parse().map_err(|_| InktParseError {
1301        message: format!("invalid f32 operand for {context}: {s}"),
1302        line: 0,
1303        col: 0,
1304    })
1305}
1306
1307fn parse_operand_u8(operands: &[P<'_>], idx: usize, context: &str) -> Result<u8, InktParseError> {
1308    let s = operand_str(operands, idx, context)?;
1309    s.parse().map_err(|_| InktParseError {
1310        message: format!("invalid u8 operand for {context}: {s}"),
1311        line: 0,
1312        col: 0,
1313    })
1314}
1315
1316fn parse_operand_u16(operands: &[P<'_>], idx: usize, context: &str) -> Result<u16, InktParseError> {
1317    let s = operand_str(operands, idx, context)?;
1318    s.parse().map_err(|_| InktParseError {
1319        message: format!("invalid u16 operand for {context}: {s}"),
1320        line: 0,
1321        col: 0,
1322    })
1323}
1324
1325fn parse_operand_def_id(
1326    operands: &[P<'_>],
1327    idx: usize,
1328    context: &str,
1329) -> Result<DefinitionId, InktParseError> {
1330    let op = operands.get(idx).ok_or_else(|| InktParseError {
1331        message: format!("missing operand {idx} for {context}"),
1332        line: 0,
1333        col: 0,
1334    })?;
1335    // Drill into the operand to get the def_id inner pair
1336    let inner = op.clone().into_inner().next().unwrap_or_else(|| op.clone());
1337    parse_def_id(inner)
1338}
1339
1340// ── Shared parse helpers ────────────────────────────────────────────────────
1341
1342#[expect(clippy::needless_pass_by_value)]
1343fn parse_def_id(pair: P<'_>) -> Result<DefinitionId, InktParseError> {
1344    let s = pair.as_str();
1345    // Format: $TT_HHHHHHHHHHHHHH
1346    if !s.starts_with('$') || s.len() < 4 {
1347        return Err(err(&pair, format!("invalid def_id: {s}")));
1348    }
1349    let tag_str = &s[1..3];
1350    let hash_str = &s[4..]; // skip $TT_
1351
1352    let tag_byte = u8::from_str_radix(tag_str, 16)
1353        .map_err(|_| err(&pair, format!("invalid tag: {tag_str}")))?;
1354    let hash = u64::from_str_radix(hash_str, 16)
1355        .map_err(|_| err(&pair, format!("invalid hash: {hash_str}")))?;
1356
1357    let tag = crate::id::DefinitionTag::from_u8(tag_byte)
1358        .ok_or_else(|| err(&pair, format!("unknown tag byte: {tag_byte:#04x}")))?;
1359
1360    Ok(DefinitionId::new(tag, hash))
1361}
1362
1363fn parse_hex_u32(s: &str) -> u32 {
1364    let hex = s.strip_prefix("0x").unwrap_or(s);
1365    u32::from_str_radix(hex, 16).unwrap_or(0)
1366}
1367
1368fn parse_hex_u64(s: &str) -> Result<u64, InktParseError> {
1369    let hex = s.strip_prefix("0x").unwrap_or(s);
1370    u64::from_str_radix(hex, 16).map_err(|_| InktParseError {
1371        message: format!("invalid hex: {s}"),
1372        line: 0,
1373        col: 0,
1374    })
1375}
1376
1377fn parse_u16(pair: &P<'_>) -> Result<u16, InktParseError> {
1378    pair.as_str().parse().map_err(|_| err(pair, "invalid u16"))
1379}
1380
1381fn unescape_string(s: &str) -> String {
1382    // Strip surrounding quotes
1383    let inner = &s[1..s.len() - 1];
1384    let mut out = String::with_capacity(inner.len());
1385    let mut chars = inner.chars();
1386    while let Some(c) = chars.next() {
1387        if c == '\\' {
1388            match chars.next() {
1389                Some('\\') | None => out.push('\\'),
1390                Some('"') => out.push('"'),
1391                Some('n') => out.push('\n'),
1392                Some('t') => out.push('\t'),
1393                Some('r') => out.push('\r'),
1394                Some(other) => {
1395                    out.push('\\');
1396                    out.push(other);
1397                }
1398            }
1399        } else {
1400            out.push(c);
1401        }
1402    }
1403    out
1404}
1405
1406fn next_rule<'a>(
1407    iter: &mut impl Iterator<Item = P<'a>>,
1408    expected: Rule,
1409    context: &str,
1410) -> Result<P<'a>, InktParseError> {
1411    for pair in iter.by_ref() {
1412        if pair.as_rule() == expected {
1413            return Ok(pair);
1414        }
1415    }
1416    Err(InktParseError {
1417        message: format!("expected {expected:?} in {context}"),
1418        line: 0,
1419        col: 0,
1420    })
1421}