diff --git a/tamer/Cargo.toml b/tamer/Cargo.toml index 4fc7122a..e8410d52 100644 --- a/tamer/Cargo.toml +++ b/tamer/Cargo.toml @@ -53,9 +53,3 @@ unicode-width = "0.1.5" # This is enabled automatically for the `test` profile. parser-trace-stderr = [] -# Derive `xmli` file from the ASG rather than a XIR token stream. This -# proves that enough information has been added to the graph for the entire -# program to be reconstructed. The `xmli` file will be a new program -# _derived from_ the original, and so will not match exactly. -wip-asg-derived-xmli = [] - diff --git a/tamer/src/bin/tamec.rs b/tamer/src/bin/tamec.rs index 81607770..20ba6534 100644 --- a/tamer/src/bin/tamec.rs +++ b/tamer/src/bin/tamec.rs @@ -20,6 +20,7 @@ // Use your judgment; // a `match` may be more clear within a given context. #![allow(clippy::single_match)] +#![feature(assert_matches)] //! This is the TAME compiler. //! @@ -46,15 +47,42 @@ use tamer::{ nir::NirToAirParseType, parse::{lowerable, FinalizeError, ParseError, Token}, pipeline::{parse_package_xml, LowerXmliError, ParsePackageXmlError}, - xir::{self, reader::XmlXirReader, DefaultEscaper}, + xir::{self, reader::XmlXirReader, writer::XmlWriter, DefaultEscaper}, }; /// Types of commands +#[derive(Debug, PartialEq)] enum Command { - Compile(String, String, String), + Compile(String, ObjectFileKind, String), Usage, } +/// The type of object file to output. +/// +/// While TAMER is under development, +/// object files serve as a transition between the new compiler and the +/// old. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum ObjectFileKind { + /// Produce something akin to an object file. + /// + /// During TAME's development, + /// this is an `xmli` file that is passed to the old compiler to pick + /// up where this one left off. + /// + /// This is the stable feature set, + /// expected to work with any package. + XmloStable, + + /// Enable experimental flag(s), + /// attempting to build the given package with an system that has not + /// yet stabalized and is bound to fail on some packages. + /// + /// This is intentionally vague. + /// It should be used only for testing. + XmloExperimental, +} + /// Create a [`XmlXirReader`] for a source file. /// /// The provided escaper must be shared between all readers and writers in @@ -79,18 +107,15 @@ fn src_reader<'a>( /// transition period between the XSLT-based TAME and TAMER. /// Writing XIR proves that the source file is being successfully parsed and /// helps to evaluate system performance. -#[cfg(not(feature = "wip-asg-derived-xmli"))] fn copy_xml_to<'e, W: io::Write + 'e>( - mut fout: W, + mut fout: Option, escaper: &'e DefaultEscaper, ) -> impl FnMut(&Result) + 'e { - use tamer::xir::writer::XmlWriter; - let mut xmlwriter = Default::default(); - move |tok_result| match tok_result { - Ok(tok) => { - xmlwriter = tok.write(&mut fout, xmlwriter, escaper).unwrap(); + move |tok_result| match (fout.as_mut(), tok_result) { + (Some(mut dest), Ok(tok)) => { + xmlwriter = tok.write(&mut dest, xmlwriter, escaper).unwrap(); } _ => (), } @@ -105,13 +130,31 @@ fn compile( src_path: &String, dest_path: &String, reporter: &mut R, + kind: ObjectFileKind, ) -> Result<(), UnrecoverableError> { let dest = Path::new(&dest_path); - #[allow(unused_mut)] // wip-asg-derived-xmli - let mut fout = BufWriter::new(fs::File::create(dest)?); + + let (fcopy, fout, parse_type) = match kind { + // Parse XML and re-emit into target verbatim + // (but missing some formatting). + // Tokens will act as no-ops after NIR. + ObjectFileKind::XmloStable => ( + Some(BufWriter::new(fs::File::create(dest)?)), + None, + NirToAirParseType::Noop, + ), + + // Parse sources into ASG and re-generate sources from there. + // This will fail if the source package utilize features that are + // not yet supported. + ObjectFileKind::XmloExperimental => ( + None, + Some(BufWriter::new(fs::File::create(dest)?)), + NirToAirParseType::LowerKnownErrorRest, + ), + }; let escaper = DefaultEscaper::default(); - let mut ebuf = String::new(); let report_err = |result: Result<(), ParsePackageXmlError<_>>| { @@ -125,23 +168,9 @@ fn compile( }) }; - // TODO: We're just echoing back out XIR, - // which will be the same sans some formatting. - let src = &mut lowerable(src_reader(src_path, &escaper)?.inspect({ - #[cfg(not(feature = "wip-asg-derived-xmli"))] - { - copy_xml_to(fout, &escaper) - } - #[cfg(feature = "wip-asg-derived-xmli")] - { - |_| () - } - })); - - #[cfg(not(feature = "wip-asg-derived-xmli"))] - let parse_type = NirToAirParseType::Noop; - #[cfg(feature = "wip-asg-derived-xmli")] - let parse_type = NirToAirParseType::LowerKnownErrorRest; + let src = &mut lowerable( + src_reader(src_path, &escaper)?.inspect(copy_xml_to(fcopy, &escaper)), + ); // TODO: Determine a good default capacity once we have this populated // and can come up with some heuristics. @@ -150,23 +179,15 @@ fn compile( DefaultAsg::with_capacity(1024, 2048), )(src, report_err)?; - match reporter.has_errors() { - false => { - #[cfg(feature = "wip-asg-derived-xmli")] - { - let asg = air_ctx.finish(); - derive_xmli(asg, fout, &escaper) - } - #[cfg(not(feature = "wip-asg-derived-xmli"))] - { - let _ = air_ctx; // unused_variables - Ok(()) - } - } - - true => Err(UnrecoverableError::ErrorsDuringLowering( + if reporter.has_errors() { + Err(UnrecoverableError::ErrorsDuringLowering( reporter.error_count(), - )), + )) + } else if let Some(dest) = fout { + let asg = air_ctx.finish(); + derive_xmli(asg, dest, &escaper) + } else { + Ok(()) } } @@ -180,16 +201,13 @@ fn compile( /// and must be an equivalent program, /// but will look different; /// TAMER reasons about the system using a different paradigm. -#[cfg(feature = "wip-asg-derived-xmli")] fn derive_xmli( asg: tamer::asg::Asg, mut fout: impl std::io::Write, escaper: &DefaultEscaper, ) -> Result<(), UnrecoverableError> { use tamer::{ - asg::visit::tree_reconstruction, - pipeline, - xir::writer::{WriterState, XmlWriter}, + asg::visit::tree_reconstruction, pipeline, xir::writer::WriterState, }; let src = lowerable(tree_reconstruction(&asg).map(Ok)); @@ -221,10 +239,10 @@ pub fn main() -> Result<(), UnrecoverableError> { let usage = opts.usage(&format!("Usage: {program} [OPTIONS] INPUT")); match parse_options(opts, args) { - Ok(Command::Compile(src_path, _, dest_path)) => { + Ok(Command::Compile(src_path, kind, dest_path)) => { let mut reporter = VisualReporter::new(FsSpanResolver); - compile(&src_path, &dest_path, &mut reporter).map_err( + compile(&src_path, &dest_path, &mut reporter, kind).map_err( |e: UnrecoverableError| { // Rendering to a string ensures buffering so that we // don't interleave output between processes. @@ -286,15 +304,12 @@ fn parse_options(opts: Options, args: Vec) -> Result { let emit = match matches.opt_str("emit") { Some(m) => match &m[..] { - "xmlo" => m, - _ => { - return Err(Fail::ArgumentMissing(String::from("--emit xmlo"))) - } + "xmlo" => Ok(ObjectFileKind::XmloStable), + "xmlo-experimental" => Ok(ObjectFileKind::XmloExperimental), + _ => Err(Fail::ArgumentMissing(String::from("--emit xmlo"))), }, - None => { - return Err(Fail::OptionMissing(String::from("--emit xmlo"))); - } - }; + None => Err(Fail::OptionMissing(String::from("--emit xmlo"))), + }?; let output = match matches.opt_str("o") { Some(m) => m, @@ -412,6 +427,7 @@ impl Diagnostic for UnrecoverableError { #[cfg(test)] mod test { use super::*; + use std::assert_matches::assert_matches; #[test] fn parse_options_help() { @@ -545,7 +561,7 @@ mod test { Ok(Command::Compile(infile, xmlo, outfile)) => { assert_eq!("foo.xml", infile); assert_eq!("foo.xmlo", outfile); - assert_eq!("xmlo", xmlo); + assert_eq!(ObjectFileKind::XmloStable, xmlo); } _ => panic!("Unexpected result"), } @@ -571,7 +587,7 @@ mod test { Ok(Command::Compile(infile, xmlo, outfile)) => { assert_eq!("foo.xml", infile); assert_eq!("foo.xmli", outfile); - assert_eq!("xmlo", xmlo); + assert_eq!(ObjectFileKind::XmloStable, xmlo); } _ => panic!("Unexpected result"), } @@ -597,9 +613,31 @@ mod test { Ok(Command::Compile(infile, xmlo, outfile)) => { assert_eq!("foo.xml", infile); assert_eq!("foo.xmli", outfile); - assert_eq!("xmlo", xmlo); + assert_eq!(ObjectFileKind::XmloStable, xmlo); } _ => panic!("Unexpected result"), } } + + #[test] + fn parse_options_xmlo_experimetal() { + let opts = get_opts(); + let xmlo = String::from("xmlo-experimental"); + let result = parse_options( + opts, + vec![ + String::from("program"), + String::from("foo.xml"), + String::from("--emit"), + xmlo, + String::from("--output"), + String::from("foo.xmli"), + ], + ); + + assert_matches!( + result, + Ok(Command::Compile(_, ObjectFileKind::XmloExperimental, _)), + ); + } } diff --git a/tamer/tests/xmli/README.md b/tamer/tests/xmli/README.md index 6f1dad1a..da34783f 100644 --- a/tamer/tests/xmli/README.md +++ b/tamer/tests/xmli/README.md @@ -22,6 +22,10 @@ slightly different representation than the original sources, but it must express an equivalent program, and the program must be at least as performant when emitted by TAMEĀ XSLT. +# Experimental Features +If a file `is-experimental` exists in a test directory, then +`--emit xmlo-experimental` will be used in place of `--emit xmlo`. + # Running Tests Test are prefixed with `test-*` and are executable. They must be invoked with the environment variable `PATH_TAMEC` set to the path of `tamec` diff --git a/tamer/tests/xmli/classify/is-experimental b/tamer/tests/xmli/classify/is-experimental new file mode 100644 index 00000000..e69de29b diff --git a/tamer/tests/xmli/combined/is-experimental b/tamer/tests/xmli/combined/is-experimental new file mode 100644 index 00000000..e69de29b diff --git a/tamer/tests/xmli/cp/expected.xml b/tamer/tests/xmli/cp/expected.xml new file mode 100644 index 00000000..8fb49c5d --- /dev/null +++ b/tamer/tests/xmli/cp/expected.xml @@ -0,0 +1,11 @@ + + + + This simply verifies that the file is copied to the destination. + This can be removed once more of tamec becomes stable. + + + + diff --git a/tamer/tests/xmli/cp/src.xml b/tamer/tests/xmli/cp/src.xml new file mode 100644 index 00000000..9f3be106 --- /dev/null +++ b/tamer/tests/xmli/cp/src.xml @@ -0,0 +1,11 @@ + + + + This simply verifies that the file is copied to the destination. + This can be removed once more of tamec becomes stable. + + + + diff --git a/tamer/tests/xmli/empty/is-experimental b/tamer/tests/xmli/empty/is-experimental new file mode 100644 index 00000000..e69de29b diff --git a/tamer/tests/xmli/package/is-experimental b/tamer/tests/xmli/package/is-experimental new file mode 100644 index 00000000..e69de29b diff --git a/tamer/tests/xmli/rate/is-experimental b/tamer/tests/xmli/rate/is-experimental new file mode 100644 index 00000000..e69de29b diff --git a/tamer/tests/xmli/template/is-experimental b/tamer/tests/xmli/template/is-experimental new file mode 100644 index 00000000..e69de29b diff --git a/tamer/tests/xmli/test-xmli b/tamer/tests/xmli/test-xmli index 7d5d68cf..5149d8cf 100755 --- a/tamer/tests/xmli/test-xmli +++ b/tamer/tests/xmli/test-xmli @@ -11,8 +11,6 @@ mypath=$(dirname "$0") # Performing this check within `<()` below won't cause a failure. : "${P_XMLLINT?}" # conf.sh -tamer-flag-or-exit-ok wip-asg-derived-xmli - run-test() { local name="${1?Missing test name}" local dir="${2?Missing dir}" @@ -45,8 +43,13 @@ timed-tamec() { # but it'll be close enough. local -i start_ns=$(date +%s%N) + local objty=xmlo + if [ -f "$dir/is-experimental" ]; then + objty=xmlo-experimental + fi + command time -f "%F/%Rfault %I/%Oio %Mrss %c/%wctx \n%C" -o "$dir/time.log" \ - "${TAMER_PATH_TAMEC?}" -o "$dir/$out" --emit xmlo "$dir/$in" \ + "${TAMER_PATH_TAMEC?}" -o "$dir/$out" --emit "$objty" "$dir/$in" \ &> "$dir/tamec-$out.log" \ || ret=$?