ternaria_dev/console.rs
1//! A character console.
2//!
3//! | Offset | Register | Behaviour |
4//! |--------|----------|-----------|
5//! | 0 | `DATA` | Write emits a byte. Read consumes one input byte, or -1 if the queue is empty. |
6//! | 1 | `STATUS` | 1 input waiting, 0 none yet, -1 end of input. |
7//!
8//! # A status with three values
9//!
10//! A binary console's status register answers one question: is a character
11//! ready? A driver that gets "no" cannot tell whether to wait or to give up,
12//! and every such system grows a second mechanism to say which.
13//!
14//! One trit answers both. Positive means a character is waiting. Zero means
15//! none is waiting yet and more may come, so a driver should wait. Negative
16//! means the input has ended and none ever will, so a driver should stop.
17//!
18//! An interactive host leaves the console open and the guest waits; a batch
19//! host closes it after queueing its input and the guest terminates on its
20//! own. Neither needs a convention outside the register.
21
22use crate::{Device, unmapped};
23use std::collections::VecDeque;
24use ternaria_arith::Tryte;
25use ternaria_mem::{MemoryError, bytes::tryte_to_byte};
26
27/// Offset of the data register.
28pub const DATA: i64 = 0;
29/// Offset of the status register.
30pub const STATUS: i64 = 1;
31
32/// A console with a byte output buffer and a byte input queue.
33#[derive(Default)]
34pub struct Console {
35 output: Vec<u8>,
36 input: VecDeque<u8>,
37 closed: bool,
38 starved: bool,
39}
40
41impl Console {
42 /// A console with no pending input.
43 pub fn new() -> Console {
44 Console::default()
45 }
46
47 /// A console preloaded with input, and closed.
48 ///
49 /// Closed because a caller supplying all its input at once has no more to
50 /// give, and a guest that waited for more would wait forever.
51 pub fn with_input(input: impl Into<Vec<u8>>) -> Console {
52 Console {
53 output: Vec::new(),
54 input: VecDeque::from(input.into()),
55 closed: true,
56 starved: false,
57 }
58 }
59
60 /// Everything written to the console so far.
61 pub fn output(&self) -> &[u8] {
62 &self.output
63 }
64
65 /// The output interpreted as UTF-8, replacing invalid sequences.
66 pub fn output_string(&self) -> String {
67 String::from_utf8_lossy(&self.output).into_owned()
68 }
69
70 /// True while input is waiting. Also the console's interrupt condition.
71 pub fn has_input(&self) -> bool {
72 !self.input.is_empty()
73 }
74
75 /// Declares that no further input will arrive.
76 ///
77 /// The status register then reads negative once the queue drains, which is
78 /// how a guest learns to stop rather than wait.
79 pub fn close(&mut self) {
80 self.closed = true;
81 }
82
83 /// True if the guest has read the status and found nothing waiting.
84 ///
85 /// An interactive host polls this to know when the guest is waiting on a
86 /// person, and reads a line only then. Reading the flag clears it.
87 pub fn take_starved(&mut self) -> bool {
88 std::mem::take(&mut self.starved)
89 }
90
91 /// Queues input bytes for the guest to read.
92 pub fn push_input(&mut self, bytes: &[u8]) {
93 self.input.extend(bytes);
94 }
95}
96
97impl Device for Console {
98 fn name(&self) -> &'static str {
99 "console"
100 }
101
102 fn read(&mut self, offset: i64) -> Result<Tryte, MemoryError> {
103 match offset {
104 // Reading DATA consumes a byte, which is why device reads take
105 // &mut self.
106 DATA => Ok(Tryte::from_value(match self.input.pop_front() {
107 Some(b) => b as i32,
108 None => -1,
109 })),
110 STATUS => {
111 if !self.input.is_empty() {
112 return Ok(Tryte::from_value(1));
113 }
114 // Nothing waiting. Whether the guest should wait or stop is
115 // the difference between an open console and a closed one.
116 self.starved = true;
117 Ok(Tryte::from_value(if self.closed { -1 } else { 0 }))
118 }
119 _ => Err(unmapped(offset)),
120 }
121 }
122
123 fn write(&mut self, offset: i64, value: Tryte) -> Result<(), MemoryError> {
124 match offset {
125 DATA => {
126 // Values outside 0..=255 are rejected rather than truncated
127 // (D-04).
128 let b = tryte_to_byte(value).ok_or(MemoryError::NotAByte {
129 value: value.value() as i64,
130 })?;
131 self.output.push(b);
132 Ok(())
133 }
134 STATUS => Err(MemoryError::Device {
135 addr: offset,
136 reason: "console status is read-only",
137 }),
138 _ => Err(unmapped(offset)),
139 }
140 }
141}