fractran_rs/
lib.rs

1#![no_std]
2#![macro_use]
3#![doc = include_str!("../README.md")]
4
5use core::{iter::FusedIterator, marker::PhantomData};
6
7/// A positive ratio of two `usize`s
8///
9/// ### Warning
10///
11/// In a true FRACTRAN program, the fractions must be in lowest terms. At no point are `Fraction`s
12/// checked for this condition. It is therefore possible to construct programs using `Fraction`s
13/// that are not entirely valid.
14#[derive(Copy, Clone)]
15pub struct Fraction {
16    pub num: usize,
17    pub den: usize,
18}
19
20/// A program in the FRACTRAN Language
21///
22/// FRACTRAN programs are simply lists of fractions and this thinly wraps a slice to such a list.
23/// However, we also encode the input and output types, `I` and `O` respectively which must expose
24/// an interface to convert to and from natural numbers (i.e. `usize`).
25pub struct FractranProgram<'a, I = usize, O = usize>
26where
27    I: Into<usize>,
28    O: From<usize>,
29{
30    instructions: &'a [Fraction],
31    _phantom: PhantomData<(I, O)>,
32}
33
34/// A in-progress run of a FRACTRAN program
35///
36/// This function exposes an iterator of `usize`. Since intermediate calculations might not have
37/// meaningful interpretation you should not convert them to `O` using `From` until the program is
38/// complete.
39pub struct FractranRun<'a, I: Into<usize>, O: From<usize>> {
40    current: usize,
41    program: &'a FractranProgram<'a, I, O>,
42}
43
44impl<'a, I: Into<usize>, O: From<usize>> FractranProgram<'a, I, O> {
45    /// Construct a new program with the given source code.
46    pub const fn new(instructions: &'a [Fraction]) -> Self {
47        Self {
48            instructions,
49            _phantom: PhantomData,
50        }
51    }
52
53    /// Create a [`FractranRun`] with a given input for this program.
54    ///
55    /// The `FractranRun` struct exposes an iterator interface that permits stepping through the
56    /// program.
57    pub fn start(&'a self, start: I) -> FractranRun<'a, I, O> {
58        FractranRun {
59            current: start.into(),
60            program: self,
61        }
62    }
63
64    /// Runs the FRACTRAN program to completion on the given input.
65    pub fn run(&self, start: I) -> O {
66        self.start(start).last().unwrap().into()
67    }
68}
69
70/// Macro for making [`FractranProgram`]s
71///
72/// Programs should be specified as a space-separated list of fractions.
73///
74/// ### Example
75/// ```rust
76/// use fractran_rs::*;
77///
78/// let program : FractranProgram<usize, usize> = fractran!(2/3 5/6);
79/// ```
80#[macro_export]
81macro_rules! fractran {
82    ($( $n:literal/$d:literal) *) => {
83        FractranProgram::new(&[$(Fraction{ num:$n, den:$d},)*])
84    };
85}
86
87impl<'a, I: Into<usize>, O: From<usize>> Iterator for FractranRun<'a, I, O> {
88    type Item = usize;
89
90    fn next(&mut self) -> Option<Self::Item> {
91        let next = 'nxt: {
92            for Fraction { num, den } in self.program.instructions {
93                if self.current % den == 0 {
94                    break 'nxt Some(self.current / den * num);
95                }
96            }
97            None
98        };
99        self.current = next.unwrap_or(self.current);
100        next
101    }
102}
103
104impl<'a, I: Into<usize>, O: From<usize>> FusedIterator for FractranRun<'a, I, O> {}