Skip to main content

cambridge_asm/parse/
mod.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
6#![allow(clippy::upper_case_acronyms)]
7
8use crate::{
9    exec::{Context, DebugInfo, ExecInst, Executor, Io, Memory, Source},
10    extend,
11    inst::InstSet,
12    inst_set,
13};
14use std::{collections::BTreeMap, fmt::Display, ops::Deref, path::Path, str::FromStr};
15
16mod lexer;
17mod parser;
18
19pub use lexer::{ErrorKind, ErrorMap, Span};
20
21inst_set! {
22    /// The core instruction set
23    ///
24    /// * Memory and register manipulation:
25    /// `LDM`, `LDD`, `LDI`, `LDX`, `LDR`, `MOV`, `STO`
26    ///
27    /// * Comparison: `CMP`, `JPE`, `JPN`, `JMP`, `CMI`
28    ///
29    /// * Basic I/O: `IN`, `OUT`, `END`
30    ///
31    /// * Arithmetic: `INC`, `DEC`, `ADD`, `SUB`
32    ///
33    /// * Bit manipulation: `AND`, `OR`, `XOR`, `LSL`, `LSR`
34    pub Core use crate::exec::{mov, cmp, io, arith, bitman}; {
35        LDM => mov::ldm,
36        LDD => mov::ldd,
37        LDI => mov::ldi,
38        LDX => mov::ldx,
39        LDR => mov::ldr,
40        MOV => mov::mov,
41        STO => mov::sto,
42
43        CMP => cmp::cmp,
44        JPE => cmp::jpe,
45        JPN => cmp::jpn,
46        JMP => cmp::jmp,
47        CMI => cmp::cmi,
48
49        IN => io::inp,
50        OUT => io::out,
51        END => io::end,
52
53        INC => arith::inc,
54        DEC => arith::dec,
55        ADD => arith::add,
56        SUB => arith::sub,
57
58        AND => bitman::and,
59        OR => bitman::or,
60        XOR => bitman::xor,
61        LSL => bitman::lsl,
62        LSR => bitman::lsr,
63    }
64}
65
66extend! {
67    /// The extended instruction set
68    ///
69    /// [`Core`], plus debugging (`DBG`), raw input (`RIN`), function `CALL` and return (`RET`), and no-op (`NOP`) instructions
70    #[cfg(feature = "extended")]
71    pub Extended extends Core use crate::exec::{io, arith::zero}; {
72        ZERO => zero,
73        DBG => io::dbg,
74        RIN => io::rin,
75        CALL => io::call,
76        RET => io::ret,
77        NOP => io::nop,
78        PRINT => io::print,
79        READ => io::read,
80    }
81}
82
83// To make docs.rs ignore the feature cfgs
84mod _default_set {
85    #[cfg(not(feature = "extended"))]
86    pub type DefaultSet = super::Core;
87
88    #[cfg(feature = "extended")]
89    pub type DefaultSet = super::Extended;
90}
91
92/// Depends on whether "extended" feature is enabled.
93///
94/// If enabled, it is `Extended`, otherwise `Core`.
95pub type DefaultSet = _default_set::DefaultSet;
96
97#[allow(clippy::type_complexity)]
98pub(crate) fn parse<T>(
99    prog: impl Deref<Target = str>,
100) -> Result<
101    (
102        BTreeMap<usize, ExecInst>,
103        BTreeMap<usize, usize>,
104        Source,
105        DebugInfo,
106    ),
107    ErrorMap,
108>
109where
110    T: InstSet,
111    <T as FromStr>::Err: Display,
112{
113    let (insts, mem, debug_info) = parser::Parser::<T>::new(&prog).parse()?;
114    let src = Source::from(prog);
115
116    let mem = mem
117        .into_iter()
118        .map(|parser::MemIr { addr, data }| (addr, data))
119        .collect();
120
121    let prog = insts
122        .into_iter()
123        .map(|parser::InstIr::<T> { addr, inst }| (addr, inst.to_exec_inst()))
124        .collect();
125
126    Ok((prog, mem, src, debug_info))
127}
128
129/// Parse a string into an [`Executor`]
130///
131/// # Arguments
132///
133/// * `T`: instruction set
134/// * `prog`: pseudo-assembly program
135/// * `io`: I/O provider, use [`make_io`]
136///
137/// returns: `Result<Executor, ErrorMap>`
138///
139/// # Example
140///
141/// ```no_run
142/// # use cambridge_asm::make_io;
143/// # use cambridge_asm::parse::{ErrorMap, DefaultSet, jit};
144///
145/// # fn foo(s: String) -> Result<(), ErrorMap> {
146/// let exec = jit::<DefaultSet>(s, make_io!())?;
147/// # Ok(())
148/// # }
149/// ```
150pub fn jit<T>(prog: impl Deref<Target = str>, io: Io) -> Result<Executor, ErrorMap>
151where
152    T: InstSet,
153    <T as FromStr>::Err: Display,
154{
155    let (prog, mem, src, debug_info) = parse::<T>(prog)?;
156
157    let exe = Executor::new(
158        src,
159        prog,
160        Context::with_io(Memory::new(mem), io),
161        debug_info,
162    );
163
164    info!("Executor created");
165    debug!(
166        "{}\n",
167        exe.display_with_opcodes::<T>()
168            .unwrap_or_else(|s| panic!("{s}"))
169    );
170    debug!("The initial context:\n{}\n", exe.ctx);
171
172    Ok(exe)
173}
174
175/// Parse a file into an [`Executor`]
176///
177/// # Arguments
178///
179/// * `T`: instruction set
180/// * `path`: path to file containing pseudo-assembly program
181/// * `io`: I/O provider, use [`make_io`]
182///
183/// returns: `Result<Executor, ErrorMap>`
184///
185/// # Example
186///
187/// ```no_run
188/// # use cambridge_asm::make_io;
189/// # use cambridge_asm::parse::{ErrorMap, DefaultSet, jit_from_file};
190///
191/// # fn foo(path: String) -> Result<(), ErrorMap> {
192/// let exec = jit_from_file::<DefaultSet>(path, make_io!())?;
193/// # Ok(())
194/// # }
195/// ```
196pub fn jit_from_file<T>(path: impl AsRef<Path>, io: Io) -> Result<Executor, ErrorMap>
197where
198    T: InstSet,
199    <T as FromStr>::Err: Display,
200{
201    let prog = std::fs::read_to_string(path).expect("Cannot read file");
202
203    info!("File read complete.");
204
205    jit::<T>(prog, io)
206}
207
208#[cfg(test)]
209mod parse_tests {
210    use crate::{
211        make_io,
212        parse::{jit, DefaultSet},
213        TestStdio, PROGRAMS,
214    };
215    use std::time::Instant;
216
217    #[test]
218    fn test() {
219        for (prog, exp, inp, out) in PROGRAMS {
220            let mut t = Instant::now();
221            let s = TestStdio::new(vec![]);
222
223            let mut exe =
224                jit::<DefaultSet>(prog, make_io!(TestStdio::new(inp), s.clone())).unwrap();
225
226            println!("Parse time: {:?}", t.elapsed());
227
228            t = Instant::now();
229
230            exe.exec::<DefaultSet>();
231
232            println!("Execution time: {:?}", t.elapsed());
233
234            assert_eq!(
235                exe.ctx.acc, exp,
236                "Expected '{}' in ACC, got '{}'",
237                exp, exe.ctx.acc
238            );
239            assert_eq!(
240                s.to_vec(),
241                out,
242                "Expected '{}' in output, got '{}'",
243                String::from_utf8_lossy(out),
244                s.try_to_string().unwrap()
245            );
246        }
247    }
248
249    #[test]
250    #[should_panic(
251        expected = "called `Result::unwrap()` on an `Err` value: {4..7: ParseIntError(ParseIntError { kind: InvalidDigit })}"
252    )]
253    fn panics() {
254        let mut exec = jit::<DefaultSet>(
255            include_str!("../../examples/panics.pasm"),
256            make_io!(std::io::stdin(), std::io::sink()),
257        )
258        .unwrap();
259        exec.exec::<DefaultSet>();
260    }
261}