fkys_rs/
fkys.rs

1//! A FKYS library for parsing scripts
2
3use {
4    anyhow::Result,
5    std::io::{Write, stdin},
6};
7
8/// Evaluate a script.
9///
10/// A string slice and a handle is passed. Handle must implement [`std::io::Write`] trait. It can be
11/// a [`Stdout`].
12///
13/// # Errors
14///
15/// Fails if there is a error while evaluating a single char.
16///
17/// ```no_run
18/// use {
19///     fkys_rs::eval,
20///     std::io::{BufWriter, stdout},
21/// };
22///
23/// let mut handle = BufWriter::new(stdout());
24/// eval(
25///     r"++++++++++++++++++++++++++++++++++++++++++++++os # number bam-bam
26/// ---------------os+++++++++++++o",
27///     &mut handle,
28/// ); // writes `46 31 44` into handle
29/// ```
30pub fn eval<W: ?Sized + Write>(script: &str, handle: &mut W) -> Result<()> {
31    let (mut code, mut collecting, mut arr, mut pointer, mut int_mode) =
32        (String::new(), false, [0; 500], 0, true);
33    for line in script.lines() {
34        for char in line.chars() {
35            match char {
36                '#' => break,
37                'e' => return Ok(()),
38                '[' => collecting = true,
39                ']' => {
40                    collecting = false;
41                    loop {
42                        for char_code in code.chars() {
43                            eval_char(
44                                char_code,
45                                handle,
46                                &mut arr,
47                                &mut pointer,
48                                &mut int_mode,
49                                false,
50                            )?;
51                        }
52                        // SAFETY: pointer is in range 0..=499
53                        if *unsafe { arr.get_unchecked(pointer) } == 0 {
54                            break;
55                        }
56                    }
57                    code.clear();
58                },
59                _ => (),
60            }
61
62            if collecting {
63                code.push(char);
64            } else {
65                eval_char(char, handle, &mut arr, &mut pointer, &mut int_mode, false)?;
66            }
67        }
68    }
69    Ok(())
70}
71
72/// Evluate a char.
73///
74/// Provides a low-level evaluation, which require
75///
76/// - char to evaluate ([`char`])
77/// - handle, which implements [`std::io::Write`],
78/// - array which is used to write things (fixed at [`i32`; 500])
79/// - pointer, which must be at range `0..=499`
80/// - int mode, which defines whether integers or chars are printed to stdout
81/// - is interactive, which enables interactive shell mode (example of such is implemented in
82///   `fkysoxide` binary)
83///
84/// # Errors
85///
86/// Function uses some methods that return [`Result`] value
87///
88/// # Panics
89///
90/// Panics when conversion from [`u32`] to [`char`] fails
91///
92/// [`Result`]: anyhow::Result
93pub(crate) fn eval_char<W: ?Sized + Write>(
94    char: char,
95    handle: &mut W,
96    arr: &mut [i32; 500],
97    pointer: &mut usize,
98    int_mode: &mut bool,
99    is_interactive: bool,
100) -> Result<()> {
101    match char {
102        '>' => {
103            // SAFETY: pointer is much less than usize::MAX
104            *pointer = unsafe { pointer.unchecked_add(1) % 500 };
105            if is_interactive {
106                write!(*handle, "> Now at {}", *pointer)?;
107            }
108        },
109        '<' => {
110            // SAFETY: pointer is much less than usize::MAX
111            *pointer = unsafe { pointer.unchecked_add(499) % 500 };
112            if is_interactive {
113                write!(*handle, "> Now at {}", *pointer)?;
114            }
115        },
116        '+' => {
117            // SAFETY: pointer is in range of 0..=499
118            unsafe {
119                *arr.get_unchecked_mut(*pointer) += 1;
120            }
121            if is_interactive {
122                // SAFETY: pointer is in range of 0..=499
123                unsafe {
124                    write!(*handle, "> {}", arr.get_unchecked(*pointer))?;
125                }
126            }
127        },
128        '-' => {
129            // SAFETY: pointer is in range of 0..=499
130            unsafe {
131                *arr.get_unchecked_mut(*pointer) -= 1;
132            }
133            if is_interactive {
134                // SAFETY: pointer is in range of 0..=499
135                unsafe {
136                    write!(*handle, "> {}", arr.get_unchecked(*pointer))?;
137                }
138            }
139        },
140        'o' =>
141            if *int_mode {
142                // SAFETY: pointer is in range of 0..=499
143                unsafe {
144                    write!(*handle, "{}", arr.get_unchecked(*pointer))?;
145                }
146            } else {
147                // SAFETY: pointer is in range of 0..=499
148                unsafe {
149                    write!(
150                        *handle,
151                        "{}",
152                        char::from_u32(arr.get_unchecked(*pointer).unsigned_abs())
153                            .unwrap_or_default()
154                    )?;
155                }
156            },
157        'p' => {
158            let mut user_input = String::with_capacity(11);
159            loop {
160                match stdin().read_line(&mut user_input) {
161                    Ok(0) => return Ok(()),
162                    Err(_) => {
163                        handle.write_all(b"> Failed to read input, try again")?;
164                        handle.flush()?;
165                        continue;
166                    },
167                    _ => break,
168                };
169            }
170            // SAFETY: pointer is in range of 0..=499
171            unsafe {
172                *arr.get_unchecked_mut(*pointer) = user_input.trim_end().parse()?;
173            }
174        },
175        'n' if !is_interactive => handle.write_all(b"\n")?,
176        's' if !is_interactive => handle.write_all(b" ")?,
177        'l' => {
178            // SAFETY: pointer is in range of 0..=499
179            unsafe {
180                *arr.get_unchecked_mut(*pointer) = 125;
181            }
182            if is_interactive {
183                handle.write_all(b"> 125")?;
184            }
185        },
186        'i' => {
187            *int_mode = true;
188            if is_interactive {
189                handle.write_all(b"> Int mode enabled")?;
190            }
191        },
192        'c' => {
193            *int_mode = false;
194            if is_interactive {
195                handle.write_all(b"> Int mode disabled")?;
196            }
197        },
198        'h' if is_interactive => handle.write_all(
199            b"> Available commands:
200
201e - exit interactive shell
202> - moves pointer right
203< - moves pointer left
204+ - increments cell
205- - decrements cell
206i - integer output mode (enabled by default)
207c - character output mode
208o - prints the contents of the cell to the console
209p - accepts input from the user into the cell
210l - sets cell value to 125
211# - comments the rest of line
212h - prints this message",
213        )?,
214        _ =>
215            if is_interactive {
216                handle.write_all(
217                    b"> Unknown command, type `h` to get list of commands, `e` to exit",
218                )?;
219            },
220    }
221    Ok(())
222}