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