1#![forbid(unsafe_code)]
21#![warn(missing_docs)]
22
23pub mod cli;
24pub mod exec;
25
26use ternaria_arith::{Tryte, Word};
27use ternaria_dev::{BlockDevice, Bus, Console, Timer};
28use ternaria_mem::{Addressable, MemoryError};
29use ternaria_vm::{Cpu, Trap, assemble_and_load};
30
31pub const SOURCE: &str = include_str!("os.s");
33
34pub const DATA_BASE: i64 = 50_000;
40
41struct Datum {
43 name: &'static str,
44 text: &'static str,
45}
46
47const DATA: &[Datum] = &[
53 Datum {
54 name: "MSG_PROMPT",
55 text: "$ ",
56 },
57 Datum {
58 name: "MSG_NOSUCH",
59 text: "no such name\n",
60 },
61 Datum {
62 name: "MSG_NOTDIR",
63 text: "not a directory\n",
64 },
65 Datum {
66 name: "MSG_EXISTS",
67 text: "already exists\n",
68 },
69 Datum {
70 name: "MSG_FULL",
71 text: "no space\n",
72 },
73 Datum {
74 name: "MSG_UNKNOWN",
75 text: "unknown command\n",
76 },
77 Datum {
78 name: "MSG_NOTEMPTY",
79 text: "directory not empty\n",
80 },
81 Datum {
82 name: "MSG_CONT",
83 text: "> ",
84 },
85 Datum {
86 name: "MSG_FAULT",
87 text: "program faulted\n",
88 },
89 Datum {
90 name: "MSG_NOTEXE",
91 text: "not a program\n",
92 },
93 Datum {
94 name: "CMD_LS",
95 text: "ls",
96 },
97 Datum {
98 name: "CMD_CD",
99 text: "cd",
100 },
101 Datum {
102 name: "CMD_CAT",
103 text: "cat",
104 },
105 Datum {
106 name: "CMD_MKDIR",
107 text: "mkdir",
108 },
109 Datum {
110 name: "CMD_RM",
111 text: "rm",
112 },
113 Datum {
114 name: "CMD_TOUCH",
115 text: "touch",
116 },
117 Datum {
118 name: "CMD_WRITE",
119 text: "write",
120 },
121 Datum {
122 name: "CMD_EXIT",
123 text: "exit",
124 },
125 Datum {
126 name: "CMD_LOGOUT",
127 text: "logout",
128 },
129 Datum {
130 name: "CMD_RUN",
131 text: "run",
132 },
133];
134
135fn data_layout() -> (Vec<(i64, &'static str)>, String) {
137 let mut placed = Vec::new();
138 let mut equates = String::new();
139 let mut at = DATA_BASE;
140 for d in DATA {
141 placed.push((at, d.text));
142 equates.push_str(&format!(".equ {}, {}\n", d.name, at));
143 let span = (d.text.len() as i64 + 3) / 3 * 3;
146 at += span;
147 }
148 (placed, equates)
149}
150
151pub fn data_placement() -> Vec<(i64, &'static str)> {
156 data_layout().0
157}
158
159pub fn source() -> String {
161 let (_, data_equates) = data_layout();
162 format!(
163 "{}{}{}{}{}{}",
164 ternaria_dev::prelude(),
165 ternaria_fs::prelude(),
166 ternaria_vm::csr::prelude(),
167 exec::prelude(),
168 data_equates,
169 SOURCE
170 )
171}
172
173fn place_data<A: Addressable>(mem: &mut A) -> Result<(), MemoryError> {
175 let (placed, _) = data_layout();
176 for (at, text) in placed {
177 for (k, byte) in text.bytes().enumerate() {
178 mem.write_tryte(
179 Word::from_value(at + k as i64),
180 Tryte::from_value(byte as i32),
181 )?;
182 }
183 mem.write_tryte(Word::from_value(at + text.len() as i64), Tryte::ZERO)?;
185 }
186 Ok(())
187}
188
189#[derive(Debug)]
191pub enum BootError {
192 Assembly(String),
194 Memory(MemoryError),
196 Trap(Trap),
198}
199
200impl std::fmt::Display for BootError {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 match self {
203 BootError::Assembly(e) => write!(f, "assembling the operating system: {e}"),
204 BootError::Memory(e) => write!(f, "writing the data segment: {e}"),
205 BootError::Trap(t) => write!(f, "the machine trapped: {t}"),
206 }
207 }
208}
209
210impl std::error::Error for BootError {}
211
212pub fn boot(image: Vec<u8>, input: &[u8], max_steps: u64) -> Result<Session, BootError> {
217 let mut bus = Bus::new(
218 Console::with_input(input.to_vec()),
219 Timer::new(),
220 BlockDevice::new(image),
221 );
222 place_data(&mut bus).map_err(BootError::Memory)?;
223
224 let mut cpu = assemble_and_load(&mut bus, Word::ZERO, &source())
225 .map_err(|e| BootError::Assembly(e.to_string()))?;
226 cpu.run(&mut bus, max_steps).map_err(BootError::Trap)?;
227
228 Ok(Session {
229 output: bus.console.output().to_vec(),
230 image: bus.block.image().to_vec(),
231 steps: cpu.steps(),
232 })
233}
234
235pub struct Session {
237 pub output: Vec<u8>,
239 pub image: Vec<u8>,
241 pub steps: u64,
243}
244
245impl Session {
246 pub fn text(&self) -> String {
248 String::from_utf8_lossy(&self.output).into_owned()
249 }
250
251 pub fn without_prompts(&self) -> String {
253 self.text().replace("$ ", "")
254 }
255}
256
257pub fn assemble() -> Result<usize, String> {
261 ternaria_isa::asm::assemble(&source())
262 .map(|p| p.instructions.len())
263 .map_err(|e| e.to_string())
264}
265
266pub fn prepare(image: Vec<u8>, input: &[u8]) -> Result<(Cpu, Bus), BootError> {
269 let mut bus = Bus::new(
270 Console::with_input(input.to_vec()),
271 Timer::new(),
272 BlockDevice::new(image),
273 );
274 place_data(&mut bus).map_err(BootError::Memory)?;
275 let cpu = assemble_and_load(&mut bus, Word::ZERO, &source())
276 .map_err(|e| BootError::Assembly(e.to_string()))?;
277 Ok((cpu, bus))
278}