mice/mir/stack/
debug.rs

1//! Basic debugging facilities for programs running on [`stack`](super).
2//!
3//! Breakpoints, steps, and printing information about the running program.
4use ::core::convert::TryInto;
5use ::std::collections::BTreeSet;
6
7#[derive(Debug, Copy, Clone)]
8#[allow(dead_code)]
9#[repr(u16)]
10pub(super) enum NopTag {
11    LoopStart = 0,
12    LoopArgument,
13    LoopEnd,
14    FunctionStart,
15    FunctionArgument,
16    Breakpoint,
17    Unknown,
18}
19impl NopTag {
20    #[allow(dead_code)]
21    pub(super) fn code(&self) -> u16 {
22        *self as u16
23    }
24    pub(super) fn from_code(code: u16) -> Self {
25        if code >= NopTag::Unknown as u16 {
26            NopTag::Unknown
27        } else {
28            // Safety: We've verified that `code` is in bounds of `NopTag`.
29            unsafe { ::core::mem::transmute(code) }
30        }
31    }
32}
33
34fn tagged<'a>(tag: &str, input: &'a str) -> Result<&'a str, ()> {
35    if input.starts_with(tag) {
36        Ok(&input[tag.len()..])
37    } else {
38        Err(())
39    }
40}
41
42fn parse_base_10_int(input: &str) -> Result<(&str, u64), ()> {
43    let mut cursor = input.as_bytes();
44    while let [b'0'..=b'9', rest @ ..] = cursor {
45        cursor = rest;
46    }
47    let len = cursor.as_ptr() as usize - input.as_ptr() as usize;
48    Ok((
49        &input[len..],
50        u64::from_str_radix(&input[..len], 10).unwrap(),
51    ))
52}
53
54pub(super) fn debugger_repl<R: ::rand::Rng>(machine: &mut super::Machine, rng: &mut R) {
55    let stdin = ::std::io::stdin();
56    let mut buf = String::with_capacity(1024);
57    let mut breakpoints = BTreeSet::<usize>::new();
58    while let Ok(_width) = stdin.read_line(&mut buf) {
59        if let Ok(rest) = tagged("print", &buf) {
60            if let Ok(rest) = tagged(" ", rest) {
61                if let Ok(_rest) = tagged("ip", rest) {
62                    println!("IP = {}", machine.instruction_pointer);
63                } else if let Ok(_rest) = tagged("temp", rest) {
64                    println!("Temporaries: {:?}", machine.value_stack);
65                } else if let Ok(_rest) = tagged("vars", rest) {
66                    println!("Vars: {:?}", machine.var_stack);
67                } else if let Ok(rest) = tagged("out", rest) {
68                    if let Ok(_rest) = tagged(" vars", rest) {
69                        println!("Output Vars: {:?}", machine.output_vars);
70                    } else {
71                        println!("Output: {:?}", machine.output);
72                    }
73                } else if let Ok(_rest) = tagged("fuel", rest) {
74                    println!("Fuel: {}", machine.fuel);
75                }
76            } else {
77                dbg!(&*machine);
78            }
79        } else if let Ok(rest) = tagged("fmt", &buf) {
80            if let [.., top] = &*machine.output {
81                let buf = if let Ok(_rest) = tagged(" short", rest) {
82                    machine
83                        .stack_top()
84                        .unwrap()
85                        .to_int()
86                        .map(|x| crate::mir::fmt::mbot_format_short(top, x))
87                } else {
88                    machine
89                        .stack_top()
90                        .unwrap()
91                        .to_int()
92                        .map(|x| crate::mir::fmt::mbot_format_default(top, x))
93                };
94                match buf {
95                    Ok(buf) => println!("{}", buf),
96                    Err(e) => println!("Failed to format: {:?}", e),
97                }
98            } else {
99                println!("No structured output to format.");
100            }
101        } else if let Ok(_rest) = tagged("bt", &buf) {
102            println!("Backtrace: {:?}", machine.call_stack);
103        } else if let Ok(_rest) = tagged("step", &buf) {
104            // TODO: parse a number of steps to take
105            let _ = dbg!(machine.step(rng));
106        } else if let Ok(_rest) = tagged("cont", &buf) {
107            use super::S;
108            loop {
109                let pre_ptr = machine.instruction_pointer;
110                match machine.step(rng) {
111                    Ok(S::Normal) => {
112                        if breakpoints.contains(&machine.instruction_pointer) {
113                            println!(
114                                "Reached (session) breakpoint at {}",
115                                machine.instruction_pointer
116                            );
117                            break;
118                        }
119                    }
120                    Ok(S::Break) => {
121                        println!("Reached (compiled) breakpoint at {}", pre_ptr);
122                        break;
123                    }
124                    Ok(S::Finished) => {
125                        println!("Reached end of program.");
126                        break;
127                    }
128                    Err(e) => {
129                        println!("Program aborted: {:?}", e);
130                        break;
131                    }
132                }
133            }
134        } else if let Ok(_rest) = tagged("restart", &buf) {
135            machine.restart();
136        } else if let Ok(rest) = tagged("break", &buf) {
137            if let Ok(rest) = tagged(" ", rest) {
138                match parse_base_10_int(rest) {
139                    Ok((_rest, pos)) => {
140                        if breakpoints.insert(pos.try_into().unwrap()) {
141                            println!("Set breakpoint at position {}.", pos)
142                        } else {
143                            println!("Position {} was already a breakpoint.", pos)
144                        }
145                    }
146                    Err(()) => continue,
147                }
148            }
149        } else if let Ok(rest) = tagged("fuel", &buf) {
150            if let Ok(rest) = tagged(" ", rest) {
151                match parse_base_10_int(rest) {
152                    Ok((_rest, amt)) => {
153                        machine.add_fuel(amt);
154                    }
155                    Err(()) => continue,
156                }
157            } else {
158                machine.fuel += 1;
159            }
160        } else if let Ok(_rest) = tagged("compile core", &buf) {
161            #[cfg(feature = "script")]
162            {
163                let core = super::super::misp::Core::compile();
164                dbg!(&core);
165            }
166            #[cfg(not(feature = "script"))]
167            {
168                println!("this binary was not built with Misp support");
169            }
170        } else if let Ok(_rest) = tagged("exit", &buf) {
171            return;
172        }
173
174        buf.clear();
175    }
176}