// TAME compiler // // Copyright (C) 2014-2021 Ryan Specialty Group, LLC. // // This file is part of TAME. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . //! This is the TAME compiler. Its job is to take each of the source code //! files and produce a compiled file. extern crate tamer; use getopts::{Fail, Options}; use std::env; use std::error::Error; use std::ffi::OsStr; use std::fs; use std::path::Path; #[cfg(feature = "wip-frontends")] use {std::io::BufReader, tamer::fs::File}; /// Types of commands enum Command { Compile(String, String, String), Usage, } /// Entrypoint for the compiler pub fn main() -> Result<(), Box> { let args: Vec = env::args().collect(); let program = &args[0]; let opts = get_opts(); let usage = opts.usage(&format!("Usage: {} [OPTIONS] INPUT", program)); match parse_options(opts, args) { Ok(Command::Compile(input, _, output)) => { let source = Path::new(&input); if source.extension() != Some(OsStr::new("xml")) { panic!("{}: file format not recognized", input); } let dest = Path::new(&output); #[cfg(not(feature = "wip-frontends"))] fs::copy(source, dest)?; #[cfg(feature = "wip-frontends")] { use std::io::BufWriter; use tamer::{ fs::PathFile, iter::into_iter_while_ok, xir::{ reader::XmlXirReader, writer::XmlWriter, DefaultEscaper, }, }; let escaper = DefaultEscaper::default(); let mut fout = BufWriter::new(fs::File::create(dest)?); let PathFile(_, file, ctx): PathFile> = PathFile::open(source)?; // Parse into XIR and re-lower into XML, // which is similar to a copy but proves that we're able // to parse source files. into_iter_while_ok( XmlXirReader::new(file, &escaper, ctx), |toks| toks.write(&mut fout, Default::default(), &escaper), )??; } Ok(()) } Ok(Command::Usage) => { println!("{}", usage); std::process::exit(exitcode::OK); } Err(e) => { eprintln!("{}", e); println!("{}", usage); std::process::exit(exitcode::USAGE); } } } /// Get 'Options' /// /// ``` /// use getopts::Options; /// /// let opts = get_opts(); /// ``` fn get_opts() -> Options { let mut opts = Options::new(); opts.optopt("o", "output", "set output file name", "NAME"); opts.optopt("", "emit", "set output type", "xmlo"); opts.optflag("h", "help", "print this help menu"); opts } /// Option parser fn parse_options(opts: Options, args: Vec) -> Result { let matches = match opts.parse(&args[1..]) { Ok(m) => m, Err(f) => { return Err(f); } }; if matches.opt_present("h") { return Ok(Command::Usage); } let input = match matches.free.len() { 0 => return Err(Fail::OptionMissing(String::from("INPUT"))), 1 => matches.free[0].clone(), _ => return Err(Fail::UnrecognizedOption(matches.free[1].clone())), }; let emit = match matches.opt_str("emit") { Some(m) => match &m[..] { "xmlo" => m, _ => { return Err(Fail::ArgumentMissing(String::from("--emit xmlo"))) } }, None => { return Err(Fail::OptionMissing(String::from("--emit xmlo"))); } }; let output = match matches.opt_str("o") { Some(m) => m, // we check that the extension is "xml" later None => format!("{}o", input), }; Ok(Command::Compile(input, emit, output)) } #[cfg(test)] mod test { use super::*; #[test] fn parse_options_help() { let opts = get_opts(); let result = parse_options( opts, vec![String::from("program"), String::from("-h")], ); match result { Ok(Command::Usage) => {} _ => panic!("Long help option did not parse"), } } #[test] fn parse_options_help_long() { let opts = get_opts(); let result = parse_options( opts, vec![String::from("program"), String::from("--help")], ); match result { Ok(Command::Usage) => {} _ => panic!("Help option did not parse"), } } #[test] fn parse_options_invalid() { let opts = get_opts(); let result = parse_options( opts, vec![String::from("program"), String::from("-q")], ); match result { Err(Fail::UnrecognizedOption(_)) => {} _ => panic!("Invalid option not caught"), } } #[test] fn parse_options_missing_input() { let opts = get_opts(); let result = parse_options(opts, vec![String::from("program")]); match result { Err(Fail::OptionMissing(message)) => { assert_eq!("INPUT", message); } _ => panic!("Missing input not caught"), } } #[test] fn parse_options_missing_emit() { let opts = get_opts(); let result = parse_options( opts, vec![String::from("program"), String::from("filename")], ); match result { Err(Fail::OptionMissing(message)) => { assert_eq!("--emit xmlo", message); } _ => panic!("Missing emit not caught"), } } #[test] fn parse_options_invalid_emit() { let opts = get_opts(); let result = parse_options( opts, vec![ String::from("program"), String::from("filename.xml"), String::from("--emit"), String::from("foo"), ], ); match result { Err(Fail::ArgumentMissing(message)) => { assert_eq!("--emit xmlo", message); } _ => panic!("Invalid emit not caught"), } } #[test] fn parse_options_too_many_args() { let opts = get_opts(); let result = parse_options( opts, vec![ String::from("program"), String::from("foo"), String::from("--emit"), String::from("bar"), String::from("baz"), ], ); match result { Err(Fail::UnrecognizedOption(message)) => { assert_eq!("baz", message); } _ => panic!("Extra option not caught"), } } #[test] fn parse_options_valid() { let opts = get_opts(); let xmlo = String::from("xmlo"); let result = parse_options( opts, vec![ String::from("program"), String::from("foo.xml"), String::from("--emit"), xmlo, ], ); match result { Ok(Command::Compile(infile, xmlo, outfile)) => { assert_eq!("foo.xml", infile); assert_eq!("foo.xmlo", outfile); assert_eq!("xmlo", xmlo); } _ => panic!("Unexpected result"), } } #[test] fn parse_options_valid_custom_out() { let opts = get_opts(); let xmlo = String::from("xmlo"); let result = parse_options( opts, vec![ String::from("program"), String::from("foo.xml"), String::from("--emit"), xmlo, String::from("-o"), String::from("foo.xmli"), ], ); match result { Ok(Command::Compile(infile, xmlo, outfile)) => { assert_eq!("foo.xml", infile); assert_eq!("foo.xmli", outfile); assert_eq!("xmlo", xmlo); } _ => panic!("Unexpected result"), } } #[test] fn parse_options_valid_custom_out_long() { let opts = get_opts(); let xmlo = String::from("xmlo"); let result = parse_options( opts, vec![ String::from("program"), String::from("foo.xml"), String::from("--emit"), xmlo, String::from("--output"), String::from("foo.xmli"), ], ); match result { Ok(Command::Compile(infile, xmlo, outfile)) => { assert_eq!("foo.xml", infile); assert_eq!("foo.xmli", outfile); assert_eq!("xmlo", xmlo); } _ => panic!("Unexpected result"), } } }