1128 lines
40 KiB
Rust
1128 lines
40 KiB
Rust
//! Utilities for presenting a common debug interface
|
|
|
|
pub mod bus;
|
|
|
|
pub mod disassembler;
|
|
|
|
pub mod cli {
|
|
//! Command line interface for boy-debug
|
|
|
|
pub mod token {
|
|
//! Tokens for the [Lexer](super::lexer::Lexer) to emit
|
|
|
|
pub use boy::cpu::disasm::{R16, R8};
|
|
use std::fmt::Display;
|
|
|
|
/// A unit of lexical information
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub enum Token {
|
|
Op(Op),
|
|
Num(usize),
|
|
Bool(bool),
|
|
Reg(Reg),
|
|
Ident(String),
|
|
Other(char),
|
|
}
|
|
|
|
impl Display for Token {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Token::Op(op) => write!(f, "{op}"),
|
|
Token::Num(num) => num.fmt(f),
|
|
Token::Bool(b) => b.fmt(f),
|
|
Token::Reg(reg) => write!(f, "{reg}"),
|
|
Token::Ident(s) => s.fmt(f),
|
|
Token::Other(c) => write!(f, "'{c}' (Invalid)"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An arithmetic or bitwise operator
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub enum Op {
|
|
/// `*`: Multiplication
|
|
Mul,
|
|
/// `/`: Division
|
|
Div,
|
|
/// `%`: Remainder
|
|
Rem,
|
|
/// `+`: Addition
|
|
Add,
|
|
/// `-`: Subtraction or Negation
|
|
Sub,
|
|
/// `>>`: Shift Right
|
|
Shr,
|
|
/// `<<`: Shift Left
|
|
Shl,
|
|
/// `&`: Bitwise And
|
|
And,
|
|
/// `|`: Bitwise Or
|
|
Or,
|
|
/// `^`: Bitwise Xor
|
|
Xor,
|
|
/// `!`: Bitwise Not
|
|
Bang,
|
|
/// `..`: Range
|
|
Range,
|
|
/// `(`: Open parenthesis
|
|
ParenOpen,
|
|
/// `)`: Close parenthesis
|
|
ParenClose,
|
|
/// `[`: Open brackets
|
|
BrackOpen,
|
|
/// `]`: Close brackets
|
|
BrackClose,
|
|
/// ',': Comma
|
|
Comma,
|
|
/// `=`: Equals Sign
|
|
Eq,
|
|
}
|
|
impl Display for Op {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Op::Mul => "*",
|
|
Op::Div => "/",
|
|
Op::Rem => "%",
|
|
Op::Add => "+",
|
|
Op::Sub => "-",
|
|
Op::Shr => ">>",
|
|
Op::Shl => "<<",
|
|
Op::And => "&",
|
|
Op::Or => "|",
|
|
Op::Xor => "^",
|
|
Op::Bang => "!",
|
|
Op::Range => "..",
|
|
Op::ParenOpen => "(",
|
|
Op::ParenClose => ")",
|
|
Op::BrackOpen => "[",
|
|
Op::BrackClose => "]",
|
|
Op::Comma => ",",
|
|
Op::Eq => "=",
|
|
}
|
|
.fmt(f)
|
|
}
|
|
}
|
|
|
|
/// Describes an abstract CPU register
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub enum Reg {
|
|
A,
|
|
F,
|
|
B,
|
|
C,
|
|
D,
|
|
E,
|
|
H,
|
|
L,
|
|
AF,
|
|
BC,
|
|
DE,
|
|
HL,
|
|
SP,
|
|
PC,
|
|
}
|
|
|
|
impl Display for Reg {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Reg::A => write!(f, "a"),
|
|
Reg::F => write!(f, "f"),
|
|
Reg::B => write!(f, "b"),
|
|
Reg::C => write!(f, "c"),
|
|
Reg::D => write!(f, "d"),
|
|
Reg::E => write!(f, "e"),
|
|
Reg::H => write!(f, "h"),
|
|
Reg::L => write!(f, "l"),
|
|
Reg::AF => write!(f, "af"),
|
|
Reg::BC => write!(f, "bc"),
|
|
Reg::DE => write!(f, "de"),
|
|
Reg::HL => write!(f, "hl"),
|
|
Reg::SP => write!(f, "sp"),
|
|
Reg::PC => write!(f, "pc"),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub mod lexer {
|
|
//! A [Lexer] lexically analyzes a stream of utf-8 characters, producing a stream of tokens
|
|
use super::token::{
|
|
Op::*,
|
|
Reg,
|
|
Token::{self, *},
|
|
};
|
|
use std::iter::Peekable;
|
|
|
|
pub trait Lexible: Iterator<Item = char> {
|
|
/// Constructs a [`Lexer`] from Self
|
|
fn lex(self) -> Lexer<Self>
|
|
where
|
|
Self: Sized;
|
|
}
|
|
|
|
impl<T: Iterator<Item = char>> Lexible for T {
|
|
fn lex(self) -> Lexer<Self> {
|
|
Lexer {
|
|
text: self.peekable(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Lexer<I: Iterator<Item = char>> {
|
|
text: Peekable<I>,
|
|
}
|
|
|
|
impl<I: Iterator<Item = char>> Lexer<I> {
|
|
fn sync(&mut self) -> &mut Self {
|
|
while self.peek().map(|c| c.is_whitespace()).unwrap_or(false) {
|
|
self.take();
|
|
}
|
|
self
|
|
}
|
|
fn peek(&mut self) -> Option<&char> {
|
|
self.text.peek()
|
|
}
|
|
fn take(&mut self) -> &mut Self {
|
|
self.text.next();
|
|
self
|
|
}
|
|
fn emit(&mut self, kind: Token) -> Option<Token> {
|
|
Some(kind)
|
|
}
|
|
fn digits<const B: u32>(&mut self) -> Option<Token> {
|
|
let mut out = 0;
|
|
while let Some(Some(next)) = self.peek().map(|c| c.to_digit(B)) {
|
|
self.take();
|
|
out = (out * B as usize) + next as usize;
|
|
}
|
|
Some(Num(out))
|
|
}
|
|
fn base(&mut self) -> Option<Token> {
|
|
match self.peek() {
|
|
Some('b') => self.take().digits::<2>(),
|
|
Some('d') => self.take().digits::<10>(),
|
|
Some('o') => self.take().digits::<8>(),
|
|
Some('x') => self.take().digits::<16>(),
|
|
_ => self.digits::<16>(),
|
|
}
|
|
}
|
|
fn ident(&mut self) -> Option<Token> {
|
|
let mut out = String::new();
|
|
while let Some(&c) = self.peek().filter(|&&c| c.is_alphanumeric() || c == '_') {
|
|
self.take();
|
|
out.push(c)
|
|
}
|
|
// Match keyword
|
|
Some(match out.as_str() {
|
|
"false" | "off" => Token::Bool(false),
|
|
"true" | "on" => Token::Bool(true),
|
|
|
|
"rA" | "a" => Token::Reg(Reg::A),
|
|
"rF" | "f" => Token::Reg(Reg::F),
|
|
"rB" | "b" => Token::Reg(Reg::B),
|
|
"rC" | "c" => Token::Reg(Reg::C),
|
|
"rD" | "d" => Token::Reg(Reg::D),
|
|
"rE" | "e" => Token::Reg(Reg::E),
|
|
"rH" | "h" => Token::Reg(Reg::H),
|
|
"rL" | "l" => Token::Reg(Reg::L),
|
|
|
|
"rAF" | "af" => Token::Reg(Reg::AF),
|
|
"rBC" | "bc" => Token::Reg(Reg::BC),
|
|
"rDE" | "de" => Token::Reg(Reg::DE),
|
|
"rHL" | "hl" => Token::Reg(Reg::HL),
|
|
"rSP" | "sp" => Token::Reg(Reg::SP),
|
|
"rPC" | "pc" => Token::Reg(Reg::PC),
|
|
_ => match usize::from_str_radix(&out, 16) {
|
|
Ok(n) => Token::Num(n),
|
|
Err(_) => Token::Ident(out),
|
|
},
|
|
})
|
|
}
|
|
/// Checks the following character, producing the provided [Token]
|
|
/// if it matches the expected [char]. Otherwise, prodices [Token::Other].
|
|
fn chain(&mut self, expect: char, becomes: Token) -> Option<Token> {
|
|
match self.peek() {
|
|
Some(c) if *c == expect => self.take().emit(becomes),
|
|
_ => self.emit(Token::Other(expect)),
|
|
}
|
|
}
|
|
|
|
fn range(&mut self) -> Option<Token> {
|
|
match self.peek() {
|
|
Some('.') => self.take(),
|
|
_ => self,
|
|
}
|
|
.emit(Token::Op(Range))
|
|
}
|
|
}
|
|
impl<I: Iterator<Item = char>> Iterator for Lexer<I> {
|
|
type Item = Token;
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
match self.sync().peek()? {
|
|
// '{' => self.take().emit(Op(CurlyOpen)),
|
|
'[' => self.take().emit(Op(BrackOpen)),
|
|
'(' => self.take().emit(Op(ParenOpen)),
|
|
// '}' => self.take().emit(Op(CurlyClose)),
|
|
']' => self.take().emit(Op(BrackClose)),
|
|
')' => self.take().emit(Op(ParenClose)),
|
|
|
|
'*' => self.take().emit(Op(Mul)),
|
|
'/' => self.take().emit(Op(Div)),
|
|
'%' => self.take().emit(Op(Rem)),
|
|
'+' => self.take().emit(Op(Add)),
|
|
'-' => self.take().emit(Op(Sub)),
|
|
'&' => self.take().emit(Op(And)),
|
|
'|' => self.take().emit(Op(Or)),
|
|
'^' => self.take().emit(Op(Xor)),
|
|
',' => self.take().emit(Op(Comma)),
|
|
'=' => self.take().emit(Op(Eq)),
|
|
'>' => self.take().chain('>', Op(Shr)),
|
|
'<' => self.take().chain('<', Op(Shl)),
|
|
'.' => self.take().range(), // special handling allows for one or two dots
|
|
|
|
'?' => self.take().emit(Token::Ident("regs".into())),
|
|
'$' | '#' => self.take().digits::<16>(),
|
|
'0' => self.take().base(),
|
|
'1'..='9' | 'A'..='F' => self.digits::<16>(),
|
|
c if c.is_alphabetic() => self.ident(),
|
|
|
|
&other => self.take().emit(Other(other)),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub mod parser {
|
|
//! A [Parser] parses a sequence of Tokens and produces a sequence of [Request]
|
|
use super::token::Token;
|
|
use crate::ast::*;
|
|
use crate::message::Request;
|
|
use std::iter::Peekable;
|
|
|
|
pub trait Parsible: Iterator<Item = Token> + Sized {
|
|
fn parse(self) -> Parser<Self>;
|
|
}
|
|
impl<T: Iterator<Item = Token> + Sized> Parsible for T {
|
|
fn parse(self) -> Parser<Self> {
|
|
Parser::new(self)
|
|
}
|
|
}
|
|
|
|
pub struct Parser<T: Iterator<Item = Token>> {
|
|
lexer: Peekable<T>,
|
|
}
|
|
|
|
impl<T: Iterator<Item = Token>> Parser<T> {
|
|
pub fn new(t: T) -> Self {
|
|
Self {
|
|
lexer: t.peekable(),
|
|
}
|
|
}
|
|
#[must_use]
|
|
fn peek(&mut self) -> Option<&Token> {
|
|
self.lexer.peek()
|
|
}
|
|
fn take(&mut self) -> Option<Token> {
|
|
self.lexer.next()
|
|
}
|
|
#[must_use]
|
|
fn then(&mut self, tok: Token) -> Option<&mut Self> {
|
|
(*self.peek()? == tok).then(|| {
|
|
self.take();
|
|
self
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<T: Iterator<Item = Token>> Iterator for Parser<T> {
|
|
type Item = Request;
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
// match self.peek()? {
|
|
// Token::Op(_) => todo!(),
|
|
// Token::Num(_) => todo!(),
|
|
// Token::Bool(_) => todo!(),
|
|
// Token::Reg(_) => todo!(),
|
|
// Token::Ident(_) => todo!(),
|
|
// Token::Other(_) => todo!(),
|
|
// }
|
|
Some(Request::Do(Stmt::parse(self)?))
|
|
}
|
|
}
|
|
|
|
impl Stmt {
|
|
pub fn parse<I: Iterator<Item = Token>>(lexer: &mut Parser<I>) -> Option<Stmt> {
|
|
let verb = Verb::parse(lexer)?;
|
|
let [dst, src] = match verb {
|
|
Verb::Set => [
|
|
Expr::parse(lexer, 0)?,
|
|
Expr::parse(lexer.then(Token::Op(Op::Comma))?, 0)?,
|
|
],
|
|
Verb::Get
|
|
| Verb::Run
|
|
| Verb::Break
|
|
| Verb::Unbreak
|
|
| Verb::Eval
|
|
| Verb::Disasm => [Expr::parse(lexer, 0)?, Expr::default()],
|
|
Verb::DumpRegs | Verb::DumpCart | Verb::Trace | Verb::Reset | Verb::Help => {
|
|
[Expr::default(), Expr::default()]
|
|
}
|
|
};
|
|
Some(Stmt { verb, dst, src })
|
|
}
|
|
}
|
|
|
|
impl Verb {
|
|
pub fn parse<I: Iterator<Item = Token>>(lexer: &mut Parser<I>) -> Option<Verb> {
|
|
let Some(Token::Ident(id)) = (match lexer.peek()? {
|
|
Token::Ident(_) => lexer.take(),
|
|
Token::Op(Op::Eq) => {
|
|
lexer.take();
|
|
return Some(Verb::Eval);
|
|
}
|
|
_ => return Some(Verb::Get),
|
|
}) else {
|
|
unreachable!()
|
|
};
|
|
Some(match id.as_str() {
|
|
"set" | "write" => Verb::Set,
|
|
"get" | "read" | "dump" => Verb::Get,
|
|
"run" | "step" => Verb::Run,
|
|
"br" | "break" => Verb::Break,
|
|
"ub" | "unbreak" => Verb::Unbreak,
|
|
"di" | "dis" => Verb::Disasm,
|
|
"eval" => Verb::Eval,
|
|
"regs" => Verb::DumpRegs,
|
|
"cart" => Verb::DumpCart,
|
|
"trace" => Verb::Trace,
|
|
"reset" => Verb::Reset,
|
|
"help" => Verb::Help,
|
|
_ => None?,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Expr {
|
|
pub fn parse<I: Iterator<Item = Token>>(p: &mut Parser<I>, level: u8) -> Option<Self> {
|
|
let mut head = match p.take()? {
|
|
Token::Reg(r) => Expr::Reg(r),
|
|
Token::Num(n) => Expr::Int(n as _),
|
|
Token::Bool(b) => Expr::Bool(b),
|
|
Token::Op(Op::ParenOpen) => {
|
|
let head = Expr::parse(p, 0)?;
|
|
if p.then(Token::Op(Op::ParenClose)).is_none() {
|
|
println!("Unterminated parentheses!")
|
|
}
|
|
head
|
|
}
|
|
Token::Op(Op::BrackOpen) => {
|
|
let mut exprs = vec![];
|
|
while p.then(Token::Op(Op::BrackClose)).is_none() {
|
|
exprs.push(Expr::parse(p, 0)?);
|
|
if p.then(Token::Op(Op::Comma)).is_none() {
|
|
break;
|
|
}
|
|
}
|
|
if p.then(Token::Op(Op::BrackClose)).is_none() {
|
|
println!("Unterminated brackets in Array!");
|
|
return None;
|
|
};
|
|
Expr::Array(exprs)
|
|
}
|
|
Token::Op(Op::Mul) => {
|
|
let after = Op::Mul.prefix()?.after();
|
|
Expr::Index(Box::new(Expr::parse(p, after)?), Box::new(Expr::Int(0)))
|
|
}
|
|
Token::Op(op) => {
|
|
let after = op.prefix()?.after();
|
|
Expr::Prefix(op, Box::new(Expr::parse(p, after)?))
|
|
}
|
|
tok => {
|
|
println!("Unexpected token: {tok}");
|
|
None?
|
|
}
|
|
};
|
|
|
|
while let Some(&Token::Op(op)) = p.peek() {
|
|
if let Some(prec) = op.postfix() {
|
|
if prec.before() < level {
|
|
break;
|
|
}
|
|
p.take();
|
|
head = match op {
|
|
Op::BrackOpen => {
|
|
let tail = Expr::parse(p, 0)?;
|
|
if p.then(Token::Op(Op::BrackClose)).is_none() {
|
|
println!("Unterminated brackets in Index!");
|
|
return None;
|
|
};
|
|
Expr::Index(Box::new(head), Box::new(tail))
|
|
}
|
|
op => Expr::Postfix(op, Box::new(head)),
|
|
};
|
|
continue;
|
|
}
|
|
if let Some(prec) = op.infix() {
|
|
if prec.before() < level {
|
|
break;
|
|
}
|
|
p.take();
|
|
let tail = Expr::parse(p, prec.after())?;
|
|
head = match op {
|
|
Op::Range => Expr::Range(head.into(), tail.into()),
|
|
_ => Expr::Binary(op, Box::new(head), Box::new(tail)),
|
|
};
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
Some(head)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub enum Level {
|
|
Postfix,
|
|
Range,
|
|
Array,
|
|
Bit,
|
|
Factor,
|
|
Shift,
|
|
Term,
|
|
Sign,
|
|
}
|
|
impl Level {
|
|
fn level(self) -> u8 {
|
|
(self as u8) << 1
|
|
}
|
|
pub fn before(self) -> u8 {
|
|
self.level()
|
|
}
|
|
pub fn after(self) -> u8 {
|
|
self.level() + 1
|
|
}
|
|
}
|
|
|
|
pub trait Precedence {
|
|
fn prefix(&self) -> Option<Level>;
|
|
fn infix(&self) -> Option<Level>;
|
|
fn postfix(&self) -> Option<Level>;
|
|
}
|
|
|
|
impl Precedence for Op {
|
|
fn prefix(&self) -> Option<Level> {
|
|
match self {
|
|
Op::Mul | Op::Add | Op::Sub | Op::Bang => Some(Level::Sign),
|
|
Op::Range => Some(Level::Range),
|
|
Op::BrackOpen => Some(Level::Array),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn infix(&self) -> Option<Level> {
|
|
match self {
|
|
Op::Mul | Op::Div | Op::Rem => Some(Level::Term),
|
|
Op::Add | Op::Sub => Some(Level::Factor),
|
|
Op::Shr | Op::Shl => Some(Level::Shift),
|
|
Op::And | Op::Or | Op::Xor => Some(Level::Bit),
|
|
Op::Bang => None,
|
|
Op::Range => Some(Level::Range),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn postfix(&self) -> Option<Level> {
|
|
match self {
|
|
Op::BrackOpen => Some(Level::Postfix),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub mod ast {
|
|
pub use crate::cli::token::{Op, Reg};
|
|
use std::{fmt::Display, mem::take};
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct Stmt {
|
|
pub verb: Verb,
|
|
pub dst: Expr,
|
|
pub src: Expr,
|
|
}
|
|
|
|
impl Stmt {
|
|
pub fn visit(&mut self, f: &impl Fn(&mut Expr)) {
|
|
self.dst.visit(f);
|
|
self.src.visit(f);
|
|
}
|
|
|
|
/// Solves the statement arguments using the provided solver
|
|
pub fn solve(&mut self, solver: &impl Fn(&mut Expr) -> Option<Expr>) -> &mut Self {
|
|
self.visit(&|expr| {
|
|
expr.solve(solver);
|
|
});
|
|
self
|
|
}
|
|
}
|
|
impl Display for Stmt {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let Self { verb, dst, src } = self;
|
|
match verb {
|
|
Verb::Set => write!(f, "{verb} {dst}, {src}"),
|
|
Verb::DumpRegs | Verb::DumpCart | Verb::Trace | Verb::Reset | Verb::Help => {
|
|
write!(f, "{verb}")
|
|
}
|
|
_ => write!(f, "{verb} {dst}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub enum Verb {
|
|
Set,
|
|
Get,
|
|
Run,
|
|
Break,
|
|
Unbreak,
|
|
Eval,
|
|
Disasm,
|
|
DumpRegs,
|
|
DumpCart,
|
|
Trace,
|
|
Reset,
|
|
Help,
|
|
}
|
|
|
|
impl Display for Verb {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Verb::Set => "set",
|
|
Verb::Get => "get",
|
|
Verb::Run => "run",
|
|
Verb::Break => "break",
|
|
Verb::Unbreak => "unbreak",
|
|
Verb::Eval => "eval",
|
|
Verb::Disasm => "dis",
|
|
Verb::DumpRegs => "regs",
|
|
Verb::DumpCart => "cart",
|
|
Verb::Trace => "trace",
|
|
Verb::Reset => "reset",
|
|
Verb::Help => "help",
|
|
}
|
|
.fmt(f)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub enum Expr {
|
|
Int(isize),
|
|
Bool(bool),
|
|
Reg(Reg),
|
|
Prefix(Op, Box<Expr>),
|
|
Binary(Op, Box<Expr>, Box<Expr>),
|
|
Postfix(Op, Box<Expr>),
|
|
Array(Vec<Expr>),
|
|
Index(Box<Expr>, Box<Expr>),
|
|
Range(Box<Expr>, Box<Expr>),
|
|
}
|
|
|
|
impl Expr {
|
|
/// Traverses the expression depth-first, calling function f() on every subexpression
|
|
pub fn visit(&mut self, f: &impl Fn(&mut Expr)) {
|
|
match self {
|
|
Expr::Int(_) | Expr::Bool(_) | Expr::Reg(_) => {}
|
|
Expr::Array(v) => v.iter_mut().for_each(|e| e.visit(f)),
|
|
Expr::Prefix(_, e) | Expr::Postfix(_, e) => e.visit(f),
|
|
Expr::Binary(_, a, b) | Expr::Range(a, b) | Expr::Index(a, b) => {
|
|
[a, b].into_iter().for_each(|e| e.visit(f))
|
|
}
|
|
}
|
|
f(self)
|
|
}
|
|
|
|
/// Replaces the expression with a solution produced by the solver.
|
|
pub fn solve(&mut self, solver: impl Fn(&mut Expr) -> Option<Expr>) -> &mut Self {
|
|
if let Some(value) = solver(self) {
|
|
*self = value;
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Gets the value of an [integer expression](Expr::Int)
|
|
pub fn int(&self) -> Option<isize> {
|
|
match self {
|
|
&Expr::Int(i) => Some(i),
|
|
_ => None,
|
|
}
|
|
}
|
|
/// Gets the value of a [bool expression](Expr::Bool)
|
|
pub fn bool(&self) -> Option<bool> {
|
|
match self {
|
|
&Expr::Bool(b) => Some(b),
|
|
_ => None,
|
|
}
|
|
}
|
|
/// Gets the value of a [register expression](Expr::Reg)
|
|
pub fn reg(&self) -> Option<Reg> {
|
|
match self {
|
|
&Expr::Reg(r) => Some(r),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn int_range(&self) -> Option<std::ops::Range<isize>> {
|
|
match self {
|
|
Expr::Range(start, end) => Some(start.int()?..end.int()?),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Attempts to solve a top-level expression using basic arithmetic rules
|
|
pub fn eval_one(&mut self) -> Option<Expr> {
|
|
match self {
|
|
Expr::Int(_) | Expr::Bool(_) | Expr::Reg(_) => None,
|
|
Expr::Array(_) | Expr::Range(_, _) => None,
|
|
Expr::Prefix(op, e) => e.eval_prefix(*op),
|
|
Expr::Binary(op, lhs, rhs) => lhs.eval_binary(*op, rhs),
|
|
Expr::Postfix(_, _) => todo!("Postfix expression: {self}"),
|
|
Expr::Index(a, idx) => a.eval_index(idx),
|
|
}
|
|
}
|
|
|
|
fn eval_prefix(&self, op: Op) -> Option<Expr> {
|
|
Some(match (op, self) {
|
|
(Op::Add, Expr::Int(v)) => Expr::Int(*v),
|
|
(Op::Sub, Expr::Int(v)) => Expr::Int(-v),
|
|
(Op::Bang, Expr::Int(v)) => Expr::Int(!v),
|
|
(Op::Bang, Expr::Bool(v)) => Expr::Bool(!v),
|
|
_ => return None,
|
|
})
|
|
}
|
|
|
|
fn eval_binary(&self, op: Op, rhs: &Self) -> Option<Expr> {
|
|
use Expr::*;
|
|
Some(match (op, self, rhs) {
|
|
(Op::Mul, Int(lhs), Int(rhs)) => Int(lhs * rhs),
|
|
(Op::Div, Int(lhs), Int(rhs)) => Int(lhs / rhs),
|
|
(Op::Rem, Int(lhs), Int(rhs)) => Int(lhs % rhs),
|
|
(Op::Add, Int(lhs), Int(rhs)) => Int(lhs + rhs),
|
|
(Op::Sub, Int(lhs), Int(rhs)) => Int(lhs - rhs),
|
|
(Op::Shr, Int(lhs), Int(rhs)) => Int(lhs >> rhs),
|
|
(Op::Shl, Int(lhs), Int(rhs)) => Int(lhs << rhs),
|
|
(Op::And, Int(lhs), Int(rhs)) => Int(lhs & rhs),
|
|
(Op::And, Bool(lhs), Bool(rhs)) => Bool(lhs & rhs),
|
|
(Op::Or, Int(lhs), Int(rhs)) => Int(lhs | rhs),
|
|
(Op::Or, Bool(lhs), Bool(rhs)) => Bool(lhs | rhs),
|
|
(Op::Xor, Int(lhs), Int(rhs)) => Int(lhs ^ rhs),
|
|
(Op::Xor, Bool(lhs), Bool(rhs)) => Bool(lhs ^ rhs),
|
|
_ => return None,
|
|
})
|
|
}
|
|
/// Evaluates an index expression: a[10]
|
|
fn eval_index(&mut self, idx: &Self) -> Option<Expr> {
|
|
match (self, idx) {
|
|
(Expr::Array(a), &Expr::Int(idx)) if a.len() > idx as _ => {
|
|
Some(take(a).swap_remove(idx as _))
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Expr {
|
|
fn default() -> Self {
|
|
Expr::Bool(false)
|
|
}
|
|
}
|
|
|
|
impl Display for Expr {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Expr::Int(i) => write!(f, "{i:02x}"),
|
|
Expr::Bool(b) => b.fmt(f),
|
|
Expr::Reg(r) => r.fmt(f),
|
|
Expr::Prefix(op, e) => write!(f, "{op}{e}"),
|
|
Expr::Binary(op, a, b) => write!(f, "{a} {op} {b}"),
|
|
Expr::Postfix(op, e) => write!(f, "{e}{op}"),
|
|
Expr::Array(v) => {
|
|
write!(f, "[")?;
|
|
for (idx, e) in v.iter().enumerate() {
|
|
if idx != 0 {
|
|
f.write_str(", ")?;
|
|
}
|
|
e.fmt(f)?
|
|
}
|
|
write!(f, "]")
|
|
}
|
|
Expr::Range(a, b) => write!(f, "{a}..{b}"),
|
|
Expr::Index(a, i) => write!(f, "{a}[{i}]"),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub enum SubExprIter {
|
|
Once(std::iter::Once<Expr>),
|
|
Many(std::vec::IntoIter<Expr>),
|
|
}
|
|
|
|
impl Iterator for SubExprIter {
|
|
type Item = Expr;
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
match self {
|
|
SubExprIter::Once(iter) => iter.next(),
|
|
SubExprIter::Many(iter) => iter.next(),
|
|
}
|
|
}
|
|
}
|
|
impl IntoIterator for Expr {
|
|
type Item = Expr;
|
|
|
|
type IntoIter = SubExprIter;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
match self {
|
|
Expr::Int(_)
|
|
| Expr::Bool(_)
|
|
| Expr::Reg(_)
|
|
| Expr::Prefix(_, _)
|
|
| Expr::Binary(_, _, _)
|
|
| Expr::Postfix(_, _)
|
|
| Expr::Range(_, _)
|
|
| Expr::Index(_, _) => SubExprIter::Once(std::iter::once(self)),
|
|
Expr::Array(a) => SubExprIter::Many(a.into_iter()),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub mod message {
|
|
//! Represents all possible debugger commands, and their return values
|
|
use crate::ast::Stmt;
|
|
use std::fmt::Display;
|
|
|
|
/// A request sent to the emulated gameboy
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
pub enum Request {
|
|
Do(Stmt),
|
|
}
|
|
|
|
/// A response returned from the emulated Gameboy
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub enum Response {
|
|
/// The request executed successfully
|
|
Success,
|
|
/// The request failed
|
|
Failure,
|
|
/// The data
|
|
Data(usize),
|
|
}
|
|
|
|
impl Display for Request {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Request::Do(stmt) => write!(f, "Execute `{stmt}`"),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub mod gameboy {
|
|
use std::io::Write;
|
|
|
|
use boy::cpu::CPU;
|
|
use boy::memory::io::*;
|
|
use boy_utils::*;
|
|
|
|
use crate::{
|
|
ast::{Expr, Reg, Stmt, Verb},
|
|
bus::BusIOTools,
|
|
disassembler::Disassemble,
|
|
message::{Request, Response},
|
|
};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Gameboy<T: BusIO> {
|
|
trace: bool,
|
|
cpu: CPU,
|
|
bus: T,
|
|
}
|
|
|
|
impl<T: BusIO> Gameboy<T> {
|
|
pub fn new(bus: T) -> Self {
|
|
Self {
|
|
trace: false,
|
|
cpu: CPU::new(),
|
|
bus,
|
|
}
|
|
}
|
|
pub fn process(&mut self, message: Request) -> Response {
|
|
match message {
|
|
Request::Do(stmt) => self.interpret(stmt),
|
|
}
|
|
}
|
|
|
|
fn hexdump(&self, start: isize, end: isize) -> Response {
|
|
let (start, end) = (start as usize, end as usize);
|
|
match bus_hexdump(&self.bus, start..end) {
|
|
Ok(_) => Response::Success,
|
|
Err(_) => Response::Failure,
|
|
}
|
|
}
|
|
|
|
fn disasm(&self, start: isize, end: isize, lim: usize) -> Response {
|
|
let (start, end) = (start as _, end as _);
|
|
self.bus
|
|
.read_iter_from(start)
|
|
.disassemble()
|
|
.take(lim)
|
|
.filter(|(idx, _)| start + idx <= end)
|
|
.for_each(|(idx, insn)| println!("{:04x}: {}", start + idx, insn));
|
|
Response::Success
|
|
}
|
|
|
|
pub fn interpret(&mut self, mut stmt: Stmt) -> Response {
|
|
// Solve statement args
|
|
if let Verb::Set = stmt.verb {
|
|
stmt.src.solve(|expr| self.substitute(expr));
|
|
} else {
|
|
stmt.solve(&|expr| self.substitute(expr));
|
|
}
|
|
|
|
// Intrepret basic statements
|
|
let Stmt { verb, dst, src } = stmt.solve(&Expr::eval_one);
|
|
match (verb, dst, src) {
|
|
(Verb::Set, Expr::Int(addr), Expr::Int(data)) => {
|
|
self.bus.write(*addr as _, *data as _);
|
|
}
|
|
(Verb::Set, Expr::Reg(reg), Expr::Int(data)) => {
|
|
set_register(&mut self.cpu, *reg, *data as _);
|
|
}
|
|
(Verb::Get, Expr::Int(addr), ..) => return self.hexdump(*addr, *addr + 0x10),
|
|
(Verb::Get, range @ Expr::Range(..), ..) => match range.int_range() {
|
|
Some(range) => return self.hexdump(range.start, range.end),
|
|
None => return Response::Failure,
|
|
},
|
|
(Verb::Run, Expr::Int(step), ..) => return self.step(*step as _),
|
|
(Verb::Break, Expr::Int(addr), ..) => {
|
|
if self.cpu.set_break(*addr as _) {
|
|
println!("Set breakpoint {addr:04x}")
|
|
}
|
|
}
|
|
(Verb::Unbreak, Expr::Int(addr), ..) => {
|
|
if self.cpu.unset_break(*addr as _) {
|
|
println!("Unset breakpoint {addr:04x}")
|
|
}
|
|
}
|
|
(Verb::Disasm, Expr::Int(addr), ..) => {
|
|
return self.disasm(*addr, 0x10000, 8);
|
|
}
|
|
(Verb::Disasm, range @ Expr::Range(..), ..) => match range.int_range() {
|
|
Some(range) => return self.disasm(range.start, range.end, usize::MAX),
|
|
None => return Response::Failure,
|
|
},
|
|
(Verb::Eval, expr, ..) => {
|
|
println!(" = {expr}");
|
|
return Response::Success;
|
|
}
|
|
(Verb::DumpRegs, ..) => println!("{}", self.cpu),
|
|
(Verb::DumpCart, ..) => bus_info(&self.bus),
|
|
(Verb::Trace, ..) => self.trace ^= true,
|
|
(Verb::Reset, ..) => self.cpu = CPU::new(),
|
|
(Verb::Help, ..) => todo!("Help"),
|
|
_ => {}
|
|
}
|
|
|
|
// Perform iteration and pairing
|
|
let Stmt { verb, dst, src } = stmt;
|
|
match (dst, src) {
|
|
// Array/range-to-array/range operations are done on zipped arguments
|
|
(Expr::Array(dst), Expr::Array(src)) => {
|
|
for (dst, src) in dst.into_iter().zip(src) {
|
|
self.interpret(Stmt { verb, dst, src });
|
|
}
|
|
}
|
|
(Expr::Array(dst), src @ Expr::Range(..)) => {
|
|
let Some(src) = src.int_range() else {
|
|
return Response::Failure;
|
|
};
|
|
for (dst, src) in dst.into_iter().zip(src) {
|
|
let src = Expr::Int(src);
|
|
self.interpret(Stmt { verb, dst, src });
|
|
}
|
|
}
|
|
(dst @ Expr::Range(..), Expr::Array(src)) => {
|
|
let Some(dst) = dst.int_range() else {
|
|
return Response::Failure;
|
|
};
|
|
for (dst, src) in dst.into_iter().zip(src) {
|
|
let dst = Expr::Int(dst);
|
|
self.interpret(Stmt { verb, dst, src });
|
|
}
|
|
}
|
|
(dst @ Expr::Range(..), src @ Expr::Range(..)) => {
|
|
let (Some(dst), Some(src)) = (dst.int_range(), src.int_range()) else {
|
|
return Response::Failure;
|
|
};
|
|
for (dst, src) in dst.into_iter().zip(src) {
|
|
let (dst, src) = (Expr::Int(dst), Expr::Int(src));
|
|
self.interpret(Stmt { verb, dst, src });
|
|
}
|
|
}
|
|
// Arrays get copied to an absolute address
|
|
(Expr::Int(addr), Expr::Array(src)) => {
|
|
for (idx, src) in src.into_iter().enumerate() {
|
|
let dst = Expr::Int(addr + idx as isize);
|
|
self.interpret(Stmt { verb, dst, src });
|
|
}
|
|
}
|
|
// Ranges get evaluated to a contiguous position
|
|
(Expr::Int(addr), src @ Expr::Range(..)) => {
|
|
let Some(src) = src.int_range() else {
|
|
return Response::Failure;
|
|
};
|
|
for (idx, src) in src.into_iter().enumerate() {
|
|
let (dst, src) = (Expr::Int(addr + idx as isize), Expr::Int(src));
|
|
self.interpret(Stmt { verb, dst, src });
|
|
}
|
|
}
|
|
// Arrays in dst position are iterated over
|
|
(Expr::Array(dst), src) => {
|
|
for dst in dst {
|
|
let src = src.clone();
|
|
self.interpret(Stmt { verb, dst, src });
|
|
}
|
|
}
|
|
// Ranges in dst position are iterated over
|
|
(dst @ Expr::Range(..), src) => {
|
|
let Some(dst) = dst.int_range() else {
|
|
return Response::Failure;
|
|
};
|
|
for dst in dst {
|
|
let (dst, src) = (Expr::Int(dst), src.clone());
|
|
self.interpret(Stmt { verb, dst, src });
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
Response::Success
|
|
}
|
|
|
|
fn substitute(&self, expr: &Expr) -> Option<Expr> {
|
|
Some(match expr {
|
|
Expr::Reg(r) => Expr::Int(get_register(&self.cpu, *r) as _),
|
|
Expr::Index(a, idx) => {
|
|
let (a, idx) = (a.int()?, idx.int()?);
|
|
Expr::Int(self.bus.read((a + idx) as _).unwrap_or(0xff) as _)
|
|
}
|
|
_ => None?,
|
|
})
|
|
}
|
|
|
|
fn step(&mut self, steps: usize) -> Response {
|
|
let Self { trace, cpu, bus } = self;
|
|
for step in 1..=steps {
|
|
let (loc, insn) = (cpu.pc(), cpu.ir());
|
|
match match trace {
|
|
true => cpu.run(&mut bus.trace()),
|
|
false => cpu.run(bus),
|
|
} {
|
|
Ok(cycles) => {
|
|
if insn == boy::cpu::disasm::Insn::Nop {
|
|
continue;
|
|
}
|
|
if steps < 100 {
|
|
print!(
|
|
"{loc:04x}: [{insn:?}]\x1b[23GTook {cycles} cycle{}\x1b[50G",
|
|
if cycles == 1 { "" } else { "s" }
|
|
);
|
|
}
|
|
if steps < 500 {
|
|
bus.read_iter_from(loc.wrapping_sub(1) as usize)
|
|
.disassemble()
|
|
.map(|(_, s)| println!("{:04x}: {s}", loc.wrapping_sub(1)))
|
|
.next();
|
|
let _ = std::io::stdout().flush();
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!(
|
|
"\n{loc:04x}: [{insn:?}]\x1b[23G{insn}\x1b[50G\x1b[31mStep {step:x}: {e}\x1b[0m"
|
|
);
|
|
return Response::Failure;
|
|
}
|
|
}
|
|
}
|
|
println!();
|
|
bus.diag(0);
|
|
Response::Success
|
|
}
|
|
}
|
|
|
|
fn get_register(cpu: &CPU, reg: Reg) -> usize {
|
|
use boy::cpu::disasm::{R16Stack, R16, R8};
|
|
match reg {
|
|
Reg::F => cpu.flags().into_bits() as _,
|
|
Reg::A => cpu[R8::A].0 as _,
|
|
Reg::B => cpu[R8::B].0 as _,
|
|
Reg::C => cpu[R8::C].0 as _,
|
|
Reg::D => cpu[R8::D].0 as _,
|
|
Reg::E => cpu[R8::E].0 as _,
|
|
Reg::H => cpu[R8::H].0 as _,
|
|
Reg::L => cpu[R8::L].0 as _,
|
|
Reg::AF => cpu[R16Stack::AF].0 as _,
|
|
Reg::BC => cpu[R16::BC].0 as _,
|
|
Reg::DE => cpu[R16::DE].0 as _,
|
|
Reg::HL => cpu[R16::HL].0 as _,
|
|
Reg::SP => cpu[R16::SP].0 as _,
|
|
Reg::PC => cpu.pc() as _,
|
|
}
|
|
}
|
|
|
|
fn set_register(cpu: &mut CPU, reg: Reg, value: u16) {
|
|
use boy::cpu::{
|
|
disasm::{R16Stack, R16, R8},
|
|
Flags,
|
|
};
|
|
match reg {
|
|
Reg::F => *cpu.flags_mut() = Flags::from_bits(value as _),
|
|
Reg::A => cpu[R8::A].0 = value as _,
|
|
Reg::B => cpu[R8::B].0 = value as _,
|
|
Reg::C => cpu[R8::C].0 = value as _,
|
|
Reg::D => cpu[R8::D].0 = value as _,
|
|
Reg::E => cpu[R8::E].0 = value as _,
|
|
Reg::H => cpu[R8::H].0 = value as _,
|
|
Reg::L => cpu[R8::L].0 = value as _,
|
|
Reg::AF => cpu[R16Stack::AF].0 = value,
|
|
Reg::BC => cpu[R16::BC].0 = value,
|
|
Reg::DE => cpu[R16::DE].0 = value,
|
|
Reg::HL => cpu[R16::HL].0 = value,
|
|
Reg::SP => cpu[R16::SP].0 = value,
|
|
Reg::PC => cpu.set_pc(value),
|
|
}
|
|
}
|
|
|
|
fn bus_info(bus: &impl BusIO) {
|
|
println!("Entry: {:02x?}", bus.entry());
|
|
println!("Logo: ");
|
|
let _ = match bus.logo() {
|
|
Ok(logo) | Err(logo) => bus_hexdump(&logo, 0..0x30),
|
|
};
|
|
println!("Title: {:02x?}", bus.title());
|
|
println!("MFCode: {:02x?}", bus.mfcode());
|
|
println!("CGB Flag: {:02x?}", bus.cgbflag());
|
|
println!("New Lic: {:02x?}", bus.newlicensee());
|
|
println!("SGB Flag: {:02x?}", bus.sgbflag());
|
|
println!("Cart Type: {:02x?}", bus.carttype());
|
|
println!("ROM Size: {:02x?}", bus.romsize());
|
|
println!("RAM Size: {:02x?}", bus.ramsize());
|
|
println!("Dest Code: {:02x?}", bus.destcode());
|
|
println!("Old Lic: {:02x?}", bus.oldlicencee());
|
|
println!("ROM Ver: {:02x?}", bus.maskromver());
|
|
println!("H Cksum: {:02x?}", bus.headercksum());
|
|
println!("G Cksum: {:04x?}", bus.globalcksum())
|
|
}
|
|
}
|