tfm/ligkern/
lang.rs

1//! Types corresponding to the "lig/kern programming language".
2//!
3//! See the documentation on the [`super`] module for information about this programming language.
4//!
5//! The types here are put in a separate module because users of this crate are generally not expected to use them.
6//! Instead, users will work with compiled lig/kern programs.
7
8use std::collections::HashMap;
9use std::collections::HashSet;
10
11use crate::Char;
12use crate::FixWord;
13
14/// A lig/kern program.
15///
16/// In theory the program also includes entrypoints.
17/// However because these are provided in different ways in .tfm and .pl files,
18/// it's easier to exclude them on this type and pass them in when needed.
19#[derive(Clone, Debug, Default, PartialEq, Eq)]
20pub struct Program {
21    pub instructions: Vec<Instruction>,
22    pub left_boundary_char_entrypoint: Option<u16>,
23    pub right_boundary_char: Option<Char>,
24    pub passthrough: HashSet<u16>,
25}
26
27/// A single instruction in a lig/kern program.
28///
29/// In TFM files, instructions are serialized to 32 bit integers.
30///
31/// In property list files, instructions are specified using a `(LIG _ _)` or `(KERN _ _)` element,
32///     and optionally a `(STOP)` or `(SKIP _)` element directly after.
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct Instruction {
35    /// Specifies the next instruction to run if this instruction is not applicable -
36    ///     e.g., if the right character of the pair is not `right_char`.
37    /// If the payload is present, that number of lig/kern instructions in the list of all instructions are skipped to
38    ///     find the next instruction.
39    /// Otherwise this is the final instruction and there are no more instructions to consider.
40    pub next_instruction: Option<u8>,
41    /// This instruction is run if the right character in the pair is this character.
42    /// Otherwise the next lig kern instruction for the current character is considered,
43    ///     using the `next_instruction` field.
44    ///
45    /// After this operation is performed,
46    ///     no more operations need to be performed on this pair.
47    /// However the result of the operation may result in a new pair being created
48    ///     and the lig/kern program will run for that pair.
49    /// See the documentation on [`PostLigOperation`] for information on that.
50    pub right_char: Char,
51    /// The operation to perform for this instruction.
52    pub operation: Operation,
53}
54
55/// A lig/kern operation to perform.
56#[derive(Debug, PartialEq, Eq, Copy, Clone)]
57pub enum Operation {
58    /// Insert a kern between the current character and the next character.
59    ///
60    /// The variant payload is the size of the kern.
61    Kern(FixWord),
62    /// Insert a kern between the current character and the next character.
63    ///
64    /// The variant payload is the index of the kern in the kerns array.
65    KernAtIndex(u16),
66    /// Perform a ligature step.
67    /// This inserts `char_to_insert` between the left and right characters,
68    ///     and then performs the post-lig operation.
69    Ligature {
70        /// Character to insert.
71        char_to_insert: Char,
72        /// What to do after inserting the character.
73        post_lig_operation: PostLigOperation,
74        /// If the tag in the .tfm file was invalid, this will be true.
75        /// In this case the post_lig_operation will be [`PostLigOperation::RetainNeitherMoveToInserted`].
76        ///
77        /// This field is used in the .tfm validation function to generate a warning for this case.
78        /// We could also generate a warning in the deserialization code itself but then the
79        /// ordering of the warning would be incorrect with respect to other validation warnings.
80        post_lig_tag_invalid: bool,
81    },
82    /// If the entrypoint for a character is this operation, go to the instruction indexed by the payload.
83    ///
84    /// This redirect mechanism exists because in .tfm files entrypoints are [`u8`]s but lig/kern
85    ///     programs can contain more than 256 instructions.
86    ///
87    /// If this operation is encountered in another situation, it is an unconditional stop.
88    ///
89    /// The boolean value in the payload is true if the boundary character should be
90    ///     serialized inside the instruction.
91    EntrypointRedirect(u16, bool),
92}
93
94impl Operation {
95    pub(crate) fn lig_kern_operation_from_bytes(op_byte: u8, remainder: u8) -> Self {
96        match op_byte.checked_sub(128) {
97            Some(r) => Self::KernAtIndex(u16::from_be_bytes([r, remainder])),
98            None => {
99                // TFtoPL.2014.77
100                let delete_next_char = op_byte.is_multiple_of(2);
101                let op_byte = op_byte / 2;
102                let delete_current_char = op_byte.is_multiple_of(2);
103                let skip = op_byte / 2;
104                use PostLigOperation::*;
105                let (post_lig_operation, post_lig_tag_invalid) =
106                    match (delete_current_char, delete_next_char, skip) {
107                        (false, false, 0) => (RetainBothMoveNowhere, false),
108                        (false, false, 1) => (RetainBothMoveToInserted, false),
109                        (false, false, 2) => (RetainBothMoveToRight, false),
110                        (false, true, 0) => (RetainLeftMoveNowhere, false),
111                        (false, true, 1) => (RetainLeftMoveToInserted, false),
112                        (true, false, 0) => (RetainRightMoveToInserted, false),
113                        (true, false, 1) => (RetainRightMoveToRight, false),
114                        (true, true, 0) => (RetainNeitherMoveToInserted, false),
115                        _ => (RetainNeitherMoveToInserted, true),
116                    };
117                Self::Ligature {
118                    char_to_insert: Char(remainder),
119                    post_lig_operation,
120                    post_lig_tag_invalid,
121                }
122            }
123        }
124    }
125}
126
127/// A post-lig operation to perform after performing a ligature operation ([`Operation::Ligature`]).
128///
129/// A lig operation starts with a pair of characters (x,y) and a "cursor" on x.
130/// The operation then inserts another character to get, say, (x,z,y).
131/// At this point the cursor is still on x.
132/// The post-lig operation does two things:
133///
134/// 1. First, it potentially deletes x or y or both.
135/// 1. Second, it potentially moves the cursor forward.
136///
137/// After this, if the cursor is not at the end of the list of characters,
138///     the lig-kern program is run for the new pair starting at the cursor.
139///
140/// For example, the post-lig operation [`PostLigOperation::RetainLeftMoveNowhere`] retains
141///     x and deletes y, leaving (x,z).
142/// It then moves the cursor nowhere, leaving it on x.
143/// As a result, the lig kern program for the pair (x,z) will run.
144///
145/// On the other hand, if the post-lig operation [`PostLigOperation::RetainLeftMoveToInserted`]
146///     runs, y is still deleted but the cursor moves to z.
147/// This is the last character in this list and there no more pairs of characters to consider.
148/// The lig/kern program thus terminates.
149///
150/// In general all of the post-lig operations are of the form `RetainXMoveY` where `X`
151///     specifies the characters to retain and `Y` specifies where the cursor should move.
152#[derive(Debug, PartialEq, Eq, Clone, Copy)]
153#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
154pub enum PostLigOperation {
155    /// Corresponds to the `/LIG/` property list element.
156    RetainBothMoveNowhere,
157    /// Corresponds to the `/LIG/>` property list element.
158    RetainBothMoveToInserted,
159    /// Corresponds to the `/LIG/>>` property list element.
160    RetainBothMoveToRight,
161    /// Corresponds to the `LIG/` property list element.
162    RetainRightMoveToInserted,
163    /// Corresponds to the `LIG/>` property list element.
164    RetainRightMoveToRight,
165    /// Corresponds to the `/LIG` property list element.
166    RetainLeftMoveNowhere,
167    /// Corresponds to the `/LIG>` property list element.
168    RetainLeftMoveToInserted,
169    /// Corresponds to the `LIG` property list element.
170    RetainNeitherMoveToInserted,
171}
172
173#[derive(Clone, Debug)]
174pub enum InvalidEntrypointError {
175    Direct { entrypoint: u8 },
176    Indirect { packed: u8, unpacked: u16 },
177}
178
179impl Program {
180    pub fn unpack_entrypoint(&mut self, entrypoint: u8) -> Result<u16, InvalidEntrypointError> {
181        match self.instructions.get(entrypoint as usize) {
182            None => Err(InvalidEntrypointError::Direct { entrypoint }),
183            Some(instruction) => match instruction.operation {
184                Operation::EntrypointRedirect(u_big, _) => {
185                    if (u_big as usize) < self.instructions.len() {
186                        self.passthrough.insert(entrypoint as u16);
187                        Ok(u_big)
188                    } else {
189                        Err(InvalidEntrypointError::Indirect {
190                            packed: entrypoint,
191                            unpacked: u_big,
192                        })
193                    }
194                }
195                _ => Ok(entrypoint as u16),
196            },
197        }
198    }
199
200    pub fn pack_entrypoints(&mut self, entrypoints: HashMap<Char, u16>) -> HashMap<Char, u8> {
201        let instructions = &mut self.instructions;
202        let ordered_entrypoints = {
203            let mut m: HashMap<u16, Vec<Char>> = Default::default();
204            for (c, u) in entrypoints {
205                m.entry(u).or_default().push(c);
206            }
207            let mut v: Vec<(u16, Vec<Char>)> = m.into_iter().collect();
208            v.sort_by_key(|(u, _)| *u);
209            v
210        };
211        let mut offset: u8 = if self.right_boundary_char.is_some() {
212            // In .tfm files the boundary char is transmitted in each entrypoint redirect instruction.
213            // If there is a boundary char, we need at least one entrypoint redirect to exist so
214            // that the boundary char is there.
215            instructions.push(Instruction {
216                next_instruction: None,
217                right_char: self.right_boundary_char.unwrap_or(Char(0)),
218                operation: Operation::EntrypointRedirect(0, true),
219            });
220            1
221        } else {
222            0
223        };
224        let mut new_entrypoints: HashMap<Char, u8> = Default::default();
225        let mut redirects: Vec<u16> = vec![];
226        for (i, (u16_entrypoint, chars)) in ordered_entrypoints.into_iter().rev().enumerate() {
227            let u: u8 = match (u16_entrypoint + offset as u16).try_into() {
228                Ok(u) => u,
229                Err(_) => {
230                    redirects.push(u16_entrypoint);
231                    if i == 0 && self.right_boundary_char.is_some() {
232                        // This implements the "optimization" "location 0 can do double duty" in PLtoTF.2014.141
233                        instructions.pop();
234                        offset = 0;
235                    }
236                    let u = offset;
237                    offset = offset.checked_add(1).expect(
238                        "offset is incremented at most once per 8-bit-char and so cannot exceed 256",
239                    );
240                    u
241                }
242            };
243            for c in chars {
244                new_entrypoints.insert(c, u);
245            }
246        }
247        for redirect in redirects {
248            instructions.push(Instruction {
249            next_instruction: None,
250            right_char: self.right_boundary_char.unwrap_or(Char(0)),
251            operation: Operation::EntrypointRedirect(
252                redirect.checked_add(offset as u16).expect("the inputted lig/kern instructions vector doesn't have enough space for new instructions"),
253            true,
254        ),
255        });
256        }
257        instructions.rotate_right(offset as usize);
258        if let Some(boundary_char_entrypoint) = self.left_boundary_char_entrypoint {
259            instructions.push(Instruction {
260                next_instruction: None,
261                right_char: Char(0),
262                operation: Operation::EntrypointRedirect(
263                    boundary_char_entrypoint + offset as u16,
264                    false,
265                ),
266            })
267        }
268        new_entrypoints
269    }
270
271    pub fn unpack_kerns(&mut self) -> Vec<FixWord> {
272        let mut kerns = vec![];
273        let mut kerns_dedup = HashMap::<FixWord, usize>::new();
274        for instruction in &mut self.instructions {
275            if let Operation::Kern(kern) = instruction.operation {
276                use std::collections::hash_map::Entry;
277                let index = match kerns_dedup.entry(kern) {
278                    Entry::Occupied(o) => *o.get(),
279                    Entry::Vacant(v) => {
280                        let l = kerns.len();
281                        v.insert(l);
282                        kerns.push(kern);
283                        l
284                    }
285                };
286                instruction.operation = Operation::KernAtIndex(index.try_into().unwrap());
287            }
288        }
289        kerns
290    }
291
292    pub fn pack_kerns(&mut self, kerns: &[FixWord]) {
293        for i in &mut self.instructions {
294            if let Operation::KernAtIndex(index) = &i.operation {
295                // TODO: log a warning if the index is not in the kerns array as
296                // in TFtoPL.2014.76
297                i.operation =
298                    Operation::Kern(kerns.get(*index as usize).copied().unwrap_or_default())
299            }
300        }
301    }
302
303    pub fn reachable_iter<I: Iterator<Item = (Char, u16)>>(
304        &self,
305        entrypoints: I,
306    ) -> ReachableIter<'_> {
307        ReachableIter {
308            next: 0,
309            reachable: self.reachable_array(entrypoints),
310            program: self,
311        }
312    }
313
314    /// Iterate over the lig/kern instructions for a specific entrypoint.
315    pub fn instructions_for_entrypoint(
316        &self,
317        entrypoint: u16,
318    ) -> InstructionsForEntrypointIter<'_> {
319        InstructionsForEntrypointIter {
320            next: entrypoint as usize,
321            instructions: &self.instructions,
322        }
323    }
324
325    pub fn is_seven_bit_safe(&self, entrypoints: HashMap<Char, u16>) -> bool {
326        entrypoints
327            .into_iter()
328            .filter(|(c, _)| c.is_seven_bit())
329            .flat_map(|(_, e)| self.instructions_for_entrypoint(e))
330            .filter(|(_, instruction)| instruction.right_char.is_seven_bit())
331            .filter_map(|(_, instruction)| match instruction.operation {
332                Operation::Ligature { char_to_insert, .. } => Some(char_to_insert),
333                _ => None,
334            })
335            .all(|c| c.is_seven_bit())
336    }
337
338    pub fn validate_and_fix<I, T>(
339        &mut self,
340        smallest_char: Char,
341        entrypoints: I,
342        char_exists: T,
343        kerns: &[FixWord],
344    ) -> Vec<ValidationWarning>
345    where
346        I: Iterator<Item = (Char, u8)>,
347        T: Fn(Char) -> bool,
348    {
349        let mut warnings = vec![];
350        // TFtoPL.2014.68
351        if let Some(entrypoint) = self.left_boundary_char_entrypoint {
352            if self.instructions.len() <= entrypoint as usize {
353                self.left_boundary_char_entrypoint = None;
354                warnings.push(ValidationWarning::InvalidBoundaryCharEntrypoint);
355            }
356        }
357        // TFtoPL.2014.69
358        let unpacked_entrypoints: Vec<(Char, u16)> = entrypoints
359            .into_iter()
360            .filter_map(|(c, e)| match self.unpack_entrypoint(e) {
361                Ok(e) => Some((c, e)),
362                Err(_) => {
363                    warnings.push(ValidationWarning::InvalidEntrypoint(c));
364                    None
365                }
366            })
367            .collect();
368        let reachable = self.reachable_array(unpacked_entrypoints.iter().cloned());
369        let n = self.instructions.len();
370        // TFtoPL.2014.70
371        self.instructions
372            .iter_mut()
373            .zip(reachable.iter())
374            .enumerate()
375            .filter(|(_, (_, reachable))| **reachable)
376            .filter_map(|(i, (instruction, _))| {
377                instruction
378                    .next_instruction
379                    .map(|inc| (i, inc, instruction))
380            })
381            .filter(|(i, inc, _)| *i + (*inc as usize) + 1 >= n)
382            .for_each(|(i, _, instruction)| {
383                instruction.next_instruction = None;
384                warnings.push(ValidationWarning::SkipTooLarge(i));
385            });
386
387        for (i, (instruction, reachable)) in self.instructions.iter_mut().zip(reachable).enumerate()
388        {
389            let is_kern_step = match instruction.operation {
390                Operation::Kern(_) | Operation::KernAtIndex(_) => true,
391                Operation::Ligature { .. } => false,
392                Operation::EntrypointRedirect(r, _) => {
393                    if let Ok(u) = i.try_into() {
394                        // If it's a passthrough instruction, don't issue a warning.
395                        if !reachable && self.passthrough.contains(&u) {
396                            continue;
397                        }
398                    }
399                    if r as usize >= n {
400                        warnings.push(ValidationWarning::EntrypointRedirectTooBig(i));
401                    }
402                    continue;
403                }
404            };
405            if !char_exists(instruction.right_char)
406                && Some(instruction.right_char) != self.right_boundary_char
407            {
408                warnings.push(if is_kern_step {
409                    ValidationWarning::KernStepForNonExistentCharacter {
410                        instruction_index: i,
411                        right_char: instruction.right_char,
412                        new_right_char: smallest_char,
413                    }
414                } else {
415                    ValidationWarning::LigatureStepForNonExistentCharacter {
416                        instruction_index: i,
417                        right_char: instruction.right_char,
418                        new_right_char: smallest_char,
419                    }
420                });
421                instruction.right_char = smallest_char;
422            }
423            match &mut instruction.operation {
424                Operation::Kern(_) => {}
425                Operation::KernAtIndex(k) => {
426                    if *k as usize >= kerns.len() {
427                        warnings.push(ValidationWarning::KernIndexTooBig(i));
428                        instruction.operation = Operation::Kern(FixWord::ZERO);
429                    }
430                }
431                Operation::Ligature {
432                    char_to_insert,
433                    post_lig_tag_invalid,
434                    ..
435                } => {
436                    if !char_exists(*char_to_insert) {
437                        warnings.push(
438                            ValidationWarning::LigatureStepProducesNonExistentCharacter {
439                                instruction_index: i,
440                                replacement_char: *char_to_insert,
441                                new_replacement_char: smallest_char,
442                            },
443                        );
444                        *char_to_insert = smallest_char;
445                    }
446                    if *post_lig_tag_invalid {
447                        warnings.push(ValidationWarning::InvalidLigTag(i));
448                        *post_lig_tag_invalid = false;
449                    }
450                }
451                Operation::EntrypointRedirect(_, _) => {}
452            }
453        }
454
455        let entrypoints: HashMap<Char, u16> = unpacked_entrypoints.into_iter().collect();
456        // We are only running the compiler to find errors and are disregarding the actual
457        // result. Thus the true design size is not needed. Using this dummy avoids having
458        // to plumb in the real design size into this function.
459        let dummy_design_size = FixWord::ONE * 10;
460        let (_, errors) =
461            super::CompiledProgram::compile(self, dummy_design_size, kerns, entrypoints);
462        for err in errors {
463            warnings.push(ValidationWarning::InfiniteLoop(err));
464        }
465        warnings
466    }
467
468    fn reachable_array<I: Iterator<Item = (Char, u16)>>(&self, entrypoints: I) -> Vec<bool> {
469        let mut reachable = vec![false; self.instructions.len()];
470        // TFtoPL.2014.68
471        for (_, entrypoint) in entrypoints {
472            if let Some(slot) = reachable.get_mut(entrypoint as usize) {
473                *slot = true;
474            }
475        }
476        // TFtoPL.2014.69
477        if let Some(entrypoint) = self.left_boundary_char_entrypoint {
478            // There is a bug (?) in Knuth's TFtoPL when the entrypoint for the boundary char
479            // points at the last instruction - i.e., the instruction containing the
480            // boundary char entrypoint. In this case the last instruction is marked as passthrough
481            // and not accessible.
482            if entrypoint as usize != self.instructions.len() - 1 {
483                if let Some(slot) = reachable.get_mut(entrypoint as usize) {
484                    *slot = true;
485                }
486            }
487        }
488        // TFtoPL.2014.70
489        for i in 0..reachable.len() {
490            if !reachable[i] {
491                continue;
492            }
493            if let Some(inc) = self.instructions[i].next_instruction {
494                if let Some(slot) = reachable.get_mut(i + inc as usize + 1) {
495                    *slot = true;
496                }
497            }
498        }
499        reachable
500    }
501}
502
503#[derive(Clone, Debug, PartialEq, Eq)]
504pub enum ValidationWarning {
505    SkipTooLarge(usize),
506    LigatureStepForNonExistentCharacter {
507        // Index of the buggy lig instruction in the program.
508        instruction_index: usize,
509        // Right character that is non existent.
510        right_char: Char,
511        // New right character after the buggy instruction is fixed.
512        //
513        // This is set to bc by tftopl. Note there is no guarantee that bc
514        // itself exists, so the instruction may still be buggy.
515        new_right_char: Char,
516    },
517    KernStepForNonExistentCharacter {
518        // Index of the buggy kern instruction in the program.
519        instruction_index: usize,
520        // Right character that is non existent.
521        right_char: Char,
522        // New right character after the buggy instruction is fixed.
523        //
524        // This is set to bc by tftopl. Note there is no guarantee that bc
525        // itself exists, so the instruction may still be buggy.
526        new_right_char: Char,
527    },
528    LigatureStepProducesNonExistentCharacter {
529        // Index of the buggy kern instruction in the program.
530        instruction_index: usize,
531        // Replacement character that is non existent.
532        replacement_char: Char,
533        // New replacement character after the buggy instruction is fixed.
534        //
535        // This is set to bc by tftopl. Note there is no guarantee that bc
536        // itself exists, so the instruction may not be fully fixed.
537        new_replacement_char: Char,
538    },
539    KernIndexTooBig(usize),
540    InvalidLigTag(usize),
541    EntrypointRedirectTooBig(usize),
542    InvalidEntrypoint(Char),
543    InvalidBoundaryCharEntrypoint,
544    InfiniteLoop(super::InfiniteLoopError),
545}
546
547impl ValidationWarning {
548    /// Returns the warning message the TFtoPL program prints for this kind of error.
549    pub fn tftopl_message(&self) -> String {
550        use ValidationWarning::*;
551        match self {
552            SkipTooLarge(i) => {
553                format!["Bad TFM file: Ligature/kern step {i} skips too far;\nI made it stop."]
554            }
555            LigatureStepForNonExistentCharacter { right_char, .. } => format![
556                "Bad TFM file: Ligature step for nonexistent character '{:03o}.",
557                right_char.0
558            ],
559            KernStepForNonExistentCharacter { right_char, .. } => format![
560                "Bad TFM file: Kern step for nonexistent character '{:03o}.",
561                right_char.0
562            ],
563            LigatureStepProducesNonExistentCharacter {
564                replacement_char, ..
565            } => format![
566                "Bad TFM file: Ligature step produces the nonexistent character '{:03o}.",
567                replacement_char.0
568            ],
569            KernIndexTooBig(_) => "Bad TFM file: Kern index too large.".to_string(),
570            InvalidLigTag(_) => "Ligature step with nonstandard code changed to LIG".to_string(),
571            EntrypointRedirectTooBig(_) => {
572                "Bad TFM file: Ligature unconditional stop command address is too big.".to_string()
573            }
574            InvalidEntrypoint(c) => {
575                format![" \nLigature/kern starting index for character '{:03o} is too large;\nso I removed it.", c.0]
576            }
577            InvalidBoundaryCharEntrypoint => {
578                " \nLigature/kern starting index for boundarychar is too large;so I removed it."
579                    .to_string()
580            }
581            InfiniteLoop(err) => err.pltotf_message(),
582        }
583    }
584
585    /// Returns the section in Knuth's TFtoPL (version 2014) in which this warning occurs.
586    pub fn tftopl_section(&self) -> u8 {
587        use ValidationWarning::*;
588        match self {
589            SkipTooLarge(_) => 70,
590            LigatureStepForNonExistentCharacter { .. }
591            | LigatureStepProducesNonExistentCharacter { .. }
592            | InvalidLigTag(_) => 77,
593            KernStepForNonExistentCharacter { .. } | KernIndexTooBig(_) => 76,
594            EntrypointRedirectTooBig(_) => 74,
595            InvalidEntrypoint(_) => 67,
596            InvalidBoundaryCharEntrypoint => 69,
597            InfiniteLoop(_) => 90,
598        }
599    }
600}
601
602pub struct ReachableIter<'a> {
603    next: u16,
604    reachable: Vec<bool>,
605    program: &'a Program,
606}
607
608#[derive(Debug)]
609pub enum ReachableIterItem {
610    Reachable { adjusted_skip: Option<u8> },
611    Unreachable,
612    Passthrough,
613}
614
615impl<'a> Iterator for ReachableIter<'a> {
616    type Item = ReachableIterItem;
617
618    fn next(&mut self) -> Option<Self::Item> {
619        let this = self.next;
620        let instruction = self.program.instructions.get(this as usize)?;
621        self.next += 1;
622        Some(if self.reachable[this as usize] {
623            let adjusted_skip = match instruction.next_instruction {
624                None | Some(0) => None,
625                Some(inc) => {
626                    match self
627                        .reachable
628                        .get(this as usize + 1..this as usize + 1 + inc as usize)
629                    {
630                        None => None,
631                        Some(n) => {
632                            let reachable_skipped: u8 =
633                                n.iter()
634                                .filter(|reachable| **reachable)
635                                .count()
636                                .try_into()
637                                .expect("iterating over at most u8::MAX elements, so the count will be at most u8::MAX");
638                            Some(reachable_skipped)
639                        }
640                    }
641                }
642            };
643            ReachableIterItem::Reachable { adjusted_skip }
644        } else if self.program.passthrough.contains(&this) {
645            ReachableIterItem::Passthrough
646        } else {
647            ReachableIterItem::Unreachable
648        })
649    }
650}
651
652/// Iterator over the lig/kern instructions for a specific entrypoint.
653///
654/// Create using [`Program::instructions_for_entrypoint`].
655pub struct InstructionsForEntrypointIter<'a> {
656    next: usize,
657    instructions: &'a [Instruction],
658}
659
660impl<'a> Iterator for InstructionsForEntrypointIter<'a> {
661    type Item = (usize, &'a Instruction);
662
663    fn next(&mut self) -> Option<Self::Item> {
664        self.instructions.get(self.next).map(|i| {
665            let this = self.next;
666            self.next = match i.next_instruction {
667                None => usize::MAX,
668                Some(inc) => self.next + inc as usize + 1,
669            };
670            (this, i)
671        })
672    }
673}