|
|
|
#[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<String, String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
#[serde(default)]
|
|
|
|
struct Config {
|
|
|
|
log: LogConfig,
|
|
|
|
#[serde(skip)]
|
|
|
|
program_file: String,
|
|
|
|
#[serde(skip)]
|
|
|
|
assemble_only: 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,
|
|
|
|
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<String, String>> {
|
|
|
|
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("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> {
|
|
|
|
self.program_file = clap.value_of("input").unwrap().to_string();
|
|
|
|
self.assemble_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"))?;
|
|
|
|
|
|
|
|
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 {
|
|
|
|
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!("------------------------");
|
|
|
|
}
|
|
|
|
|
|
|
|
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(())
|
|
|
|
}
|