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> {}