dcpu 0.5.0

An assembler, debugger and emulator for the DCPU-16
Documentation
use std::fmt::{Debug, Display};

use clap;
use nom;

pub use assembler::types::Expression;
use assembler::parser::nom_parser::{expression, pos_number};

error_chain! {
    foreign_links {
        Clap(clap::Error);
    }

    errors {
        Nom(e: String) {
            description("parsing error")
            display("parsing error: {}", e)
        }
    }
}

#[derive(Debug, Clone)]
pub enum Command {
    Step(u16),
    PrintRegisters,
    Disassemble {
        from: Expression,
        size: u16,
    },
    Examine {
        from: Expression,
        size: u16,
    },
    Breakpoint(Expression),
    Continue,
    ShowBreakpoints,
    DeleteBreakpoint(u16),
    ShowDevices,
    Hook(Box<Command>),
    Logs,
    M35fd(u16, M35fdCmd),
    Stack(u16),
    Symbols,
    List(u16),
}

#[derive(Debug, Clone)]
pub enum M35fdCmd {
    Eject,
    Load(String),
}

fn clap_parser<'a, 'b>() -> clap::App<'a, 'b> {
    clap::App::new("DCPU debugger")
        .author(crate_authors!())
        .version(crate_version!())
        .setting(clap::AppSettings::VersionlessSubcommands)
        .setting(clap::AppSettings::SubcommandRequiredElseHelp)
        .setting(clap::AppSettings::NoBinaryName)
        .subcommand(clap::SubCommand::with_name("step")
            .visible_alias("s")
            .help("Execute one instruction.")
            .arg(clap::Arg::with_name("count")
                .default_value("1")))
        .subcommand(clap::SubCommand::with_name("registers")
            .visible_alias("r")
            .help("Show the registers."))
        .subcommand(clap::SubCommand::with_name("disassemble")
            .help("Disassemble a memory part.")
            .arg(clap::Arg::with_name("base")
                .help("From where to disassemble (default [PC]).")
                .required(true))
            .arg(clap::Arg::with_name("length")
                .help("Number of instructions to disassemble.")
                .default_value("10")))
        .subcommand(clap::SubCommand::with_name("examine")
            .visible_alias("x")
            .help("Print a memory slice as hexadecimal.")
            .arg(clap::Arg::with_name("base")
                .help("From where to disassemble (default [PC]).")
                .required(true))
            .arg(clap::Arg::with_name("length")
                .help("Number of instructions to disassemble.")
                .default_value("10")))
        .subcommand(clap::SubCommand::with_name("break")
            .visible_alias("b")
            .help("Add a breakpoint.")
            .arg(clap::Arg::with_name("expression")
                .multiple(true)
                .required(true)))
        .subcommand(clap::SubCommand::with_name("continue")
            .visible_alias("c")
            .help("Continue the execution."))
        .subcommand(clap::SubCommand::with_name("breakpoints")
            .help("Show the active breakpoints"))
        .subcommand(clap::SubCommand::with_name("delete")
            .visible_alias("d")
            .help("Delete a breakpoint.")
            .arg(clap::Arg::with_name("id")
                .required(true)))
        .subcommand(clap::SubCommand::with_name("devices")
            .help("Show the connected devices."))
        .subcommand(clap::SubCommand::with_name("hook")
            .help("Run this command before each new prompt.")
            .arg(clap::Arg::with_name("command")
                .multiple(true)
                .required(true)))
        .subcommand(clap::SubCommand::with_name("logs")
            .help("Show the values in the LOG buffer."))
        .subcommand(clap::SubCommand::with_name("m35fd")
            .setting(clap::AppSettings::SubcommandRequired)
            .help("M35FD-specific commands.")
            .arg(clap::Arg::with_name("id")
                .required(true))
            .subcommand(clap::SubCommand::with_name("eject")
                .help("Eject the floppy."))
            .subcommand(clap::SubCommand::with_name("load")
                .help("Load a new floppy.")
                .arg(clap::Arg::with_name("file")
                    .required(true))))
        .subcommand(clap::SubCommand::with_name("stack")
            .help("Show <count> bytes from the stack.")
            .arg(clap::Arg::with_name("count")
                .default_value("10")))
        .subcommand(clap::SubCommand::with_name("symbols")
            .help("Show the symbols."))
        .subcommand(clap::SubCommand::with_name("list")
            .visible_alias("l")
            .help("Show <count> instructions around PC.")
            .arg(clap::Arg::with_name("count")
                .default_value("10")))
}

