#[macro_use] extern crate log; use std::collections::HashMap; use std::time::Duration; use clappconfig::{AppConfig, clap}; use clappconfig::clap::ArgMatches; use serde::{Deserialize, Serialize}; use crsn::asm::data::literal::Addr; use crsn::module::{OpTrait, CrsnUniq}; use crsn::runtime::run_thread::{RunThread, ThreadToken, ThreadParams}; use crsn_arith::ArithOps; use crsn_screen::ScreenOps; use crsn_stdio::StdioOps; use crsn_buf::BufOps; mod read_file; mod serde_duration_millis; #[derive(Debug, Clone, Serialize, Deserialize)] struct LogConfig { level: String, modules: HashMap, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] struct Config { log: LogConfig, #[serde(skip)] program_file: String, #[serde(skip)] assemble_only: bool, #[serde(skip)] assemble_debug: bool, #[serde(with = "serde_duration_millis")] cycle_time: Duration, } impl Default for Config { fn default() -> Self { Self { log: LogConfig { level: "warn".to_string(), modules: Default::default(), }, program_file: "".to_string(), assemble_only: false, assemble_debug: false, cycle_time: Duration::default(), } } } impl AppConfig for Config { type Init = Self; fn logging(&self) -> &str { &self.log.level } fn logging_mod_levels(&self) -> Option<&HashMap> { Some(&self.log.modules) } fn pre_log_println(_message: String) { // shut up } fn print_banner(_name: &str, _version: &str) { // No banner } /// Add args to later use in the `configure` method. fn add_args<'a: 'b, 'b>(clap: clap::App<'a, 'b>) -> clap::App<'a, 'b> { // Default impl clap .arg( clap::Arg::with_name("input") .value_name("FILE") .help("Program to run") .required_unless("default-config") .takes_value(true), ) .arg( clap::Arg::with_name("asm-only") .short("P") .long("asm") .help("Only assemble, do not run."), ) .arg( clap::Arg::with_name("asm-debug") .short("D") .long("asm-debug") .help("Only assemble, do not run. Print the result in debug format."), ) .arg( clap::Arg::with_name("cycle") .long("cycle") .short("C") .value_name("MICROS") .help("Cycle time (us, append \"s\" or \"ms\" for coarser time)") .takes_value(true), ) } fn configure(mut self, clap: &ArgMatches) -> anyhow::Result { self.program_file = clap.value_of("input").unwrap().to_string(); self.assemble_debug = clap.is_present("asm-debug"); self.assemble_only = self.assemble_debug || clap.is_present("asm-only"); if let Some(t) = clap.value_of("cycle") { let (t, mul) = if t.ends_with("us") { (&t[..(t.len()-2)], 1) } else if t.ends_with("ms") { (&t[..(t.len()-2)], 1000) } else if t.ends_with("m") { (&t[..(t.len()-1)], 1000) } else if t.ends_with("u") { (&t[..(t.len()-1)], 1) } else if t.ends_with("s") { (&t[..(t.len()-1)], 1000_000) } else { (t, 1) }; self.cycle_time = Duration::from_micros(t.parse::().expect("parse -C value") * mul); println!("ct = {:?}", self.cycle_time); } Ok(self) } } fn main() -> anyhow::Result<()> { let config = Config::init("crsn", "crsn.json5", env!("CARGO_PKG_VERSION"))?; debug!("Loading {}", config.program_file); let source = read_file::read_file(&config.program_file)?; let uniq = CrsnUniq::new(); let parsed = crsn::asm::assemble(&source, &uniq, vec![ ArithOps::new(), BufOps::new(), ScreenOps::new(), StdioOps::new(), ])?; if config.assemble_only { println!("--- {} ---", config.program_file); for (n, op) in parsed.ops.iter().enumerate() { if config.assemble_debug { println!("{:04} : {:?}", n, op); } else { println!("{:04} : {}", n, op.to_sexp()); } } return Ok(()); } else { trace!("--- Compiled program ---"); for (n, op) in parsed.ops.iter().enumerate() { trace!("{:04} : {}", n, op.to_sexp()); } trace!("------------------------"); } debug!("Start runtime"); let args = &[]; let thread = RunThread::new(ThreadParams { id: ThreadToken(0), uniq, program: parsed, pc: Addr(0), cycle_time: config.cycle_time, args }); // run without spawning, so it is on the main thread - required by some extensions thread.run(); debug!("Runtime shut down."); Ok(()) }