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}