1use std::io::{BufRead, Write};
20
21use crate::source;
22use ternaria_arith::{Tryte, Word};
23use ternaria_dev::{BlockDevice, Bus, Console, Timer};
24use ternaria_fs::Builder;
25use ternaria_mem::Addressable;
26use ternaria_vm::assemble_and_load;
27
28const DISK_BLOCKS: usize = 243;
30
31const STEP_LIMIT: u64 = 2_000_000_000;
36
37pub fn main() {
39 let mut args = std::env::args().skip(1);
40 let mut format = false;
41 let mut path: Option<String> = None;
42 for arg in args.by_ref() {
43 match arg.as_str() {
44 "--format" | "-f" => format = true,
45 "--help" | "-h" => {
46 print_help();
47 return;
48 }
49 other => path = Some(other.to_string()),
50 }
51 }
52
53 let image = match load_image(path.as_deref(), format) {
54 Ok(image) => image,
55 Err(e) => {
56 eprintln!("ternaria: {e}");
57 std::process::exit(1);
58 }
59 };
60
61 match run(image, path.as_deref()) {
62 Ok(()) => {}
63 Err(e) => {
64 eprintln!("ternaria: {e}");
65 std::process::exit(1);
66 }
67 }
68}
69
70fn print_help() {
72 println!("ternaria - a balanced ternary machine running a small operating system");
73 println!();
74 println!(" cargo run boot a fresh disk held in memory");
75 println!(" cargo run -- <image> boot <image>, creating it if absent");
76 println!(" cargo run -- --format <image>");
77 println!(" overwrite <image> with a fresh disk");
78 println!();
79 println!("Commands inside the machine:");
80 println!(" ls list the working directory");
81 println!(" cd <name> enter a directory");
82 println!(" cat <name> print a file");
83 println!(" mkdir <name> create a directory");
84 println!(" touch <name> create an empty file");
85 println!(" write <name> create a file from the lines that follow,");
86 println!(" ending with a line holding only a full stop");
87 println!(" rm <name> remove a file or an empty directory");
88 println!();
89 println!(" run <name> load and run a program, at user level");
90 println!(" exit, logout leave the machine");
91 println!();
92 println!("An image given on the command line is written back when the shell");
93 println!("exits, whether through `exit` or Ctrl-D. Interrupting with Ctrl-C");
94 println!("kills the host before it can write, and loses the session.");
95}
96
97fn load_image(path: Option<&str>, format: bool) -> std::io::Result<Vec<u8>> {
99 match path {
100 Some(p) if !format && std::path::Path::new(p).exists() => std::fs::read(p),
101 _ => Ok(fresh_disk()),
102 }
103}
104
105fn fresh_disk() -> Vec<u8> {
107 let mut b = Builder::new(DISK_BLOCKS);
108 b.write_file(
109 "/readme",
110 b"This machine computes in base three.\n\
111 Digits are -1, 0 and +1.\n\
112 \n\
113 Try: ls, cat readme, mkdir work, cd work, write notes\n",
114 )
115 .expect("the fresh disk fits");
116 b.mkdir("/docs").expect("mkdir");
117 b.write_file(
118 "/docs/trits",
119 b"A trit holds -1, 0 or +1.\nNine trits are a tryte.\n",
120 )
121 .expect("write");
122 b.finish()
123}
124
125fn run(image: Vec<u8>, save_to: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
127 let mut bus = Bus::new(Console::new(), Timer::new(), BlockDevice::new(image));
128
129 place_data(&mut bus)?;
132 let mut cpu = assemble_and_load(&mut bus, Word::ZERO, &source())?;
133
134 let stdin = std::io::stdin();
135 let mut lines = stdin.lock().lines();
136 let mut emitted = 0usize;
137 let mut stopped: Option<String> = None;
138
139 loop {
140 match cpu.step(&mut bus) {
143 Ok(true) => {}
144 Ok(false) => break,
145 Err(trap) => {
146 stopped = Some(trap.to_string());
147 break;
148 }
149 }
150 bus.tick();
151
152 let out = bus.console.output();
156 if out.len() > emitted {
157 let mut stdout = std::io::stdout();
158 stdout.write_all(&out[emitted..])?;
159 stdout.flush()?;
160 emitted = out.len();
161 }
162
163 if bus.console.take_starved() {
167 match lines.next() {
168 Some(line) => {
169 let mut line = line?;
170 line.push('\n');
171 bus.console.push_input(line.as_bytes());
172 }
173 None => {
174 println!();
177 bus.console.close();
178 }
179 }
180 }
181
182 if cpu.steps() > STEP_LIMIT {
183 stopped = Some("step limit reached".to_string());
184 break;
185 }
186 }
187
188 let out = bus.console.output();
190 if out.len() > emitted {
191 let mut stdout = std::io::stdout();
192 stdout.write_all(&out[emitted..])?;
193 stdout.flush()?;
194 }
195
196 if let Some(p) = save_to {
197 std::fs::write(p, bus.block.image())?;
198 eprintln!("ternaria: wrote {p}");
199 }
200 if let Some(why) = stopped {
201 eprintln!("ternaria: {why}");
202 }
203 Ok(())
204}
205
206fn place_data(bus: &mut Bus) -> Result<(), ternaria_mem::MemoryError> {
208 for (at, text) in crate::data_placement() {
209 for (k, byte) in text.bytes().enumerate() {
210 bus.write_tryte(
211 Word::from_value(at + k as i64),
212 Tryte::from_value(byte as i32),
213 )?;
214 }
215 bus.write_tryte(Word::from_value(at + text.len() as i64), Tryte::ZERO)?;
216 }
217 Ok(())
218}