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 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 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
330fn 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
387fn 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
421fn 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
481fn 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
547fn 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
586fn 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(); 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 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
958fn 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 "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 "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 "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 "not" => Ok(Opcode::Not),
1022 "and" => Ok(Opcode::And),
1023 "or" => Ok(Opcode::Or),
1024
1025 "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 "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 "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 "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 "enter_container" => Ok(Opcode::EnterContainer(parse_operand_def_id(
1064 &operands, 0, mnemonic,
1065 )?)),
1066 "exit_container" => Ok(Opcode::ExitContainer),
1067
1068 "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 "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 "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 "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 "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 "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 "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 "call_external" => {
1161 let id = parse_operand_def_id(&operands, 0, mnemonic)?;
1162 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_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 "done" => Ok(Opcode::Done),
1189 "yield" => Ok(Opcode::Yield),
1190 "end" => Ok(Opcode::End),
1191 "nop" => Ok(Opcode::Nop),
1192
1193 "begin_string_eval" => Ok(Opcode::BeginStringEval),
1195 "end_string_eval" => Ok(Opcode::EndStringEval),
1196
1197 "begin_fragment" => Ok(Opcode::BeginFragment),
1199 "end_fragment" => Ok(Opcode::EndFragment),
1200
1201 "source_location" => {
1203 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
1269fn 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 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 let inner = op.clone().into_inner().next().unwrap_or_else(|| op.clone());
1337 parse_def_id(inner)
1338}
1339
1340#[expect(clippy::needless_pass_by_value)]
1343fn parse_def_id(pair: P<'_>) -> Result<DefinitionId, InktParseError> {
1344 let s = pair.as_str();
1345 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..]; 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 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}