pub fn parse_command(cmd: &str) -> Result<Command> {
    let raw = try!(clap_parser().get_matches_from_safe(cmd.split(" ")));
    Command::try_from(&raw)
}

impl Command {
    fn try_from(matches: &clap::ArgMatches) -> Result<Command> {
        match matches.subcommand() {
            ("step", Some(val)) => {
                let str_count = val.value_of("count").unwrap();
                let count = try!(conv_iresult(pos_number(str_count.as_bytes())));
                Ok(Command::Step(count))
            }
            ("registers", _) => Ok(Command::PrintRegisters),
            ("disassemble", Some(args)) => {
                let str_from = args.value_of("base").unwrap();
                let from = try!(conv_iresult(expression(str_from.as_bytes())));
                let str_len = args.value_of("length").unwrap();
                let len = try!(conv_iresult(pos_number(str_len.as_bytes())));
                Ok(Command::Disassemble {
                    from: from,
                    size: len,
                })
            }
            ("examine", Some(args)) => {
                let str_from = args.value_of("base").unwrap();
                let from = try!(conv_iresult(expression(str_from.as_bytes())));
                let str_len = args.value_of("length").unwrap();
                let len = try!(conv_iresult(pos_number(str_len.as_bytes())));
                Ok(Command::Examine {
                    from: from,
                    size: len,
                })
            }
            ("break", Some(val)) => {
                let str_expr = val.values_of("expression")
                                  .unwrap()
                                  .collect::<Vec<_>>()
                                  .join(" ");
                let expr = try!(conv_iresult(expression(str_expr.as_bytes())));
                Ok(Command::Breakpoint(expr))
            }
            ("continue", _) => Ok(Command::Continue),
            ("breakpoints", _) => Ok(Command::ShowBreakpoints),
            ("delete", Some(id)) => {
                let str_id = id.value_of("id").unwrap();
                let id = try!(conv_iresult(pos_number(str_id.as_bytes())));
                Ok(Command::DeleteBreakpoint(id))
            }
            ("devices", Some(_)) => Ok(Command::ShowDevices),
            ("hook", Some(cmd)) => {
                let parsed = try!(parse_command(&cmd.values_of("command")
                                                    .unwrap()
                                                    .collect::<Vec<_>>()
                                                    .join(" ")));
                Ok(Command::Hook(Box::new(parsed)))
            }
            ("logs", Some(_)) => Ok(Command::Logs),
            ("m35fd", Some(args)) => {
                let str_id = args.value_of("id").unwrap();
                let id = try!(conv_iresult(pos_number(str_id.as_bytes())));
                match args.subcommand() {
                    ("eject", Some(_)) =>
                        Ok(Command::M35fd(id, M35fdCmd::Eject)),
                    ("load", Some(args)) => {
                        let file = args.value_of("file").unwrap();
                        Ok(Command::M35fd(id, M35fdCmd::Load(file.into())))
                    }
                    _ => unreachable!(),
                }
            }
            ("stack", Some(args)) => {
                let str_count = args.value_of("count").unwrap();
                let count = try!(conv_iresult(pos_number(str_count.as_bytes())));
                Ok(Command::Stack(count))
            }
            ("symbols", Some(_)) => Ok(Command::Symbols),
            ("list", Some(args)) => {
                let str_count = args.value_of("count").unwrap();
                let count = try!(conv_iresult(pos_number(str_count.as_bytes())));
                Ok(Command::List(count))
            }
            (cmd, args) => {
                try!(Err(format!("unknown command \"{}\" ({:?})", cmd, args)))
            }
        }
    }
}

fn conv_iresult<O: Display + Debug>(ires: nom::IResult<&[u8], O>) -> Result<O> {
    use nom::IResult;

    match ires {
        IResult::Done(i, o) => {
            if i.len() == 0 {
                Ok(o)
            } else {
                try!(Err(format!("garbage after {}", o)))
            }
        }
        IResult::Error(e) => try!(Err(ErrorKind::Nom(format!("{}", e)))),
        IResult::Incomplete(_) => unreachable!(),
    }
}