Skip to main content

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}