Skip to main content

cambridge_asm/exec/
io.rs

1// Copyright (c) 2021 Saadi Save
2// This Source Code Form is subject to the terms of the Mozilla Public
3// License, v. 2.0. If a copy of the MPL was not distributed with this
4// file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
6use crate::{exec::RtError::*, inst};
7use std::io::{Read, Write};
8
9inst!(
10    /// No-op
11    ///
12    /// Start functions with this if you don't want to compromise readability
13    ///
14    /// # Syntax
15    /// `NOP`
16    pub nop {}
17);
18
19inst!(
20    /// End a program
21    /// Note that this is **NOT A NO-OP**. It will have effects on execution flow in code that uses functions
22    pub end (ctx) {
23        ctx.end = true;
24    }
25);
26
27inst!(
28    /// Output
29    ///
30    /// Convert an ASCII code to a character and print to STDOUT
31    ///
32    /// # Syntax
33    /// 1. `OUT` - output `ACC`
34    /// 2. `OUT [lit | reg | addr]`
35    /// 3. `OUT [lit | reg | addr], ...` - output value in all ops as bytes
36    pub out (ctx, op) {
37        match op {
38            Null => {
39                ctx.io.write.write_all(&[ctx.acc.try_into().map_err(|_| InvalidUtf8Byte(ctx.acc))?])?;
40            }
41            src if src.is_usizeable() => {
42                let src = ctx.read(src)?;
43
44                ctx.io.write.write_all(&[src.try_into().map_err(|_| InvalidUtf8Byte(src))?])?;
45            }
46            MultiOp(ops) if ops.iter().all(inst::Op::is_usizeable) => for op in ops {
47                out(ctx, op)?;
48            }
49            _ => return Err(InvalidOperand),
50        }
51    }
52);
53
54inst!(
55    /// Print bytes from memory to stdout
56    ///
57    /// # Syntax
58    /// `PRINT [addr], [n:lit]` - print `n` bytes from memory to stdout starting at address `addr`
59    #[cfg(feature = "extended")]
60    pub print (ctx, op) {
61        match op {
62            MultiOp(ops) => match &ops[..] {
63                &[ref addr, Literal(n)] if addr.is_address() => {
64                    let addr = ctx.as_address(addr)?;
65                    let mut buf = Vec::with_capacity(n);
66
67                    for address in addr..addr+n {
68                        let byte = ctx.read(&Addr(address))?;
69
70                        buf.push(byte.try_into().map_err(|_| InvalidUtf8Byte(byte))?);
71                    }
72
73                    ctx.io.write.write_all(&buf)?;
74                }
75                _ => return Err(InvalidMultiOp),
76            }
77            _ => return Err(InvalidOperand)
78        }
79    }
80);
81
82inst!(
83    /// Input
84    ///
85    /// Read a single character from stdin, convert to ASCII code and
86    /// store
87    ///
88    /// # Panics
89    /// If error is encountered when reading stdin
90    ///
91    /// # Syntax
92    /// 1. `IN` - read to `ACC`
93    /// 2. `IN [reg | addr]`
94    pub inp (ctx, op) {
95        match op {
96            Null => {
97                let mut buf = [0; 1];
98
99                ctx.io.read.read_exact(&mut buf)?;
100
101                ctx.acc = buf[0] as usize;
102            }
103            dest if dest.is_read_write() => {
104                let mut buf = [0; 1];
105
106                ctx.io.read.read_exact(&mut buf)?;
107
108                ctx.modify(dest, |d| *d = buf[0] as usize)?;
109            }
110            _ => return Err(InvalidOperand),
111        }
112    }
113);
114
115inst!(
116    /// Read n bytes into memory
117    ///
118    /// # Syntax
119    /// `READ [addr], [n:lit]` - read `n` bytes from stdin to memory starting at address `addr`
120    #[cfg(feature = "extended")]
121    pub read (ctx, op) {
122        match op {
123            MultiOp(ops) => match &ops[..] {
124                &[ref start, Literal(n)] if start.is_address() => {
125                    let start = ctx.as_address(start)?;
126
127                    let mut buf = Vec::with_capacity(n);
128
129                    ctx.io.read.read_exact(&mut buf)?;
130
131                    for (start, byte) in (start..start+n).zip(buf) {
132                        ctx.modify(&Addr(start), |d| *d = byte as usize)?;
133                    }
134                }
135                _ => return Err(InvalidMultiOp)
136            }
137            _ => return Err(InvalidOperand)
138        }
139    }
140);
141
142// Custom instruction for debug logging
143inst!(
144    /// Print debug representation
145    ///
146    /// # Syntax
147    /// 1. `DBG` - print entire execution context
148    /// 2. `DBG [lit | reg | addr]` - print value
149    /// 3. `DBG [lit | reg | addr], ...` - print value of all ops
150    #[cfg(feature = "extended")]
151    pub dbg (ctx, op) {
152        let out = match op {
153            Null => format!("{ctx:?}"),
154            src if src.is_usizeable() => format!("{}", ctx.read(src)?),
155            MultiOp(ops) if ops.iter().all(inst::Op::is_usizeable) => ops
156                .iter()
157                .filter_map(|op| ctx.read(op).ok())
158                .enumerate()
159                .fold(String::new(), |acc, (idx, op)| {
160                    if idx == ops.len() - 1 {
161                        format!("{acc}{op}")
162                    } else {
163                        format!("{acc}{op}, ")
164                    }
165                }),
166            MultiOp(_) => return Err(InvalidMultiOp),
167            _ => return Err(InvalidOperand),
168        };
169
170        writeln!(ctx.io.write, "{out}")?;
171    }
172);
173
174// Raw input - directly input integers
175inst!(
176    /// Raw input
177    /// Take integer input and store
178    ///
179    /// # Syntax
180    /// 1. `RIN` - store to `ACC`
181    /// 2. `RIN [reg | addr]`
182    #[cfg(feature = "extended")]
183    pub rin (ctx, op) {
184        use std::io::BufRead;
185        use super::RtResult;
186        const LF: u8 = 0xA;
187
188        fn input(inp: &mut impl BufRead) -> RtResult<usize> {
189            let mut buf = Vec::with_capacity(32);
190            inp.read_until(LF, &mut buf)?;
191
192            let str = String::from_utf8_lossy(&buf);
193            let str = str.trim();
194            let res = str.parse()
195                .map_err(|e| format!("Unable to parse {str:?} because {e}"))?;
196
197            Ok(res)
198        }
199
200        match op {
201            Null => ctx.acc = input(&mut ctx.io.read)?,
202            dest if dest.is_read_write() => {
203                let input = input(&mut ctx.io.read)?;
204                ctx.modify(dest, |d| *d = input)?;
205            }
206            _ => return Err(InvalidOperand),
207        }
208    }
209);
210
211inst!(
212    /// Call a function
213    ///
214    /// # Syntax
215    /// `CALL [addr]`
216    #[cfg(feature = "extended")]
217    pub call (ctx, op) {
218        match op {
219            &Addr(addr) => {
220                ctx.ret = ctx.mar + 1;
221                ctx.override_flow_control();
222                ctx.mar = addr;
223            }
224            _ => return Err(InvalidOperand),
225        }
226    }
227);
228
229inst!(
230    /// Return to address in `Ar`
231    ///
232    /// # Syntax
233    /// `RET`
234    #[cfg(feature = "extended")]
235    pub ret (ctx) {
236        ctx.override_flow_control();
237        ctx.mar = ctx.ret;
238    }
239);