#[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_stacks::StackOps; use crsn_stdio::StdioOps; 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)] asm_only: bool, #[serde(with = "serde_duration_millis")] cycle_time: Duration, } impl Default for Config { fn default() -> Self { Self { log: LogConfig { level: "info".to_string(), modules: Default::default(), }, program_file: "".to_string(), asm_only: 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) } /// 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("cycle") .long("cycle") .short("C") .value_name("MILLIS") .help("Cycle time (ms)") .validator(|s| { let t = s.trim(); if t.is_empty() { Err("cycle time requires an argument".into()) } else { if t.chars() .find(|c| !c.is_ascii_digit()) .is_some() { Err("cycle time requires an integer number".into()) } else { Ok(()) } } }) .takes_value(true), ) } fn configure(mut self, clap: &ArgMatches) -> anyhow::Result { self.program_file = clap.value_of("input").unwrap().to_string(); self.asm_only = clap.is_present("asm-only"); if let Some(c) = clap.value_of("cycle") { self.cycle_time = Duration::from_millis(c.parse().unwrap()); } Ok(self) } } fn main() -> anyhow::Result<()> { let config = Config::init("crsn", "crsn.json5", env!("CARGO_PKG_VERSION"))?; info!("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(), StackOps::new(), ScreenOps::new(), StdioOps::new(), ])?; if config.asm_only { for (n, op) in parsed.ops.iter().enumerate() { 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!("------------------------"); } info!("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(); info!("Runtime shut down."); Ok(()) }