#![allow(dead_code)] mod common; mod vm; use structopt::StructOpt; use snafu::Snafu; use std::{fs, io::{stdin, stdout, Write, Read}, process, path::PathBuf}; const DEFAULT_MAX_MEM: usize = 64 * 1024 * 1024; #[derive(Snafu, Debug, Clone, PartialEq)] enum ArgParseError<'a> { #[snafu(display("invalid specified size: {} (sizes may end in G, M, or K)", text))] InvalidSize { text: &'a str } } fn from_bytes_size<'a>(mut text: &'a str) -> std::result::Result> { if text.is_empty() { return Err(ArgParseError::InvalidSize { text }) } let multiplier = match text.chars().last().unwrap() { 'k' | 'K' => 1024, 'm' | 'M' => 1024 * 1024, 'g' | 'G' => 1024 * 1024 * 1024, _ => 1, }; // trim the last character if multiplier != 1 { text = &text[0 .. text.len() - 1]; } let value = text.parse::().map_err(|_| ArgParseError::InvalidSize { text })?; Ok(value * multiplier) } #[test] fn test_bytes_size() { assert_eq!(from_bytes_size("64G"), Ok(64 * 1024 * 1024 * 1024)); assert_eq!(from_bytes_size("64M"), Ok(64 * 1024 * 1024)); assert_eq!(from_bytes_size("64K"), Ok(64 * 1024)); assert_eq!(from_bytes_size("64g"), Ok(64 * 1024 * 1024 * 1024)); assert_eq!(from_bytes_size("64m"), Ok(64 * 1024 * 1024)); assert_eq!(from_bytes_size("64k"), Ok(64 * 1024)); assert_eq!(from_bytes_size("64"), Ok(64)); assert!(from_bytes_size("64B").is_err()); assert!(from_bytes_size("64MB").is_err()); assert!(from_bytes_size("64GB").is_err()); assert!(from_bytes_size("64KB").is_err()); assert!(from_bytes_size("64b").is_err()); assert!(from_bytes_size("64mb").is_err()); assert!(from_bytes_size("64gb").is_err()); assert!(from_bytes_size("64kb").is_err()); } #[derive(StructOpt, Debug)] struct Options { // maybe some other options: // * debug /// The input file to work with. /// /// By default, the file will be executed. If -c is passed, it will not run and only compile. /// Supplying - for the path will read from STDIN. #[structopt(name = "FILE", parse(from_os_str))] input: PathBuf, /// The maximum amount of virtual memory allowed. #[structopt(long, parse(try_from_str = from_bytes_size))] max_mem: Option, /// Whether to compile the input file to an object. #[structopt(short, long)] compile: bool, /// The output file for compiled files. /// /// If not supplied, a name will be inferred from the input file. Supplying - for the path will /// output to STDOUT. #[structopt(short = "o", long)] compile_out: Option } type Result = std::result::Result>; fn main() -> Result<()> { use vm::{ state::State, obj::{ assemble::{Asm, Assemble}, syn::{lexer, parser}, } }; // get the object either from reading it, or from parsing an assembly file let opt = Options::from_args(); let text = if Some("-") == opt.input.to_str() { let mut text = String::new(); let stdin = stdin(); stdin.lock().read_to_string(&mut text)?; text } else { fs::read_to_string(&opt.input)? }; let lexerdef = lexer::lexerdef(); let lexer = lexerdef.lexer(&text); let (res, errors) = parser::parse(&lexer); // print errors for err in errors.iter() { println!("{}", err.pp(&lexer, &parser::token_epp)); } if !errors.is_empty() { process::exit(1); } let res = res.unwrap(); let mut asm = Asm::default(); let object = res.assemble(&mut asm)?; if opt.compile { let outfile = opt.compile_out.clone().unwrap_or_else(|| { let mut outfile = opt.input.clone(); assert!(outfile.set_extension("obj")); outfile }); let bytes = object.to_bytes(); if Some("-") == outfile.to_str() { let stdout = stdout(); stdout.lock().write(&bytes)?; } else { // write compiled file here fs::write(outfile, &bytes)?; } Ok(()) } else { let mut state = State::new(); state.load_object(object, opt.max_mem.unwrap_or(DEFAULT_MAX_MEM))?; let status = state.exec()?; process::exit((status & 0xffff_ffff) as i32); } }